/**
 * SET 58 — the run surface, Andy's standing spec, from scratch:
 *   1. Pipeline log (top, open)   — stepper + tone-coded, substrate-marked,
 *                                   unbounded, autoscrolling event rows.
 *   2. Run report (middle, open)  — 3-function scorecard + six categories.
 *   3. Prompt (bottom, collapsed) — read-only submitted prompt.
 * THIS RUN ONLY — aggregate/install/site numbers live on Admin, never here.
 *
 * Live run renders from tier1:status; any other run renders from its record.
 * Missing data states are explicit and tone-coded — never a lying "starting…".
 */
import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
import { t1EventToneCss, isSubstrateEvent } from '../../../src/tier1/eventTone';
import { stageProgress } from '../stagePipeline';
import { formatRelOffset } from '../exportRunLog';
import { RunReport } from '../RunReport';
import {
  cancelRun, downloadArtifact, exportRun, getSnapshot, refreshRecord, resumeRun, subscribe,
  type ShellEvent, type ShellRecord, type ShellStatus,
} from './store';

const COLLAPSE_LOG = 'rha.shell.collapse.log';
const COLLAPSE_REPORT = 'rha.shell.collapse.report';
const COLLAPSE_PROMPT = 'rha.shell.collapse.prompt';

function readOpen(key: string, dflt: boolean): boolean {
  try {
    const v = localStorage.getItem(key);
    return v == null ? dflt : v === '1';
  } catch { return dflt; }
}

function writeOpen(key: string, open: boolean): void {
  try { localStorage.setItem(key, open ? '1' : '0'); } catch { /* private mode */ }
}

function Section({ storageKey, dflt, summary, children, marker }: {
  storageKey: string; dflt: boolean; summary: React.ReactNode;
  children: React.ReactNode; marker: string;
}) {
  const [open, setOpen] = useState(() => readOpen(storageKey, dflt));
  return (
    <details
      className="card sh-section"
      data-rha-shell={marker}
      open={open}
      onToggle={e => {
        const v = (e.target as HTMLDetailsElement).open;
        setOpen(v);
        writeOpen(storageKey, v);
      }}
    >
      <summary>{summary}</summary>
      <div className="sh-section-body">{children}</div>
    </details>
  );
}

function StageStrip({ events, running }: { events: ShellEvent[]; running: boolean }) {
  const stages = stageProgress(events, running);
  return (
    <div className="sh-stages">
      {stages.map(st => (
        <span key={st.phase} className={`sh-stage stage-${st.status}`}>
          {st.label}
          {st.elapsedMs != null && (
            <em className="dim"> {Math.round(st.elapsedMs / 1000)}s</em>
          )}
        </span>
      ))}
    </div>
  );
}

function LogRows({ events, startTs }: { events: ShellEvent[]; startTs: string }) {
  return (
    <>
      {events.map((e, i) => {
        const prevPhase = i > 0 ? events[i - 1]!.phase : null;
        const substrate = isSubstrateEvent({ phase: e.phase, message: e.message });
        return (
          <div key={i} style={{ display: 'contents' }}>
            {e.phase !== prevPhase && (
              <div className="logrow stage-break sh-stage-break">— {e.phase} —</div>
            )}
            <div
              className={`logrow tone-${t1EventToneCss(e)}${substrate ? ' substrate-mark' : ''}`}
              title={e.phase}
            >
              <span className="logts">{formatRelOffset(startTs, e.ts)}</span>
              <span className="loglabel">
                [{e.phase}] {e.featureId ? `${e.featureId} · ` : ''}{e.message}
              </span>
            </div>
          </div>
        );
      })}
    </>
  );
}

/**
 * SET 68 — judgment gate card: the identical evidence card the unsupervised
 * LLM judge sees, surfaced to the operator with the three resolutions.
 * The decision travels back on tier1:resume and becomes the case's license.
 */
