/* Native Statistics tab — modern redesign in the prototype's own design
   language (cards, Hanken Grotesk, blue accent). Mirrors the real app's
   analytics: KPI overview, by-item, by-designer, and a designer×month grid.
   Demo data is generated deterministically from the prototype's designers. */
(function () {
const { useState, useMemo, useEffect } = React;

const POS_COLOR = {
  "Motion Designer":    { bg: "#eef0ff", fg: "#4f46e5" },
  "Graphic Designer":   { bg: "#e7f6ee", fg: "#0c9a67" },
  "Innovation Manager": { bg: "#fdf0e1", fg: "#c0752a" },
};
const initials = (n) => n.split(" ").filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
const r1 = (n) => Math.round(n * 10) / 10;
const fmtSP = (n) => (Math.round(n * 10) / 10).toLocaleString("en-US");

const MONTHS_SHORT = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];

/* ---- task-type catalog (shared with Designer Tables) ---- */
const TT_KEY = "nove8_tasktypes_v1";
const DEFAULT_TASK_TYPES = [
  { id: "cs",       label: "creatives S", color: "#f0625a" },
  { id: "cm",       label: "creatives M", color: "#6c79f6" },
  { id: "smm",      label: "SMM",         color: "#16b277" },
  { id: "product",  label: "Product",     color: "#f59e0b" },
  { id: "hrpr",     label: "HR/PR",       color: "#ec4899" },
  { id: "meetings", label: "Meetings",    color: "#0ea5e9" },
];
function loadTaskTypes() {
  try { const r = localStorage.getItem(TT_KEY); if (r) { const a = JSON.parse(r); if (Array.isArray(a) && a.length) return a; } } catch (e) {}
  return DEFAULT_TASK_TYPES;
}

/* ---- work weeks of a month (Mon–Fri, by Wednesday majority) — mirrors Designer Tables ---- */
function _workWeekMondays(year, m) {
  const out = [];
  let mon = new Date(year, m, 1);
  mon.setDate(mon.getDate() - ((mon.getDay() + 6) % 7));
  for (let g = 0; g < 8; g++) {
    const wed = new Date(mon); wed.setDate(mon.getDate() + 2);
    const cmp = (wed.getFullYear() - year) * 12 + (wed.getMonth() - m);
    if (cmp > 0) break;
    if (cmp === 0) out.push(new Date(mon));
    mon = new Date(mon); mon.setDate(mon.getDate() + 7);
  }
  return out;
}

/* ---- real events, derived from Designer Tables (one task row = one event) ---- */
const TABLES_KEY = "nove8_tables_v1";
function buildEvents(designers) {
  let store = {};
  try { store = JSON.parse(localStorage.getItem(TABLES_KEY)) || {}; } catch (e) {}
  const byId = new Map(designers.map((d) => [d.id, d]));
  const DAY = 86400000;
  const evts = [];
  Object.keys(store).forEach((key) => {
    const sep = key.indexOf("|");
    if (sep < 0) return;
    const dId = key.slice(0, sep), ym = key.slice(sep + 1);
    const d = byId.get(dId);
    if (!d) return;
    const dash = ym.indexOf("-");
    const year = parseInt(ym.slice(0, dash), 10), m = parseInt(ym.slice(dash + 1), 10);
    if (isNaN(year) || isNaN(m)) return;
    const entry = store[key];
    if (!entry || !entry.weeks) return;
    const mondays = _workWeekMondays(year, m);
    mondays.forEach((monday, wi) => {
      const wk = entry.weeks[wi];
      if (!wk || !wk.rows) return;
      const ts = monday.getTime() + 2 * DAY + 12 * 3600000; // Wednesday noon — safely inside the week
      wk.rows.forEach((r, ri) => {
        if (!r || !r.app) return;
        const sp = parseFloat(r.sp);
        if (isNaN(sp) || sp <= 0) return;
        evts.push({
          id: key + "-" + wi + "-" + ri,
          ts,
          designer: d.name,
          designerId: d.id,
          productId: r.app || d.productId || null,
          position: d.position,
          result: Math.round(sp * 10) / 10,
          taskType: r.task || "",
          kind: r.type || "",
        });
      });
    });
  });
  return evts.sort((a, b) => b.ts - a.ts);
}

const PERIODS = [
  { id: "tw",  label: "This week" },
  { id: "pw",  label: "Prev. week" },
  { id: "tm",  label: "This month" },
  { id: "pm",  label: "Prev. month" },
  { id: "ty",  label: "This year" },
  { id: "py",  label: "Prev. year" },
  { id: "all", label: "All time" },
  { id: "custom", label: "Custom" },
];

const ms = (d) => d.getTime();
function startOfWeek(d) { const x = new Date(d); const day = (x.getDay() + 6) % 7; x.setHours(0, 0, 0, 0); x.setDate(x.getDate() - day); return x; }
/* a work week (Mon–Fri) belongs to the month containing its Wednesday (majority of weekdays) */
function weekMonth(monday) { const w = new Date(monday); w.setDate(w.getDate() + 2); return { y: w.getFullYear(), m: w.getMonth() }; }
function workWeeksOfMonth(year, m) {
  const out = [];
  let mon = new Date(year, m, 1);
  mon.setDate(mon.getDate() - ((mon.getDay() + 6) % 7));
  for (let g = 0; g < 8; g++) {
    const wm = weekMonth(mon);
    const cmp = (wm.y - year) * 12 + (wm.m - m);
    if (cmp > 0) break;
    if (cmp === 0) out.push(new Date(mon));
    mon = new Date(mon); mon.setDate(mon.getDate() + 7);
  }
  return out;
}
function weekIndexInMonth(monday) {
  const wm = weekMonth(monday);
  const list = workWeeksOfMonth(wm.y, wm.m);
  const i = list.findIndex((d) => ms(startOfWeek(d)) === ms(startOfWeek(new Date(monday))));
  return { idx: i < 0 ? 0 : i, month: wm.m, year: wm.y };
}
function monthWorkSpan(year, m) {
  const weeks = workWeeksOfMonth(year, m);
  const first = weeks[0];
  const last = weeks[weeks.length - 1];
  const lastEnd = new Date(last); lastEnd.setDate(lastEnd.getDate() + 6); lastEnd.setHours(23, 59, 59, 999);
  return { from: ms(startOfWeek(first)), to: ms(lastEnd) };
}
function periodRange(period, cf, ct) {
  const now = new Date();
  switch (period) {
    case "tw": return { from: ms(startOfWeek(now)), to: Date.now() };
    case "pw": { const s = startOfWeek(now); const ps = new Date(s); ps.setDate(ps.getDate() - 7); const pe = new Date(s); pe.setMilliseconds(-1); return { from: ms(ps), to: ms(pe) }; }
    case "tm": { const sp = monthWorkSpan(now.getFullYear(), now.getMonth()); return { from: sp.from, to: Math.min(Date.now(), sp.to) }; }
    case "pm": { const d = new Date(now.getFullYear(), now.getMonth() - 1, 1); return monthWorkSpan(d.getFullYear(), d.getMonth()); }
    case "ty": return { from: ms(new Date(now.getFullYear(), 0, 1)), to: Date.now() };
    case "py": { const s = new Date(now.getFullYear() - 1, 0, 1); const e = new Date(now.getFullYear(), 0, 1); e.setMilliseconds(-1); return { from: ms(s), to: ms(e) }; }
    case "custom": { const f = cf ? ms(new Date(cf + "T00:00:00")) : -Infinity; const t = ct ? ms(new Date(ct + "T23:59:59")) : Infinity; return { from: f, to: t }; }
    default: return { from: -Infinity, to: Infinity };
  }
}
function prevRange(r) {
  if (!isFinite(r.from) || !isFinite(r.to)) return null;
  const span = r.to - r.from;
  return { from: r.from - span - 1, to: r.from - 1 };
}

const GROUPBYS = [{ id: "day", label: "Days" }, { id: "week", label: "Weeks" }, { id: "month", label: "Months" }];
function isoWeek(d) {
  const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
  const dayNum = (date.getUTCDay() + 6) % 7;
  date.setUTCDate(date.getUTCDate() - dayNum + 3);
  const firstThu = new Date(Date.UTC(date.getUTCFullYear(), 0, 4));
  return 1 + Math.round(((date - firstThu) / 86400000 - 3 + ((firstThu.getUTCDay() + 6) % 7)) / 7);
}
function bucketKey(ts, groupBy) {
  const d = new Date(ts);
  if (groupBy === "month") return "m" + d.getFullYear() + "-" + d.getMonth();
  if (groupBy === "week") return "w" + ms(startOfWeek(d));
  return "d" + d.getFullYear() + "-" + d.getMonth() + "-" + d.getDate();
}
const CAP = { day: 62, week: 27, month: 18 };
function buildBuckets(range, groupBy, events) {
  let from = range.from, to = range.to;
  if (!isFinite(from) || !isFinite(to)) {
    const ts = events.map((e) => e.ts);
    from = ts.length ? Math.min(...ts) : Date.now();
    to = ts.length ? Math.max(...ts) : Date.now();
  }
  const end = new Date(to);
  const out = [];
  if (groupBy === "month") {
    let d = new Date(new Date(from).getFullYear(), new Date(from).getMonth(), 1);
    while (d <= end) { out.push({ key: "m" + d.getFullYear() + "-" + d.getMonth(), label: MONTHS_SHORT[d.getMonth()], sub: "'" + String(d.getFullYear()).slice(2), ts: ms(d) }); d = new Date(d.getFullYear(), d.getMonth() + 1, 1); }
  } else if (groupBy === "week") {
    let d = startOfWeek(from);
    while (d <= end) { const wi = weekIndexInMonth(d); out.push({ key: "w" + ms(d), label: MONTHS_SHORT[wi.month] + " W" + (wi.idx + 1), sub: (d.getDate()) + "." + (d.getMonth() + 1), ts: ms(d) }); d = new Date(d); d.setDate(d.getDate() + 7); }
  } else {
    let d = new Date(from); d.setHours(0, 0, 0, 0);
    while (d <= end) { const wknd = d.getDay() === 0 || d.getDay() === 6; out.push({ key: "d" + d.getFullYear() + "-" + d.getMonth() + "-" + d.getDate(), label: String(d.getDate()), sub: MONTHS_SHORT[d.getMonth()], ts: ms(d), weekend: wknd }); d = new Date(d); d.setDate(d.getDate() + 1); }
  }
  return out.slice(-(CAP[groupBy] || 18));
}

/* ---- tiny presentational helpers ---- */
function Delta({ now, prev }) {
  if (prev === 0 && now === 0) return <span className="st-delta flat">—</span>;
  if (prev === 0) return <span className="st-delta up">▲ new</span>;
  const pct = Math.round(((now - prev) / prev) * 100);
  if (pct === 0) return <span className="st-delta flat">0%</span>;
  return <span className={"st-delta " + (pct > 0 ? "up" : "down")}>{pct > 0 ? "▲" : "▼"} {Math.abs(pct)}%</span>;
}

function Avatar({ name, position }) {
  const c = POS_COLOR[position] || { bg: "#eef0f4", fg: "#4a515c" };
  return <span className="st-av" style={{ background: c.bg, color: c.fg }}>{initials(name)}</span>;
}

/* ---- views ---- */
function ItemView({ events, taskTypes, products }) {
  const [metric, setMetric] = useState("sp"); // sp | count
  const [dim, setDim] = useState("task"); // task | product
  const ttMap = useMemo(() => new Map((taskTypes || []).map((t) => [t.id, t])), [taskTypes]);
  const prodMap = useMemo(() => new Map((products || []).map((p) => [p.id, p])), [products]);

  const rows = useMemo(() => {
    const m = new Map();
    events.forEach((ev) => {
      let id, label, color;
      if (dim === "product") {
        id = ev.productId || "__none__";
        const p = prodMap.get(ev.productId);
        label = p ? p.name : "No product";
        color = p ? p.color : "#c2c8d0";
      } else {
        id = ev.taskType || "__none__";
        const t = ttMap.get(ev.taskType);
        label = t ? t.label : "Unspecified";
        color = t ? t.color : "#c2c8d0";
      }
      let r = m.get(id);
      if (!r) { r = { id, label, color, count: 0, sp: 0 }; m.set(id, r); }
      r.count += 1;
      r.sp += ev.result;
    });
    return [...m.values()].sort((a, b) => metric === "sp" ? b.sp - a.sp : b.count - a.count);
  }, [events, metric, dim, ttMap, prodMap]);
  const max = Math.max(1, ...rows.map((r) => metric === "sp" ? r.sp : r.count));

  return (
    <div className="st-panel">
      <div className="st-panel-head">
        <div><h3>{dim === "product" ? "Story points by product" : "Story points by task type"}</h3><p>{rows.length} {dim === "product" ? "products" : "task types"} in this period</p></div>
        <div className="st-head-controls">
          <div className="st-seg">
            <button className={dim === "task" ? "on" : ""} onClick={() => setDim("task")}>Task type</button>
            <button className={dim === "product" ? "on" : ""} onClick={() => setDim("product")}>Product</button>
          </div>
          <div className="st-seg">
            <button className={metric === "sp" ? "on" : ""} onClick={() => setMetric("sp")}>Total SP</button>
            <button className={metric === "count" ? "on" : ""} onClick={() => setMetric("count")}>Tasks</button>
          </div>
        </div>
      </div>
      <div className="st-itemlist">
        {rows.map((r, i) => {
          const val = metric === "sp" ? r.sp : r.count;
          return (
            <div className="st-item" key={r.id}>
              <span className="st-rank">{i + 1}</span>
              <span className="st-gdot" style={{ background: r.color }}></span>
              <span className="st-item-name">{r.label}</span>
              <div className="st-item-bar"><span style={{ width: (val / max * 100) + "%", background: r.color }}></span></div>
              <span className="st-item-val">{metric === "sp" ? fmtSP(r.sp) : r.count}</span>
            </div>
          );
        })}
        {rows.length === 0 && <div className="st-empty">No data in this period</div>}
      </div>
    </div>
  );
}

function TeamView({ events, designers, range, groupBy }) {
  const [metric, setMetric] = useState("sp");
  const [open, setOpen] = useState(null);
  const buckets = useMemo(() => buildBuckets(range, groupBy, events), [range, groupBy, events]);
  const rows = useMemo(() => {
    const m = new Map();
    designers.forEach((d) => m.set(d.name, { name: d.name, position: d.position, count: 0, sp: 0, cells: {} }));
    events.forEach((ev) => {
      const r = m.get(ev.designer); if (!r) return;
      r.count += 1; r.sp += ev.result;
      const k = bucketKey(ev.ts, groupBy);
      r.cells[k] = (r.cells[k] || 0) + ev.result;
    });
    return [...m.values()].filter((r) => r.count > 0).sort((a, b) => metric === "sp" ? b.sp - a.sp : b.count - a.count);
  }, [events, designers, metric, groupBy]);
  const max = Math.max(1, ...rows.map((r) => metric === "sp" ? r.sp : r.count));

  return (
    <div className="st-panel">
      <div className="st-panel-head">
        <div><h3>Designers by output</h3><p>{rows.length} active designers · expand for {groupBy === "day" ? "daily" : groupBy === "week" ? "weekly" : "monthly"} trend</p></div>
        <div className="st-seg">
          <button className={metric === "sp" ? "on" : ""} onClick={() => setMetric("sp")}>Total SP</button>
          <button className={metric === "count" ? "on" : ""} onClick={() => setMetric("count")}>Tasks</button>
        </div>
      </div>
      <div className="st-teamlist">
        {rows.map((r) => {
          const val = metric === "sp" ? r.sp : r.count;
          const isOpen = open === r.name;
          const bmax = Math.max(1, ...buckets.map((b) => r.cells[b.key] || 0));
          return (
            <div className={"st-team" + (isOpen ? " open" : "")} key={r.name}>
              <div className="st-team-head" onClick={() => setOpen(isOpen ? null : r.name)}>
                <Avatar name={r.name} position={r.position} />
                <div className="st-team-meta">
                  <div className="st-team-name">{r.name}</div>
                  <div className="st-team-sub">{r.position}</div>
                </div>
                <div className="st-team-bar"><span style={{ width: (val / max * 100) + "%" }}></span></div>
                <div className="st-team-val"><b>{metric === "sp" ? fmtSP(r.sp) : r.count}</b><span>{metric === "sp" ? "SP" : "tasks"}</span></div>
              </div>
              {isOpen && (
                <div className="st-team-detail">
                  <div className="st-mini-row">
                    {buckets.map((b) => (
                      <div className="st-mini" key={b.key} title={(b.label) + " · " + fmtSP(r.cells[b.key] || 0) + " SP"}>
                        <div className="st-mini-bar"><span style={{ height: ((r.cells[b.key] || 0) / bmax * 100) + "%" }}></span></div>
                        <span className="st-mini-lbl">{b.label}</span>
                      </div>
                    ))}
                  </div>
                  <div className="st-team-stats">
                    <span><b>{fmtSP(r.sp)}</b> total SP</span>
                    <span><b>{r.count}</b> tasks</span>
                    <span><b>{fmtSP(r.sp / r.count)}</b> avg SP</span>
                  </div>
                </div>
              )}
            </div>
          );
        })}
        {rows.length === 0 && <div className="st-empty">No data in this period</div>}
      </div>
    </div>
  );
}

function GridView({ events, designers, range, groupBy }) {
  const grid = useMemo(() => {
    const buckets = buildBuckets(range, groupBy, events);
    const keyset = new Set(buckets.map((b) => b.key));
    const rows = designers.map((d) => ({ name: d.name, position: d.position, cells: {}, total: 0 }));
    const byName = new Map(rows.map((r) => [r.name, r]));
    const colTotals = {};
    events.forEach((ev) => {
      const key = bucketKey(ev.ts, groupBy);
      if (!keyset.has(key)) return;
      const r = byName.get(ev.designer); if (!r) return;
      r.cells[key] = (r.cells[key] || 0) + ev.result; r.total += ev.result;
      colTotals[key] = (colTotals[key] || 0) + ev.result;
    });
    const active = rows.filter((r) => r.total > 0).sort((a, b) => b.total - a.total);
    // when grouped by day, also group day-columns into weeks for boundaries + weekly summaries
    let weeks = null;
    if (groupBy === "day") {
      const wm = new Map();
      buckets.forEach((b) => {
        const sw = startOfWeek(new Date(b.ts)); const wk = "w" + ms(sw);
        if (!wm.has(wk)) { const wi = weekIndexInMonth(sw); wm.set(wk, { key: wk, label: MONTHS_SHORT[wi.month] + " W" + (wi.idx + 1), sub: "from " + sw.getDate() + "." + (sw.getMonth() + 1), days: [] }); }
        wm.get(wk).days.push(b);
      });
      weeks = [...wm.values()];
    }
    const grand = active.reduce((s, r) => s + r.total, 0);
    const max = Math.max(1, ...active.flatMap((r) => buckets.map((b) => r.cells[b.key] || 0)));
    return { buckets, rows: active, colTotals, weeks, grand, max };
  }, [events, designers, range, groupBy]);

  const shade = (v) => {
    if (!v) return { background: "#f5f6f8", color: "#c2c8d0" };
    const t = Math.min(1, v / grid.max);
    const a = 0.12 + t * 0.78;
    return { background: `rgba(47,102,255,${a})`, color: t > 0.5 ? "#fff" : "#1f3a8a" };
  };
  const unit = groupBy === "day" ? "day" : groupBy === "week" ? "week" : "month";
  const sumDays = (cells, days) => days.reduce((s, b) => s + (cells[b.key] || 0), 0);

  if (grid.rows.length === 0) {
    return (
      <div className="st-panel">
        <div className="st-panel-head"><div><h3>Designer × {unit}</h3><p>Story points per designer, by {unit}</p></div></div>
        <div className="st-empty">No data in this period</div>
      </div>
    );
  }

  /* ---- day mode: week-grouped header with weekly Σ per designer ---- */
  if (grid.weeks) {
    return (
      <div className="st-panel">
        <div className="st-panel-head"><div><h3>Designer × day</h3><p>Daily story points with week boundaries · Σ = weekly total</p></div></div>
        <div className="st-grid-wrap">
          <table className="st-grid st-grid-weeks">
            <thead>
              <tr>
                <th className="st-grid-corner" rowSpan={2}>Designer</th>
                {grid.weeks.map((w) => (
                  <th key={w.key} className="st-wkgroup" colSpan={w.days.length + 1}><span className="st-grid-h">{w.label}</span><span className="st-grid-hsub">{w.sub}</span></th>
                ))}
                <th className="st-grid-total" rowSpan={2}>Total</th>
              </tr>
              <tr>
                {grid.weeks.map((w) => [
                  ...w.days.map((b, i) => <th key={b.key} className={(i === 0 ? "st-wkstart" : "") + (b.weekend ? " st-wknd" : "")}><span className="st-grid-h">{b.label}</span><span className="st-grid-hsub">{b.sub}</span></th>),
                  <th key={w.key + "-s"} className="st-wksum-h">Σ</th>,
                ])}
              </tr>
            </thead>
            <tbody>
              {grid.rows.map((r) => (
                <tr key={r.name}>
                  <td className="st-grid-name"><Avatar name={r.name} position={r.position} /><span>{r.name}</span></td>
                  {grid.weeks.map((w) => [
                    ...w.days.map((b, i) => {
                      const v = r.cells[b.key] || 0;
                      return <td key={b.key} className={"st-grid-cell" + (i === 0 ? " st-wkstart" : "") + (b.weekend ? " st-wknd" : "")}><span style={shade(v)}>{v ? fmtSP(v) : ""}</span></td>;
                    }),
                    <td key={w.key + "-s"} className="st-wksum"><b>{(() => { const s = sumDays(r.cells, w.days); return s ? fmtSP(s) : ""; })()}</b></td>,
                  ])}
                  <td className="st-grid-total"><b>{fmtSP(r.total)}</b></td>
                </tr>
              ))}
            </tbody>
            <tfoot>
              <tr className="st-grid-foot">
                <td className="st-grid-name st-foot-name">All designers</td>
                {grid.weeks.map((w) => [
                  ...w.days.map((b, i) => <td key={b.key} className={"st-foot-cell" + (i === 0 ? " st-wkstart" : "") + (b.weekend ? " st-wknd" : "")}>{grid.colTotals[b.key] ? fmtSP(grid.colTotals[b.key]) : ""}</td>),
                  <td key={w.key + "-s"} className="st-wksum st-foot-wksum"><b>{(() => { const s = w.days.reduce((a, b) => a + (grid.colTotals[b.key] || 0), 0); return s ? fmtSP(s) : ""; })()}</b></td>,
                ])}
                <td className="st-grid-total st-foot-grand"><b>{fmtSP(grid.grand)}</b></td>
              </tr>
            </tfoot>
          </table>
        </div>
      </div>
    );
  }

  /* ---- week / month mode ---- */
  return (
    <div className="st-panel">
      <div className="st-panel-head"><div><h3>Designer × {unit}</h3><p>Story points per designer, by {unit} · bottom row = all designers</p></div></div>
      <div className="st-grid-wrap">
        <table className="st-grid">
          <thead>
            <tr>
              <th className="st-grid-corner">Designer</th>
              {grid.buckets.map((b) => <th key={b.key}><span className="st-grid-h">{b.label}</span>{b.sub && <span className="st-grid-hsub">{b.sub}</span>}</th>)}
              <th className="st-grid-total">Total</th>
            </tr>
          </thead>
          <tbody>
            {grid.rows.map((r) => (
              <tr key={r.name}>
                <td className="st-grid-name"><Avatar name={r.name} position={r.position} /><span>{r.name}</span></td>
                {grid.buckets.map((b) => {
                  const v = r.cells[b.key] || 0;
                  return <td key={b.key} className="st-grid-cell"><span style={shade(v)}>{v ? fmtSP(v) : ""}</span></td>;
                })}
                <td className="st-grid-total"><b>{fmtSP(r.total)}</b></td>
              </tr>
            ))}
          </tbody>
          <tfoot>
            <tr className="st-grid-foot">
              <td className="st-grid-name st-foot-name">All designers</td>
              {grid.buckets.map((b) => <td key={b.key} className="st-foot-cell">{grid.colTotals[b.key] ? fmtSP(grid.colTotals[b.key]) : ""}</td>)}
              <td className="st-grid-total st-foot-grand"><b>{fmtSP(grid.grand)}</b></td>
            </tr>
          </tfoot>
        </table>
      </div>
    </div>
  );
}

/* ---- designer / product include-exclude filter ---- */
function FilterMenu({ products, designers, selected, setSelected }) {
  const [open, setOpen] = useState(false);
  const groups = useMemo(() => {
    const byProd = products.map((p) => ({ id: p.id, name: p.name, color: p.color, list: designers.filter((d) => d.productId === p.id) }));
    const none = designers.filter((d) => !d.productId || !products.some((p) => p.id === d.productId));
    if (none.length) byProd.push({ id: "__none__", name: "No product", color: "#c2c8d0", list: none });
    return byProd.filter((g) => g.list.length);
  }, [products, designers]);

  const total = designers.length;
  const on = selected.size;
  const toggle = (name) => setSelected((s) => { const n = new Set(s); n.has(name) ? n.delete(name) : n.add(name); return n; });
  const toggleGroup = (list) => setSelected((s) => {
    const n = new Set(s);
    const allOn = list.every((d) => n.has(d.name));
    list.forEach((d) => { allOn ? n.delete(d.name) : n.add(d.name); });
    return n;
  });
  const all = () => setSelected(new Set(designers.map((d) => d.name)));
  const none = () => setSelected(new Set());

  const label = on === total ? "All designers" : on === 0 ? "No designers" : `${on} of ${total} designers`;

  return (
    <div className="st-filter">
      <button className={"st-filter-btn" + (on < total ? " active" : "")} onClick={() => setOpen((o) => !o)}>
        <svg width="14" height="14" viewBox="0 0 14 14"><path d="M1.5 2.5h11l-4.2 5v3.5l-2.6 1.3V7.5z" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round"/></svg>
        {label}
        <svg width="11" height="11" viewBox="0 0 12 12"><path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
      </button>
      {open && (
        <>
          <div className="st-filter-backdrop" onClick={() => setOpen(false)}></div>
          <div className="st-filter-panel">
            <div className="st-filter-top">
              <span>Filter designers</span>
              <div className="st-filter-quick">
                <button onClick={all}>All</button>
                <button onClick={none}>None</button>
              </div>
            </div>
            <div className="st-filter-list">
              {groups.map((g) => {
                const allOn = g.list.every((d) => selected.has(d.name));
                const someOn = g.list.some((d) => selected.has(d.name));
                return (
                  <div className="st-fgroup" key={g.id}>
                    <button className="st-fgroup-head" onClick={() => toggleGroup(g.list)}>
                      <span className={"st-check" + (allOn ? " on" : someOn ? " part" : "")} style={allOn ? { background: g.color, borderColor: g.color } : {}}>
                        {allOn ? "✓" : someOn ? "–" : ""}
                      </span>
                      <span className="st-prod-dot" style={{ background: g.color }}></span>
                      <span className="st-fgroup-name">{g.name}</span>
                      <span className="st-fgroup-count">{g.list.filter((d) => selected.has(d.name)).length}/{g.list.length}</span>
                    </button>
                    {g.list.map((d) => {
                      const isOn = selected.has(d.name);
                      return (
                        <button className="st-frow" key={d.id} onClick={() => toggle(d.name)}>
                          <span className={"st-check" + (isOn ? " on" : "")} style={isOn ? { background: "var(--blue)", borderColor: "var(--blue)" } : {}}>{isOn ? "✓" : ""}</span>
                          <Avatar name={d.name} position={d.position} />
                          <span className="st-frow-name">{d.name}</span>
                        </button>
                      );
                    })}
                  </div>
                );
              })}
            </div>
          </div>
        </>
      )}
    </div>
  );
}

/* ---- prefs persistence ---- */
const PREFS_KEY = "nove8_stats_prefs_v1";
function loadPrefs() { try { return JSON.parse(localStorage.getItem(PREFS_KEY)) || {}; } catch (e) { return {}; } }

/* ---- main ---- */
function StatsTab({ products, designers }) {
  const [tick, setTick] = useState(0);
  const taskTypes = useMemo(loadTaskTypes, [tick]);
  const allEvents = useMemo(() => buildEvents(designers), [designers, tick]);

  // re-read when Designer Tables change (calculate push, manual edits, cross-tab)
  useEffect(() => {
    const bump = () => setTick((t) => t + 1);
    const onStorage = (e) => { if (!e || !e.key || e.key === TABLES_KEY || e.key === TT_KEY) bump(); };
    window.addEventListener("storage", onStorage);
    window.addEventListener("nove8-tables-changed", bump);
    window.addEventListener("focus", bump);
    return () => { window.removeEventListener("storage", onStorage); window.removeEventListener("nove8-tables-changed", bump); window.removeEventListener("focus", bump); };
  }, []);

  const prefs = useMemo(loadPrefs, []);
  const [period, setPeriod] = useState(prefs.period || "ty");
  const [view, setView] = useState(prefs.view || "item");
  const [groupBy, setGroupBy] = useState(prefs.groupBy || "month");
  const [customFrom, setCustomFrom] = useState(prefs.customFrom || "");
  const [customTo, setCustomTo] = useState(prefs.customTo || "");
  const [selected, setSelected] = useState(() => {
    const all = designers.map((d) => d.name);
    if (Array.isArray(prefs.selected)) { const keep = prefs.selected.filter((n) => all.includes(n)); return new Set(keep.length ? keep : all); }
    return new Set(all);
  });
  const [posFilter, setPosFilter] = useState(prefs.posFilter || "__all__");
  const POSITIONS = useMemo(() => [...new Set(designers.map((d) => d.position).filter(Boolean))], [designers]);
  const posOf = useMemo(() => { const m = {}; designers.forEach((d) => { m[d.name] = d.position; }); return m; }, [designers]);

  useEffect(() => {
    try { localStorage.setItem(PREFS_KEY, JSON.stringify({ period, view, groupBy, customFrom, customTo, posFilter, selected: [...selected] })); } catch (e) {}
  }, [period, view, groupBy, customFrom, customTo, posFilter, selected]);

  const range = useMemo(() => periodRange(period, customFrom, customTo), [period, customFrom, customTo]);
  const prev = useMemo(() => prevRange(range), [range]);

  const inSel = (ev) => selected.has(ev.designer) && (posFilter === "__all__" || posOf[ev.designer] === posFilter);
  const events = useMemo(() => allEvents.filter((e) => e.ts >= range.from && e.ts <= range.to && inSel(e)), [allEvents, range, selected, posFilter]);
  const prevEvents = useMemo(() => prev ? allEvents.filter((e) => e.ts >= prev.from && e.ts <= prev.to && inSel(e)) : [], [allEvents, prev, selected, posFilter]);
  const selDesigners = useMemo(() => designers.filter((d) => selected.has(d.name) && (posFilter === "__all__" || d.position === posFilter)), [designers, selected, posFilter]);

  const kpi = useMemo(() => {
    const sum = (arr) => arr.reduce((a, b) => a + b.result, 0);
    const distinct = (arr) => new Set(arr.map((e) => e.designer)).size;
    return {
      calc: events.length, calcPrev: prevEvents.length,
      sp: sum(events), spPrev: sum(prevEvents),
      active: distinct(events), activePrev: distinct(prevEvents),
      perTask: events.length ? sum(events) / events.length : 0, perTaskPrev: prevEvents.length ? sum(prevEvents) / prevEvents.length : 0,
    };
  }, [events, prevEvents]);
  const noPrev = !prev;

  return (
    <div className="st">
      <div className="st-toolbar">
        <div className="st-title">
          <h1>Statistics</h1>
          <p>Story points delivered across the team</p>
        </div>
        <div className="st-toolbar-right">
          <div className="st-roles">
            <button className={posFilter === "__all__" ? "on" : ""} onClick={() => setPosFilter("__all__")}>All roles</button>
            {POSITIONS.map((p) => {
              const c = POS_COLOR[p] || {};
              return <button key={p} className={posFilter === p ? "on" : ""} onClick={() => setPosFilter(p)}>
                <span className="st-role-dot" style={{ background: c.fg || "#98a0ac" }}></span>{p.replace(" Designer", "").replace(" Manager", "")}
              </button>;
            })}
          </div>
          <FilterMenu products={products} designers={designers} selected={selected} setSelected={setSelected} />
        </div>
      </div>

      <div className="st-periodbar">
        <div className="st-seg st-period">
          {PERIODS.map((p) => (
            <button key={p.id} className={period === p.id ? "on" : ""} onClick={() => setPeriod(p.id)}>{p.label}</button>
          ))}
        </div>
        {period === "custom" && (
          <div className="st-custom">
            <input type="date" className="st-date" value={customFrom} onChange={(e) => setCustomFrom(e.target.value)} />
            <span className="st-custom-dash">–</span>
            <input type="date" className="st-date" value={customTo} onChange={(e) => setCustomTo(e.target.value)} />
          </div>
        )}
        <div className="st-groupby">
          <span className="st-groupby-label">Group by</span>
          <div className="st-seg">
            {GROUPBYS.map((g) => (
              <button key={g.id} className={groupBy === g.id ? "on" : ""} onClick={() => setGroupBy(g.id)}>{g.label}</button>
            ))}
          </div>
        </div>
      </div>

      <div className="st-kpis">
        <div className="st-kpi">
          <div className="st-kpi-top"><span className="st-kpi-label">Tasks logged</span>{!noPrev && <Delta now={kpi.calc} prev={kpi.calcPrev} />}</div>
          <div className="st-kpi-num">{kpi.calc}</div>
        </div>
        <div className="st-kpi">
          <div className="st-kpi-top"><span className="st-kpi-label">Total SP</span>{!noPrev && <Delta now={kpi.sp} prev={kpi.spPrev} />}</div>
          <div className="st-kpi-num">{fmtSP(kpi.sp)}</div>
        </div>
        <div className="st-kpi">
          <div className="st-kpi-top"><span className="st-kpi-label">Active designers</span>{!noPrev && <Delta now={kpi.active} prev={kpi.activePrev} />}</div>
          <div className="st-kpi-num">{kpi.active}</div>
        </div>
        <div className="st-kpi">
          <div className="st-kpi-top"><span className="st-kpi-label">Avg SP / task</span>{!noPrev && <Delta now={kpi.perTask} prev={kpi.perTaskPrev} />}</div>
          <div className="st-kpi-num">{fmtSP(kpi.perTask)}</div>
        </div>
      </div>

      <div className="st-viewswitch">
        <button className={view === "item" ? "on" : ""} onClick={() => setView("item")}>Breakdown</button>
        <button className={view === "team" ? "on" : ""} onClick={() => setView("team")}>By designer</button>
        <button className={view === "grid" ? "on" : ""} onClick={() => setView("grid")}>Time grid</button>
      </div>

      <div className="st-body">
        {view === "item" && <ItemView events={events} taskTypes={taskTypes} products={products} />}
        {view === "team" && <TeamView events={events} designers={selDesigners} range={range} groupBy={groupBy} />}
        {view === "grid" && <GridView events={events} designers={selDesigners} range={range} groupBy={groupBy} />}
      </div>
    </div>
  );
}

window.StatsTab = StatsTab;
})();
