"use client";

import { useState } from "react";
import { usePool } from "@enode/core/ble/sensor-pool";
import { useNow } from "@enode/core/use-now";
import {
  diagnoseSensor,
  getSensorDiagnosticsSnapshot,
  resetSensorDiagnostics,
  useSensorDiagnostics,
  type DiagnosticsLevel,
  type SensorDiagnosticsRecord,
} from "@enode/core/ble/sensor-diagnostics";
import {
  getThroughputSnapshot,
  useSensorThroughput,
} from "@enode/core/ble/throughput-monitor";

// Developer-only Sensors tab: for every sensor seen this app session, what the
// app commanded (mode, configuration, settings read-back), what the sensor
// reported (rep events, firmware rejections) and where reps got lost. Records
// survive disconnects, so a feedback session can be inspected after closing
// it. "Copy report" puts the whole thing on the clipboard for a ticket.
// Debug-only (never shipped in a normal build), so its copy isn't translated —
// same as the other /debug tabs.

function ago(at: number, now: number): string {
  const s = Math.max(0, Math.round((now - at) / 1000));
  if (s < 60) return `${s} s ago`;
  const m = Math.round(s / 60);
  if (m < 60) return `${m} min ago`;
  return `${Math.round(m / 60)} h ago`;
}

function clock(at: number): string {
  return new Date(at).toLocaleTimeString([], {
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
  });
}

const LEVEL_CLASS: Record<DiagnosticsLevel, string> = {
  info: "text-foreground-muted",
  warn: "text-foreground-red-orange/70",
  error: "text-foreground-red-orange",
};

export function SensorDiagnosticsView() {
  const records = useSensorDiagnostics();
  const rates = useSensorThroughput();
  const pool = usePool();
  const [copied, setCopied] = useState(false);
  // Keeps the "… ago" labels fresh.
  const now = useNow(1_000);

  const live = (r: SensorDiagnosticsRecord) => r.state !== "removed";
  const ordered = [...records].sort(
    (a, b) => Number(live(b)) - Number(live(a)),
  );

  async function copyReport() {
    const hzById = new Map(getThroughputSnapshot().map((t) => [t.id, t.hz]));
    const report = {
      generatedAt: new Date().toISOString(),
      userAgent: navigator.userAgent,
      sensors: getSensorDiagnosticsSnapshot().map((r) => ({
        ...r,
        hz: hzById.get(r.id) ?? null,
        findings: diagnoseSensor(r, hzById.get(r.id) ?? null),
      })),
    };
    await navigator.clipboard.writeText(JSON.stringify(report, null, 2));
    setCopied(true);
    window.setTimeout(() => setCopied(false), 2000);
  }

  return (
    <div className="scrollbar-none min-h-0 flex-1 overflow-y-auto px-4 py-5">
      <div className="mx-auto w-full max-w-2xl space-y-4">
        <div className="flex items-start gap-3">
          <div className="min-w-0 flex-1">
            <h1 className="heading-large">Sensors</h1>
            <p className="body-small mt-1 text-foreground-muted">
              What the app sent to each sensor and what came back. Kept after
              disconnecting — connect, train, then check here.
            </p>
          </div>
          <button
            type="button"
            onClick={() => void copyReport()}
            disabled={records.length === 0}
            className="shrink-0 rounded-full bg-surface-base px-3 py-2 body-small shadow ring-1 ring-surface-stroke-muted hover:bg-surface-base-muted disabled:opacity-40"
          >
            {copied ? "Copied" : "Copy report"}
          </button>
          <button
            type="button"
            onClick={resetSensorDiagnostics}
            disabled={records.length === 0}
            className="shrink-0 rounded-full bg-surface-base px-3 py-2 body-small shadow ring-1 ring-surface-stroke-muted hover:bg-surface-base-muted disabled:opacity-40"
          >
            Reset
          </button>
        </div>

        {ordered.length === 0 ? (
          <p className="body-normal rounded-2xl border border-surface-stroke bg-surface-base p-4 text-foreground-muted">
            No sensor connected this session yet. Connect one in a workout or
            in feedback training, then come back.
          </p>
        ) : (
          ordered.map((record) => {
            const hz = rates.find((t) => t.id === record.id)?.hz ?? null;
            const pooled = pool.find((p) => p.id === record.id);
            return (
              <SensorCard
                key={record.id}
                record={record}
                hz={live(record) ? hz : null}
                station={
                  pooled?.stationId
                    ? `${pooled.stationId}${pooled.position ? ` · ${pooled.position}` : ""}`
                    : null
                }
                now={now}
              />
            );
          })
        )}
      </div>
    </div>
  );
}