function JudgmentGateCard({ card }: { card: NonNullable<ShellStatus['judgmentCard']> }) {
  const [intent, setIntent] = useState('');
  const decide = (action: 'amend' | 'direct' | 'drop') => {
    void resumeRun({
      caseName: card.caseName,
      action,
      intentText: action === 'direct' ? intent.trim() : undefined,
    }).catch(() => {});
  };
  return (
    <div className="sh-gate card" data-rha-shell="judgment-gate">
      <b>⚖ Judgment required — case “{card.caseName}” <span className="dim">[{card.classKey}]</span></b>
      <span className="dim small">
        This expectation has no licensing chain to your prompt, and honest implementations keep
        failing it. That makes it a decision, not a bug — your ruling becomes the license.
      </span>
      <pre className="small" style={{ whiteSpace: 'pre-wrap', margin: '6px 0' }}>{card.assertion}</pre>
      {(card.expected != null || card.received != null) && (
        <div className="small">
          {card.expected != null && <div>expected: <code>{card.expected}</code></div>}
          {card.received != null && <div>received: <code>{card.received}</code></div>}
        </div>
      )}
      {card.exhibit && <pre className="small dim" style={{ whiteSpace: 'pre-wrap' }}>{card.exhibit}</pre>}
      {card.trace && <pre className="small dim" style={{ whiteSpace: 'pre-wrap' }}>{card.trace}</pre>}
      <div className="dim small">{card.licenseSearch}</div>
      <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap', marginTop: 6 }}>
        <button type="button" className="primary" onClick={() => decide('amend')}>
          Amend case — accept implementation behavior
        </button>
        <button type="button" className="btn" onClick={() => decide('drop')}>
          Drop case — coverage reduced, honest
        </button>
      </div>
      <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginTop: 6 }}>
        <input
          type="text"
          value={intent}
          placeholder="…or state the missing intent (one line) and direct the repair"
          onChange={e => setIntent(e.target.value)}
          style={{ flex: 1 }}
        />
        <button type="button" className="btn" disabled={!intent.trim()} onClick={() => decide('direct')}>
          Direct repair
        </button>
      </div>
    </div>
  );
}

