/* Eno.Health Console — Streams: wearable & smart-device signal ingestion (scaffold) */
const S = window.ENO;

function StreamsView({ onSelectCell, onOpenDevice }) {
  const [tab, setTab] = useState("fleet");
  const tabs = [
    { key: "fleet", label: "Device fleet", count: S.DEVICES.length },
    { key: "signals", label: "Signal streams" },
    { key: "integrations", label: "Integrations" },
  ];
  return h("div", { className: "view" },
    h(PageHead, { title: "Streams", sub: "Wearable and smart-device signal ingestion — time-series telemetry from connected devices" }),
    h("div", { className: "rbac-banner" },
      h(Icon, { name: "runs", size: 15 }),
      h("div", null, h("strong", null, "New ingestion modality."),
        " Signals are continuous time-series — they run a parallel pre-processing path (decode → ", h(Mono, null, "normalize units"), " → ", h(Mono, null, "range check"), " → de-identify → time-series store). The device fleet below is live; stream drill-down and integration management are next.")),
    h(StreamKpis, null),
    h(Tabs, { tabs, active: tab, onChange: setTab }),
    h("div", { className: "tabbody" },
      tab === "fleet" && h(DeviceFleet, { onOpenDevice }),
      tab === "signals" && h(SignalStreamsComingSoon, null),
      tab === "integrations" && h(IntegrationsComingSoon, null)));
}

function StreamKpis() {
  const k = S.streamKpis();
  return h("div", { className: "stat-grid stat-grid-4" },
    h(Card, { pad: false }, h(Stat, { label: "Active devices", value: k.activeDevices, unit: "/ " + k.totalDevices, tone: "ok", icon: "tenants" })),
    h(Card, { pad: false }, h(Stat, { label: "Readings / min", value: fmtNum(k.readingsPerMin), spark: S.series(2, 16, 30, 9), tone: "info", icon: "runs" })),
    h(Card, { pad: false }, h(Stat, { label: "Median sync lag", value: k.syncLag, unit: "s", tone: "ok", icon: "clock" })),
    h(Card, { pad: false }, h(Stat, { label: "Needs attention", value: k.needsAttention, tone: k.needsAttention ? "bad" : "muted", delta: k.needsAttention ? "stale / expired / offline" : "all healthy", icon: "alert" })));
}

const DEV_STATUS_FILTERS = [
  { value: "all", label: "All statuses" },
  { value: "connected", label: "Connected" },
  { value: "syncing", label: "Syncing" },
  { value: "stale", label: "Stale" },
  { value: "token_expired", label: "Token expired" },
  { value: "disconnected", label: "Disconnected" },
];

