/**
 * SET 74 — supergit in TIER 1 (Andy, 2026-07-27: "that was literally the OG
 * spec"). L3 of the architecture has run in Tier 2 only since Tier 1 was
 * born: `src/core/substrate.ts` is filesystem-and-git bound (fs.writeFileSync,
 * execFileSync git, commit trailers), so the browser port silently dropped it.
 *
 * This is the browser-native equivalent: the SAME append-only ledger schema
 * (`LedgerRow`, `.supergit/ledger.csv` header, `N####` ids, monotone steps,
 * tree parents, backward cause links, forward `edge` rows), held in memory
 * during the run, serialized into the run record, and materialized into the
 * exported repository as `.supergit/ledger.csv` + `.supergit/graph.json`.
 *
 * Decision calculus granularity is per UNIT OF WORK, not per phase: one node
 * per planned case, per generated node, per repair executed, per measurement,
 * per lesson — each carrying the cause that produced it.
 */
import type { LedgerNodeType, LedgerRow } from '../core/types';

/** Byte-identical to src/core/substrate.ts — one ledger format, two tiers. */
export const LEDGER_HEADER =
  'step,id,type,feature_id,parent_id,cause_id,edge_from,edge_to,artifact_path,bug_class,status,summary,ts';

export interface RecordNodeInput {
  type: LedgerNodeType;
  featureId?: string | null;
  parentId?: string | null;
  causeId?: string | null;
  artifactPath?: string | null;
  bugClass?: string | null;
  status?: LedgerRow['status'];
  summary: string;
}

function csvEscape(v: string | number | null): string {
  if (v == null) return '';
  const s = String(v);
  return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
}

export function rowToCsv(row: LedgerRow): string {
  return [
    row.step, row.id, row.type, row.featureId, row.parentId, row.causeId,
    row.edgeFrom, row.edgeTo, row.artifactPath, row.bugClass, row.status,
    row.summary, row.ts,
  ].map(csvEscape).join(',');
}

/**
 * In-memory append-only ledger. Never mutates a written row: corrections and
 * later-discovered dependencies are new rows, exactly as on disk in Tier 2.
 */
export class Tier1Supergit {
  private step = 0;
  private rows: LedgerRow[] = [];
  /** Most recent node id per feature — the default tree parent. */
  private lastByFeature = new Map<string, string>();
  /** Node id of the frozen suite per feature (test-node anchor). */
  private testNode = new Map<string, string>();
  /** Node id of the latest code artifact per feature. */
  private codeNode = new Map<string, string>();

  constructor(private readonly clock: () => string = () => new Date().toISOString()) {
    this.recordNode({ type: 'genesis', summary: 'supergit substrate initialized (tier 1)' });
  }

  private nextId(): string {
    this.step += 1;
    return `N${String(this.step).padStart(4, '0')}`;
  }

  recordNode(input: RecordNodeInput): string {
    const id = this.nextId();
    const featureId = input.featureId ?? null;
    const row: LedgerRow = {
      step: this.step,
      id,
      type: input.type,
      featureId,
      parentId: input.parentId ?? (featureId ? this.lastByFeature.get(featureId) ?? null : null),
      causeId: input.causeId ?? null,
      edgeFrom: null,
      edgeTo: null,
      artifactPath: input.artifactPath ?? null,
      bugClass: input.bugClass ?? null,
      status: input.status ?? null,
      summary: input.summary.slice(0, 400),
      ts: this.clock(),
    };
    this.rows.push(row);
    if (featureId) this.lastByFeature.set(featureId, id);
    if (input.type === 'test' && featureId) this.testNode.set(featureId, id);
    if (input.type === 'code' && featureId) this.codeNode.set(featureId, id);
    return id;
  }

  /** Forward dependency pointer: `from` changing puts `to` in the blast radius. */
  recordEdge(from: string, to: string, summary = 'downstream-dependency'): string {
    const id = this.nextId();
    this.rows.push({
      step: this.step, id, type: 'edge',
      featureId: null, parentId: null, causeId: null,
      edgeFrom: from, edgeTo: to,
      artifactPath: null, bugClass: null, status: null,
      summary, ts: this.clock(),
    });
    return id;
  }

  /** The frozen-suite node for a feature — the cause of its result rows. */
  testNodeFor(featureId: string): string | null {
    return this.testNode.get(featureId) ?? null;
  }

  codeNodeFor(featureId: string): string | null {
    return this.codeNode.get(featureId) ?? null;
  }

  lastNodeFor(featureId: string): string | null {
    return this.lastByFeature.get(featureId) ?? null;
  }

  allRows(): LedgerRow[] {
    return [...this.rows];
  }

  rowsForFeature(featureId: string): LedgerRow[] {
    return this.rows.filter(r => r.featureId === featureId);
  }

  /**
   * Causal lineage: backward cause links, then tree parents — the decision
   * trail bounded repair consumes as `lineage` context.
   */
  lineage(nodeId: string, max = 8): string[] {
    const byId = new Map(this.rows.map(r => [r.id, r]));
    const out: string[] = [];
    let cur = byId.get(nodeId);
    const seen = new Set<string>();
    while (cur && out.length < max && !seen.has(cur.id)) {
      seen.add(cur.id);
      out.push(`${cur.id} [${cur.type}${cur.featureId ? ' ' + cur.featureId : ''}${cur.bugClass ? ' ' + cur.bugClass : ''}] ${cur.summary}`);
      const next = cur.causeId ?? cur.parentId;
      cur = next ? byId.get(next) : undefined;
    }
    return out;
  }

  /** Everything downstream of a node (forward pointers out of it). */
  downstream(nodeId: string): string[] {
    return this.rows
      .filter(r => r.type === 'edge' && r.edgeFrom === nodeId)
      .map(r => r.edgeTo)
      .filter((v): v is string => v != null);
  }

  toCsv(): string {
    return [LEDGER_HEADER, ...this.rows.map(rowToCsv)].join('\n') + '\n';
  }

  /** Deterministically rebuildable materialization RHA traverses. */
  toGraph(): {
    nodes: Omit<LedgerRow, 'edgeFrom' | 'edgeTo' | 'ts'>[];
    edges: { from: string | null; to: string | null }[];
  } {
    return {
      nodes: this.rows.filter(r => r.type !== 'edge').map(r => ({
        id: r.id, step: r.step, type: r.type, featureId: r.featureId,
        parentId: r.parentId, causeId: r.causeId, status: r.status,
        bugClass: r.bugClass, artifactPath: r.artifactPath, summary: r.summary,
      })),
      edges: this.rows.filter(r => r.type === 'edge').map(r => ({ from: r.edgeFrom, to: r.edgeTo })),
    };
  }

  /** `.supergit/*` files for the exported repository. */
  toFiles(): Record<string, string> {
    return {
      '.supergit/ledger.csv': this.toCsv(),
      '.supergit/graph.json': JSON.stringify(this.toGraph(), null, 2),
    };
  }

  get size(): number {
    return this.rows.length;
  }
}

export function formatSupergitSummary(sg: Tier1Supergit): string {
  const rows = sg.allRows();
  const byType = new Map<string, number>();
  for (const r of rows) byType.set(r.type, (byType.get(r.type) ?? 0) + 1);
  const parts = [...byType.entries()]
    .sort((a, b) => a[0].localeCompare(b[0]))
    .map(([t, n]) => `${t}:${n}`);
  return `supergit: ${rows.length} ledger nodes (${parts.join(', ')}) — .supergit/ledger.csv + graph.json in the export`;
}
