/*
    "צבע שחור" — לולאת רקע (Offscreen Document).
    משיכת אירועים ב-polling מ-GET /notifications (אין WebSocket — SPEC §4.6),
    סינון לפי בחירת המשתמש, popup + מפה + צליל, וסיום אירוע לפי TTL (expireAt).

    כתובות השרת (NOTIFICATIONS_API_URL / LISTS_VERSIONS_URL) מוגדרות ב-js/config.js.
*/

var currentCities = [];
var currentAlertCities = []; // כל ערי האירוע (לטקסט ההתרעה/העתקה)
var interval;
const notifCitiesMap = {}; // notificationId (desktop) → קבוצת הערים שלו, לקליק "פתח/העתק"

const POLL_INTERVAL_MS = 10000; // ~10ש'; השרת מקאשט max-age=5 ב-edge (SPEC §4.9)
const WATCHDOG_MS = 125000; // אין תשובה תקינה > ~2 דק' → חיווי "אין חיבור" (SPEC §4.6)

window.addEventListener("load", async (_) => {
  try {
    await City.loadingDataPromise;
  } catch {}
  await fetchAndCheckLists().catch(() => null);
  startPolling();
});

async function checkListsVersion({ polygons, cities }) {
  await Promise.allSettled([
    Preferences.setPolygonsVersion(polygons),
    Preferences.setCitiesVersion(cities),
  ]);
  await City.loadData();
}

async function fetchAndCheckLists() {
  const data = await fetch(LISTS_VERSIONS_URL).then(async (r) => r.json());
  if (data.polygons && data.cities) await checkListsVersion(data);
}

// בדיקת גרסאות רשימות כל ~24ש' (לא בכל סבב poll)
setInterval(() => fetchAndCheckLists().catch(() => null), 24 * 60 * 60 * 1000);

/*
    Polling — הערוץ היחיד (אין WS, אין "גיבוי").
    connectionStatus: 0 = מחובר, 1 = אין חיבור (watchdog).
    הלולאה חיה כאן (Offscreen Document) כי ה-SW של MV3 נהרג ב-idle.
*/
let connectionStatus = 0;
let lastOkTime = Date.now();
let pollFailures = 0;

async function pollNotifications() {
  try {
    const res = await fetch(NOTIFICATIONS_API_URL); // נספג ב-edge cache (max-age=5)
    if (!res.ok) throw new Error(res.status);
    const events = await res.json(); // מערך אירועים פעילים (ללא reporterId/location)
    lastOkTime = Date.now();
    connectionStatus = 0;
    pollFailures = 0;
    for (const ev of events) getAlerts(ev, "poll"); // getAlerts עושה dedup לפי notificationId
  } catch (_) {
    pollFailures++;
    if (Date.now() - lastOkTime > WATCHDOG_MS) connectionStatus = 1;
  }
}

// לולאה מבוססת setTimeout (ולא setInterval): מונעת חפיפת בקשות, מוסיפה jitter (±15%)
// לפיזור עומס בין לקוחות, ו-backoff מעריכי בכשל (עד ~60ש') כדי לא להציף שרת שנפל.
function scheduleNextPoll() {
  const base =
    pollFailures > 0
      ? Math.min(POLL_INTERVAL_MS * Math.pow(2, pollFailures - 1), 60000)
      : POLL_INTERVAL_MS;
  const delay = base * (0.85 + Math.random() * 0.3);
  setTimeout(runPollCycle, delay);
}

async function runPollCycle() {
  await pollNotifications();
  scheduleNextPoll();
}

function startPolling() {
  runPollCycle(); // ריצה ראשונה מיידית (snapshot של הפעילים), ואז תזמון הסבב הבא
}

var rcvNotificationIds = [];

