/**
 * Core domain types for the RHA Wrapper pipeline.
 *
 * Layer 1 (L1): Test-First IR — the frozen test suite is the intermediate
 *   representation between prompt and implementation.
 * Layer 2 (L2): RHA — recursive, bounded, failure-triggered repair over the
 *   feature tree.
 * Layer 3 (L3): Supergit — append-only decision ledger on a real git repo,
 *   with typed artifacts, causal links, forward dependency pointers and a
 *   step index.
 */

import type { IRCaseGen, IRImport, IRParts } from './irScaffold';

// ---------------------------------------------------------------------------
// Feature encoding (Phase A)
// ---------------------------------------------------------------------------

export interface FeatureExport {
  /** Implementation file (workspace-relative, e.g. "src/codec.ts"). */
  file: string;
  /** Exported symbol the rest of the system wires against. */
  symbol: string;
}

export interface FeatureNode {
  /** Feature code, e.g. "F1", "F2.1". Doubles as the tree address. */
  id: string;
  name: string;
  /** What the feature actually does — used to derive edge cases and tests. */
  description: string;
  /** Parent feature id, null for roots. */
  parentId: string | null;
  /** Feature ids this feature depends on (build order + blast radius). */
  deps: string[];
  /** Implementation files this feature OWNS. Bounded repair may only touch these. */
  files: string[];
  /** Frozen test file for this feature (workspace-relative). */
  testFile: string;
  /** Edge cases the IR must capture, derived from the feature's function. */
  edgeCases: string[];
  /** Wiring contract: symbols that must exist for dependents. */
  exports: FeatureExport[];
  /** Environment variables the feature reads, if any. */
  envVars: string[];
  /**
   * Interface stubs — the feature's header files. Compilable TS at the
   * feature's own file paths: exact public signatures, bodies that throw.
   * Both the frozen suites (via the consistency gate) and the implementation
   * conform to these, which is what keeps cross-feature contracts consistent.
   */
  stubs: GeneratedFile[];
}

export interface FeatureSpec {
  title: string;
  prompt: string;
  features: FeatureNode[];
}

// ---------------------------------------------------------------------------
// IR artifact (Phase B)
// ---------------------------------------------------------------------------

export interface IRTestCase {
  featureId: string;
  name: string;
  kind: 'behavior' | 'edge' | 'integration';
}

export interface IRManifest {
  version: 1;
  title: string;
  prompt: string;
  features: FeatureNode[];
  cases: IRTestCase[];
  /** sha256 of each frozen test file — tamper detection before build & RHA. */
  freezeHashes: Record<string, string>;
  frozenAt: string | null;
}

// ---------------------------------------------------------------------------
// Supergit ledger (L3)
// ---------------------------------------------------------------------------

export type LedgerNodeType =
  | 'genesis'
  | 'decision' // a pipeline decision (feature encoding, freeze, escalation)
  | 'spec'     // feature spec artifact
  | 'test'     // frozen IR test artifact
  | 'code'     // implementation artifact
  | 'result'   // verification result (test run / detector sweep)
  | 'error'    // localized error node
  | 'repair'   // bounded repair applied (attempt — NOT a resolution)
  | 'resolution' // confirmed defect resolution after a clean measurement
  | 'lesson'   // documented resolution idiom (substrate + data/lessons.jsonl)
  | 'edge';    // forward dependency pointer (from -> to), append-only

export interface LedgerRow {
  step: number;
  id: string;
  type: LedgerNodeType;
  featureId: string | null;
  /** Tree parent (structural position in the derivation tree). */
  parentId: string | null;
  /** Backward causal link: the node that caused this one. */
  causeId: string | null;
  /** Forward pointer endpoints — only used by 'edge' rows. */
  edgeFrom: string | null;
  edgeTo: string | null;
  artifactPath: string | null;
  bugClass: string | null;
  status: 'pass' | 'fail' | 'open' | 'healed' | 'info' | null;
  summary: string;
  ts: string;
}

// ---------------------------------------------------------------------------
// Verification / taxonomy (L2 oracle)
// ---------------------------------------------------------------------------