function DeviceFleet({ onOpenDevice }) {
  const [status, setStatus] = useState("all");
  const [source, setSource] = useState("all");
  const [q, setQ] = useState("");
  const rows = S.DEVICES.filter((d) => {
    if (status !== "all" && d.status !== status) return false;
    if (source !== "all" && d.source !== source) return false;
    if (q && !(d.id + d.model + d.patient + S.sourceName(d.source)).toLowerCase().includes(q.toLowerCase())) return false;
    return true;
  });
  const head = ["Device", "Patient", "Tenant", "Source", "Streams", "Battery", "Last sync", "Status"];
  const body = rows.map((d) => {
    const st = S.DEVICE_STATUS[d.status];
    return h("tr", { key: d.id, className: "row-click", onClick: () => onOpenDevice(d) },
      h("td", null, h("div", { className: "cell-doc" }, h("span", { className: "cell-file" }, d.model), h(Mono, { className: "cell-id" }, d.id))),
      h("td", null, h(Mono, null, d.patient)),
      h("td", null, S.tenantName(d.tenant)),
      h("td", null, h("span", { className: "src-cell" }, h("span", { className: "src-glyph" }, S.STREAM_SOURCES.find((s) => s.key === d.source).glyph || "♥"), S.sourceName(d.source))),
      h("td", null, h("span", { className: "tags" }, d.streams.slice(0, 4).map((s) => h("span", { className: "tag", key: s }, S.signalLabel(s))),
        d.streams.length > 4 ? h("span", { className: "tag" }, "+" + (d.streams.length - 4)) : null)),
      h("td", null, d.battery == null ? h("span", { className: "dim" }, "mains") : h(Battery, { pct: d.battery })),
      h("td", { className: "dim" }, d.status === "syncing" ? h("span", { className: "sync-now" }, h(Icon, { name: "spinner", size: 12, className: "spin" }), "now") : fmtAgo(d.lastSync)),
      h("td", null, h(Pill, { tone: st.tone, soft: true }, st.label)));
  });
  return h(Card, { pad: false },
    h("div", { className: "toolbar" },
      h("div", { className: "search" }, h(Icon, { name: "search", size: 16 }),
        h("input", { className: "search-input", placeholder: "Search device, model, patient, source…", value: q, onChange: (e) => setQ(e.target.value) })),
      h(Select, { value: status, onChange: setStatus, options: DEV_STATUS_FILTERS }),
      h(Select, { value: source, onChange: setSource, options: [{ value: "all", label: "All sources" }, ...S.STREAM_SOURCES.map((s) => ({ value: s.key, label: s.name }))] }),
      h("span", { className: "toolbar-count" }, rows.length, " of ", S.DEVICES.length)),
    h("div", { className: "table-wrap" },
      h("table", { className: "table" },
        h("thead", null, h("tr", null, head.map((c) => h("th", { key: c }, c)))),
        h("tbody", null, body,
          !rows.length && h("tr", null, h("td", { colSpan: 8 }, h(Empty, { title: "No devices match", sub: "Try clearing filters." })))))));
}

function Battery({ pct }) {
  const tone = pct <= 15 ? "bad" : pct <= 35 ? "warn" : "ok";
  return h("span", { className: "battery" },
    h("span", { className: "battery-shell" }, h("span", { className: "battery-fill", style: { width: pct + "%", background: `var(--${tone})` } })),
    h("span", { className: "battery-pct mono" }, pct + "%"));
}

function SignalStreamsComingSoon() {
  return h(Card, null,
    h("div", { className: "soon-head" },
      h("h3", { className: "card-title" }, "Signal stream explorer"),
      h(Pill, { tone: "accent" }, "Coming soon")),
    h("p", { className: "soon-lead" }, "Per-metric stream drill-down — the signal equivalent of the document debugger. Each stream will expose:"),
    h("div", { className: "soon-grid" },
      [
        ["runs", "Sync timeline & gaps", "Reading cadence with dropout windows highlighted, so you can see exactly when a device stopped streaming."],
        ["database", "Unit normalization", "Raw → canonical units (e.g. mg/dL ↔ mmol/L, °F ↔ °C) with the mapping that was applied."],
        ["shield", "Range & plausibility checks", "Out-of-range and implausible readings flagged before they reach storage."],
        ["lock", "De-identification", "Same PHI/PII posture as documents, plus location-from-GPS and pattern-based re-identification guards."],
      ].map(([icon, title, body]) => h("div", { className: "soon-card", key: title },
        h("div", { className: "soon-icon" }, h(Icon, { name: icon, size: 18 })),
        h("div", null, h("div", { className: "soon-title" }, title), h("div", { className: "soon-sub" }, body))))),
    h("div", { className: "soon-sample" },
      h("div", { className: "soon-sample-head" }, h(Mono, null, "hr"), " · Heart rate · ", h("span", { className: "dim" }, "Apple Watch S9 · pt_a91f")),
      h(MiniBars, { data: S.series(7, 40, 62, 16), tone: "info", height: 54 }),
      h("div", { className: "dim small" }, "bpm · last 40 min · preview of the stream sparkline")));
}

