/* global React */

// ──────────────────────────────────────────────────────────────
// Formatters
// ──────────────────────────────────────────────────────────────
const fmtBRL = (n) => Number(n || 0).toLocaleString("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const fmtInt = (n) => Number(n || 0).toLocaleString("pt-BR");
const fmtDec = (n, d = 2) => Number(n || 0).toLocaleString("pt-BR", { minimumFractionDigits: d, maximumFractionDigits: d });
const fmtPct = (n) => {
  const v = Number(n || 0);
  const sign = v >= 0 ? "+" : "";
  return `${sign}${fmtDec(v, 1)}%`;
};
const deltaPct = (now, prev) => {
  if (!prev || prev === 0) return { delta: now > 0 ? "+∞" : "0%", dir: (now || 0) >= 0 ? "up" : "down" };
  const d = ((now - prev) / Math.abs(prev)) * 100;
  return { delta: fmtPct(d), dir: d >= 0 ? "up" : "down" };
};
const deltaAbs = (now, prev, formatter = fmtBRL, prefix = "") => {
  const d = (now || 0) - (prev || 0);
  const sign = d >= 0 ? "+" : "−";
  return { delta: `${sign}${prefix}${formatter(Math.abs(d))}`, dir: d >= 0 ? "up" : "down" };
};

const ddmmFromIso = (s) => /^\d{4}-\d{2}-\d{2}$/.test(s) ? `${s.slice(8, 10)}/${s.slice(5, 7)}` : s;

// ──────────────────────────────────────────────────────────────
// Sparkline
// ──────────────────────────────────────────────────────────────
function Sparkline({ values = [], color, dark, width = 120, height = 28 }) {
  if (!values.length) return <svg width={width} height={height} />;
  const cum = []; let s = 0;
  for (let i = 0; i < values.length; i++) { s += values[i] || 0; cum.push(s); }
  const max = Math.max(...cum, 1);
  const pad = 2;
  const pts = cum.map((v, i) => [
    pad + ((width - pad * 2) * i) / (Math.max(cum.length - 1, 1)),
    pad + (height - pad * 2) * (1 - v / max),
  ]);
  const d = "M " + pts.map((p) => p.join(",")).join(" L ");
  return (
    <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
      <path d={d} fill="none" stroke={color} strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" opacity={dark ? 0.7 : 0.55} />
    </svg>
  );
}

function Delta({ value, dir, mono = true }) {
  const positive = dir === "up";
  const color = positive ? "var(--pos)" : "var(--neg)";
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 4, color, fontFamily: mono ? "'JetBrains Mono', monospace" : "inherit", fontSize: 12, letterSpacing: "0.02em" }}>
      <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke={color} strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round">
        {positive
          ? <path d="M2 6 L5 3 L8 6 M5 3 V8" />
          : <path d="M2 4 L5 7 L8 4 M5 7 V2" />}
      </svg>
      {value}
    </span>
  );
}

function KpiTile({ label, value, prefix, suffix, delta, dir, sparkValues, sparkColor, compare, comparativeValue, dark, live, loading }) {
  return (
    <div className="kpi">
      <div className="kpi__head">
        <span className="kpi__label">{label}</span>
        {live && <span className="kpi__live" aria-label="ao vivo" />}
      </div>
      <div className="kpi__value">
        {prefix && <span className="kpi__prefix">{prefix}</span>}
        <span className={"kpi__num" + (loading ? " kpi__num--loading" : "")}>{value}</span>
        {suffix && <span className="kpi__suffix">{suffix}</span>}
      </div>
      <div className="kpi__foot">
        {delta && <Delta value={delta} dir={dir} />}
        {compare && comparativeValue && (
          <span className="kpi__compare">vs {comparativeValue}</span>
        )}
        <div className="kpi__spark">
          <Sparkline values={sparkValues} color={sparkColor} dark={dark} />
        </div>
      </div>
    </div>
  );
}

