// Notifications — derive real notifications from app state, the full
// notifications screen (Glocke → list), and best-effort push scheduling
// (a reminder a configurable number of minutes before a game night).

// ── Seen state (drives the unread dot) ───────────────────────────────────────
const NOTIF_SEEN_KEY = 'zocker_notif_seen';

function notifSeenSet() {
  try {
    const raw = localStorage.getItem(NOTIF_SEEN_KEY);
    return new Set(raw ? JSON.parse(raw) : []);
  } catch (_) { return new Set(); }
}
function notifSaveSeen(set) {
  try { localStorage.setItem(NOTIF_SEEN_KEY, JSON.stringify([...set])); } catch (_) {}
}

// ── Building notifications from state ─────────────────────────────────────────

// Turn a recurring session config into the session object SheetSession expects.
function recurringToSession(r) {
  const group = window.GROUPS_BY_ID[r.groupId];
  const friends = [...(r.confirmed || []), ...(r.maybe || [])];
  return {
    dow: r.dow, from: r.from, to: r.to, friends,
    topGame: window.GAMES_BY_ID[r.gameId], topVotes: (r.confirmed || []).length,
    dayOffset: ((r.dow - window.TODAY_DOW) + 7) % 7,
    groups: group ? [group] : [],
    recurring: r,
  };
}

// Upcoming game nights within the next 7 days (computed overlaps + recurring).
function upcomingGameNights() {
  const out = [];
  const seenKeys = new Set();
  for (const s of (window.getSessions('all') || [])) {
    if (s.dayOffset > 6) continue;
    const key = window.sessionKey(s);
    if (seenKeys.has(key)) continue;
    seenKeys.add(key);
    out.push({ session: s, dayOffset: s.dayOffset });
  }
  for (const r of (window.RECURRING_SESSIONS || [])) {
    const s = recurringToSession(r);
    if (!window.GROUPS_BY_ID[r.groupId]) continue;
    const key = window.sessionKey(s);
    if (seenKeys.has(key)) continue;
    seenKeys.add(key);
    out.push({ session: s, dayOffset: s.dayOffset });
  }
  return out.sort((a, b) =>
    (a.dayOffset - b.dayOffset) || (a.session.from - b.session.from));
}

function nightTimeLabel(s) {
  const t = window.t;
  const day = s.dayOffset === 0 ? t('heute')
    : s.dayOffset === 1 ? t('morgen')
    : window.dayLabelsLong()[s.dow];
  return `${day} · ${window.fmtHour(s.from)}`;
}

// All notifications, newest/soonest first.
function buildNotifications() {
  const t = window.t;
  const de = (window.__LANG || 'de') !== 'en';
  const list = [];

  // 1) Upcoming game nights
  for (const { session, dayOffset } of upcomingGameNights()) {
    if (dayOffset > 2) continue; // only surface the imminent ones in the bell
    const game = session.topGame;
    list.push({
      id: 'night_' + window.sessionKey(session),
      kind: 'night',
      glyph: '🎮',
      hue: 145,
      title: de ? 'Spieleabend steht an' : 'Game night coming up',
      body: `${nightTimeLabel(session)}${game ? ' · ' + game.title : ''} · ${t('n_dabei', session.friends.length)}`,
      sort: dayOffset * 1000 + session.from,
      session,
    });
  }

  // 2) Group memberships ("du wurdest zu einer Gruppe hinzugefügt")
  const myGroups = (window.GROUPS || []).filter(g => g.memberIds.includes('me'));
  for (const g of myGroups) {
    const ts = g.createdAt && g.createdAt.seconds ? g.createdAt.seconds * 1000 : 0;
    list.push({
      id: 'group_' + g.id,
      kind: 'group',
      glyph: g.glyph,
      hue: g.hue,
      title: de ? `Du bist in „${window.groupName(g)}"` : `You're in "${window.groupName(g)}"`,
      body: de ? `${g.memberIds.length} Mitglieder in dieser Gruppe`
               : `${g.memberIds.length} members in this group`,
      // Keep groups after the imminent game nights; newer groups first.
      sort: 100000 - (ts / 1e9),
      ts,
    });
  }

  // Game nights first (low sort), then groups (already 100000+)
  return list.sort((a, b) => a.sort - b.sort);
}