async function getAlerts(alert, source, testAlert = false) {
  if (!alert.notificationId) alert.notificationId = String(Math.random());
  if (typeof alert.eventType == "undefined") alert.eventType = 8;

  // Dedup לפי notificationId+version: עדכון אירוע (version עולה) מתריע מחדש (#3);
  // סגירה (version עולה, status="closed") מטופלת פעם אחת (#4); אותו אירוע שחוזר ללא
  // שינוי בכל סבב poll נבלע.
  const dedupKey = alert.notificationId + ":" + (alert.version || 1);
  if (rcvNotificationIds.includes(dedupKey)) return;
  rcvNotificationIds.push(dedupKey);
  if (rcvNotificationIds.length > 100) rcvNotificationIds.shift();

  // סיום אירוע ידני (#4) — עדכון שקט: מסיר את האירוע מהתצוגה הפעילה ומציג חיווי ללא צליל.
  if (alert.status === "closed") return endAlert(alert);

  const [selectionCitiesIDs, selectionAreasIDs, selectedEventTypes, silentNotSelected, withinQuietHours] =
    await Promise.all([
      Preferences.getSelectedCities(),
      Preferences.getSelectedAreas(),
      Preferences.getSelectedEventTypes(),
      Preferences.getSilentNotSelected(),
      Preferences.isWithinQuietHours(),
    ]);

  // לשמירה עבור popup - כפתור "פתח התרעה"
  localStorage.setItem("lastAlertServerTime", alert["time"]);

  // expireAt מהשרת (TTL); אם חסר — נופלים ל-time/עכשיו + שעה וחצי
  const expireAt =
    alert.expireAt || ((alert.time || Math.floor(Date.now() / 1000)) + 5400);
  var alertCities = alert["cities"].map(
    (v) => new City(v, alert.eventType, expireAt, Math.floor(Date.now() / 1000))
  );
  // שיוך מטא-נתוני האירוע לכל עיר: notificationId (סיום מדויק של אירוע זה, #4),
  // note (הערה), address (מיקום מדויק כפי שהזין המדווח), ו-lat/lng המדויקים —
  // אם נמסרו, דורסים את מרכז-העיר (cities.json) כדי לנעוץ את הנקודה המדויקת במפה.
  alertCities.forEach((c) => {
    c.notificationId = alert.notificationId;
    c.note = alert.note || "";
    c.address = alert.address || "";
    if (typeof alert.lat === "number") c.lat = alert.lat;
    if (typeof alert.lng === "number") c.lng = alert.lng;
  });

  const selectAll = selectionCitiesIDs.length == 0 && selectionAreasIDs.length == 0;
  alertCities = alertCities.filter((c) => {
    if (selectedEventTypes.length != 0 && !selectedEventTypes.includes(c.eventType)) return false;
    return (
      silentNotSelected ||
      selectionCitiesIDs.includes(c.id) ||
      selectionAreasIDs.includes(c.areaID) ||
      selectAll ||
      testAlert ||
      City.virtualCitiesIds.includes(c.id)
    );
  });
  if (alertCities.length == 0) return;

  const anyContainsSelection = alertCities.some(
    (c) =>
      selectionCitiesIDs.includes(c.id) ||
      selectionAreasIDs.includes(c.areaID) ||
      City.virtualCitiesIds.includes(c.id)
  );

  // alert.silent (סגירה ידנית) → ללא צליל; השתקת שעות (quiet hours) → ללא צליל (ההתרעה עדיין מוצגת);
  // אחרת לפי הגדרת "התרעות שקטות באזורים שלא נבחרו"
  if (!alert.silent && !withinQuietHours && (!(silentNotSelected && !anyContainsSelection) || selectAll))
    Preferences.playSound(
      alertCities.map((c) => c.id).filter((id) => id != -1).sort((a, b) => a - b),
      alert.eventType
    );

  // עדכון אירוע קיים (#3): מחליפים את הערים הישנות של אותו notificationId במקום להכפיל
  // (לאירוע חדש המסנן לא מסיר דבר). מבטיח שהעיר לא תוצג פעמיים אחרי עריכה.
  currentCities = currentCities.filter((c) => c.notificationId !== alert.notificationId);
  currentAlertCities = currentAlertCities.filter((c) => c.notificationId !== alert.notificationId);
  currentCities = currentCities.concat(alertCities);
  currentAlertCities = currentAlertCities.concat(alertCities);

  // isPriorityAlert: אירוע שתאם עיר/אזור שנבחרו במפורש — עוקף את cooldown הפוקוס
  const isPriorityAlert = anyContainsSelection;
  updateData(true, alertCities, silentNotSelected && !anyContainsSelection, isPriorityAlert);
}