function IntegrationsComingSoon() {
  return h(Card, null,
    h("div", { className: "soon-head" },
      h("h3", { className: "card-title" }, "Integrations" ),
      h(Pill, { tone: "accent" }, "Coming soon")),
    h("p", { className: "soon-lead" }, "Connection health for each device-data provider — OAuth token status, API rate-limit headroom, and per-source ingest volume."),
    h("div", { className: "integ-grid" },
      S.STREAM_SOURCES.map((s) => {
        const devs = S.DEVICES.filter((d) => d.source === s.key);
        const issues = devs.filter((d) => d.status === "token_expired" || d.status === "disconnected").length;
        return h("div", { className: "integ-card", key: s.key },
          h("div", { className: "integ-top" },
            h("span", { className: "src-glyph lg" }, s.glyph || "♥"),
            h("div", null, h("div", { className: "integ-name" }, s.name), h("div", { className: "dim small" }, s.kind))),
          h("div", { className: "integ-meta" },
            h("span", null, h("strong", null, devs.length), " devices"),
            issues ? h(Pill, { tone: "warn", soft: true }, issues + " need auth") : h(Pill, { tone: "ok", soft: true }, "healthy")));
      })));
}

/* ======================================================================== */
/* DEVICE / SIGNAL STREAM DRILL-DOWN                                         */
/* ======================================================================== */
const SIG_BASE = { hr: [64, 18], hrv: [55, 22], spo2: [97, 2], sleep: [78, 12], steps: [42, 30], temp: [36.6, 0.5], bp: [120, 12], ecg: [60, 22], glucose: [112, 34] };
const SIG_CAD = { hr: "1 / min · 5s on workout", hrv: "nightly + spot", spo2: "1 / min", sleep: "nightly summary", steps: "1 / min rollup", temp: "every 5 min", bp: "on demand", ecg: "on demand · 30s", glucose: "every 5 min" };
const SIG_NORM = { hr: "bpm (canonical)", hrv: "ms (canonical)", spo2: "fraction → %", sleep: "vendor stages → canonical", steps: "cumulative → delta", temp: "°F → °C", bp: "mmHg → systolic / diastolic", ecg: "250 Hz → samples", glucose: "mg/dL ↔ mmol/L" };

function streamStat(dev, key) {
  const [b, a] = SIG_BASE[key] || [50, 15];
  const seed = (dev.id.charCodeAt(4) || 5) + key.length;
  const data = S.series(seed, 32, b, a);
  const offline = ["stale", "token_expired", "disconnected"].includes(dev.status);
  const primary = dev.streams[0] === key;
  const gap = offline ? (primary ? dev.gap : Math.min(100, +(dev.gap * 0.7).toFixed(1))) : +((seed % 5) * 0.35).toFixed(1);
  const inRange = Math.max(88, 100 - gap * 0.25 - (seed % 7) * 0.3);
  const flagged = Math.round((100 - inRange) / 100 * data.length * 4);
  const meta = S.SIGNAL_TYPES.find((s) => s.key === key) || { label: key, unit: "" };
  const last = data[data.length - 1];
  const lastDisp = key === "bp" ? `${Math.round(last)}/${Math.round(last * 0.64)}` : key === "temp" ? last.toFixed(1) : Math.round(last);
  return { meta, data, lastDisp, gap, inRange: inRange.toFixed(1), flagged, cad: SIG_CAD[key], norm: SIG_NORM[key], offline };
}

function syncEvents(dev) {
  const out = [];
  if (dev.status === "token_expired") out.push({ label: "OAuth token expired", service: S.sourceName(dev.source), at: dev.lastSync, state: "failed", note: "Refresh token rejected by provider — patient must reconnect the integration." });
  else if (dev.status === "disconnected") out.push({ label: "Device unreachable", service: S.sourceName(dev.source), at: dev.lastSync, state: "failed", note: "No successful sync in over 24h — battery depleted or app uninstalled." });
  else if (dev.status === "stale") out.push({ label: "Sync overdue", service: "bucket-watcher", at: dev.lastSync, state: "active", note: "Last batch is older than the freshness threshold — gap accumulating." });
  const n = dev.offline ? 2 : 5;
  const offsets = [dev.lastMin + 6, dev.lastMin + 13, dev.lastMin + 22, dev.lastMin + 34, dev.lastMin + 49];
  for (let i = 0; i < n; i++) out.push({ label: `Synced ${12 + (i * 9) % 41} readings`, service: S.sourceName(dev.source), at: S.ago(offsets[i] * S.MIN), state: "done" });
  return out;
}

