import { useEffect, useState } from 'react';
import {
  BrowserEngineClient,
  EngineClientError,
  loadModelManifestV2,
  type EngineCapabilities,
  type BenchmarkWasmFlavor,
  type ModelManifestV2,
  type RuntimeBackend,
} from '../engine';
import {
  parseReleasePromptMatrix,
  RELEASE_PROMPT_MATRIX_MAX_TOKENS,
  RELEASE_PROMPT_MATRIX_PATH,
  type ReleasePromptCase,
} from './prompt-matrix';

const MANIFEST_PATH = 'manifest/models.json';
const MATRIX_MODEL_ID = '1_7b';
const MATRIX_CONTEXT_SIZE = 2_048;

let matrixClient: BrowserEngineClient | null = null;

function client(): BrowserEngineClient {
  matrixClient ??= new BrowserEngineClient();
  return matrixClient;
}

function assetUrl(path: string): string {
  return new URL(`${import.meta.env.BASE_URL}${path}`, document.baseURI).href;
}

function errorMessage(error: unknown): string {
  if (error instanceof EngineClientError) {
    return `${error.code}: ${error.message}${error.details === undefined ? '' : `\n${JSON.stringify(error.details)}`}`;
  }
  return error instanceof Error ? error.message : String(error);
}

async function sha256Text(value: string): Promise<string> {
  const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value));
  return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('');
}

interface BootstrapState {
  manifest: ModelManifestV2;
  capabilities: EngineCapabilities;
  cases: ReleasePromptCase[];
  fixtureSha256: string;
}