// ──────────────────────────────────────────────────────────────
// Date helpers
// ──────────────────────────────────────────────────────────────
function todayBrtStr() {
  const fmt = new Intl.DateTimeFormat("en-CA", { timeZone: "America/Sao_Paulo", year: "numeric", month: "2-digit", day: "2-digit" });
  return fmt.format(new Date()); // en-CA gives YYYY-MM-DD
}
function addDaysStr(s, n) {
  const [y, m, d] = s.split("-").map(Number);
  const dt = new Date(Date.UTC(y, m - 1, d));
  dt.setUTCDate(dt.getUTCDate() + n);
  return `${dt.getUTCFullYear()}-${String(dt.getUTCMonth() + 1).padStart(2, "0")}-${String(dt.getUTCDate()).padStart(2, "0")}`;
}
function diffDays(from, to) {
  const [y1, m1, d1] = from.split("-").map(Number);
  const [y2, m2, d2] = to.split("-").map(Number);
  const a = Date.UTC(y1, m1 - 1, d1);
  const b = Date.UTC(y2, m2 - 1, d2);
  return Math.round((b - a) / 86400000);
}

// ──────────────────────────────────────────────────────────────
// Period picker
// ──────────────────────────────────────────────────────────────
function PeriodPicker({ label, from, to, min, max, onChange }) {
  return (
    <div className="picker">
      <span className="picker__label">{label}</span>
      <input type="date" value={from} min={min} max={max}
        onChange={(e) => onChange({ from: e.target.value, to: to < e.target.value ? e.target.value : to })} />
      <span className="picker__sep">→</span>
      <input type="date" value={to} min={from} max={max}
        onChange={(e) => onChange({ from, to: e.target.value })} />
    </div>
  );
}

function PresetSelect({ value, onChange, today, minDate }) {
  return (
    <select className="preset" value={value} onChange={(e) => onChange(e.target.value)}>
      <option value="period">Período (12/05 → hoje)</option>
      <option value="today">Hoje</option>
      <option value="yesterday">Ontem</option>
      <option value="last3">Últimos 3 dias</option>
      <option value="custom">Personalizado</option>
    </select>
  );
}

function resolvePreset(name, today, minDate) {
  switch (name) {
    case "today":     return { from: today, to: today };
    case "yesterday": return { from: addDaysStr(today, -1), to: addDaysStr(today, -1) };
    case "last3":     return { from: addDaysStr(today, -2), to: today };
    case "period":
    default:          return { from: minDate, to: today };
  }
}

// ──────────────────────────────────────────────────────────────
// Header
// ──────────────────────────────────────────────────────────────
function Header({ stale, error, period, setPeriod, compare, setCompare, compareOn, setCompareOn, minDate, todayStr, preset, setPreset }) {
  const [now, setNow] = React.useState(new Date());
  React.useEffect(() => {
    const id = setInterval(() => setNow(new Date()), 1000);
    return () => clearInterval(id);
  }, []);
  const fmt = new Intl.DateTimeFormat("pt-BR", {
    timeZone: "America/Sao_Paulo",
    hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false,
  });
  const clock = fmt.format(now);

  return (
    <header className="hdr">
      <div className="hdr__left">
        <div className="brand">
          <span className="brand__mark" />
          <span className="brand__name">Operação Triade</span>
        </div>
      </div>
      <div className="hdr__right">
        <div className="clock">
          <span className={"clock__dot" + (stale || error ? " clock__dot--stale" : "")} />
          <span className="clock__txt">{stale || error ? "OFFLINE" : "AO VIVO"} · {clock}</span>
        </div>
        <PresetSelect value={preset} onChange={(v) => {
          setPreset(v);
          if (v !== "custom") setPeriod(resolvePreset(v, todayStr, minDate));
        }} today={todayStr} minDate={minDate} />
        <PeriodPicker label="Período" from={period.from} to={period.to} min={minDate} max={todayStr}
          onChange={(p) => { setPreset("custom"); setPeriod(p); }} />
        <label className="cmp-toggle">
          <input type="checkbox" checked={compareOn} onChange={(e) => setCompareOn(e.target.checked)} />
          <span>vs</span>
        </label>
        {compareOn && (
          <PeriodPicker label="Comparar" from={compare.from} to={compare.to} min={minDate} max={todayStr}
            onChange={setCompare} />
        )}
      </div>
    </header>
  );
}

