// MissedCallAlert.jsx — full-screen takeover when the store line rings and
// nobody picks up (v15.03).
//
// Source of truth is the `phone_calls` table, written by the phone-calls-webhook
// edge function from Quo's `call.missed` event. We subscribe to INSERTs (the
// table joined supabase_realtime in the v15.03 migration) rather than polling,
// so the alert lands within a second of the phone giving up.
//
// Two guards worth keeping:
//   • FRESHNESS — only calls that started within MISSED_FRESH_MS raise the
//     alert. Realtime can replay on reconnect, and a backfill/webhook retry
//     would otherwise scream about a call from last Tuesday.
//   • AUTO-EXPIRY — the overlay clears itself after MISSED_EXPIRE_MS even if
//     nobody taps Dismiss, so an unattended iPad isn't stuck behind a takeover
//     (and muted) all night.
//
// Colour/icon come from the configured theme (Settings → Alerts), resolved via
// window.AlertSound. All top-level idents are mc/Mc-prefixed — Babel-standalone
// shares one global scope (HANDBOOK §9).

const { useState: useMc, useEffect: useMcEffect } = React;

const MISSED_FRESH_MS = 3 * 60 * 1000;    // ignore anything older than this
const MISSED_EXPIRE_MS = 10 * 60 * 1000;  // self-clear after this

const mcFmtTime = (iso) => {
  try {
    return new Date(iso).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
  } catch (e) { return ''; }
};

// "15 minutes" / "1 minute" / "1 hour" — the configured callback window in
// words. Falls back to 30 (the column default) for a missing/garbage value.
const mcWindowLabel = (min) => {
  const n = Number(min);
  const m = Number.isFinite(n) && n > 0 ? Math.round(n) : 30;
  if (m === 60) return '1 hour';
  if (m > 60 && m % 60 === 0) return (m / 60) + ' hours';
  return m + (m === 1 ? ' minute' : ' minutes');
};

// "+18322261805" → "(832) 226-1805". Falls back to the raw string.
const mcFmtPhone = (raw) => {
  const d = String(raw || '').replace(/[^\d]/g, '');
  if (d.length === 11 && d.startsWith('1')) return `(${d.slice(1, 4)}) ${d.slice(4, 7)}-${d.slice(7)}`;
  if (d.length === 10) return `(${d.slice(0, 3)}) ${d.slice(3, 6)}-${d.slice(6)}`;
  return raw || 'Unknown number';
};

// Returns [call | null, dismiss]. `call` = { id, number, at }.
//
// `storeLine` is restaurants.quo_store_number_id — the ONE Quo number the crew
// answers. The workspace has others (personal/second lines) and v15.03 popped
// this takeover for all of them (v15.06 fix). Realtime's `filter` param takes
// a single expression, already spent on restaurant_id, so the line test runs
// here on the payload. Falsy storeLine = don't filter, matching the webhook's
// fail-open rule: better one stray alert than a silent alert outage.
// Scored hours (v16.12). An unanswered ring before opening or after close is
// nobody failing to pick up, so it must not put a full-screen alert on the
// line either — the alert and the score have to agree about what a miss is.
// Same half-open [open, close) test as StorePhone.jsx and the `missed` CTE in
// scorecard_snapshot_range; change one, change all three (§9).
const mcInHours = (iso, openH, closeH) => {
  const h = new Date(iso).getHours();
  return h >= openH && h < closeH;
};

const useMissedCallAlert = (storeLine, openHour, closeHour) => {
  const [call, setCall] = useMc(null);

  useMcEffect(() => {
    if (!window.supa) return;
    const ch = window.supa
      .channel('ipad-missed-calls:' + window.RESTAURANT_ID)
      .on('postgres_changes', {
        event: 'INSERT', schema: 'public', table: 'phone_calls',
        filter: 'restaurant_id=eq.' + window.RESTAURANT_ID,
      }, (payload) => {
        const r = payload.new;
        if (!r) return;
        if (r.direction !== 'incoming' || r.answered) return;
        if (storeLine && r.quo_phone_number_id !== storeLine) return;
        // Fails open like the line filter above: unset hours alert on
        // everything rather than going silently deaf.
        if (openHour != null && closeHour != null
            && !mcInHours(r.started_at, openHour, closeHour)) return;
        const started = new Date(r.started_at || 0).getTime();
        if (!Number.isFinite(started) || Date.now() - started > MISSED_FRESH_MS) return;
        setCall({ id: r.id, number: r.external_number || '', at: r.started_at });
      })
      .subscribe();
    return () => window.supa.removeChannel(ch);
  }, [storeLine]);

  // Self-clear so the takeover can't camp on the screen indefinitely.
  useMcEffect(() => {
    if (!call) return;
    const t = setTimeout(() => setCall(null), MISSED_EXPIRE_MS);
    return () => clearTimeout(t);
  }, [call && call.id]);

  return [call, () => setCall(null)];
};