export function RunPane() {
  const s = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
  const logRef = useRef<HTMLDivElement | null>(null);
  const pinnedRef = useRef(true);

  const viewId = s.viewingId ?? (s.liveRunning ? s.liveId : (s.status?.id ?? null));
  // SET 58c — source ladder: prefer tier1:status whenever it speaks for the
  // viewed run AND is at least as fresh as the record; otherwise the record
  // (which checkpoint-follow keeps refreshing while unfinalized).
  const record: ShellRecord | 'missing' | 'loading' | null =
    viewId ? (s.records[viewId] ?? null) : null;
  const rec = record && record !== 'missing' && record !== 'loading' ? record : null;
  const statusMatches = viewId != null && s.status?.id != null && String(s.status.id) === viewId;
  const statusEvents = statusMatches ? (s.status?.events?.length ?? 0) : -1;
  const recEvents = rec ? (rec.events?.length ?? 0) : -1;
  const source: ShellStatus | ShellRecord | null =
    statusMatches && s.status && statusEvents >= recEvents ? s.status : (rec ?? (statusMatches ? s.status : null));
  const events: ShellEvent[] = Array.isArray(source?.events) ? source!.events! : [];
  const [downloading, setDownloading] = useState(false);
  const running = !!(viewId === s.liveId && s.liveRunning)
    || !!(rec && source === rec && (rec.running || rec.status === 'running'));
  const checkpointFollowing = !!(rec && source === rec && !statusMatches
    && (rec.running || rec.status === 'running' || rec.finalized !== true));
  const startTs = events[0]?.ts ?? new Date().toISOString();

  useEffect(() => {
    if (!running || !pinnedRef.current || !logRef.current) return;
    logRef.current.scrollTop = logRef.current.scrollHeight;
  }, [events.length, running]);

  if (!viewId) {
    return (
      <div className="sh-main" data-rha-shell="run">
        <h2>No job selected</h2>
        <p className="dim">
          Compile a spec from the left, or open a run from the list. The pipeline
          log, the run report, and the prompt render here — this run only.
        </p>
      </div>
    );
  }

  const persistError = (source as ShellRecord | null)?.persistError ?? null;

  return (
    <div className="sh-main" data-rha-shell="run">
      <div className="sh-runhead">
        <h2>Run</h2>
        <span className="jid dim">{viewId}</span>
        {running && <span className="warn small">· live</span>}
        {source?.model ? <span className="dim small">· {source.model}</span> : null}
        <span className="sh-runhead-actions">
          {/* SET 79 — cancel is offered whenever ANY job is live, not only
              when you happen to be viewing it; a runaway run must be
              stoppable from wherever you are. */}
          {(running || s.liveRunning) && (
            <button
              type="button"
              className="btn"
              title={running ? 'Stop this run after the current draw' : 'Stop the live run after the current draw'}
              onClick={() => void cancelRun().catch(() => {})}
            >
              {running ? 'Cancel job' : 'Cancel live job'}
            </button>
          )}
          {/* SET 79 — the artifact itself: a real git repo with the frozen
              suites, the implementations, and the supergit ledger. */}
          <button
            type="button"
            className="primary"
            disabled={downloading}
            title="Download the built repository (source + frozen tests + .supergit ledger)"
            onClick={() => {
              setDownloading(true);
              void downloadArtifact()
                .catch(() => {})
                .finally(() => setDownloading(false));
            }}
          >
            {downloading ? 'Preparing…' : 'Download artifact'}
          </button>
          <button type="button" className="btn" onClick={() => void exportRun().catch(() => {})}>
            Export log
          </button>
        </span>
      </div>

      {statusMatches && s.status?.paused && s.status.pausedGate === 'judgment' && s.status.judgmentCard && (
        <JudgmentGateCard card={s.status.judgmentCard} />
      )}
      {statusMatches && s.status?.paused && s.status.pausedGate !== 'judgment' && (
        <div className="sh-gate card" data-rha-shell="gate">
          <b>
            {s.status.pausedGate === 'ir-contract'
              ? '⏸ IR contract frozen — awaiting your approval before build'
              : '⏸ Build complete — approve to authenticate & repair'}
          </b>
          <span className="dim small">
            Review the pipeline log below; nothing spends tokens until you approve.
          </span>
          <button
            type="button"
            className="primary"
            onClick={() => void resumeRun().catch(() => {})}
          >
            Approve &amp; continue
          </button>
        </div>
      )}

      <Section
        storageKey={COLLAPSE_LOG}
        dflt
        marker="pipeline-log"
        summary={<><b>Pipeline log</b> — {events.length} event{events.length === 1 ? '' : 's'}</>}
      >
        {checkpointFollowing && (
          <div className="warn small" style={{ marginBottom: 6 }}>
            live channel unavailable — following the checkpointed record (log advances every ~25 events / 30s)
          </div>
        )}
        {events.length > 0 && <StageStrip events={events} running={running} />}
        <div
          className="logbody sh-logbody"
          ref={logRef}
          onScroll={e => {
            const el = e.currentTarget;
            pinnedRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 48;
          }}
        >
          {events.length > 0 ? (
            <LogRows events={events} startTs={startTs} />
          ) : record === 'loading' ? (
            <div className="dim small">loading record…</div>
          ) : record === 'missing' ? (
            <div className="bad small">
              no record saved for this run{persistError ? ` — ${persistError}` : ' (persist failed or never saved)'}
              {' '}<button type="button" className="sh-linkbtn" onClick={() => refreshRecord(viewId)}>retry</button>
            </div>
          ) : running ? (
            <div className="dim small">waiting for first event…</div>
          ) : (
            <div className="bad small">no events available for this run</div>
          )}
        </div>
      </Section>

      <Section
        storageKey={COLLAPSE_REPORT}
        dflt
        marker="run-report"
        summary={<><b>Run report</b> <span className="dim small">— this run only</span></>}
      >
        {source?.metrics ? (
          <RunReport metrics={source.metrics} costUsd={source.cost?.usdTotal} />
        ) : (
          <div className="dim small">no metrics captured for this run</div>
        )}
      </Section>

      <Section
        storageKey={COLLAPSE_PROMPT}
        dflt={false}
        marker="prompt"
        summary={<b>Prompt</b>}
      >
        {source?.prompt
          ? <pre className="sh-prompt">{source.prompt}</pre>
          : <div className="dim small">prompt not captured for this run</div>}
      </Section>
    </div>
  );
}