/** Canonical taxonomy of mechanical AI-induced bug classes (V1 set). */
export type BugClass =
  | 'syntax'
  | 'unresolved-import'
  | 'undefined-symbol'
  | 'type-mismatch'
  | 'unwired-export'
  | 'test-orphan'
  | 'todo-stub'
  | 'env-var-unset'
  | 'test-failure'; // idiosyncratic: behavior diverges from the frozen IR

export interface Finding {
  bugClass: BugClass;
  file: string;
  line: number | null;
  message: string;
  featureId: string | null;
  /** Test case name for test-failure findings. */
  testName?: string;
}

export interface FeatureTestResult {
  featureId: string;
  passed: number;
  failed: number;
  failures: { name: string; message: string }[];
  durationMs: number;
}

// ---------------------------------------------------------------------------
// Pipeline job state
// ---------------------------------------------------------------------------

export type JobPhase =
  | 'created'
  | 'encoding'      // Phase A: feature encoding
  | 'generating-ir' // Phase B: IR/test generation
  | 'awaiting-ir-approval'
  | 'building'      // Phase C: implementation + real-time authentication
  | 'awaiting-build-approval'
  | 'healing'       // Phase D: RHA (automatic)
  | 'complete'
  | 'failed'
  /**
   * Mid-phase server stop / operator interrupt. Workspace + ledger preserved;
   * resumable via POST /api/jobs/:id/resume. Reserved vs `failed` (dead-end / SEV1).
   */
  | 'interrupted'
  /**
   * Terminal ANALYSIS outcome, not an error: the prompt is not demarcated
   * into features clearly enough to compile. RHA does not synthesize NL into
   * procedures — convergence criteria are human-defined and live at the
   * feature level. The gate reports what is missing; the human revises.
   */
  | 'insufficient-input';

export interface PromptDeficiency {
  /** The aspect of the prompt that is under-demarcated (e.g. "persistence"). */
  aspect: string;
  /** Why tests cannot be derived from it without guessing intent. */
  issue: string;
  /** The question the human must answer to close the gap. */
  question: string;
}

export interface PromptAssessment {
  sufficient: boolean;
  /** Feature boundaries the prompt already demarcates clearly. */
  featuresIdentified: string[];
  deficiencies: PromptDeficiency[];
  /**
   * The same information reorganized into demarcated features. Gaps are
   * marked [DECIDE: ...] — never silently filled. Human-editable seed for
   * the next attempt.
   */
  suggestedRewrite: string;
  /** Per-feature verdicts, persisted so a reassess round only re-probes what changed. */
  verdicts?: { name: string; scope: string; clear: boolean; assertion: string }[];
}

export interface RepairEpisode {
  episode: number;
  featureId: string;
  bugClass: BugClass;
  attempt: number;
  outcome: 'healed' | 'escalated' | 'failed';
  parentRecheck: 'pass' | 'fail' | 'n/a';
  summary: string;
}

/**
 * One entry per feature node still failing when RHA stops — the "surface to
 * user" document. Rendered in the UI, written to .supergit/escalation.md
 * (so it travels in the export), and recorded in the ledger.
 */
export interface EscalationEntry {
  featureId: string;
  featureName: string;
  bugClass: BugClass;
  /** Bounded repair attempts spent on this node before stopping. */
  attempts: number;
  findings: Finding[];
  /**
   * True when non-runtime findings (tsc/taxonomy) point INTO the frozen IR:
   * the suite itself doesn't hold against the declared interfaces. That is a
   * contract problem — unhealable by code repair, by design.
   */
  frozenContractFindings: boolean;
  /** Suspected cross-feature contract skew (green dependencies named). */
  suspectedContractSkew: { withFeatures: string[]; evidence: string } | null;
  /** Ledger trail of the node's last error (causal lineage). */
  ledgerTrail: string[];
  recommendation: string;
}

export interface JobReport {
  totalTests: number;
  passed: number;
  failed: number;
  episodes: RepairEpisode[];
  filesTouchedByRha: string[];
  wholeArtifactRegenerated: false; // structurally impossible; asserted in report
  ledgerRows: number;
  commits: number;
  escalations: EscalationEntry[];
}