function SensorCard({
  record,
  hz,
  station,
  now,
}: {
  record: SensorDiagnosticsRecord;
  hz: number | null;
  station: string | null;
  now: number;
}) {
  const findings = diagnoseSensor(record, hz);
  const c = record.counters;
  const appDropped =
    c.appDropped["no-sensor-mode"] + c.appDropped["mode-mismatch"];

  return (
    <section className="space-y-3 rounded-2xl border border-surface-stroke bg-surface-base p-4">
      <header className="flex items-start gap-3">
        <div className="min-w-0 flex-1">
          <h2 className="heading-normal truncate">
            {record.name ?? record.id}
          </h2>
          <p className="body-small truncate text-foreground-muted">
            {record.serial ?? "serial ?"}
            {station ? ` · ${station}` : ""}
          </p>
        </div>
        <span
          className={`shrink-0 rounded-full px-2.5 py-1 body-small ${
            record.state === "removed" || record.state === "disconnected"
              ? "bg-surface-base-muted text-foreground-muted"
              : record.state === "reconnecting"
                ? "bg-foreground-red-orange/10 text-foreground-red-orange"
                : "bg-foreground-platinum-green/12 text-foreground-platinum-green"
          }`}
        >
          {record.state}
        </span>
      </header>

      {findings.length === 0 ? (
        <p className="body-small rounded-xl bg-foreground-platinum-green/10 px-3 py-2 text-foreground-platinum-green">
          No problems found.
        </p>
      ) : (
        <ul className="space-y-1 rounded-xl bg-foreground-red-orange/5 px-3 py-2">
          {findings.map((f) => (
            <li key={f.text} className={`body-small ${LEVEL_CLASS[f.level]}`}>
              {f.level === "error" ? "✕ " : "! "}
              {f.text}
            </li>
          ))}
        </ul>
      )}

      <dl className="grid grid-cols-2 gap-x-4 gap-y-1 body-small sm:grid-cols-3">
        <Fact label="API" value={record.apiVersion ?? "unknown"} bad={!record.apiVersion} />
        <Fact label="Firmware" value={record.firmware ?? "—"} />
        <Fact label="Battery" value={record.battery >= 0 ? `${record.battery} %` : "—"} />
        <Fact label="Data rate" value={hz !== null ? `${hz.toFixed(0)} Hz` : "—"} />
        <Fact label="Connects" value={String(record.connects)} />
        <Fact label="Drops" value={String(record.drops)} bad={record.drops > 0} />
      </dl>

      <dl className="space-y-1 body-small">
        <Fact
          label="Mode sent"
          value={
            record.lastSetMode
              ? `${record.lastSetMode.mode} → wire ${record.lastSetMode.wireMode} · ${ago(record.lastSetMode.at, now)}`
              : "never"
          }
          bad={
            record.lastSetMode?.wireMode === "none" &&
            record.lastSetMode.mode !== "none"
          }
        />
        <Fact
          label="Configure"
          value={
            record.lastConfigure
              ? `${record.lastConfigure.mode} · ${record.lastConfigure.outcome} (attempt ${record.lastConfigure.attempts}) · ${ago(record.lastConfigure.at, now)}`
              : "never"
          }
          bad={record.lastConfigure?.outcome === "failed"}
        />
        <Fact
          label="Read-back"
          value={
            record.lastVerify
              ? `${record.lastVerify.outcome} · ${ago(record.lastVerify.at, now)}`
              : "—"
          }
          bad={!!record.lastVerify && record.lastVerify.outcome !== "match"}
        />
      </dl>

      <div className="grid grid-cols-3 gap-2 sm:grid-cols-6">
        <Counter label="Rep starts" value={c.repStarts} />
        <Counter label="Reps" value={c.repEndsValid} />
        <Counter label="Firmware rejected" value={c.firmwareRejected} tone="warn" />
        <Counter label="Undecodable" value={c.decodeFailures} tone="error" />
        <Counter label="App dropped" value={appDropped} tone="error" />
        <Counter label="Range errors" value={c.rangeErrors} tone="warn" />
      </div>

      <details>
        <summary className="body-small cursor-pointer text-foreground-muted">
          Timeline ({record.timeline.length})
        </summary>
        <ol className="mt-2 space-y-0.5 font-mono text-xs">
          {[...record.timeline].reverse().map((entry, i) => (
            <li key={`${entry.at}-${i}`} className={LEVEL_CLASS[entry.level]}>
              {clock(entry.at)} {entry.text}
            </li>
          ))}
        </ol>
      </details>
    </section>
  );
}

function Fact({
  label,
  value,
  bad = false,
}: {
  label: string;
  value: string;
  bad?: boolean;
}) {
  return (
    <div className="flex min-w-0 gap-2">
      <dt className="shrink-0 text-foreground-muted">{label}</dt>
      <dd
        className={`min-w-0 truncate ${bad ? "text-foreground-red-orange" : "text-foreground-default"}`}
      >
        {value}
      </dd>
    </div>
  );
}

function Counter({
  label,
  value,
  tone,
}: {
  label: string;
  value: number;
  tone?: "warn" | "error";
}) {
  const highlighted = value > 0 && tone !== undefined;
  return (
    <div
      className={`rounded-xl px-2 py-1.5 ${highlighted ? "bg-foreground-red-orange/10" : "bg-surface-base-muted"}`}
    >
      <div
        className={`heading-normal tabular-nums ${highlighted ? LEVEL_CLASS[tone] : ""}`}
      >
        {value}
      </div>
      <div className="text-xs leading-tight text-foreground-muted">{label}</div>
    </div>
  );
}