// `windowMinutes` comes from restaurants.call_match_window_minutes (Store
// Phone page) rather than being baked in — otherwise changing the callback
// requirement would leave this screen telling the crew the wrong number
// (v15.04).
const MissedCallAlert = ({ call, theme, onDismiss, windowMinutes }) => {
  const [mounted, setMounted] = useMc(false);
  useMcEffect(() => {
    const raf = requestAnimationFrame(() => setMounted(true));
    return () => cancelAnimationFrame(raf);
  }, []);

  const th = theme || { bg: '#78350F', accent: '#D97706', fg: '#FFFFFF', icon: 'ri-phone-lock-line' };

  return (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 902, // above temp (901) + checklist (900)
      background: th.bg, color: th.fg,
      display: 'flex', flexDirection: 'column',
      padding: '64px 56px 52px',
      overflow: 'hidden',
      transition: 'opacity 260ms ease-out, transform 260ms ease-out',
      opacity: mounted ? 1 : 0,
      transform: mounted ? 'scale(1)' : 'scale(0.97)',
    }}>
      {/* Decorative rings, matching the other takeovers' visual language. */}
      <div style={{
        position: 'absolute', top: -180, right: -180, width: 480, height: 480,
        borderRadius: '50%', border: '1px solid rgba(255,255,255,0.12)', pointerEvents: 'none',
      }} />
      <div style={{
        position: 'absolute', top: -100, right: -100, width: 320, height: 320,
        borderRadius: '50%', border: '1px solid rgba(255,255,255,0.10)', pointerEvents: 'none',
      }} />

      <div style={{ display: 'flex', alignItems: 'center', gap: 14, position: 'relative', zIndex: 1 }}>
        <i className={th.icon} style={{ fontSize: 24, lineHeight: 1 }} />
        <div style={{ fontSize: 13, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase' }}>
          Missed call
        </div>
      </div>

      <div style={{ height: 1, background: 'rgba(255,255,255,0.18)', marginTop: 22, marginBottom: 28, position: 'relative', zIndex: 1 }} />

      <div style={{ fontSize: 16, opacity: 0.8, position: 'relative', zIndex: 1, fontFamily: 'var(--font-num)' }}>
        {mcFmtTime(call.at)}
      </div>

      <div style={{
        flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center',
        position: 'relative', zIndex: 1,
      }}>
        <div style={{
          fontSize: 56, fontWeight: 500, letterSpacing: '-0.03em', lineHeight: 1.12,
          fontFamily: 'var(--font-num)',
        }}>
          {mcFmtPhone(call.number)}
        </div>
        <div style={{ fontSize: 18, opacity: 0.78, marginTop: 14, maxWidth: 620 }}>
          Nobody picked up the store line. Call them back within {mcWindowLabel(windowMinutes)} and
          it won't count as a missed call.
        </div>
      </div>

      <div style={{ display: 'flex', gap: 14, position: 'relative', zIndex: 1 }}>
        <button onClick={onDismiss} style={{
          flex: 1, height: 88, borderRadius: 20,
          background: '#FFFFFF', color: th.bg,
          fontSize: 24, fontWeight: 600, letterSpacing: '-0.01em',
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 12,
          cursor: 'pointer', border: 'none',
          boxShadow: '0 12px 32px rgba(0,0,0,0.25)',
        }}>
          Got it
        </button>
      </div>
    </div>
  );
};

Object.assign(window, { MissedCallAlert, useMissedCallAlert, mcFmtPhone, mcWindowLabel });