/** Token expenditure of a single LLM call. */
export interface LlmCallRecord {
  ts: string;
  op: 'encode' | 'ir' | 'build' | 'repair' | 'fixStub' | 'vanilla' | 'referee' | 'concept' | 'judge' | 'watch';
  featureId: string | null;
  model: string;
  ms: number;
  inputTokens: number;
  outputTokens: number;
  cacheReadTokens: number;
  cacheWriteTokens: number;
  /** Part N: which role performed the call (default operator when unset). */
  role?: 'operator' | 'supervisor' | 'pool';
  /** Pool slot index when role === 'pool'. */
  poolIdx?: number;
  /** Provider type label for KPI splits. */
  provider?: string;
  /** SET 22b — tokens/sec for this call (in+out over wall ms). */
  tokensPerSec?: number;
  /** SET 22b — transport retries before success. */
  retries?: number;
  /** SET 22a — desync reissues of the identical call. */
  reissues?: number;
  /**
   * SET 28a — every call states its own purpose; this is what makes the
   * pipeline log line human-legible ("fill \"CategoryModel\"", a file path
   * list for a build draw, a classKey for a repair draw, …).
   */
  purpose?: string;
  /** K-of-N position within a permutation-select batch. */
  candidate?: { n: number; k: number };
  /** Attempt/pass counter for redraw ladders. */
  attempt?: number;
  attemptLabel?: 'attempt' | 'pass';
  /**
   * Fully formatted `llm call — …` line, built once at the source of truth
   * (the client that knows the real token counts) so tokens land on THIS
   * line and never drift onto an adjacent event by heuristic annotation.
   */
  logLine?: string;
}

export type UsageSink = (record: LlmCallRecord) => void;

/**
 * Full machine-readable I/O trace for ONE llm call — the dense debug log,
 * separate from the human audit log. Because RHA hand-holds every LLM I/O, we
 * capture the exact system+user prompt sent, the raw response before parsing,
 * plus op/feature/model/temperature/tokens/latency. One JSON object per call;
 * the sink appends these as JSONL.
 */
export interface LlmDebugEntry {
  ts: string;
  op: LlmCallRecord['op'];
  featureId: string | null;
  model: string;
  temperature: number | null;
  ms: number;
  inputTokens: number;
  outputTokens: number;
  system: string;
  user: string;
  schemaName: string;
  responseRaw: string;
}

export type DebugSink = (entry: LlmDebugEntry) => void;

export interface JobEvent {
  seq: number;
  ts: string;
  kind: string;
  data: Record<string, unknown>;
}

/**
 * One escalation, from ANY gate, with honest provenance: which gate raised
 * it, its own diagnostics, and — when the feature was excluded only because a
 * dependency was excluded — the feature it was collateral of. The report must
 * never launder a spec-gate cascade through IR-gate language, and collateral
 * must never read as a defect.
 */
export interface EscalationRecord {
  featureId: string;
  featureName: string;
  diagnostics: string[];
  /** Gate of origin. Absent on records persisted before provenance existed → treat as 'ir'. */
  gate?: 'spec' | 'stub' | 'ir';
  /** Set when excluded solely as a dependent of this feature id — no defect of its own. */
  collateralOf?: string;
}

export interface JobState {
  id: string;
  title: string;
  prompt: string;
  phase: JobPhase;
  createdAt: string;
  /** Manual list position (drag-and-drop); falls back to createdAt order. */
  sortIndex?: number;
  /**
   * Unsupervised E2E: every human hard-stop (input gate, IR approval, build
   * approval) is converted into a RECORDED autopilot decision in the ledger
   * and the run proceeds — escalations ride through to the final report for
   * post-hoc authentication. Default is supervised.
   */
  autopilot?: boolean;
  workspaceDir: string;
  spec: FeatureSpec | null;
  manifest: IRManifest | null;
  buildResults: FeatureTestResult[];
  report: JobReport | null;
  /** Set when phase is 'insufficient-input' — the input-gate analysis. */
  promptAssessment: PromptAssessment | null;
  /**
   * Input-gate reassess rounds on this job. Bounded by RHA_GATE_ROUNDS;
   * a revised prompt (new compile) starts a fresh job at 0.
   */
  gateRounds?: number;
  /**
   * Features whose IR could not be made to converge. They are excluded from
   * the frozen manifest and the build; the rest of the run proceeds. One stuck
   * feature no longer kills the whole job — suites are independent given the
   * frozen stubs, so an unfixable one escalates alone.
   */
  irEscalations: EscalationRecord[];
  error: string | null;
  events: JobEvent[];
  llmUsage: LlmCallRecord[];
  /**
   * When phase is `interrupted`, the mid-phase that was cut short — used by
   * resume() to re-enter the correct pipeline stage. Cleared on successful resume.
   */
  resumeFrom?: JobPhase | null;
  /** Part U1: cosmetic favorite / filter bookmark. */
  starred?: boolean;
  /** Part U1: living project — unlocks iterate-in-place UI. */
  pinned?: boolean;
  /** Attached from an external synced folder — workspace lives outside workspaces/. */
  attached?: boolean;
  /** Part U3: last scaffolding warning (external edits), if any. */
  scaffoldWarning?: string | null;
}