var lastData = [];
var popupCreatingPromise;
async function updateData(isNewData, alertCities, forcePreventFoucus = false, isPriorityAlert = false) {
  // סינון לפי TTL — מסיר עיר שפג זמנה
  currentCities = currentCities.filter((city) => city.getCountdown() > 0);
  if (currentCities.length == 0) return finishAlert();

  // רענון כל שנייה (כדי להסיר ערים בפקיעת TTL)
  if (interval == null)
    interval = setInterval(() => {
      updateData(false, []);
    }, 1000);

  if (!equalArrays(currentCities, lastData)) {
    if (isNewData) {
      const desktop = await Preferences.getSelectedDesktop();
      if (desktop && alertCities.length) {
        notify(alertCities);
      } else {
        if (popupCreatingPromise) await popupCreatingPromise;
        else {
          popupCreatingPromise = popup(forcePreventFoucus, isPriorityAlert);
          await popupCreatingPromise;
          popupCreatingPromise = null;
        }
      }
    }

    if (popupCreatingPromise) await popupCreatingPromise;
    if (popupWindow) chrome.runtime.sendMessage({ cities: this.currentCities });
  }

  lastData = currentCities;
}

function finishAlert() {
  currentCities = [];
  currentAlertCities = [];
  lastData = [];
  if (interval != null) {
    clearInterval(interval);
    interval = null;
  }
  if (popupWindow) chrome.runtime.sendMessage({ cities: this.currentCities });
}

// סיום אירוע ידני (#4): מסיר את ערי האירוע הספציפי (לפי notificationId) מהתצוגה הפעילה,
// מציג חיווי מערכת שקט, ומבהב את החלונית הצפה עם "האירוע הסתיים" כדי שהמשתמש ישים לב.
// אם המשתמש כלל לא הציג את האירוע — שקט מוחלט (אין מה לסיים).
async function endAlert(alert) {
  const id = alert.notificationId;
  const before = currentCities.length;
  currentCities = currentCities.filter((c) => c.notificationId !== id);
  currentAlertCities = currentAlertCities.filter((c) => c.notificationId !== id);
  if (currentCities.length === before) return;
  await notifyEnded(alert);

  if (currentCities.length === 0) {
    // האירוע האחרון הסתיים — במקום לסגור את החלונית בשקט, מבהבים אותה עם "האירוע הסתיים".
    // אם אינה פתוחה (desktop / נסגרה) — פותחים אותה ואז משדרים את חיווי הסיום.
    finishAlertState();
    await flashEnded();
  } else {
    updateData(false, []); // עדיין יש אירועים פעילים — רק מרעננים את התצוגה
  }
}

// מאפס את מצב ההתרעה (זהה ל-finishAlert) בלי לשדר לחלונית — השידור נעשה ע"י flashEnded.
function finishAlertState() {
  currentCities = [];
  currentAlertCities = [];
  lastData = [];
  if (interval != null) {
    clearInterval(interval);
    interval = null;
  }
}

// דגל "סיום ממתין": חלון שנפתח טרי קורא אותו ב-load (מונע מצב מירוץ שבו השידור מקדים
// את רישום המאזין). מתאפס אחרי שהחלון מושך אותו, או אחרי טיים-אאוט קצר ליתר ביטחון.
let pendingFlashEnded = false;

