/**
 * Turn a Tier1Result into a REAL git repository and a downloadable zip.
 *
 * The handoff artifact is not a text dump: the zip contains a working tree
 * AND a genuine .git history that mirrors the algorithm's phases — one commit
 * for the encoded spec + interface stubs, one for the frozen IR, then one per
 * feature build, each carrying an RHA-Node trailer (supergit provenance).
 * Unzip → `npm install` → `npx vitest run` and the frozen suites execute.
 */
import git from 'isomorphic-git';
import { zipSync } from 'fflate';
import type { Tier1Result } from './runner';
import { topoOrder } from '../core/specGates';
import { createMemFs } from './memFs';
import type { MemFs } from './memFs';
import { renderExcludedTraceabilitySection } from './reportEvidence';

const AUTHOR = { name: 'RHA Wrapper (Tier 1)', email: 'artifact@rha.software' };

export interface RepoBuild {
  fs: MemFs;
  dir: string;
  /** Commit oids, oldest first. */
  commits: string[];
}

/** Scaffold files that make the exported repo runnable as-is. */
function scaffoldFiles(result: Tier1Result): Record<string, string> {
  const name = result.spec.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'rha-artifact';
  return {
    'package.json': JSON.stringify({
      name,
      private: true,
      type: 'module',
      scripts: { test: 'vitest run tests', typecheck: 'tsc --noEmit' },
      devDependencies: { typescript: '^5.9.3', vitest: '^4.1.10' },
    }, null, 2) + '\n',
    'tsconfig.json': JSON.stringify({
      compilerOptions: {
        target: 'ES2022', module: 'ESNext', moduleResolution: 'bundler',
        strict: true, skipLibCheck: true, types: ['vitest/globals'],
      },
      include: ['src', 'tests'],
    }, null, 2) + '\n',
    'rha-spec.json': JSON.stringify(result.spec, null, 2) + '\n',
    'README.md': [
      `# ${result.spec.title}`,
      '',
      'Generated by the RHA Tier 1 runner (https://rha.software).',
      '',
      'The `tests/` suites are the frozen IR: they were generated from the spec',
      'BEFORE any implementation existed and define the behavioral contract.',
      'The git history mirrors the pipeline — spec, frozen IR, then one build',
      'commit per feature node (see the RHA-Node trailers).',
      '',
      'See `TRACEABILITY.md` for the case → export/edge → feature → prompt chains.',
      '',
      '```',
      'npm install',
      'npx vitest run   # execute the frozen contract',
      '```',
      '',
    ].join('\n'),
    'TRACEABILITY.md': renderTraceability(result),
  };
}

/** SET 3c — intent provenance rendered into the exported repo. */
function renderTraceability(result: Tier1Result): string {
  const lines = [
    `# TRACEABILITY — ${result.spec.title}`,
    '',
    'Each frozen IR case is licensed by a chain back to the user prompt.',
    'Unlicensed cases (if any) appear as problems on the run record.',
    '',
  ];
  if (result.contractVersion) {
    lines.push(`Harness contract version: \`${result.contractVersion}\``, '');
  }
  // SET 44 — exclusion honesty (section body owned by reportEvidence).
  lines.push(...renderExcludedTraceabilitySection(result.deferred ?? []));
  const byFeature = new Map<string, typeof result.provenance>();
  for (const p of result.provenance ?? []) {
    if (!byFeature.has(p.featureId)) byFeature.set(p.featureId, []);
    byFeature.get(p.featureId)!.push(p);
  }
  if (!byFeature.size) {
    lines.push('_No provenance recorded for this run._', '');
    return lines.join('\n');
  }
  for (const [fid, rows] of byFeature) {
    lines.push(`## ${fid}`);
    lines.push('');
    for (const r of rows) {
      lines.push(`- **${r.kind}:** \`${r.caseName}\``);
      lines.push(`  - license ← \`${r.source}\`: ${r.license}`);
      lines.push(`  - feature.description ← ${r.featureDescription}`);
      lines.push(`  - prompt ← ${r.prompt.split('\n')[0]!.slice(0, 120)}`);
    }
    lines.push('');
  }
  return lines.join('\n');
}