// ──────────────────────────────────────────────────────────────
// Data hook
// ──────────────────────────────────────────────────────────────
function useSummary(period, compareOn, compare, intervalMs = 15000) {
  const [state, setState] = React.useState({ data: null, error: null, loading: true, lastUpdate: null });
  const key = `${period.from}|${period.to}|${compareOn ? compare.from + "|" + compare.to : ""}`;

  React.useEffect(() => {
    let alive = true;
    let timer = null;
    const fetchOnce = async () => {
      try {
        const params = new URLSearchParams();
        params.set("from", period.from);
        params.set("to", period.to);
        if (compareOn) { params.set("compareFrom", compare.from); params.set("compareTo", compare.to); }
        const r = await fetch(`/api/summary?${params}`, { cache: "no-store" });
        const j = await r.json();
        if (!alive) return;
        if (!r.ok) setState(s => ({ ...s, error: j.error || `HTTP ${r.status}`, loading: false }));
        else setState({ data: j, error: null, loading: false, lastUpdate: Date.now() });
      } catch (e) {
        if (!alive) return;
        setState(s => ({ ...s, error: e.message, loading: false }));
      } finally {
        if (alive) timer = setTimeout(fetchOnce, intervalMs);
      }
    };
    setState(s => ({ ...s, loading: true }));
    fetchOnce();
    return () => { alive = false; if (timer) clearTimeout(timer); };
  }, [key, intervalMs]);
  return state;
}

// ──────────────────────────────────────────────────────────────
// App
// ──────────────────────────────────────────────────────────────
const TWEAK_DEFAULTS = { theme: "dark", chartType: "area" };
const MIN_DATE_FALLBACK = "2026-05-12";