// משדר לחלונית הצפה את חיווי הסיום ("האירוע הסתיים" + הבהוב). אם אינה פתוחה — פותח אותה תחילה.
// החלונית מבהבת ~8ש' ואז נסגרת לבד (alertMap.js → showEndedFlash).
async function flashEnded() {
  const wasOpen = !!popupWindow;
  pendingFlashEnded = true;
  if (!wasOpen) {
    await popup(false, false);
    if (popupCreatingPromise) await popupCreatingPromise;
  }
  // חלון שכבר היה פתוח — מאזין ל-onMessage, אז שידור פעיל יתפוס מיד;
  // חלון טרי ימשוך את הדגל ב-load דרך ההודעה "pendingFlash".
  if (wasOpen && popupWindow) chrome.runtime.sendMessage({ flash: "ended" });
  // ניקוי הדגל אם איש לא משך אותו (למשל החלון לא נפתח) — לא ישפיע על אירוע סיום עתידי
  setTimeout(() => (pendingFlashEnded = false), 15000);
}

// חיווי "האירוע הסתיים" — התראת מערכת *שקטה* (ללא playSound), בכל מצב התראות.
async function notifyEnded(alert) {
  const siteLanguage = await Preferences.getSelectedLanguage();
  City.siteLanguage = siteLanguage;
  const names = (alert.cities || []).map((v) => new City(v).getLocalizationCityName());
  const lang = (siteLanguage || "HE").toLowerCase();
  // טקסט סיום שהזין החמ"ל (#4) — מצורף לשורת הערים אם קיים.
  const note = (alert.note || "").trim();
  chrome.notifications.create({
    type: "basic",
    title: STRINGS.eventEnded[lang] || STRINGS.eventEnded.he,
    message: names.join(", ") + (note ? "\n" + note : ""),
    iconUrl: "../img/notify.png",
  });
}

function equalArrays(one, two) {
  if ((one.length == 0 && two.length == 0) || one.length != two.length) return false;
  var same = true;
  one.forEach((a, i) => {
    if (a != two[i]) same = false;
  });
  return same;
}

async function notify(cities) {
  const siteLanguage = await Preferences.getSelectedLanguage();
  City.siteLanguage = siteLanguage;

  // קיבוץ לפי eventType
  const byType = {};
  cities.forEach((c) => {
    (byType[c.eventType] = byType[c.eventType] || []).push(c);
  });

  Object.keys(byType).forEach((eventType) => {
    const group = byType[eventType];
    // כותרת = סוג האירוע; גוף = "עיר, כתובת" לכל מיקום + הערה (ללא קיבוץ לפי גוש-אזור)
    const locations = group.map((c) => {
      const name = c.getLocalizationCityName();
      return c.address ? `${name}, ${c.address}` : name;
    });
    const notes = [];
    group.forEach((c) => {
      if (c.note && !notes.includes(c.note)) notes.push(c.note);
    });

    const title = City.getLocalizationEventTypeTitle(Number(eventType));
    const message = locations.join(" · ") + (notes.length ? "\n" + notes.join(" · ") : "");

    var options = {
      type: "basic",
      title,
      message,
      iconUrl: "../img/notify.png",
      buttons: [
        { title: STRINGS.openMapButton[siteLanguage.toLowerCase()] || STRINGS.openMapButton.he },
        { title: STRINGS.copyButton[siteLanguage.toLowerCase()] || STRINGS.copyButton.he },
      ],
    };
    // ממפים notificationId → הקבוצה שלו, כדי שקליק על "פתח/העתק" יפעל על הערים הנכונות
    chrome.notifications.create(options, (id) => {
      if (id) notifCitiesMap[id] = group;
    });
  });
}

function desktopNotificationClicked(notifId, btnIdx) {
  const cities = notifCitiesMap[notifId] || this.currentAlertCities;
  if (!btnIdx) return Preferences.launchSiteMap(cities);
  Preferences.copyAlert(cities);
}