function notifUnreadCount() {
  const seen = notifSeenSet();
  return buildNotifications().filter(n => !seen.has(n.id)).length;
}

function notifMarkAllSeen() {
  const seen = notifSeenSet();
  for (const n of buildNotifications()) seen.add(n.id);
  notifSaveSeen(seen);
}

// ── Push scheduling ───────────────────────────────────────────────────────────
// Best-effort: while the app is open, fire a reminder `leadMin` minutes before a
// game night that is scheduled for today (anchored to the real wall clock).
function scheduleGameNightPushes(showToast) {
  (window.__pushTimers || []).forEach(clearTimeout);
  window.__pushTimers = [];

  if (!window.__MY_PUSH) return;
  if (typeof Notification !== 'undefined' && Notification.permission !== 'granted') return;

  const t = window.t;
  const de = (window.__LANG || 'de') !== 'en';
  const leadMin = Number(window.__MY_PUSH_LEAD) || 15;
  const leadMs = leadMin * 60000;
  const now = Date.now();

  for (const { session, dayOffset } of upcomingGameNights()) {
    if (dayOffset !== 0) continue; // only "today" can be anchored to the real clock
    const hourDec = ((session.from % 24) + 24) % 24;
    const start = new Date();
    start.setHours(Math.floor(hourDec), Math.round((hourDec - Math.floor(hourDec)) * 60), 0, 0);
    const fireAt = start.getTime() - leadMs;
    if (fireAt <= now || fireAt - now > 24 * 3600 * 1000) continue;

    const game = session.topGame;
    const title = de ? 'Spieleabend in Kürze' : 'Game night soon';
    const body = de
      ? `In ${leadMin} Min: ${window.fmtHour(session.from)}${game ? ' · ' + game.title : ''}`
      : `In ${leadMin} min: ${window.fmtHour(session.from)}${game ? ' · ' + game.title : ''}`;

    const id = setTimeout(() => {
      try {
        if (typeof Notification !== 'undefined' && Notification.permission === 'granted') {
          new Notification(title, { body, icon: 'icons/android-chrome-192x192.png' });
        }
      } catch (_) { /* ignore */ }
      showToast && showToast({ title, body, session });
    }, fireAt - now);
    window.__pushTimers.push(id);
  }
}

// ── Readiness-check prompts ───────────────────────────────────────────────────
// There's no server push, so — exactly like the game-night reminders — a ready
// check is delivered best-effort while the app is open: on each data refresh we
// scan the user's sessions for an active check someone ELSE started and, if they
// haven't answered it yet, fire a local notification + an in-app toast with
// Bereit / Komme actions. Seen checks are remembered so we prompt only once.
const READY_SEEN_KEY = 'zocker_ready_seen';

function readySeenSet() {
  try {
    const raw = localStorage.getItem(READY_SEEN_KEY);
    return new Set(raw ? JSON.parse(raw) : []);
  } catch (_) { return new Set(); }
}
function readySaveSeen(set) {
  // Keep this from growing forever — last 50 checks is plenty.
  try { localStorage.setItem(READY_SEEN_KEY, JSON.stringify([...set].slice(-50))); } catch (_) {}
}

function checkReadyPrompts(showToast) {
  const myUid = window.__FB_UID;
  if (!myUid) return;
  const t = window.t;
  const seen = readySeenSet();
  let changed = false;

  for (const { session } of (window.upcomingGameNights() || [])) {
    const check = window.sessionReadyCheckActive(session);
    if (!check) continue;
    if (check.by === myUid) continue;                       // I started it
    if (!window.sessionReadyTargets(session).includes('me')) continue; // I'm not accepted
    const id = window.sessionKey(session) + '_' + check.at;
    if (seen.has(id)) continue;
    seen.add(id); changed = true;
    if (window.sessionReadyStatus(session, 'me')) continue;  // already answered

    const whoName = window.FRIENDS_BY_ID[check.by]?.name || (t('teilnehmer') || 'Crew');
    const title = t('ready_ask_title');
    const body = t('ready_ask_body', whoName);
    try {
      if (typeof Notification !== 'undefined' && Notification.permission === 'granted') {
        new Notification(title, { body, icon: 'icons/android-chrome-192x192.png' });
      }
    } catch (_) { /* ignore */ }
    showToast && showToast({
      title, body, session,
      actions: [
        { label: t('ready_yes'),    color: 'var(--accent)',       ready: 'yes' },
        { label: t('ready_coming'), color: 'oklch(0.82 0.16 75)', ready: 'coming' },
      ],
    });
  }
  if (changed) readySaveSeen(seen);
}