// ---------------------------------------------------------------------------
// LLM boundary
// ---------------------------------------------------------------------------

export interface GeneratedFile {
  path: string;
  content: string;
}

/** One feature the prompt describes, with the text span that describes it. */
export interface FeatureCandidate {
  name: string;
  scope: string;
}

/** A bounded per-feature verdict from the input gate. */
export interface FeatureProbe {
  clear: boolean;
  /** A concrete observable assertion, when clear. */
  assertion: string;
  /** The single intent question, when not clear. */
  question: string;
}

/**
 * Per-draw decorrelation controls for fresh-take / swarm sampling.
 * Continuity belongs to the substrate; amnesia belongs to the agent — these
 * make a "new instance" statistically real, not just contextually real:
 *  - temperature: identical prompt + identical sampling = the same mode; a
 *    ladder across draws reaches basins a single setting cannot.
 *  - avoid: tabu constraints distilled from the ledger's ORACLE facts
 *    (expected vs got from discarded attempts) — the graveyard's shape
 *    without the old code, so it informs without re-anchoring.
 */
export interface DrawOpts {
  temperature?: number;
  /** Bypass synthetic provider-stage spacing for an in-phase advisory read. */
  bypassStageSpacing?: boolean;
  avoid?: string;
  /**
   * SET 36d — extra self-contained prompt context (e.g. build-stage
   * superbuffer). Each call remains conversation-stateless; this is only
   * additional user-message content for THIS request.
   */
  context?: string;
  /** SET 28a — overrides the call's default purpose label when the caller knows better (e.g. a repair draw's focus classKey). */
  purpose?: string;
  /** SET 28a — K-of-N position within a permutation-select batch. */
  candidate?: { n: number; k: number };
  /** SET 64a — single-target empty-tail rescue draw (distinct from K-draw
   * batch structure for audits and metering). */
  rescue?: boolean;
  /** SET 28a — attempt/pass counter for redraw ladders. */
  attempt?: number;
  /** Label for the attempt bracket segment (default 'attempt'). */
  attemptLabel?: 'attempt' | 'pass';
}