var popupWindow;
var isPopupCreating = false;
const lastPopupFocusTimes = {}; // keyed by popup type: "alert"
const POPUP_FOCUS_COOLDOWN_MS = 60_000;

async function popup(forcePreventFoucus = false, isPriorityAlert = false) {
  if (isPopupCreating) return;
  isPopupCreating = true;

  // חלון אנכי וצר: מיקום+הערה למעלה, מפה למטה (אירוע יחיד — אין פאנל ערים)
  const WIN_W = 420;
  const WIN_H = 420;
  var options = {
    url: "../alert.html",
    type: "popup",
    width: WIN_W,
    height: WIN_H,
    left: 0,
    top: window.screen.height - WIN_H,
  };

  try {
    const [preventfocus, preventFrequentFocus] = await Promise.all([
      Preferences.getSelectedBackgroundHidePopup(),
      Preferences.getPreventFrequentPopupFocus(),
    ]);

    const now = Date.now();
    const popupLastFocusAt = lastPopupFocusTimes["alert"] ?? 0;
    const canFocusAgain =
      now - popupLastFocusAt > POPUP_FOCUS_COOLDOWN_MS || !preventFrequentFocus;
    const shouldFocus = !forcePreventFoucus && !preventfocus && canFocusAgain;

    if (shouldFocus) {
      options.focused = true;
      lastPopupFocusTimes["alert"] = now;
    }

    if (!popupWindow) {
      popupWindow = await new Promise((resolve) =>
        chrome.windows.create(options, (window) => resolve(window))
      );
    } else {
      popupWindow = await new Promise((resolve) =>
        chrome.windows.update(
          popupWindow.id,
          shouldFocus ? { focused: true } : {},
          (window) =>
            !window
              ? chrome.windows.create(options, (window) => resolve(window))
              : resolve(popupWindow)
        )
      );
    }
  } catch (error) {}
  isPopupCreating = false;
}

// "בדיקת התראה" — מחזורי בין סוגי האירוע הזמינים (3 הסגרה, 0 מעצר מ.צ., 2 מחסומים)
const TEST_EVENT_TYPES = [3, 0, 2];
let testEventTypeIdx = 0;
const getTestEventType = () => {
  const t = TEST_EVENT_TYPES[testEventTypeIdx % TEST_EVENT_TYPES.length];
  testEventTypeIdx++;
  return t;
};

chrome.runtime.onMessage.addListener(async (value, sender, sendResponse) => {
  switch (value) {
    case "currentCities":
      return sendResponse(this.currentCities);

    case "currentAlertCities":
      return sendResponse(this.currentAlertCities);

    case "testAlert": {
      const now = Math.floor(Date.now() / 1000);
      return getAlerts(
        {
          cities: ["בדיקה"],
          eventType: getTestEventType(),
          time: now,
          expireAt: now + 60,
          notificationId: "test-" + Date.now(),
        },
        "test",
        true
      );
    }

    case "isConnectedToServer":
      return sendResponse(connectionStatus == 0);

    case "pendingFlash": {
      // חלון טרי מושך את חיווי הסיום הממתין (אם יש) — מונע מירוץ עם שידור flashEnded
      const flash = pendingFlashEnded ? "ended" : null;
      pendingFlashEnded = false;
      return sendResponse(flash);
    }
  }

  if (typeof value != "object") return;

  const { type, data } = value;
  switch (type) {
    case "NOTIFICATION_CLICKED":
      return desktopNotificationClicked(...data);
  }
});

// לולאת ניקוי כללית — שומר את הודעת ההתרעה קצרה
setInterval(() => {
  currentAlertCities = currentAlertCities.filter(
    (c) => Date.now() - c.timestamp * 1000 < 3 * 60 * 1000
  );
}, 1 * 60 * 1000);