function App() {
  const [t, setTweak] = window.useTweaks(TWEAK_DEFAULTS);
  const dark = t.theme === "dark";
  React.useEffect(() => { document.documentElement.dataset.theme = t.theme; }, [t.theme]);

  const today = todayBrtStr();
  const minDate = MIN_DATE_FALLBACK;

  const [preset, setPreset] = React.useState("period");
  const [period, setPeriod] = React.useState({ from: minDate, to: today });
  const [compareOn, setCompareOn] = React.useState(false);
  const [compare, setCompare] = React.useState({ from: minDate, to: minDate });

  const { data, error, loading, lastUpdate } = useSummary(period, compareOn, compare);
  const stale = lastUpdate ? (Date.now() - lastUpdate > 90000) : false;

  const lineColor  = dark ? "#f5f6f8" : "#101114";
  const sparkSlate = dark ? "#9a9da4" : "#6b6e76";

  const cur = data?.current;
  const cmpData = data?.compare;
  const curKpi = cur?.kpi;
  const cmpKpi = cmpData?.kpi || { faturamento: 0, investido: 0, lucro: 0, roas: 0, vendas: 0, cpa: 0, ticket: 0 };

  // for hourly "today" view we want the cursor at current hour
  const cursorIndex = (cur?.granularity === "hourly" && period.to === today)
    ? (data?.nowHour ?? 0) : null;

  const tiles = !curKpi ? [] : [
    {
      label: "Lucro", prefix: "R$",
      value: fmtBRL(curKpi.lucro),
      ...(compareOn ? deltaPct(curKpi.lucro, cmpKpi.lucro) : {}),
      compareVal: compareOn ? `R$ ${fmtBRL(cmpKpi.lucro)}` : null,
      spark: cur.revenueSeries.map((r, i) => r - (cur.spendSeries[i] || 0)),
      sparkColor: lineColor, live: true,
    },
    {
      label: "Faturamento", prefix: "R$",
      value: fmtBRL(curKpi.faturamento),
      ...(compareOn ? deltaPct(curKpi.faturamento, cmpKpi.faturamento) : {}),
      compareVal: compareOn ? `R$ ${fmtBRL(cmpKpi.faturamento)}` : null,
      spark: cur.revenueSeries, sparkColor: lineColor, live: true,
    },
    {
      label: "Investido", prefix: "R$",
      value: fmtBRL(curKpi.investido),
      ...(compareOn ? deltaPct(curKpi.investido, cmpKpi.investido) : {}),
      compareVal: compareOn ? `R$ ${fmtBRL(cmpKpi.investido)}` : null,
      spark: cur.spendSeries, sparkColor: sparkSlate, live: true,
    },
    {
      label: "ROAS",
      value: fmtDec(curKpi.roas, 2), suffix: "×",
      ...(compareOn ? deltaAbs(curKpi.roas, cmpKpi.roas, (n) => fmtDec(n, 2)) : {}),
      compareVal: compareOn ? `${fmtDec(cmpKpi.roas, 2)}×` : null,
      spark: cur.revenueSeries.map((r, i) => (cur.spendSeries[i] > 0 ? r / cur.spendSeries[i] : 0)),
      sparkColor: sparkSlate,
    },
    {
      label: "Vendas",
      value: fmtInt(curKpi.vendas),
      ...(compareOn ? deltaAbs(curKpi.vendas, cmpKpi.vendas, fmtInt) : {}),
      compareVal: compareOn ? fmtInt(cmpKpi.vendas) : null,
      spark: cur.revenueSeries, sparkColor: sparkSlate,
    },
    {
      label: "CPA", prefix: "R$",
      value: fmtBRL(curKpi.cpa),
      ...(compareOn ? deltaPct(curKpi.cpa, cmpKpi.cpa) : {}),
      compareVal: compareOn ? `R$ ${fmtBRL(cmpKpi.cpa)}` : null,
      spark: cur.spendSeries, sparkColor: sparkSlate,
    },
    {
      label: "Ticket médio", prefix: "R$",
      value: fmtBRL(curKpi.ticket),
      ...(compareOn ? deltaPct(curKpi.ticket, cmpKpi.ticket) : {}),
      compareVal: compareOn ? `R$ ${fmtBRL(cmpKpi.ticket)}` : null,
      spark: cur.revenueSeries, sparkColor: sparkSlate,
    },
  ];

  const margemPct  = curKpi && curKpi.faturamento > 0 ? (curKpi.lucro / curKpi.faturamento) * 100 : 0;
  const margemPctC = cmpKpi.faturamento > 0 ? (cmpKpi.lucro / cmpKpi.faturamento) * 100 : 0;
  const margemDelta = (() => {
    const d = margemPct - margemPctC;
    return { value: `${d >= 0 ? "+" : "−"}${Math.abs(d).toFixed(1)} pp`, dir: d >= 0 ? "up" : "down" };
  })();

  const days = cur ? (diffDays(cur.from, cur.to) + 1) : 0;
  const periodLabel = cur ? (
    cur.from === cur.to
      ? ddmmFromIso(cur.from)
      : `${ddmmFromIso(cur.from)} → ${ddmmFromIso(cur.to)} (${days}d)`
  ) : "—";

  return (
    <>
      <div className="shell">
        <Header
          stale={stale} error={!!error}
          period={period} setPeriod={setPeriod}
          compare={compare} setCompare={setCompare}
          compareOn={compareOn} setCompareOn={setCompareOn}
          minDate={minDate} todayStr={today}
          preset={preset} setPreset={setPreset}
        />

        <main className="main">
          {error && (
            <div className="errorbar">erro carregando dados · {error}</div>
          )}

          <section className="kpis kpis--7">
            {tiles.map((k) => (
              <KpiTile key={k.label}
                label={k.label} value={k.value}
                prefix={k.prefix} suffix={k.suffix}
                delta={compareOn ? k.delta : null} dir={k.dir}
                sparkValues={k.spark} sparkColor={k.sparkColor}
                compare={compareOn} comparativeValue={k.compareVal}
                dark={dark} live={k.live} loading={loading && !data} />
            ))}
          </section>

          <section className="chartcard">
            <div className="chartcard__head">
              <div>
                <div className="chartcard__eyebrow">PERÍODO · {periodLabel} · {cur?.granularity === "hourly" ? "hora a hora" : "dia a dia"}</div>
                <h2 className="chartcard__title">Faturamento × Valor investido</h2>
              </div>
              <div className="chartcard__legend">
                <div className="lgn">
                  <span className="lgn__sw lgn__sw--solid" />
                  <span className="lgn__lbl">Faturamento</span>
                  <span className="lgn__val">R$ {fmtBRL(curKpi?.faturamento)}</span>
                </div>
                <div className="lgn">
                  <span className="lgn__sw lgn__sw--dashed" />
                  <span className="lgn__lbl">Investido</span>
                  <span className="lgn__val">R$ {fmtBRL(curKpi?.investido)}</span>
                </div>
                {compareOn && cmpData && (
                  <div className="lgn">
                    <span className="lgn__sw lgn__sw--ghost" />
                    <span className="lgn__lbl lgn__lbl--mute">{ddmmFromIso(cmpData.from)} → {ddmmFromIso(cmpData.to)}</span>
                  </div>
                )}
              </div>
            </div>

            <ChartHost dark={dark} chartType={t.chartType}
              granularity={cur?.granularity}
              labels={cur?.labels || []}
              revenueSeries={cur?.revenueSeries || []}
              spendSeries={cur?.spendSeries || []}
              compareLabels={cmpData?.labels || []}
              compareRevenueSeries={cmpData?.revenueSeries || []}
              compareSpendSeries={cmpData?.spendSeries || []}
              showCompare={compareOn && !!cmpData}
              cursorIndex={cursorIndex} />

            <div className="chartcard__foot">
              <div className="foot-stat">
                <span className="foot-stat__lbl">Margem</span>
                <span className="foot-stat__val">{curKpi ? `${margemPct.toFixed(1)}%` : "—"}</span>
                {compareOn && curKpi && <Delta value={margemDelta.value} dir={margemDelta.dir} />}
              </div>
              <div className="foot-stat">
                <span className="foot-stat__lbl">Impressões</span>
                <span className="foot-stat__val">{fmtInt(cur?.impressions || 0)}</span>
              </div>
              <div className="foot-stat">
                <span className="foot-stat__lbl">Cliques</span>
                <span className="foot-stat__val">{fmtInt(cur?.clicks || 0)}</span>
              </div>
              <div className="foot-stat">
                <span className="foot-stat__lbl">Status</span>
                <span className="foot-stat__val foot-stat__val--ok">
                  <span className={"status-dot" + (stale || error ? " status-dot--bad" : "")} />
                  {stale || error ? "Sem dados" : "Saudável"}
                </span>
              </div>
            </div>
          </section>
        </main>
      </div>
    </>
  );
}

function ChartHost({ dark, chartType, granularity, labels, revenueSeries, spendSeries, compareLabels, compareRevenueSeries, compareSpendSeries, showCompare, cursorIndex }) {
  const ref = React.useRef(null);
  const [w, setW] = React.useState(1200);
  React.useLayoutEffect(() => {
    if (!ref.current) return;
    const ro = new ResizeObserver(([e]) => setW(e.contentRect.width));
    ro.observe(ref.current);
    return () => ro.disconnect();
  }, []);
  return (
    <div ref={ref} className="chartcard__canvas">
      <window.LiveChart
        width={Math.max(640, Math.floor(w))} height={380}
        dark={dark} chartType={chartType}
        granularity={granularity || "daily"}
        labels={labels}
        revenueSeries={revenueSeries} spendSeries={spendSeries}
        compareLabels={compareLabels}
        compareRevenueSeries={compareRevenueSeries} compareSpendSeries={compareSpendSeries}
        showCompare={showCompare}
        cursorIndex={cursorIndex} />
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