export interface LlmClient {
  /**
   * Phase 0a: enumerate the features a prompt describes (name + the text span
   * describing each). No judgment — just the split, done once over the whole
   * prompt so per-feature probes can run on small, isolated context.
   */
  enumerateFeatures(prompt: string, opts?: DrawOpts): Promise<FeatureCandidate[]>;
  /**
   * Phase 0b: the BOUNDED input-gate unit. Judge ONE feature from its own
   * text alone. Small context keeps a weak model coherent; cheap enough to
   * resample for variance. Returns clear + an assertion, or a single question.
   * `opts.temperature` walks the decorrelation ladder across probe retries.
   */
  probeFeature(candidate: FeatureCandidate, opts?: DrawOpts): Promise<FeatureProbe>;
  /** Phase A: prompt -> feature-coded spec. `stubFeedback` carries tsc diagnostics on a retry. */
  encodeFeatures(prompt: string, stubFeedback?: string, opts?: DrawOpts): Promise<FeatureSpec>;
  /**
   * Phase A bounded repair: fix ONLY the listed offending features' fields;
   * every other feature must remain untouched by the caller. Never returns a
   * smaller feature set — collapse is rejected upstream.
   */
  repairFeatures(
    spec: FeatureSpec,
    offendingIds: string[],
    problems: string[],
    opts?: DrawOpts,
  ): Promise<FeatureNode[]>;
  /**
   * Phase A repair: regenerate ONE interface stub from diagnostics + context.
   * Fresh-only: never pass the broken stub body — `ctx` carries diagnostics,
   * calling lines, contract text, and checked idioms.
   */
  fixStub(
    path: string,
    ctx: { diagnostics: string; callingLines?: string; contractText?: string; checkedIdioms?: string },
    opts?: DrawOpts,
  ): Promise<string>;
  /**
   * Phase B: feature spec + interface stubs -> a vitest suite. Returns the
   * assembled file AND its structured parts (imports + cases) so a later gate
   * can repair a single failing case instead of regenerating the whole suite.
   * `parts` is absent only for clients that emit fixed fixtures (the mock).
   * `feedback` carries deterministic-gate diagnostics on a retry.
   */
  generateTests(spec: FeatureSpec, feature: FeatureNode, feedback?: string, opts?: DrawOpts): Promise<{ content: string; parts?: IRParts }>;
  /**
   * Phase B repair (bounded): fix ONLY the listed failing test cases against
   * the stubs, preserving every other case. Returns one corrected case per
   * input case, in the same order, plus corrected imports if imports failed.
   */
  fixTestCases(
    spec: FeatureSpec,
    feature: FeatureNode,
    brokenCases: { name: string; kind: IRCaseGen['kind']; errors: string[] }[],
    importErrors: string[],
    currentImports: IRImport[],
    opts?: DrawOpts,
  ): Promise<{ imports?: IRImport[]; cases: IRCaseGen[] }>;
  /** Phase C: frozen tests + spec -> implementation files for ONE feature. */
  generateImplementation(
    spec: FeatureSpec,
    feature: FeatureNode,
    frozenTest: string,
    dependencyContext: GeneratedFile[],
    opts?: DrawOpts,
  ): Promise<GeneratedFile[]>;
  /**
   * Phase D: bounded repair for one error node.
   * `opts` is plumbed for interface uniformity; RHA keeps per-attempt repair
   * anchored (error context) — freshness enters via the entropy-reset path.
   */
  repair(input: RepairInput, opts?: DrawOpts): Promise<GeneratedFile[]>;
  /**
   * SET 37f — ONE bounded concept draw per feature (high t). Returns prose
   * for the superbuffer `concept` page. Never code; Integration sanitizes.
   * Optional so mock/fixture clients need not implement it.
   */
  drawConcept?(
    specPage: string,
    feature: FeatureNode,
    opts?: DrawOpts,
  ): Promise<string>;
  /**
   * SET 68 — unsupervised judgment ruling: ONE fresh decoupled call over the
   * evidence card. Returns the canonical two-line ruling text
   * (`ACTION: …` / `INTENT: …`); the caller parses deterministically and
   * fail-safes to drop on anything unparseable. Optional — mock/fixture
   * clients and supervised-only hosts need not implement it.
   */
  judgeRuling?(evidencePrompt: string, opts?: DrawOpts): Promise<string>;
  /**
   * SET 75 — the watcher's engine: ONE reading per epoch over a digest the
   * agent assembled deterministically. Returns the canonical IDIOM/TROPE/
   * SCHEME/UNION lines; the caller parses deterministically and falls back
   * to the digest alone on anything unparseable. Optional.
   */
  watchEpoch?(digestPrompt: string, opts?: DrawOpts): Promise<string>;
  /**
   * SET 85 — ONE observation poll: a single question over AST facts. The
   * observatory issues these separately (idioms, dependencies, tropes,
   * schemes) so a small model answers one thing at a time, and each call is a
   * fresh decoupled instance. Returns `ITEM: <subject> — <claim>` lines.
   */
  observe?(pollPrompt: string, system: string, opts?: DrawOpts): Promise<string>;
}

export interface RepairInput {
  spec: FeatureSpec;
  feature: FeatureNode;
  findings: Finding[];
  /** Same-bug-class findings elsewhere in the tree (generalization context). */
  parallelInstances: Finding[];
  frozenTest: string;
  ownedFiles: GeneratedFile[];
  dependencyContext: GeneratedFile[];
  failureOutput: string;
  attempt: number;
  /** Ledger lineage lines for the target node (decision trail context). */
  lineage: string[];
}