function DeviceDrawer({ device, onClose }) {
  const [tab, setTab] = useState("signals");
  const toast = useToast();
  useEffect(() => { setTab("signals"); }, [device && device.id]);
  if (!device) return null;
  const st = S.DEVICE_STATUS[device.status];

  const footer = h("div", { className: "drawer-actions" },
    h(Button, { variant: "ghost", size: "sm", icon: "copy", onClick: () => { navigator.clipboard?.writeText(device.id); toast("Device ID copied"); } }, "Copy ID"),
    h(Button, { variant: "default", size: "sm", icon: "refresh", onClick: () => toast("Re-sync requested for " + device.id) }, "Force sync"),
    (device.status === "token_expired" || device.status === "disconnected") &&
      h(Button, { variant: "primary", size: "sm", icon: "webhooks", onClick: () => toast("Reconnect link sent to patient") }, "Reconnect"),
    h(Button, { variant: "danger-ghost", size: "sm", icon: "trash", onClick: () => toast("Device unlinked from patient") }, "Unlink"));

  const tabs = [
    { key: "signals", label: "Signals", count: device.streams.length },
    { key: "sync", label: "Sync timeline" },
    { key: "validation", label: "Validation" },
    { key: "deid", label: "De-identification" },
    { key: "meta", label: "Metadata" },
  ];
  return h(Drawer, {
    open: true, onClose, width: 780, footer,
    title: device.model,
    sub: h("div", { className: "drawer-subline" },
      h(Mono, { copy: true }, device.id), h(Pill, { tone: st.tone }, st.label),
      h("span", { className: "src-cell" }, h("span", { className: "src-glyph" }, S.STREAM_SOURCES.find((s) => s.key === device.source).glyph || "♥"), S.sourceName(device.source))),
  },
    h(Tabs, { tabs, active: tab, onChange: setTab }),
    h("div", { className: "drawer-tabbody" },
      tab === "signals" && h(SignalsTab, { device }),
      tab === "sync" && h(SyncTab, { device }),
      tab === "validation" && h(ValidationTab, { device }),
      tab === "deid" && h(DeidTab, { device }),
      tab === "meta" && h(MetaDeviceTab, { device })));
}

function SignalsTab({ device }) {
  return h("div", { className: "stream-list" },
    device.streams.map((key) => {
      const s = streamStat(device, key);
      const gapTone = s.gap > 20 ? "bad" : s.gap > 5 ? "warn" : "ok";
      return h("div", { className: "stream-card", key },
        h("div", { className: "stream-head" },
          h("div", null,
            h("div", { className: "stream-name" }, s.meta.label, h("span", { className: "stream-unit mono" }, s.meta.unit)),
            h("div", { className: "stream-cad dim" }, s.cad)),
          h("div", { className: "stream-last" }, h("span", { className: "stream-val" }, s.lastDisp), h("span", { className: "stream-val-unit dim" }, s.meta.unit))),
        s.offline && s.gap >= 100
          ? h("div", { className: "stream-offline" }, h(Icon, { name: "alert", size: 13 }), "No readings — stream halted")
          : h(MiniBars, { data: s.data, tone: s.offline ? "warn" : "info", height: 42 }),
        h("div", { className: "stream-meta" },
          h("span", { className: "stream-chip" }, h("span", { className: "dim" }, "Normalize"), h(Mono, null, s.norm)),
          h("span", { className: "stream-chip" }, h("span", { className: "dim" }, "Range"), h(Pill, { tone: +s.inRange > 98 ? "ok" : +s.inRange > 95 ? "warn" : "bad", soft: true }, s.inRange + "% in range")),
          h("span", { className: "stream-chip" }, h("span", { className: "dim" }, "Gap"), h(Pill, { tone: gapTone, soft: true }, s.gap + "%"))));
    }));
}

