// Direction B — "Operator"
// Grafana-leaning. KPI tiles, gauges, more status color. Cooler slate base.

const B = (() => {
  const { useMemo, useState } = React;
  const { bytes, speed, pct, relTime, duration, airTime, weekday,
          STATUS_TONE, seedingHealth, sourceLink, LINKS } = window.HL;

  const tones = {
    ok: "var(--b-ok)", bad: "var(--b-bad)", warn: "var(--b-warn)",
    info: "var(--b-info)", muted: "var(--b-muted)",
  };

  // Per-panel collapsed state, persisted to localStorage so it survives
  // reloads and sessions. Key is derived from the panel title.
  const COLLAPSE_KEY_PREFIX = "dashboard:collapsed:";
  const slugify = (s) =>
    String(s || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
  const readCollapsed = (slug) => {
    if (!slug) return false;
    try { return window.localStorage.getItem(COLLAPSE_KEY_PREFIX + slug) === "1"; }
    catch { return false; }
  };
  const writeCollapsed = (slug, val) => {
    if (!slug) return;
    try {
      if (val) window.localStorage.setItem(COLLAPSE_KEY_PREFIX + slug, "1");
      else window.localStorage.removeItem(COLLAPSE_KEY_PREFIX + slug);
    } catch { /* private mode etc — fall back to session-only state */ }
  };

  const Chip = ({ tone = "muted", children, solid }) => (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 6,
      padding: "3px 9px", borderRadius: 4, fontSize: 11,
      fontFamily: "var(--mono)", letterSpacing: 0.3,
      color: solid ? "#0b1220" : tones[tone],
      background: solid ? tones[tone] : `color-mix(in oklab, ${tones[tone]} 16%, transparent)`,
      border: solid ? "none" : `1px solid color-mix(in oklab, ${tones[tone]} 32%, transparent)`,
    }}>{children}</span>
  );

  // `href` → a single "OPEN ↗" link. `links` → one labelled
  // "OPEN <label> ↗" per entry ([{ href, label }, ...]); use when a panel
  // can route to more than one owning app.
  const Panel = ({ title, count, error, updated, href, links, children, tone, dense, action }) => {
    const slug = useMemo(() => slugify(title), [title]);
    const [collapsed, setCollapsed] = useState(() => readCollapsed(slug));
    const collapsible = !!slug;
    const toggle = () => {
      const next = !collapsed;
      setCollapsed(next);
      writeCollapsed(slug, next);
    };
    const stop = (e) => e.stopPropagation();

    return (
      <section style={{
        background: "var(--b-surface)",
        // When expanded, the body content carries the status colors and the
        // border stays neutral. When collapsed, surface the tone on the
        // border so the status is still visible at a glance.
        border: `1px solid ${collapsed && tone
          ? `color-mix(in oklab, ${tones[tone]} 65%, var(--b-border))`
          : "var(--b-border)"}`,
        borderRadius: 8, overflow: "hidden",
        boxShadow: collapsed && tone
          ? `inset 0 0 0 1px color-mix(in oklab, ${tones[tone]} 30%, transparent)`
          : undefined,
      }}>
        <header
          onClick={collapsible ? toggle : undefined}
          style={{
            padding: dense ? "10px 14px" : "12px 16px",
            borderBottom: collapsed ? "none" : "1px solid var(--b-border)",
            display: "flex", alignItems: "center", gap: 10,
            background: "color-mix(in oklab, var(--b-text) 3%, var(--b-surface))",
            cursor: collapsible ? "pointer" : "default",
            userSelect: "none",
          }}>
          {collapsible && (
            <span aria-hidden="true" style={{
              fontSize: 11, color: "var(--b-muted)", fontFamily: "var(--mono)",
              width: 10, display: "inline-flex", justifyContent: "center",
              transition: "transform 120ms ease",
              transform: collapsed ? "rotate(-90deg)" : "rotate(0deg)",
            }}>▾</span>
          )}
          <span style={{
            fontSize: 12, fontWeight: 600, letterSpacing: 0.4,
            textTransform: "uppercase", color: "var(--b-text)",
          }}>{title}</span>
          {count != null && <Chip tone="muted">{count}</Chip>}
          {error && <Chip tone="bad">degraded</Chip>}
          <div style={{ flex: 1 }} />
          {action && <span onClick={stop}>{action}</span>}
          {updated && <span style={{ fontSize: 10, color: "var(--b-muted)", fontFamily: "var(--mono)" }}>
            {relTime(updated)}
          </span>}
          {(links && links.length ? links : href ? [{ href }] : []).map((l, i) => (
            <a key={i} href={l.href} target="_blank" rel="noreferrer"
               onClick={stop}
               style={{
                 fontSize: 10, color: "var(--b-muted)", textDecoration: "none",
                 fontFamily: "var(--mono)", marginLeft: 8,
               }}>OPEN{l.label ? ` ${l.label.toUpperCase()}` : ""} ↗</a>
          ))}
        </header>
        {!collapsed && <div>{children}</div>}
      </section>
    );
  };

  // ── triage ──────────────────────────────────────────────────────────
  function triage(sections) {
    const failedTasks = (sections.tasks?.data || []).filter(t => t.last_status === "failed");
    const stoppedApps = sections.services?.data || [];
    const degradedPools = (sections.storage?.data || []).filter(p => !p.healthy);
    const failedDownloads = sections.failed_downloads?.data || [];
    const seedingAnnotated = (sections.downloads?.data || [])
      .map(d => ({ d, h: seedingHealth(d) }))
      .filter(x => x.h);
    const strikes = seedingAnnotated.filter(x => x.h.level === "risk");
    const watches = seedingAnnotated.filter(x => x.h.level === "watch");
    return { failedTasks, stoppedApps, degradedPools, failedDownloads, strikes, watches };
  }

  // ── KPI tile (big) ──────────────────────────────────────────────────
  const Tile = ({ label, value, tone = "muted", sub, gauge }) => (
    <div style={{
      background: "var(--b-surface)", border: "1px solid var(--b-border)",
      borderLeft: `3px solid ${tones[tone]}`,
      borderRadius: 6, padding: "14px 16px",
      flex: 1, minWidth: 0,
      display: "flex", flexDirection: "column", gap: 8,
    }}>
      <div style={{ fontSize: 10, color: "var(--b-muted)", letterSpacing: 0.8,
                    textTransform: "uppercase", fontFamily: "var(--mono)" }}>{label}</div>
      <div style={{ display: "flex", alignItems: "baseline", gap: 10 }}>
        <span style={{ fontSize: 30, fontFamily: "var(--mono)", color: tones[tone], lineHeight: 1, fontWeight: 500 }}>
          {value}
        </span>
        {gauge != null && (
          <div style={{ flex: 1, height: 4, background: "color-mix(in oklab, var(--b-text) 8%, transparent)", borderRadius: 999, overflow: "hidden" }}>
            <div style={{ width: `${Math.max(0, Math.min(1, gauge)) * 100}%`, height: "100%", background: tones[tone] }} />
          </div>
        )}
      </div>
      {sub && <div style={{ fontSize: 11, color: "var(--b-muted)", fontFamily: "var(--mono)" }}>{sub}</div>}
    </div>
  );

  function Hero({ sections, t, narrow }) {
    const dl = sections.downloads?.data || [];
    const dlSpeed = dl.reduce((a, x) => a + (x.dl_speed || 0), 0);
    const upSpeed = dl.filter(x => x.client === "qbittorrent").reduce((a, x) => a + (x.up_speed || 0), 0);
    const pool = (sections.storage?.data || []).reduce((a, p) => ({
      size: a.size + (p.size_bytes || 0), free: a.free + (p.free_bytes || 0),
    }), { size: 0, free: 0 });
    const usedFrac = pool.size ? (1 - pool.free / pool.size) : 0;
    // Status tile = TrueNAS lab health only. Strike-risk seeds, failed imports, and
    // Shoko unrecognized are INDEPENDENT KPIs reflected in their own tiles/panels.
    const truenasAlerts = t.failedTasks.length + t.stoppedApps.length + t.degradedPools.length;
    return (
      <div style={{ display: "grid",
                    gridTemplateColumns: narrow ? "1fr 1fr" : "1.4fr 1fr 1fr 1fr",
                    gap: 10 }}>
        <Tile label="TrueNAS status" tone={truenasAlerts === 0 ? "ok" : "bad"}
              value={truenasAlerts === 0 ? "ALL GREEN" : `${truenasAlerts} ALERT${truenasAlerts > 1 ? "S" : ""}`}
              sub={truenasAlerts === 0
                ? "0 task · 0 app · 0 pool"
                : `${t.failedTasks.length} task · ${t.stoppedApps.length} app · ${t.degradedPools.length} pool`} />
        <Tile label="Capacity" tone={usedFrac > 0.85 ? "warn" : "ok"}
              value={pct(usedFrac * 100, 0)} gauge={usedFrac}
              sub={`${bytes(pool.free)} free / ${bytes(pool.size)}`} />
        <Tile label="↓ Download" tone={dlSpeed ? "info" : "muted"}
              value={dlSpeed ? speed(dlSpeed) : "idle"}
              sub={`${dl.filter(x => x.state === "downloading").length} active · ${dl.length} total`} />
        <Tile label="↑ Seeding"
              tone={t.strikes.length ? "bad" : t.watches.length ? "warn" : "ok"}
              value={upSpeed ? speed(upSpeed) : "idle"}
              sub={t.strikes.length
                ? `${t.strikes.length} strike-risk${t.watches.length ? ` · ${t.watches.length} watch` : ""}`
                : t.watches.length ? `${t.watches.length} on watch` : "private trackers healthy"} />
      </div>
    );
  }

  // ── alerts banner ───────────────────────────────────────────────────
  function Alerts({ sections, t }) {
    const lines = [];
    t.failedTasks.forEach(x => lines.push({
      tone: "bad", code: "TASK", title: x.name,
      sub: `failed ${relTime(x.last_run)}` + (x.next_run ? ` · retry ${relTime(x.next_run)}` : ""),
      href: x.category === "backup" ? LINKS.urbackup : LINKS.truenas,
    }));
    t.stoppedApps.forEach(x => lines.push({
      tone: "bad", code: "APP",  title: x.name,
      sub: `state: ${x.state.toLowerCase()} — open logs on TrueNAS`,
      href: LINKS.truenasApp(x.name),
    }));
    t.degradedPools.forEach(p => lines.push({
      tone: "bad", code: "POOL", title: p.name,
      sub: `${p.status} · ${p.scrub_errors|0} scrub errors · ${pct(p.used_percent, 0)} used`,
      href: LINKS.truenasPool(p.name),
    }));
    // Strike-risk seeds, failed imports, and Shoko unrecognized are INDEPENDENT
    // KPIs/panels — not part of the TrueNAS alerts list.

    if (lines.length === 0) return null;

    return (
      <Panel title="TrueNAS alerts" count={lines.length} tone="bad">
        <ul style={{ listStyle: "none", margin: 0, padding: 0 }}>
          {lines.map((l, i) => (
            <li key={i} style={{
              padding: "10px 16px", display: "grid",
              gridTemplateColumns: "80px 1fr auto", gap: 14, alignItems: "center",
              borderBottom: i < lines.length - 1 ? "1px solid var(--b-border)" : "none",
            }}>
              <Chip tone={l.tone} solid>{l.code}</Chip>
              <div style={{ minWidth: 0 }}>
                <div style={{ fontFamily: "var(--mono)", fontSize: 13, color: "var(--b-text)",
                              overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                  {l.title}
                </div>
                <div style={{ fontSize: 11, color: "var(--b-muted)", marginTop: 2 }}>{l.sub}</div>
              </div>
              {l.href && (
                <a href={l.href} target="_blank" rel="noreferrer" style={{
                  fontSize: 11, color: tones.info, fontFamily: "var(--mono)", textDecoration: "none",
                  border: `1px solid color-mix(in oklab, ${tones.info} 35%, transparent)`,
                  padding: "4px 10px", borderRadius: 4,
                }}>OPEN ↗</a>
              )}
            </li>
          ))}
        </ul>
      </Panel>
    );
  }

  return { tones, Chip, Panel, Tile, triage, Hero, Alerts };
})();

window.DirectionB = B;