export function PromptMatrixApp() {
  const [bootstrap, setBootstrap] = useState<BootstrapState | null>(null);
  const [backend, setBackend] = useState<RuntimeBackend>('wasm');
  const [wasmFlavor, setWasmFlavor] = useState<BenchmarkWasmFlavor>('auto');
  const [running, setRunning] = useState(false);
  const [progress, setProgress] = useState('Inspecting the pinned fixture and runtime');
  const [error, setError] = useState<string | null>(null);
  const [report, setReport] = useState<unknown>(null);

  useEffect(() => {
    const controller = new AbortController();
    const engine = client();
    void Promise.all([
      loadModelManifestV2(assetUrl(MANIFEST_PATH), controller.signal),
      engine.capabilities(),
      fetch(assetUrl(RELEASE_PROMPT_MATRIX_PATH), { signal: controller.signal }).then(async (response) => {
        if (!response.ok) throw new Error(`Prompt fixture request failed with HTTP ${response.status}.`);
        const text = await response.text();
        return {
          cases: parseReleasePromptMatrix(JSON.parse(text)),
          fixtureSha256: await sha256Text(text),
        };
      }),
    ]).then(([manifest, capabilities, fixture]) => {
      setBootstrap({ manifest, capabilities, ...fixture });
      setProgress('Ready · model remains unloaded until the matrix is started');
    }).catch((bootstrapError) => {
      if (controller.signal.aborted) return;
      setError(errorMessage(bootstrapError));
      setProgress('Prompt matrix bootstrap failed');
    });
    return () => controller.abort();
  }, []);

  const runMatrix = () => {
    if (!bootstrap || running) return;
    const engine = client();
    setRunning(true);
    setError(null);
    setReport(null);
    const startedAt = new Date().toISOString();
    void (async () => {
      await engine.unload();
      const loadStarted = performance.now();
      const load = await engine.loadModel({
        manifest: bootstrap.manifest,
        modelId: MATRIX_MODEL_ID,
        backend,
        contextSize: MATRIX_CONTEXT_SIZE,
        ...(wasmFlavor === 'auto' ? {} : {
          benchmarkTuning: {
            flashMode: 'off' as const,
            kvCacheType: 'f16' as const,
            wasmFlavor,
          },
        }),
      }, {
        onProgress: (event) => {
          const shard = event.shardIndex === null ? '' : ` · shard ${event.shardIndex + 1}/${event.shardCount}`;
          setProgress(`Loading 1.7B ${backend.toUpperCase()} · ${event.phase}${shard}`);
        },
      });
      const loadMs = performance.now() - loadStarted;
      const results = [];
      for (const [index, fixture] of bootstrap.cases.entries()) {
        setProgress(`Generating ${fixture.id} · ${index + 1}/${bootstrap.cases.length}`);
        let generated;
        try {
          generated = await engine.generate({
            messages: fixture.messages,
            maxTokens: RELEASE_PROMPT_MATRIX_MAX_TOKENS,
            temperature: 0,
            topP: 1,
            topK: 1,
            seed: 42,
            ...(fixture.tools.length > 0 ? {
              tools: fixture.tools,
              toolChoice: 'auto' as const,
            } : { toolChoice: 'none' as const }),
            cachePrompt: false,
            returnTokenIds: true,
          });
        } catch (generationError) {
          throw new Error(`${fixture.id}: ${errorMessage(generationError)}`);
        }
        if (!generated.tokenIds || !generated.tokenTrace
            || generated.tokenIds.length === 0
            || generated.tokenIds.length !== generated.tokenTrace.length
            || generated.tokenTrace.some((entry, tokenIndex) => (
              entry.selected.id !== generated.tokenIds?.[tokenIndex]
              || entry.topCandidates.length !== 5
              || entry.topCandidates[0]?.id !== generated.tokenIds?.[tokenIndex]
            ))) {
          throw new Error(`${fixture.id}: runtime returned an invalid greedy top-five token trace.`);
        }
        results.push({
          id: fixture.id,
          messages: fixture.messages,
          tools: fixture.tools,
          generatedTokenIds: generated.tokenIds,
          tokenTrace: generated.tokenTrace,
          tokenTraceAccounting: generated.tokenTraceAccounting ?? null,
          text: generated.text,
          reasoningText: generated.reasoningText,
          toolCalls: generated.toolCalls,
          finishReason: generated.finishReason,
          usage: generated.usage,
          timings: generated.timings,
        });
      }
      const backendReport = await engine.backendReport();
      const evidence = {
        schemaVersion: 1,
        kind: 'bonsai-browser-prompt-matrix',
        startedAt,
        completedAt: new Date().toISOString(),
        endpoint: window.location.origin,
        policy: {
          modelTier: MATRIX_MODEL_ID,
          contextSize: MATRIX_CONTEXT_SIZE,
          backend,
          wasmFlavor,
          maxTokens: RELEASE_PROMPT_MATRIX_MAX_TOKENS,
          temperature: 0,
          topK: 1,
          topP: 1,
          seed: 42,
          cachePrompt: false,
          topLogprobs: 5,
        },
        fixture: {
          path: RELEASE_PROMPT_MATRIX_PATH,
          sha256: bootstrap.fixtureSha256,
          cases: bootstrap.cases.length,
        },
        runtime: bootstrap.capabilities.runtime,
        browser: bootstrap.capabilities.browser,
        crossOriginIsolated: bootstrap.capabilities.crossOriginIsolated,
        sharedArrayBuffer: bootstrap.capabilities.sharedArrayBuffer,
        load: {
          durationMs: loadMs,
          selectedBackend: load.backend,
          context: load.context,
          tuning: load.tuning,
          backendReport: load.backendReport,
        },
        backendReport,
        cases: results,
        checks: {
          atLeastSixCases: results.length >= 6,
          everyCaseGenerated: results.every((entry) => entry.generatedTokenIds.length > 0),
          everyCaseHasGreedyTopFiveTrace: results.every((entry) => (
            entry.generatedTokenIds.length === entry.tokenTrace.length
            && entry.tokenTrace.every((trace, tokenIndex) => (
              trace.selected.id === entry.generatedTokenIds[tokenIndex]
              && trace.topCandidates.length === 5
              && trace.topCandidates[0]?.id === entry.generatedTokenIds[tokenIndex]
            ))
          )),
        },
      };
      if (Object.values(evidence.checks).some((value) => value !== true)) {
        throw new Error(`Prompt matrix checks failed: ${JSON.stringify(evidence.checks)}`);
      }
      setReport(evidence);
      setProgress('Prompt matrix complete · JSON evidence is ready');
    })().catch((runError) => {
      setError(errorMessage(runError));
      setProgress('Prompt matrix failed');
    }).finally(() => setRunning(false));
  };

  return (
    <main className="matrix-page">
      <header>
        <div>
          <p className="eyebrow">Release diagnostics</p>
          <h1>Bonsai prompt matrix</h1>
        </div>
        <a href="./">Return to chat</a>
      </header>
      <section className="matrix-controls">
        <label>
          Backend
          <select
            aria-label="Matrix backend"
            value={backend}
            disabled={running}
            onChange={(event) => setBackend(event.target.value as RuntimeBackend)}
          >
            <option value="wasm">CPU · WASM</option>
            <option value="webgpu">WebGPU</option>
          </select>
        </label>
        <label>
          WASM runtime
          <select
            aria-label="Matrix WASM runtime"
            value={wasmFlavor}
            disabled={running}
            onChange={(event) => setWasmFlavor(event.target.value as BenchmarkWasmFlavor)}
          >
            <option value="auto">Auto · browser-selected</option>
            <option value="jspi">JSPI · explicit</option>
            <option value="compat">Compat / Asyncify · explicit</option>
          </select>
        </label>
        <button type="button" disabled={!bootstrap || running} onClick={runMatrix}>
          {running ? 'Running matrix…' : 'Run release prompt matrix'}
        </button>
      </section>
      <p className="matrix-progress" aria-live="polite">{progress}</p>
      {error ? <pre className="matrix-error" role="alert">{error}</pre> : null}
      {report ? (
        <pre className="matrix-report" data-testid="prompt-matrix-report">
          {JSON.stringify(report, null, 2)}
        </pre>
      ) : null}
    </main>
  );
}
