/* NewsPage — the US economic-news calendar, part of the OPTIONS world. A read-only list of
   upcoming high- and medium-impact US data releases (the ForexFactory calendar): the CPI / NFP /
   FOMC / ISM prints that move index options. Only red (High) and orange (Medium) USD events are
   shown — low-impact and non-US noise is dropped upstream.

   Data source: the Supabase edge function `news-calendar`, which pulls ForexFactory's free weekly
   JSON server-side (no key), keeps US High/Medium, and returns normalized events. The browser
   can't call the feed directly (no CORS), so the proxy is required. The normalizer below also
   tolerates the raw jBlanked shape, so the source can be swapped without touching this page.

   This page is deliberately self-contained like SwingsPage: it owns its own fetch + polling and
   reads nothing from the trading tables. Times are shown in US Eastern, because these releases
   are scheduled off the US market clock. */

/* Accepts either the normalized shape the edge function is meant to return
   ({ name, currency, impact, datetime (ISO), category, actual, forecast, previous }) OR the raw
   jBlanked shape ({ Name, Currency, Impact, Date "YYYY.MM.DD HH:MM:SS", Actual, … }). Whichever
   arrives, one event object comes out — so the page keeps working if the proxy passes raw rows. */
const NW_num = (v) => {
  if (v == null || v === "" || v === "-") return null;
  const x = Number(String(v).replace(/[%,]/g, "").trim());
  return isNaN(x) ? String(v) : x;   // keep non-numeric strings (e.g. "1.2M") verbatim
};
/* jBlanked's default Date is GMT+3 with no offset marker ("2024.02.08 15:30:00"). If the field
   already looks like an ISO stamp (has a "T" or a timezone), trust it; otherwise read it as
   GMT+3 and pin it to a real instant so the ET conversion below is correct. */
const NW_toDate = (raw) => {
  if (!raw) return null;
  const s = String(raw).trim();
  if (/t/i.test(s) || /[zZ]|[+\-]\d\d:?\d\d$/.test(s)) {
    const d = new Date(s);
    return isNaN(d.getTime()) ? null : d;
  }
  const m = s.match(/^(\d{4})[.\-/](\d{2})[.\-/](\d{2})[ T](\d{2}):(\d{2})(?::(\d{2}))?$/);
  if (!m) { const d = new Date(s); return isNaN(d.getTime()) ? null : d; }
  const [, y, mo, d, h, mi, se] = m;
  // GMT+3 -> UTC is minus three hours.
  const ms = Date.UTC(+y, +mo - 1, +d, +h - 3, +mi, +(se || 0));
  return isNaN(ms) ? null : new Date(ms);
};
const NW_norm = (e) => {
  const impactRaw = String(e.impact ?? e.Impact ?? "").toLowerCase();
  const impact = impactRaw.indexOf("high") >= 0 ? "high"
               : (impactRaw.indexOf("med") >= 0 ? "medium" : (impactRaw || "low"));
  return {
    name: e.name ?? e.Name ?? e.event ?? "—",
    currency: (e.currency ?? e.Currency ?? "USD").toUpperCase(),
    category: e.category ?? e.Category ?? null,
    impact,
    when: NW_toDate(e.datetime ?? e.date ?? e.Date ?? e.time ?? e.Time),
    actual: NW_num(e.actual ?? e.Actual),
    forecast: NW_num(e.forecast ?? e.Forecast),
    previous: NW_num(e.previous ?? e.Previous),
  };
};

/* Formatters pinned to Amsterdam — Gianni reads the calendar on his own clock (auto-DST). */
const NW_ET_TIME = new Intl.DateTimeFormat("nl-NL", { timeZone: "Europe/Amsterdam", hour: "2-digit", minute: "2-digit", hour12: false });
const NW_ET_DAYKEY = new Intl.DateTimeFormat("en-CA", { timeZone: "Europe/Amsterdam", year: "numeric", month: "2-digit", day: "2-digit" });
const NW_ET_DAYLABEL = new Intl.DateTimeFormat("en-GB", { timeZone: "Europe/Amsterdam", weekday: "short", day: "2-digit", month: "short" });
const NW_time = (d) => (d ? NW_ET_TIME.format(d) : "—");
const NW_dayKey = (d) => (d ? NW_ET_DAYKEY.format(d) : "0000-00-00");
const NW_todayKey = () => NW_ET_DAYKEY.format(new Date());
const NW_val = (v) => (v == null ? "—" : (typeof v === "number" ? String(v) : v));

/* Impact chip: red dot + HIGH for high-impact, orange dot + MED for medium. Reuses the same
   two hue pairs SwingsPage uses — loss (red) and dryrun (amber) — so nothing new is invented. */