function SyncTab({ device }) {
  const ev = syncEvents(device);
  return h("div", { className: "timeline" },
    ev.map((t, i) => h("div", { className: "tl-row", key: i },
      h("div", { className: `tl-marker tl-${t.state}` }, h(Icon, { name: t.state === "failed" ? "x" : t.state === "active" ? "dot" : "check", size: 13 })),
      h("div", { className: "tl-main" },
        h("div", { className: "tl-label" }, t.label, h("span", { className: "tl-service mono" }, t.service)),
        h("div", { className: "tl-time" }, fmtTime(t.at), " · ", fmtAgo(t.at)),
        t.note && h("div", { className: "tl-note" }, h(Icon, { name: "alert", size: 13 }), t.note)),
      i < ev.length - 1 && h("div", { className: "tl-line" }))));
}

function ValidationTab({ device }) {
  const head = ["Stream", "In-range", "Flagged", "Normalization"];
  const body = device.streams.map((key) => {
    const s = streamStat(device, key);
    return h("tr", { key },
      h("td", null, s.meta.label),
      h("td", null, h(Pill, { tone: +s.inRange > 98 ? "ok" : +s.inRange > 95 ? "warn" : "bad", soft: true }, s.inRange + "%")),
      h("td", { className: "ta-c mono" }, s.flagged),
      h("td", null, h(Mono, { className: "dim" }, s.norm)));
  });
  return h("div", null,
    h("div", { className: "audit-banner" }, h(Icon, { name: "shield", size: 14 }), "Out-of-range and implausible readings are flagged before the time-series store. Flagged values are kept but marked, never silently dropped."),
    h("div", { className: "table-wrap flush" },
      h("table", { className: "table table-tight" },
        h("thead", null, h("tr", null, head.map((c) => h("th", { key: c }, c)))),
        h("tbody", null, body))));
}

function DeidTab({ device }) {
  const items = [
    ["Device serial / IDFV", "pseudonymized", "up"],
    ["GPS / location", "stripped", "up"],
    ["Patient identifiers", device.patient + " pseudonym", "up"],
    ["Re-identification pattern guard", "active", "up"],
    ["Timestamps", "jittered ±60s", "up"],
  ];
  return h("div", null,
    h("div", { className: "audit-banner" }, h(Icon, { name: "lock", size: 14 }), "Signals carry the same PHI/PII posture as documents, plus location and pattern-based re-identification guards."),
    h("div", { className: "engine-grid deid-grid" },
      items.map(([label, state, dot]) => h("div", { className: "engine", key: label },
        h("span", { className: `svc-dot svc-${dot}` }),
        h("span", { className: "engine-name" }, label),
        h("span", { className: "engine-state" }, state)))));
}

function MetaDeviceTab({ device }) {
  const rows = [
    ["Device ID", device.id, true], ["Patient", device.patient + " (pseudonymous)"],
    ["Tenant", S.tenantName(device.tenant) + " (" + device.tenant + ")"],
    ["Source", S.sourceName(device.source)], ["Model", device.model],
    ["Firmware", device.fw], ["Battery", device.battery == null ? "Mains powered" : device.battery + "%"],
    ["Cell", device.cell], ["Connection", S.DEVICE_STATUS[device.status].label],
    ["Streams", device.streams.map(S.signalLabel).join(", ")],
    ["Readings / min", device.rpm], ["Last sync", fmtTime(device.lastSync) + " · " + fmtAgo(device.lastSync)],
  ];
  return h("div", { className: "meta-grid" },
    rows.map(([k, v, mono], i) => h("div", { className: "meta-row", key: i },
      h("span", { className: "meta-k" }, k),
      h("span", { className: "meta-v" }, mono ? h(Mono, { copy: true }, v) : v))));
}

Object.assign(window, { StreamsView, DeviceDrawer });
