/* screen-insights.jsx — Insights (F10): month recap, 6-month bars, days-to-paid,
   top clients → window.QScreens.Insights. Pushed route under Home. */
(function () {
  const Q = window.Q, LIB = window.LIB, Icons = window.Icons;
  const { Icon } = Icons;
  const { useState, useEffect, useRef } = React;
  const { AppBar, Card, Kicker, AnimatedNumber, Button, Avatar, EntityLink, Money, Row } = Q;
  const t = window.t;

  /* Reduced-motion gate (mirrors primitives' REDUCE; locally scoped). */
  const REDUCE = () => typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;

  /* Count-up for a PLAIN number (the day count earns a reveal, but it's not money
     so AnimatedNumber — which always renders currency — can't carry it). render(n)
     formats the integer back into its i18n string. Counts up once per session per key. */
  const seenDays = new Map();
  function CountUp({ value, animKey, duration = 750, render, ...rest }) {
    const seen = animKey != null && seenDays.get(animKey) === value;
    const [disp, setDisp] = useState(seen ? value : 0);
    const rafRef = useRef(null);
    useEffect(() => {
      if (animKey != null) seenDays.set(animKey, value);
      if (REDUCE() || seen) { setDisp(value); return; }
      const t0 = performance.now();
      const tick = (now) => {
        const p = Math.min(1, (now - t0) / duration);
        const e = 1 - Math.pow(1 - p, 3);
        setDisp(Math.round(value * e));
        if (p < 1) rafRef.current = requestAnimationFrame(tick);
      };
      rafRef.current = requestAnimationFrame(tick);
      return () => cancelAnimationFrame(rafRef.current);
    }, [value]);
    return <div {...rest}>{render(disp)}</div>;
  }

  /* Same legend markup as Home's split-bar legend (gold/success). */
  function Legend({ color, label }) {
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
        <span style={{ width: 8, height: 8, borderRadius: 2, background: color }} />
        <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--color-text-3)" }}>{label}</span>
      </div>
    );
  }

  function Insights({ api }) {
    const { db, today } = api;
    const ins = LIB.insights(db, today);
    const cur = db.COMPANY.currency;
    const now = ins.months[ins.months.length - 1];

    /* {count} in the recap body = invoices that went out this month. */
    const issuedCount = (db.INVOICES || []).filter(i =>
      i.kind === "invoice" && i.status !== "draft" && String(i.issued || "").slice(0, 7) === now.key
    ).length;

    /* Brief copy→check swap on the button confirms the recap copied, in-place,
       on top of the toast — a tactile "share your month" demo beat. */
    const [copied, setCopied] = useState(false);
    const copiedRef = useRef(null);
    useEffect(() => () => clearTimeout(copiedRef.current), []);
    const copyRecap = () => {
      try { navigator.clipboard.writeText(LIB.recapText(db, today)); } catch (e) { /* clipboard may be unavailable; the toast still confirms the intent */ }
      Q.toast(t("insights.shared"), "copy");
      setCopied(true);
      clearTimeout(copiedRef.current);
      copiedRef.current = setTimeout(() => setCopied(false), 1400);
    };

    /* Bars scale: max across BOTH series, never below 1 (empty months keep a sane axis).
       A single blowout month on a LINEAR axis flattens every other bar to a sliver
       (insights-chart-outlier-flattens-bars). Map heights through sqrt so a 10× month
       reads ~3× taller — small months stay legible while the tallest still wins. The
       axis tick still shows the true money max; only the visual encoding is compressed. */
    const max = Math.max(1, ...ins.months.map(mo => Math.max(mo.billed, mo.collected)));
    const sqrtMax = Math.sqrt(max);
    const barH = v => (v > 0 ? Math.max(3, Math.round((Math.sqrt(v) / sqrtMax) * 72)) : 0);

    /* Choreography: the recap numbers count up first; the bars then grow left→right
       (220ms base + 40ms/col), and the current-month value labels settle on top once
       the final bar has finished. One source of truth so the cascade stays in sync. */
    const BAR_BASE = 220, BAR_STEP = 40, BAR_DUR = 500;
    const labelDelay = BAR_BASE + (ins.months.length - 1) * BAR_STEP + BAR_DUR;

    /* daysToPaidAvg is null when nothing has been paid yet — "0 days" reads as
       "instant payment" which is a lie (days-to-paid-zero-empty-state). Branch the
       card on hasDaysToPaid and show guidance instead of a fake zero. */
    const hasDaysToPaid = ins.daysToPaidAvg != null;
    const d = hasDaysToPaid ? ins.daysToPaidAvg : 0;

    /* MoM (harsh-mom-down-narrative / insights-partial-month-crash render half):
       LIB already computes delta as MTD-vs-same-elapsed-window, so the number is
       honest on a partial month. The remaining job here is TONE: a partial-month
       dip is not an alarm, so we drop the alert-red terracotta on a "down" arrow
       while the month is still incomplete and add a quiet "N days in" caption so
       the comparison is legible as month-to-date, not a finished verdict. */
    const incomplete = ins.mom.incomplete;
    const delta = ins.mom.deltaPct;
    const isDown = delta != null && delta < 0;
    const momKey = delta == null || delta === 0 ? "insights.mom.flat" : delta > 0 ? "insights.mom.up" : "insights.mom.down";
    /* down + still-incomplete month → neutral text-3 (not terracotta alert) */
    const downColor = incomplete ? "var(--color-text-3)" : "var(--color-accent)";
    const momIcon = delta == null || delta === 0 ? null
      : delta > 0 ? { name: "arrowUpRight", color: "var(--color-success)" }
      : { name: "trendingDown", color: downColor };
    const momTextColor = isDown && incomplete ? "var(--color-text-3)" : "var(--color-text-2)";

    return (
      <div style={{ paddingBottom: 4 }}>
        <AppBar large onBack={() => api.back()} title={t("insights.title")} kicker={t("insights.kicker")} />

        <div style={{ padding: "4px 16px 0", display: "flex", flexDirection: "column", gap: 16 }}>
          {/* ── Recap card (display money: serif gold; status = tone dot) ── */}
          <Card pad={18}>
            <Kicker>{t("insights.recap.kicker")}</Kicker>
            <div style={{ fontFamily: "var(--font-serif)", fontWeight: 600, fontSize: 24, letterSpacing: "-0.02em", lineHeight: 1.1, color: "var(--color-text-1)", marginTop: 8 }}>
              {t("insights.recap.title", { month: t("monfull." + now.m) })}
            </div>
            <div style={{ display: "flex", gap: 24, marginTop: 16 }}>
              <div>
                {/* MoneyDisplay voice (Fraunces 600 gold) + count-up session memory */}
                <AnimatedNumber amount={now.billed} currency={cur} cents={false} size={22} color="var(--color-gold)" animKey="ins.billed"
                  style={{ fontFamily: "var(--font-serif)", lineHeight: 1.05, letterSpacing: "-0.02em" }} />
                <div style={{ display: "flex", alignItems: "center", gap: 5, marginTop: 4 }}>
                  <span aria-hidden="true" style={{ width: 5, height: 5, borderRadius: "50%", background: "var(--color-gold)", flexShrink: 0 }} />
                  <Kicker>{t("insights.billed")}</Kicker>
                </div>
                {/* MoM is a billed-revenue delta: it lives under BILLED (up = success;
                   down = terracotta, but neutral text-3 while the month is incomplete
                   so a partial-month dip doesn't read as a red alarm). */}
                <div style={{ display: "flex", alignItems: "center", gap: 4, marginTop: 8 }}>
                  {momIcon && <Icon name={momIcon.name} size={14} color={momIcon.color} />}
                  <span style={{ fontSize: 12, color: momTextColor }}>
                    {t(momKey, { pct: Math.abs(delta == null ? 0 : delta) })}
                  </span>
                </div>
                {/* "N days in" makes clear the comparison is month-to-date vs the same
                   window last month, not a finished month. Only while incomplete. */}
                {incomplete && (
                  <div style={{ fontSize: 11, color: "var(--color-text-3)", marginTop: 3 }}>
                    {t("insights.mom.daysin", { d: ins.mom.daysElapsed })}
                  </div>
                )}
              </div>
              <div>
                {/* Collected = money that actually LANDED, so it reads in success-green
                   to match its dot (collected-gold-number-green-dot: a gold number over
                   a green dot mixed the "billed/owed" and "paid/in" semantics). Billed
                   stays gold — money invoiced, not yet collected. */}
                <AnimatedNumber amount={now.collected} currency={cur} cents={false} size={22} color="var(--color-success)" animKey="ins.collected"
                  style={{ fontFamily: "var(--font-serif)", lineHeight: 1.05, letterSpacing: "-0.02em" }} />
                <div style={{ display: "flex", alignItems: "center", gap: 5, marginTop: 4 }}>
                  <span aria-hidden="true" style={{ width: 5, height: 5, borderRadius: "50%", background: "var(--color-success)", flexShrink: 0 }} />
                  <Kicker>{t("insights.collected")}</Kicker>
                </div>
              </div>
            </div>
            <div style={{ fontSize: 13, color: "var(--color-text-2)", lineHeight: 1.5, marginTop: 12 }}>
              {t("insights.recap.count", { count: issuedCount })}
            </div>
            <div style={{ marginTop: 16 }}>
              <Button variant="secondary" size="md" icon={copied ? "check" : "copy"} onClick={copyRecap}>
                {copied ? t("insights.shared") : t("insights.share")}
              </Button>
            </div>
          </Card>

          {/* ── 6-month double-bar chart ───────────────────────────── */}
          <Card pad={18}>
            <Kicker>{t("insights.6months")}</Kicker>
            {/* paddingTop 16 = headroom for the max tick + current-month values;
               track spans 16..88 inside the padding box, so gridline (50%) = 52, baseline = 88 */}
            <div style={{ position: "relative", marginTop: 16, paddingTop: 16 }}>
              <span style={{ position: "absolute", right: 0, top: 0, fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--color-text-3)" }}>
                {window.fmtMoney(max, cur, { cents: false })}
              </span>
              <div aria-hidden="true" style={{ position: "absolute", left: 0, right: 0, top: 52, height: 1, background: "var(--color-border)" }} />
              <div aria-hidden="true" style={{ position: "absolute", left: 0, right: 0, top: 88, height: 1, background: "var(--color-border)" }} />
              {/* paddingRight 6 + right-anchored annotation on the final column keep the € labels off the card edge */}
              <div style={{ position: "relative", display: "flex", alignItems: "flex-end", gap: 8, paddingRight: 6 }}>
                {ins.months.map((mo, i) => (
                  <div key={mo.key} role="img"
                    aria-label={t("insights.bar.aria", {
                      month: t("mon." + mo.m),
                      billed: window.fmtMoney(mo.billed, cur, { cents: false }),
                      collected: window.fmtMoney(mo.collected, cur, { cents: false }),
                    })}
                    style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 8, minWidth: 0 }}>
                    <div style={{ height: 72, display: "flex", alignItems: "flex-end", gap: 4, position: "relative" }}>
                      {i === ins.months.length - 1 && (
                        /* values fade in only after the final bar has grown, so they settle on top instead of floating in empty space */
                        <div aria-hidden="true" style={{ position: "absolute", bottom: Math.max(barH(mo.billed), barH(mo.collected)) + 4, right: 0, display: "flex", flexDirection: "column", alignItems: "flex-end", fontFamily: "var(--font-mono)", fontSize: 10, lineHeight: 1.3, whiteSpace: "nowrap", animation: "q-fade .3s ease both", animationDelay: labelDelay + "ms" }}>
                          <span style={{ color: "var(--color-gold)" }}>{window.fmtMoney(mo.billed, cur, { cents: false })}</span>
                          <span style={{ color: "var(--color-text-1)" }}>{window.fmtMoney(mo.collected, cur, { cents: false })}</span>
                        </div>
                      )}
                      <div style={{ width: 12, height: barH(mo.billed), borderRadius: "2px 2px 0 0", background: "var(--color-gold)", transformOrigin: "bottom", animation: "q-bar-v .5s cubic-bezier(.32,.72,0,1) both", animationDelay: (BAR_BASE + i * BAR_STEP) + "ms" }} />
                      <div style={{ width: 12, height: barH(mo.collected), borderRadius: "2px 2px 0 0", background: "var(--color-success)", transformOrigin: "bottom", animation: "q-bar-v .5s cubic-bezier(.32,.72,0,1) both", animationDelay: (BAR_BASE + i * BAR_STEP) + "ms" }} />
                    </div>
                    <span style={{ fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--color-text-3)", whiteSpace: "nowrap" }}>{t("mon." + mo.m)}</span>
                  </div>
                ))}
              </div>
            </div>
            <div style={{ display: "flex", gap: 16, marginTop: 16 }}>
              <Legend color="var(--color-gold)" label={t("insights.billed")} />
              <Legend color="var(--color-success)" label={t("insights.collected")} />
            </div>
          </Card>

          {/* ── Days to get paid (serif display / not gold: a day count is NOT money, so text-1) ── */}
          <Card pad={18}>
            <Kicker>{t("insights.daysToPaid")}</Kicker>
            {hasDaysToPaid ? (
              /* a day count isn't money, but it still earns a count-up reveal (text-1, not gold) */
              <CountUp value={d} animKey="ins.daysToPaid"
                style={{ marginTop: 8, fontFamily: "var(--font-serif)", fontWeight: 600, fontSize: 30, letterSpacing: "-0.02em", color: "var(--color-text-1)" }}
                render={(n) => t("insights.daysToPaid.value", { d: n })} />
            ) : (
              /* No invoice has been paid yet → "0 days" would read as instant payment.
                 Show guidance instead (days-to-paid-zero-empty-state). */
              <div style={{ marginTop: 8, fontSize: 13, color: "var(--color-text-3)", lineHeight: 1.5 }}>
                {t("insights.daysToPaid.empty")}
              </div>
            )}
          </Card>

          {/* ── Top clients ────────────────────────────────────────── */}
          {ins.topClients.length > 0 && (
            <div>
              {/* The list is aggregated YEAR-TO-DATE (lib filters issued-year === current);
                 without a timeframe "Top clients" reads as all-time and the totals don't
                 reconcile with anything (top-clients-no-timeframe). State the window. */}
              <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 8, marginBottom: 8 }}>
                <Kicker>{t("insights.topClients")}</Kicker>
                <span style={{ fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--color-text-3)", whiteSpace: "nowrap" }}>{t("tax.period.ytd")}</span>
              </div>
              <Card pad={0}>
                {ins.topClients.map((c, i) => (
                  <Row key={c.id} last={i === ins.topClients.length - 1}
                    onClick={() => api.goTab("clients", "client", { id: c.id })}
                    style={{ animation: "q-fade-up .4s cubic-bezier(.32,.72,0,1) both", animationDelay: (0.04 + i * 0.05) + "s" }}>
                    {/* ordinal rank turns "a list" into "a ranking" — mono, muted, gold for #1 */}
                    <span aria-hidden="true" style={{ width: 16, flexShrink: 0, textAlign: "center", fontFamily: "var(--font-mono)", fontSize: 12, fontWeight: 600, color: i === 0 ? "var(--color-gold)" : "var(--color-text-3)" }}>{i + 1}</span>
                    <Avatar name={c.name} size={34} />
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <EntityLink kind="client" name={c.name} icon={null} size={14}
                        onClick={() => api.goTab("clients", "client", { id: c.id })} />
                    </div>
                    <Money amount={c.total} currency={cur} cents={false} size={14} />
                  </Row>
                ))}
              </Card>
            </div>
          )}
        </div>
      </div>
    );
  }

  window.QScreens = Object.assign(window.QScreens || {}, { Insights });
})();