const NW_IMPACT = {
  high: { label: "HIGH", c: "var(--loss)", bg: "var(--loss-bg)" },
  medium: { label: "MED", c: "var(--dryrun)", bg: "var(--dryrun-bg)" },
};
function NW_ImpactPill({ impact }) {
  const m = NW_IMPACT[impact] || NW_IMPACT.medium;
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 6, height: 22, padding: "0 9px", borderRadius: 999,
                   background: m.bg, color: m.c, font: "var(--w-semibold) var(--t-2xs)/1 var(--font-sans)", letterSpacing: "var(--ls-caps)", textTransform: "uppercase", flex: "none" }}>
      <span style={{ width: 6, height: 6, borderRadius: "50%", background: m.c }}></span>{m.label}
    </span>
  );
}

function NewsPage() {
  const NT = window.NitroTraderDesignSystem_95e598;
  const isMobile = useIsMobile();
  const [rows, setRows] = React.useState(null);   // null = loading, [] = loaded-empty
  const [err, setErr] = React.useState(null);

  const load = React.useCallback(async () => {
    const SB = window.NT_SUPABASE || {};
    if (!SB.url) { setErr("not configured"); setRows([]); return; }
    // Prefer the signed-in user's JWT (same pattern as AdvisorPage / AppMobile); fall back to
    // the anon key so the page still works before auth resolves.
    let jwt = SB.key;
    try { const s = await window.NT_CLIENT.auth.getSession(); if (s && s.data && s.data.session) jwt = s.data.session.access_token; } catch (e) {}
    try {
      const r = await fetch((SB.url || "") + "/functions/v1/news-calendar", {
        headers: { apikey: SB.key, Authorization: "Bearer " + jwt },
      });
      if (!r.ok) throw new Error("HTTP " + r.status);
      const j = await r.json();
      const list = Array.isArray(j) ? j : (j.events || j.data || []);
      setErr(null);
      setRows(list.map(NW_norm));
    } catch (e) {
      setErr(e.message || String(e));
      setRows((prev) => prev || []);   // keep whatever we last had rather than blanking
    }
  }, []);

  // Refetch every five minutes — the calendar barely moves and jBlanked is rate-limited, so
  // there is no reason to poll it hard from the browser.
  React.useEffect(() => { load(); const t = setInterval(load, 300000); return () => clearInterval(t); }, [load]);
  React.useEffect(() => { if (window.lucide) window.lucide.createIcons(); });

  if (rows == null) return <div style={{ color: "var(--text-tertiary)", font: "var(--w-regular) var(--t-sm)/1 var(--font-sans)" }}>Loading…</div>;

  // Upcoming first (what's coming), then the archive below (what printed, with actuals) —
  // the events table keeps history forever, so this list grows week by week.
  const now = Date.now();
  const relevant = rows.filter((e) => e.impact === "high" || e.impact === "medium");
  const upcoming = relevant
    .filter((e) => e.when == null || e.when.getTime() >= now - 3600000)   // 1h grace so a just-released print lingers
    .sort((a, b) => (a.when ? a.when.getTime() : Infinity) - (b.when ? b.when.getTime() : Infinity));
  const past = relevant
    .filter((e) => e.when != null && e.when.getTime() < now - 3600000)
    .sort((a, b) => b.when.getTime() - a.when.getTime());

  // Group by ET day so a busy week reads as days, not a wall of rows (mirrors SwingsPage alerts).
  const todayKey = NW_todayKey();
  const NW_group = (list, desc) => {
    const by = {};
    list.forEach((e) => { const k = NW_dayKey(e.when); (by[k] = by[k] || []).push(e); });
    const keys = Object.keys(by).sort();
    if (desc) keys.reverse();
    return keys.map((k) => ({
      key: k,
      label: k === todayKey ? "Today · " + (by[k][0].when ? NW_ET_DAYLABEL.format(by[k][0].when) : k)
           : (by[k][0].when ? NW_ET_DAYLABEL.format(by[k][0].when) : "Unscheduled"),
      rows: by[k],
    }));
  };
  const groups = NW_group(upcoming, false);
  const pastGroups = NW_group(past, true);

  const emptyBox = (text) => (
    <div style={{ padding: "40px 20px", textAlign: "center", color: "var(--text-tertiary)", font: "var(--w-regular) var(--t-sm)/1.6 var(--font-sans)" }}>{text}</div>
  );
  const valStyle = { font: "var(--w-regular) var(--t-xs)/1 var(--font-mono)", fontVariantNumeric: "tabular-nums" };
  const valLabel = { font: "var(--w-medium) var(--t-2xs)/1 var(--font-sans)", letterSpacing: "var(--ls-wide)", textTransform: "uppercase", color: "var(--text-tertiary)" };

  // One event = one card row: time · name (+ category) · impact pill · forecast / previous / actual.
  const valTriple = (e) => (
    <div style={{ display: "flex", gap: isMobile ? 14 : 22, flexWrap: "wrap" }}>
      {[["forecast", e.forecast], ["previous", e.previous], ["actual", e.actual]].map(([lab, v]) => (
        <span key={lab} style={{ display: "flex", flexDirection: "column", gap: 3, minWidth: 46 }}>
          <span style={valLabel}>{lab}</span>
          <span style={{ ...valStyle, color: lab === "actual" && v != null ? "var(--text-primary)" : "var(--text-secondary)" }}>{NW_val(v)}</span>
        </span>
      ))}
    </div>
  );

  const eventRow = (e, i) => (
    <div key={i} style={{ background: "var(--surface-card)", border: "1px solid var(--border)",
          borderLeft: "3px solid " + (NW_IMPACT[e.impact] || NW_IMPACT.medium).c, borderRadius: "var(--radius-md)",
          padding: isMobile ? "12px 14px" : "14px 16px",
          display: isMobile ? "flex" : "grid", flexDirection: "column",
          gridTemplateColumns: isMobile ? undefined : "88px 1fr auto", gap: isMobile ? 8 : 16, alignItems: isMobile ? "stretch" : "center" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 10, minWidth: 0 }}>
        <span style={{ font: "var(--w-regular) var(--t-sm)/1 var(--font-mono)", fontVariantNumeric: "tabular-nums", color: "var(--text-secondary)", flex: "none" }}>{NW_time(e.when)}</span>
        {isMobile ? <NW_ImpactPill impact={e.impact} /> : null}
      </div>
      <div style={{ minWidth: 0 }}>
        <div style={{ font: "var(--w-medium) var(--t-body)/1.2 var(--font-sans)", color: "var(--text-primary)", overflow: "hidden", textOverflow: "ellipsis" }}>{e.name}</div>
        {e.category ? <div style={{ marginTop: 3, font: "var(--w-regular) var(--t-2xs)/1 var(--font-sans)", color: "var(--text-tertiary)" }}>{e.category}</div> : null}
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: isMobile ? 14 : 20, justifyContent: isMobile ? "space-between" : "flex-end" }}>
        {valTriple(e)}
        {isMobile ? null : <NW_ImpactPill impact={e.impact} />}
      </div>
    </div>
  );

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: "var(--gap-grid)" }}>
      <PageHead title="News" subtitle="US economic calendar · high & medium impact · your local time"
        right={<span style={{ display: "inline-flex", alignItems: "center", gap: 8, font: "var(--w-regular) var(--t-xs)/1 var(--font-sans)", color: "var(--text-tertiary)" }}>
          <Ico name="calendar-clock" size={16} color="var(--text-tertiary)" />
          {err ? "feed unavailable" : (upcoming.length + " upcoming")}
        </span>} />

      {groups.length === 0
        ? <NT.Card padding={20}>{emptyBox(err
            ? "Couldn’t reach the news feed right now — the calendar service isn’t answering. It’ll fill back in on the next refresh; red and orange US events show up here."
            : "No high- or medium-impact US events on the calendar right now. Check back — CPI, NFP and FOMC days will appear here as they’re scheduled.")}</NT.Card>
        : groups.map((g) => (
          <div key={g.key} style={{ display: "flex", flexDirection: "column", gap: 8 }}>
            <span style={{ font: "var(--w-medium) var(--t-2xs)/1 var(--font-sans)", letterSpacing: "var(--ls-caps)", textTransform: "uppercase", color: "var(--text-tertiary)", padding: "2px 2px 0" }}>{g.label}</span>
            {g.rows.map((e, i) => eventRow(e, g.key + "-" + i))}
          </div>
        ))}

      {pastGroups.length > 0 && (
        <div style={{ display: "flex", flexDirection: "column", gap: "var(--gap-grid)", opacity: 0.75 }}>
          <span style={{ font: "var(--w-medium) var(--t-2xs)/1 var(--font-sans)", letterSpacing: "var(--ls-caps)", textTransform: "uppercase", color: "var(--text-tertiary)", borderTop: "1px solid var(--border)", paddingTop: 14, marginTop: 6 }}>Earlier — the archive (grows forever)</span>
          {pastGroups.map((g) => (
            <div key={"p" + g.key} style={{ display: "flex", flexDirection: "column", gap: 8 }}>
              <span style={{ font: "var(--w-medium) var(--t-2xs)/1 var(--font-sans)", letterSpacing: "var(--ls-caps)", textTransform: "uppercase", color: "var(--text-tertiary)", padding: "2px 2px 0" }}>{g.label}</span>
              {g.rows.map((e, i) => eventRow(e, "p" + g.key + "-" + i))}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}