// A sample toast for the demo / preview button.
function pushSampleToast() {
  const sessions = window.getSessions('all') || [];
  const session = sessions[0] || null;
  return {
    title: window.t('notif_title'),
    body: window.t('notif_body'),
    session,
  };
}

// ── Notifications screen (Glocke → list) ──────────────────────────────────────
function SheetNotifications({ open, onClose, onOpenSession }) {
  const t = window.t;
  const de = (window.__LANG || 'de') !== 'en';
  const Sheet = window.Sheet;
  const notifications = React.useMemo(() => (open ? buildNotifications() : []), [open]);

  React.useEffect(() => {
    if (open) {
      // Defer marking-seen a tick so the unread dot is correct on the frame
      // the user taps the bell, then clears.
      const id = setTimeout(() => notifMarkAllSeen(), 400);
      return () => clearTimeout(id);
    }
  }, [open]);

  return (
    <Sheet open={open} onClose={onClose} title={t('notif_title_screen')} fullHeight>
      <div style={{ padding: '0 16px 28px' }}>
        {notifications.length === 0 ? (
          <div style={{
            textAlign: 'center', padding: '48px 16px',
            color: 'var(--muted)', fontFamily: 'Space Grotesk, system-ui',
          }}>
            <div style={{ fontSize: 34, marginBottom: 12 }}>🔔</div>
            <div style={{ fontSize: 14, lineHeight: 1.5 }}>{t('notif_empty')}</div>
          </div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {notifications.map(n => {
              const clickable = n.kind === 'night' && n.session;
              return (
                <button key={n.id} type="button"
                  onClick={() => { if (clickable) { onClose(); onOpenSession?.(n.session); } }}
                  style={{
                    appearance: 'none', textAlign: 'left', width: '100%',
                    cursor: clickable ? 'pointer' : 'default',
                    background: 'var(--surface)', border: '1px solid var(--border)',
                    borderRadius: 16, padding: 14,
                    display: 'flex', alignItems: 'flex-start', gap: 12,
                  }}>
                  <div style={{
                    width: 40, height: 40, borderRadius: 11, flexShrink: 0,
                    background: `oklch(0.3 0.06 ${n.hue})`,
                    color: `oklch(0.88 0.16 ${n.hue})`,
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    fontSize: 20, border: `1px solid oklch(0.85 0.16 ${n.hue} / 0.4)`,
                  }}>{n.glyph}</div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{
                      fontFamily: 'Space Grotesk, system-ui', fontSize: 14, fontWeight: 600,
                      color: 'var(--text)', marginBottom: 3,
                    }}>{n.title}</div>
                    <div style={{
                      fontFamily: 'Space Grotesk, system-ui', fontSize: 12.5,
                      color: 'var(--muted)', lineHeight: 1.4,
                    }}>{n.body}</div>
                  </div>
                  {clickable && (
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none"
                      stroke="var(--muted)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
                      style={{ flexShrink: 0, marginTop: 2 }}><path d="M9 6l6 6-6 6"/></svg>
                  )}
                </button>
              );
            })}
          </div>
        )}
      </div>
    </Sheet>
  );
}

Object.assign(window, {
  buildNotifications, notifUnreadCount, notifMarkAllSeen, notifSeenSet,
  scheduleGameNightPushes, pushSampleToast, upcomingGameNights,
  checkReadyPrompts,
  SheetNotifications,
});