export async function buildRepo(result: Tier1Result): Promise<RepoBuild> {
  const fs = createMemFs();
  const dir = '/repo';
  const gfs = { promises: fs.promises };
  await fs.promises.mkdir(dir);
  await git.init({ fs: gfs, dir, defaultBranch: 'main' });
  const commits: string[] = [];

  const writeAll = async (files: Record<string, string>) => {
    for (const [rel, content] of Object.entries(files)) {
      const parts = rel.split('/');
      for (let i = 1; i < parts.length; i++) {
        const d = `${dir}/${parts.slice(0, i).join('/')}`;
        await fs.promises.mkdir(d).catch(() => {}); // EEXIST is fine
      }
      await fs.promises.writeFile(`${dir}/${rel}`, content);
      await git.add({ fs: gfs, dir, filepath: rel });
    }
  };
  const commit = async (message: string) => {
    commits.push(await git.commit({ fs: gfs, dir, message, author: AUTHOR }));
  };

  // SET 74 — supergit materialization: the ledger the run actually recorded.
  // `.supergit/ledger.csv` + `graph.json` + `spec.json`, and commit trailers
  // that synchronize code position with ledger position in both directions.
  //
  // SET 89 §§10 / 11 — additionally materialize the canonical defect
  // registry and action ledger as JSONL. Written into the initial commit
  // (encode phase) so every downstream tool has access from commit 1.
  const sg = result.supergit;
  const sgFiles: Record<string, string> = sg
    ? {
      '.supergit/ledger.csv': sg.csv,
      '.supergit/graph.json': JSON.stringify(sg.graph, null, 2),
      '.supergit/spec.json': JSON.stringify(result.spec, null, 2),
    }
    : {};
  if (result.defectRegistry) {
    const dr = result.defectRegistry;
    const lines: string[] = [];
    for (const o of dr.occurrences) lines.push(JSON.stringify({ kind: 'occurrence', ...o }));
    for (const r of dr.resolutions) lines.push(JSON.stringify({ kind: 'resolution', ...r }));
    for (const i of dr.identities) lines.push(JSON.stringify({ kind: 'identity', ...i }));
    sgFiles['.supergit/defects.jsonl'] = lines.join('\n') + (lines.length ? '\n' : '');
  }
  if (result.actionLedger) {
    const al = result.actionLedger;
    const lines: string[] = [];
    for (const s of al.starts) lines.push(JSON.stringify({ ledgerKind: 'start', ...s }));
    for (const t of al.terminals) lines.push(JSON.stringify({ ledgerKind: 'terminal', ...t }));
    for (const d of al.decisions) lines.push(JSON.stringify({ ledgerKind: 'decision', ...d }));
    sgFiles['.supergit/actions.jsonl'] = lines.join('\n') + (lines.length ? '\n' : '');
  }
  /** Ledger coordinates of the newest node of a type (optionally per feature). */
  const nodeOf = (type: string, featureId?: string) => {
    const rows = (sg?.rows ?? []).filter(r =>
      r.type === type && (featureId == null || r.featureId === featureId));
    return rows.length ? rows[rows.length - 1]! : null;
  };
  const trailer = (n: { id: string; step: number; type: string } | null) =>
    n ? `\nSupergit-Node: ${n.id}\nSupergit-Step: ${n.step}\nSupergit-Type: ${n.type}` : '';

  // Commit 1 — encode: spec, scaffold, interface stubs (pre-implementation).
  const ordered = topoOrder(result.spec.features);
  const stubFiles: Record<string, string> = {};
  for (const f of ordered) for (const s of f.stubs) stubFiles[s.path] = s.content;
  await writeAll({ ...scaffoldFiles(result), ...stubFiles, ...sgFiles });
  await commit('encode: feature spec + interface stubs\n\nRHA-Phase: A'
    + trailer(nodeOf('decision')));

  // Commit 2 — the frozen IR, before implementations.
  const testFiles: Record<string, string> = {};
  for (const t of result.frozenTests) testFiles[t.path] = t.content;
  await writeAll(testFiles);
  await commit(`ir: freeze ${result.frozenTests.length} test suites\n\nRHA-Phase: B`
    + trailer(nodeOf('test')));

  // One build commit per feature node, dependency order, with trailers.
  const byPath = new Map(result.files.map(f => [f.path, f]));
  for (const feature of ordered) {
    const owned: Record<string, string> = {};
    let changed = false;
    for (const p of feature.files) {
      const built = byPath.get(p);
      if (built && !built.content.includes('stub: not implemented')) {
        owned[p] = built.content;
        changed = true;
      }
    }
    if (!changed) continue;
    await writeAll(owned);
    await commit(`build(${feature.id}): ${feature.name}\n\nRHA-Phase: C\nRHA-Node: ${feature.id}`
      + trailer(nodeOf('code', feature.id)));
  }

  return { fs, dir, commits };
}

/** Zip the repo — working tree AND .git — so the download is a real repository. */
export function repoToZip(build: RepoBuild): Uint8Array {
  const files: Record<string, Uint8Array> = {};
  for (const [rel, data] of build.fs.snapshot(build.dir)) {
    files[rel] = data;
  }
  return zipSync(files, { level: 6 });
}

export async function buildRepoZip(result: Tier1Result): Promise<Uint8Array> {
  return repoToZip(await buildRepo(result));
}
