// src/core/prims.ts
// Production primitives for OmegaLLM CEKS machine
// Originally from test/helpers/prims.ts - moved to production

import type { Env } from "./eval/env";
import { envEmpty, envSet } from "./eval/env";
import type { Store } from "./eval/store";
import type { State, Frame, StepOutcome } from "./eval/machine";
import { stepOnce } from "./eval/machineStep";
import type { Val } from "./eval/values";
import { VUnit, VTrue, VFalse } from "./eval/values";
import type { Expr } from "./ast";
import { rule, rewriteOnce, rewriteFixpoint, rewriteTrace, detectConflicts, substitute } from "../package-bridge/oracle-language";
import { matchAST, isMeaning } from "../package-bridge/oracle-language";
import type { Rule, Strategy } from "../package-bridge/oracle-language";
import { evidenceId as computeEvidenceId, type Evidence } from "./provenance/evidence";
import { registerConditionPrims } from "./conditions/prims";
import { captureValueResumption } from "./effects/capture";
import { registerProvenancePrims } from "./provenance/prims";
import { registerSolverPrims, registerSolvePrims, registerTierBPrims } from "../package-bridge/solve";
import { registerContinuationPrims } from "../package-bridge/continuation-calculus";
import { compileTextToExpr } from "./pipeline/compileText";
import { parseSexp, type Sexp } from "./sexp/sexp";
import { parseDefinePackage } from "./require/manifest";

/** Structural equality for Val (used by map/set primitives) */
function valEqual(a: Val, b: Val): boolean {
  if (a.tag !== b.tag) return false;
  switch (a.tag) {
    case "Num": return a.n === (b as any).n;
    case "Str": return a.s === (b as any).s;
    case "Sym": return a.name === (b as any).name;
    case "Bool": return a.b === (b as any).b;
    case "Unit": return true;
    case "Char": return a.c === (b as any).c;
    default: return a === b;  // reference equality for complex types
  }
}

function valToManifestDatum(v: Val): unknown {
  if (v.tag === "Sym") return v.name;
  if (v.tag === "Str") return v.s;
  if (v.tag === "Num") return v.n;
  if (v.tag === "Bool") return v.b;
  if (v.tag === "Unit") return [];

  if (v.tag === "Vector") {
    const asList = maybeListToArray(v);
    if (asList) {
      return asList.map(valToManifestDatum);
    }
    return v.items.map(valToManifestDatum);
  }

  return v.tag;
}

function maybeListToArray(v: Val): Val[] | null {
  if (v.tag !== "Vector" || v.items.length !== 2) {
    return null;
  }
  const out: Val[] = [];
  let current: Val = v;
  while (current.tag === "Vector" && current.items.length === 2) {
    out.push(current.items[0]);
    current = current.items[1];
  }
  if (current.tag !== "Unit") {
    return null;
  }
  return out;
}

// ─────────────────────────────────────────────────────────────────
// Sync Effect Handlers for Solve
//
// Effects in solve algebra procedures run synchronously. Register
// handlers here for effects that need to work inside solve.
// ─────────────────────────────────────────────────────────────────

type SyncEffectHandler = (args: Val[], state: State) => Val;
const syncEffectHandlers = new Map<string, SyncEffectHandler>();

/**
 * Register a synchronous effect handler for use inside solve.
 * This allows effects like infer.op to work within solve algebra procedures.
 */
export function registerSyncEffectHandler(op: string, handler: SyncEffectHandler): void {
  syncEffectHandlers.set(op, handler);
}

/**
 * Get a registered sync effect handler.
 */
export function getSyncEffectHandler(op: string): SyncEffectHandler | undefined {
  return syncEffectHandlers.get(op);
}

/**
 * Clear all sync effect handlers (for testing).
 */
export function clearSyncEffectHandlers(): void {
  syncEffectHandlers.clear();
}

export interface PrimitiveInstallOptions {
  includePackageManifest?: boolean;
  includeExtendedPrims?: boolean;
}

export function installPrims(
  store: Store,
  options: PrimitiveInstallOptions = {},
): { env: Env; store: Store } {
  const includePackageManifest = options.includePackageManifest ?? true;
  const includeExtendedPrims = options.includeExtendedPrims ?? true;
  let env: Env = envEmpty();
  let st: Store = store;

  function def(name: string, v: Val) {
    const [st2, addr] = st.alloc(v);
    st = st2;
    env = envSet(env, name, addr);
  }

  function isCallable(proc: Val): proc is Val {
    return proc.tag === "Native" || proc.tag === "Closure";
  }

  function ensureArity(proc: Val, expected: number, name: string): void {
    if (proc.tag === "Native") {
      if (proc.arity !== "variadic" && proc.arity !== expected) {
        throw new Error(`${name}: expected procedure of arity ${expected}`);
      }
      return;
    }
    if (proc.tag === "Closure") {
      if (proc.params.length !== expected) {
        throw new Error(`${name}: expected procedure of arity ${expected}`);
      }
      return;
    }
    throw new Error(`${name}: expected a procedure`);
  }

  function applyProcedure(proc: Val, procArgs: Val[], state: State): State | StepOutcome {
    if (proc.tag === "Native") {
      return proc.fn(procArgs, state);
    }
    if (proc.tag === "Closure") {
      if (proc.params.length !== procArgs.length) {
        throw new Error(`procedure arity mismatch: expected ${proc.params.length}, got ${procArgs.length}`);
      }
      let store = state.store;
      let env2 = proc.env;
      for (let i = 0; i < proc.params.length; i++) {
        const [store2, addr] = store.alloc(procArgs[i]);
        store = store2;
        env2 = envSet(env2, proc.params[i], addr);
      }
      const kont = state.kont.concat([{ tag: "KCall", savedEnv: state.env } as Frame]);
      return { ...state, control: { tag: "Expr", e: proc.body }, env: env2, store, kont };
    }
    throw new Error("expected procedure");
  }

  // Condition system primitives
  registerConditionPrims(def, { applyProcedure, ensureArity, isCallable });

  function promptKey(v: Val): string {
    switch (v.tag) {
      case "Sym": return `sym:${v.name}`;
      case "Str": return `str:${v.s}`;
      case "Num": return `num:${v.n}`;
      case "Bool": return `bool:${v.b}`;
      default:
        try {
          return `${v.tag}:${JSON.stringify(v)}`;
        } catch {
          return v.tag;
        }
    }
  }

  let gensymCounter = 0;
  function gensym(prefix: string): string {
    gensymCounter += 1;
    return `${prefix}${gensymCounter}`;
  }

  // *uninit*: placeholder for letrec uninitialized bindings
  // Returns Unit as a placeholder value that will be overwritten by set!
  def("*uninit*", { tag: "Native", name: "*uninit*", arity: 0, fn: (_args, s) => {
    return { ...s, control: { tag: "Val", v: VUnit } };
  }});

  // define-package: package declaration marker used by canonical package roots.
  // It validates the form shape but does not mutate runtime package registries
  // (those are sourced from manifest.json by the package loader).
  if (includePackageManifest) {
    def("define-package", { tag: "Native", name: "define-package", arity: "variadic", fn: (args, s) => {
      if (args.length === 0) {
        throw new Error("define-package: expected at least a package name");
      }

      const manifestArgs = args.map(valToManifestDatum);
      try {
        parseDefinePackage(manifestArgs as any[]);
      } catch (err: any) {
        const msg = err instanceof Error ? err.message : String(err);
        throw new Error(`define-package: ${msg}`);
      }

      return { ...s, control: { tag: "Val", v: VTrue } };
    }});
  }

  // arithmetic and predicates (reference-grade)
  def("+", { tag: "Native", name: "+", arity: "variadic", fn: (args, s) => ({ ...s, control: { tag: "Val", v: { tag: "Num", n: args.reduce((a, x) => a + (x as any).n, 0) } } }) });
  def("-", { tag: "Native", name: "-", arity: "variadic", fn: (args, s) => {
    const ns = args.map(a => (a as any).n as number);
    const n = ns.length === 1 ? -ns[0] : ns.slice(1).reduce((a, x) => a - x, ns[0]);
    return { ...s, control: { tag: "Val", v: { tag: "Num", n } } };
  }});

  def("*", { tag: "Native", name: "*", arity: "variadic", fn: (args, s) => {
    const n = args.reduce((a, x) => a * (x as any).n, 1);
    return { ...s, control: { tag: "Val", v: { tag: "Num", n } } };
  }});

  def("=", { tag: "Native", name: "=", arity: 2, fn: (args, s) => {
    const a = (args[0] as any).n, b = (args[1] as any).n;
    return { ...s, control: { tag: "Val", v: a === b ? VTrue : VFalse } };
  }});

  def("<", { tag: "Native", name: "<", arity: 2, fn: (args, s) => {
    const a = (args[0] as any).n, b = (args[1] as any).n;
    return { ...s, control: { tag: "Val", v: a < b ? VTrue : VFalse } };
  }});

  def(">", { tag: "Native", name: ">", arity: 2, fn: (args, s) => {
    const a = (args[0] as any).n, b = (args[1] as any).n;
    return { ...s, control: { tag: "Val", v: a > b ? VTrue : VFalse } };
  }});

  def("<=", { tag: "Native", name: "<=", arity: 2, fn: (args, s) => {
    const a = (args[0] as any).n, b = (args[1] as any).n;
    return { ...s, control: { tag: "Val", v: a <= b ? VTrue : VFalse } };
  }});

  def(">=", { tag: "Native", name: ">=", arity: 2, fn: (args, s) => {
    const a = (args[0] as any).n, b = (args[1] as any).n;
    return { ...s, control: { tag: "Val", v: a >= b ? VTrue : VFalse } };
  }});

  def("/", { tag: "Native", name: "/", arity: 2, fn: (args, s) => {
    const a = (args[0] as any).n, b = (args[1] as any).n;
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: a / b } } };
  }});

  def("modulo", { tag: "Native", name: "modulo", arity: 2, fn: (args, s) => {
    const a = (args[0] as any).n, b = (args[1] as any).n;
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: a % b } } };
  }});

  def("max", { tag: "Native", name: "max", arity: "variadic", fn: (args, s) => {
    if (args.length === 0) throw new Error("max: expected at least one argument");
    const nums = args.map(a => (a as any).n as number);
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: Math.max(...nums) } } };
  }});

  def("min", { tag: "Native", name: "min", arity: "variadic", fn: (args, s) => {
    if (args.length === 0) throw new Error("min: expected at least one argument");
    const nums = args.map(a => (a as any).n as number);
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: Math.min(...nums) } } };
  }});

  def("abs", { tag: "Native", name: "abs", arity: 1, fn: (args, s) => {
    const n = (args[0] as any).n as number;
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: Math.abs(n) } } };
  }});

  def("quotient", { tag: "Native", name: "quotient", arity: 2, fn: (args, s) => {
    const a = (args[0] as any).n as number;
    const b = (args[1] as any).n as number;
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: Math.trunc(a / b) } } };
  }});

  def("floor", { tag: "Native", name: "floor", arity: 1, fn: (args, s) => {
    const n = (args[0] as any).n as number;
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: Math.floor(n) } } };
  }});

  def("ceiling", { tag: "Native", name: "ceiling", arity: 1, fn: (args, s) => {
    const n = (args[0] as any).n as number;
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: Math.ceil(n) } } };
  }});

  def("even?", { tag: "Native", name: "even?", arity: 1, fn: (args, s) => {
    const n = (args[0] as any).n as number;
    return { ...s, control: { tag: "Val", v: n % 2 === 0 ? VTrue : VFalse } };
  }});

  def("not", { tag: "Native", name: "not", arity: 1, fn: (args, s) => {
    const b = (args[0] as any).b as boolean;
    return { ...s, control: { tag: "Val", v: (!b ? VTrue : VFalse) } };
  }});

  // Logical or (non-short-circuit in primitive form, returns first truthy value)
  def("or", { tag: "Native", name: "or", arity: "variadic", fn: (args, s) => {
    for (const arg of args) {
      const truthy = !(arg.tag === "Bool" && !arg.b) && arg.tag !== "Unit";
      if (truthy) {
        return { ...s, control: { tag: "Val", v: arg } };
      }
    }
    return { ...s, control: { tag: "Val", v: VFalse } };
  }});

  // Logical and (non-short-circuit, returns last argument when all truthy)
  def("and", { tag: "Native", name: "and", arity: "variadic", fn: (args, s) => {
    let last: Val = VTrue;
    for (const arg of args) {
      const truthy = !(arg.tag === "Bool" && !arg.b) && arg.tag !== "Unit";
      if (!truthy) {
        return { ...s, control: { tag: "Val", v: arg } };
      }
      last = arg;
    }
    return { ...s, control: { tag: "Val", v: last } };
  }});

  // Monadic primitives for nondeterminism
  function emitAmbChoose(thunks: Val[], s: State): StepOutcome {
    const suspended: State = { ...s, control: { tag: "Val", v: VUnit } };
    const resumption = captureValueResumption(suspended);
    const choices: Val = { tag: "Vector", items: thunks };
    const opcall = { op: "amb.choose", args: [choices], ctxDigest: s.env.cid, resumption };
    return { tag: "Op", opcall, state: suspended };
  }

  def("unit", { tag: "Native", name: "unit", arity: 1, fn: (args, s) => ({ ...s, control: { tag: "Val", v: args[0] } }) });

  def("mzero", { tag: "Native", name: "mzero", arity: 0, fn: (_args, s) => emitAmbChoose([], s) });

  def("mplus", { tag: "Native", name: "mplus", arity: 2, lazyArgs: [0, 1], fn: (args, s) => {
    return emitAmbChoose(args, s);
  }});

  def("bind", { tag: "Native", name: "bind", arity: 2, lazyArgs: [0], fn: (args, s) => {
    const [thunk, fn] = args;
    if (!isCallable(fn)) {
      throw new Error("bind expects a procedure as second argument");
    }
    ensureArity(fn, 1, "bind");

    if (!isCallable(thunk)) {
      throw new Error("bind expects a computation (thunk) as first argument");
    }
    ensureArity(thunk, 0, "bind");

    const kont = s.kont.concat([{ tag: "KBind", fn, env: s.env } as Frame]);
    const stWithKont: State = { ...s, kont };
    return applyProcedure(thunk, [], stWithKont);
  }});

  // ─────────────────────────────────────────────────────────────────
  // Continuations and prompts
  // ─────────────────────────────────────────────────────────────────
  def("call/cc", { tag: "Native", name: "call/cc", arity: 1, fn: (args, s: State) => {
    const proc = args[0];
    if (!isCallable(proc)) {
      throw new Error("call/cc expects a procedure");
    }
    ensureArity(proc, 1, "call/cc");

    const cont: Val = {
      tag: "Continuation",
      kont: s.kont.slice(),
      env: s.env,
      store: s.store,
      handlers: s.handlers.slice(),
    } as Val;

    return applyProcedure(proc, [cont], s);
  }});

  def("call-with-prompt", { tag: "Native", name: "call-with-prompt", arity: 3, fn: (args, s: State) => {
    const [tagVal, thunk, handler] = args;
    if (!isCallable(thunk)) throw new Error("call-with-prompt: expected thunk");
    if (!isCallable(handler)) throw new Error("call-with-prompt: expected handler");
    ensureArity(thunk, 0, "call-with-prompt");
    ensureArity(handler, 2, "call-with-prompt");

    const promptFrame: Frame = {
      tag: "KPrompt",
      promptTag: tagVal,
      handler,
      env: s.env,
      savedKont: s.kont.slice(),
      savedHandlersDepth: s.handlers.length,
    };

    const stWithPrompt: State = { ...s, kont: s.kont.concat([promptFrame]) };
    return applyProcedure(thunk, [], stWithPrompt);
  }});

  def("abort-to-prompt", { tag: "Native", name: "abort-to-prompt", arity: 2, fn: (args, s: State) => {
    const [tagVal, value] = args;
    const targetKey = promptKey(tagVal);

    let found = -1;
    for (let i = s.kont.length - 1; i >= 0; i--) {
      const fr = s.kont[i] as Frame;
      if (fr.tag === "KPrompt" && promptKey((fr as any).promptTag) === targetKey) {
        found = i;
        break;
      }
    }

    if (found < 0) {
      throw new Error(`abort-to-prompt: no prompt for tag ${targetKey}`);
    }

    const pf = s.kont[found] as any;
    const handlerState: State = {
      ...s,
      env: pf.env,
      kont: pf.savedKont,
      handlers: s.handlers.slice(0, pf.savedHandlersDepth),
    };

    const kVal: Val = {
      tag: "Continuation",
      kont: s.kont.slice(found),
      env: s.env,
      store: s.store,
      handlers: s.handlers.slice(0, pf.savedHandlersDepth),
    } as Val;

    return applyProcedure(pf.handler, [kVal, value], handlerState);
  }});

  // ─────────────────────────────────────────────────────────────────
  // Metacircular primitives (cons, car, cdr, null?, pair?)
  // With these you can build list, append, map, filter, etc.
  // ─────────────────────────────────────────────────────────────────

  // cons: construct a pair
  def("cons", { tag: "Native", name: "cons", arity: 2, fn: (args, s) => {
    const pair: Val = { tag: "Vector", items: [args[0], args[1]] };
    return { ...s, control: { tag: "Val", v: pair } };
  }});

  // car: first element of pair
  def("car", { tag: "Native", name: "car", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    if (v.tag === "Vector" && v.items.length >= 1) {
      return { ...s, control: { tag: "Val", v: v.items[0] } };
    }
    throw new Error("car: expected pair");
  }});

  // cdr: rest of pair
  def("cdr", { tag: "Native", name: "cdr", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    if (v.tag === "Vector" && v.items.length >= 2) {
      return { ...s, control: { tag: "Val", v: v.items[1] } };
    }
    throw new Error("cdr: expected pair");
  }});

  // null?: check if value is null/empty
  def("null?", { tag: "Native", name: "null?", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    const isNull = v.tag === "Unit" || (v.tag === "Vector" && v.items.length === 0);
    return { ...s, control: { tag: "Val", v: isNull ? VTrue : VFalse } };
  }});

  // pair?: check if value is a pair
  def("pair?", { tag: "Native", name: "pair?", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    const isPair = v.tag === "Vector" && v.items.length >= 2;
    return { ...s, control: { tag: "Val", v: isPair ? VTrue : VFalse } };
  }});

  // list?: check if value is a list (null or pair)
  def("list?", { tag: "Native", name: "list?", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    const isNull = v.tag === "Unit" || (v.tag === "Vector" && v.items.length === 0);
    const isPair = v.tag === "Vector" && v.items.length >= 2;
    return { ...s, control: { tag: "Val", v: (isNull || isPair) ? VTrue : VFalse } };
  }});

  // list: construct a list from arguments (variadic)
  def("list", { tag: "Native", name: "list", arity: "variadic", fn: (args, s) => {
    // Build proper list from args: (list 1 2 3) => (cons 1 (cons 2 (cons 3 null)))
    let result: Val = VUnit; // null terminator
    for (let i = args.length - 1; i >= 0; i--) {
      result = { tag: "Vector", items: [args[i], result] };
    }
    return { ...s, control: { tag: "Val", v: result } };
  }});

  // append: concatenate lists (variadic)
  def("append", { tag: "Native", name: "append", arity: "variadic", fn: (args, s) => {
    // Flatten all lists into one
    const items: Val[] = [];
    for (const arg of args) {
      let current = arg as any;
      while (current.tag === "Vector" && current.items.length >= 2) {
        items.push(current.items[0]);
        current = current.items[1];
      }
      // If it's a non-null atom at the end, include it (improper list)
      if (current.tag !== "Unit") {
        items.push(current);
      }
    }
    // Rebuild as proper list
    let result: Val = VUnit;
    for (let i = items.length - 1; i >= 0; i--) {
      result = { tag: "Vector", items: [items[i], result] };
    }
    return { ...s, control: { tag: "Val", v: result } };
  }});

  // eq?: identity/equality comparison
  def("eq?", { tag: "Native", name: "eq?", arity: 2, fn: (args, s) => {
    const a = args[0] as any, b = args[1] as any;
    let eq = false;
    if (a.tag === b.tag) {
      if (a.tag === "Num") eq = a.n === b.n;
      else if (a.tag === "Bool") eq = a.b === b.b;
      else if (a.tag === "Str") eq = a.s === b.s;
      else if (a.tag === "Sym") eq = a.name === b.name;
      else if (a.tag === "Unit") eq = true;
      else eq = a === b; // reference equality
    }
    return { ...s, control: { tag: "Val", v: eq ? VTrue : VFalse } };
  }});

  // NOTE: For nested LLM calls, use the built-in Oracle Protocol:
  //   (infer.op payload)  - invoke LLM with payload, returns Meaning
  //   (int.op payload)    - same as infer.op
  // The LLM can then use eval/apply/observe tools to interact with Lisp.

  // ─────────────────────────────────────────────────────────────────
  // Model Selection Primitives
  // For selecting which LLM provider/model to use at runtime
  // ─────────────────────────────────────────────────────────────────

  // with-model: wrap a payload with model specification
  // Usage: (effect infer.op (with-model "gpt-4o" "question"))
  // The driver registry will route to the appropriate driver
  def("with-model", { tag: "Native", name: "with-model", arity: 2, fn: (args, s) => {
    const modelArg = args[0];
    const payload = args[1];

    // Model can be string or symbol
    let model: string;
    if (modelArg.tag === "Str") {
      model = modelArg.s;
    } else if (modelArg.tag === "Sym") {
      model = modelArg.name;
    } else {
      throw new Error("with-model: first argument must be string or symbol");
    }

    // Build Map with :model and :payload keys (format expected by OracleBackend)
    const mapVal: Val = {
      tag: "Map",
      entries: [
        [{ tag: "Str", s: "model" }, { tag: "Str", s: model }],
        [{ tag: "Str", s: "payload" }, payload],
      ],
    };
    return { ...s, control: { tag: "Val", v: mapVal } };
  }});

  // with-plugin: wrap payload with explicit plugin and model specification
  // Usage: (effect infer.op (with-plugin "my-driver" "my-model" "question"))
  def("with-plugin", { tag: "Native", name: "with-plugin", arity: 3, fn: (args, s) => {
    const pluginArg = args[0];
    const modelArg = args[1];
    const payload = args[2];

    let plugin: string;
    if (pluginArg.tag === "Str") {
      plugin = pluginArg.s;
    } else if (pluginArg.tag === "Sym") {
      plugin = pluginArg.name;
    } else {
      throw new Error("with-plugin: first argument must be string or symbol");
    }

    let model: string;
    if (modelArg.tag === "Str") {
      model = modelArg.s;
    } else if (modelArg.tag === "Sym") {
      model = modelArg.name;
    } else {
      throw new Error("with-plugin: second argument must be string or symbol");
    }

    // Build Map with :plugin, :model, and :payload keys
    const mapVal: Val = {
      tag: "Map",
      entries: [
        [{ tag: "Str", s: "plugin" }, { tag: "Str", s: plugin }],
        [{ tag: "Str", s: "model" }, { tag: "Str", s: model }],
        [{ tag: "Str", s: "payload" }, payload],
      ],
    };
    return { ...s, control: { tag: "Val", v: mapVal } };
  }});

  // with-plugin-cwd: like with-plugin but includes working directory for the agent
  // Usage: (effect infer.op (with-plugin-cwd "codex-cli" "gpt-5.3-codex" "/path/to/project" "prompt"))
  def("with-plugin-cwd", { tag: "Native", name: "with-plugin-cwd", arity: 4, fn: (args, s) => {
    const pluginArg = args[0];
    const modelArg = args[1];
    const cwdArg = args[2];
    const payload = args[3];

    let plugin: string;
    if (pluginArg.tag === "Str") plugin = pluginArg.s;
    else if (pluginArg.tag === "Sym") plugin = pluginArg.name;
    else throw new Error("with-plugin-cwd: first argument must be string or symbol");

    let model: string;
    if (modelArg.tag === "Str") model = modelArg.s;
    else if (modelArg.tag === "Sym") model = modelArg.name;
    else throw new Error("with-plugin-cwd: second argument must be string or symbol");

    if (cwdArg.tag !== "Str") throw new Error("with-plugin-cwd: third argument (cwd) must be string");

    const mapVal: Val = {
      tag: "Map",
      entries: [
        [{ tag: "Str", s: "plugin" }, { tag: "Str", s: plugin }],
        [{ tag: "Str", s: "model" }, { tag: "Str", s: model }],
        [{ tag: "Str", s: "cwd" }, cwdArg],
        [{ tag: "Str", s: "payload" }, payload],
      ],
    };
    return { ...s, control: { tag: "Val", v: mapVal } };
  }});

  // ─────────────────────────────────────────────────────────────────
  // Distribution primitives (Dist<Val>)
  // For working with nondeterministic distributions from oracle ops
  // ─────────────────────────────────────────────────────────────────

  // dist: create a point distribution from a single value
  def("dist", { tag: "Native", name: "dist", arity: 1, fn: (args, s) => {
    const v = args[0];
    const d: Val = { tag: "Dist", support: [{ v, w: 1 }], normalized: true };
    return { ...s, control: { tag: "Val", v: d } };
  }});

  // dist?: check if value is a distribution
  def("dist?", { tag: "Native", name: "dist?", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    const isDist = v.tag === "Dist";
    return { ...s, control: { tag: "Val", v: isDist ? VTrue : VFalse } };
  }});

  // dist-count: number of support elements
  def("dist-count", { tag: "Native", name: "dist-count", arity: 1, fn: (args, s) => {
    const d = args[0] as any;
    if (d.tag !== "Dist") throw new Error("dist-count: expected Dist");
    const n = d.support.length;
    return { ...s, control: { tag: "Val", v: { tag: "Num", n } } };
  }});

  // dist-value-at: get the value at index i
  def("dist-value-at", { tag: "Native", name: "dist-value-at", arity: 2, fn: (args, s) => {
    const d = args[0] as any;
    const i = (args[1] as any).n;
    if (d.tag !== "Dist") throw new Error("dist-value-at: expected Dist");
    if (i < 0 || i >= d.support.length) throw new Error("dist-value-at: index out of bounds");
    return { ...s, control: { tag: "Val", v: d.support[i].v } };
  }});

  // dist-weight-at: get the weight at index i
  def("dist-weight-at", { tag: "Native", name: "dist-weight-at", arity: 2, fn: (args, s) => {
    const d = args[0] as any;
    const i = (args[1] as any).n;
    if (d.tag !== "Dist") throw new Error("dist-weight-at: expected Dist");
    if (i < 0 || i >= d.support.length) throw new Error("dist-weight-at: index out of bounds");
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: d.support[i].w } } };
  }});

  // dist-normalize: normalize distribution weights to sum to 1
  def("dist-normalize", { tag: "Native", name: "dist-normalize", arity: 1, fn: (args, s) => {
    const d = args[0] as any;
    if (d.tag !== "Dist") throw new Error("dist-normalize: expected Dist");
    const sum = d.support.reduce((a: number, it: any) => a + it.w, 0);
    if (sum <= 0) {
      const empty: Val = { tag: "Dist", support: [], normalized: true, meta: d.meta };
      return { ...s, control: { tag: "Val", v: empty } };
    }
    const support = d.support.map((it: any) => ({ v: it.v, w: it.w / sum }));
    const norm: Val = { tag: "Dist", support, normalized: true, meta: d.meta };
    return { ...s, control: { tag: "Val", v: norm } };
  }});

  // dist-sample: sample a value from distribution with seed
  def("dist-sample", { tag: "Native", name: "dist-sample", arity: 2, fn: (args, s) => {
    const d0 = args[0] as any;
    const seed = (args[1] as any).n;
    if (d0.tag !== "Dist") throw new Error("dist-sample: expected Dist");

    // Normalize if needed
    let d = d0;
    if (!d.normalized) {
      const sum = d.support.reduce((a: number, it: any) => a + it.w, 0);
      if (sum > 0) {
        d = { ...d, support: d.support.map((it: any) => ({ v: it.v, w: it.w / sum })), normalized: true };
      }
    }

    if (d.support.length === 0) throw new Error("dist-sample: empty support");

    // Mulberry32 PRNG
    let t = seed >>> 0;
    t += 0x6D2B79F5;
    let x = t;
    x = Math.imul(x ^ (x >>> 15), x | 1);
    x ^= x + Math.imul(x ^ (x >>> 7), x | 61);
    const r = ((x ^ (x >>> 14)) >>> 0) / 4294967296;

    let acc = 0;
    for (const it of d.support) {
      acc += it.w;
      if (r <= acc) return { ...s, control: { tag: "Val", v: it.v } };
    }
    return { ...s, control: { tag: "Val", v: d.support[d.support.length - 1].v } };
  }});

  // dist-topk: get top k elements by weight
  def("dist-topk", { tag: "Native", name: "dist-topk", arity: 2, fn: (args, s) => {
    const d0 = args[0] as any;
    const k = (args[1] as any).n;
    if (d0.tag !== "Dist") throw new Error("dist-topk: expected Dist");

    const sorted = d0.support.slice().sort((a: any, b: any) => b.w - a.w);
    const support = sorted.slice(0, Math.max(0, k));
    const topk: Val = { tag: "Dist", support, normalized: d0.normalized, meta: d0.meta };
    return { ...s, control: { tag: "Val", v: topk } };
  }});

  // dist-from-list: create distribution from list of (value weight) pairs
  def("dist-from-list", { tag: "Native", name: "dist-from-list", arity: 1, fn: (args, s) => {
    const lst = args[0] as any;
    const support: Array<{ v: Val; w: number }> = [];

    // Walk the list (list of pairs where each pair is (value . weight))
    let cur = lst;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      const pair = cur.items[0] as any;
      if (pair.tag === "Vector" && pair.items.length >= 2) {
        const v = pair.items[0];
        const w = (pair.items[1] as any).n ?? 1;
        support.push({ v, w });
      }
      cur = cur.items[1];
    }

    const d: Val = { tag: "Dist", support, normalized: false };
    return { ...s, control: { tag: "Val", v: d } };
  }});

  // dist-to-list: convert distribution to list of (value weight) pairs
  def("dist-to-list", { tag: "Native", name: "dist-to-list", arity: 1, fn: (args, s) => {
    const d = args[0] as any;
    if (d.tag !== "Dist") throw new Error("dist-to-list: expected Dist");

    // Build list of pairs
    let result: Val = VUnit;
    for (let i = d.support.length - 1; i >= 0; i--) {
      const it = d.support[i];
      const pair: Val = { tag: "Vector", items: [it.v, { tag: "Num", n: it.w }] };
      result = { tag: "Vector", items: [pair, result] };
    }
    return { ...s, control: { tag: "Val", v: result } };
  }});

  // ─────────────────────────────────────────────────────────────────
  // String primitives (semantic text toolbox)
  // These allow mechanical/extensional string processing
  // ─────────────────────────────────────────────────────────────────

  // string=?: string equality
  def("string=?", { tag: "Native", name: "string=?", arity: 2, fn: (args, s) => {
    const a = args[0] as any, b = args[1] as any;
    if (a.tag !== "Str" || b.tag !== "Str") throw new Error("string=?: expected strings");
    return { ...s, control: { tag: "Val", v: a.s === b.s ? VTrue : VFalse } };
  }});

  // string-contains?: check if string contains substring
  def("string-contains?", { tag: "Native", name: "string-contains?", arity: 2, fn: (args, s) => {
    const str = args[0] as any, sub = args[1] as any;
    if (str.tag !== "Str" || sub.tag !== "Str") throw new Error("string-contains?: expected strings");
    return { ...s, control: { tag: "Val", v: str.s.includes(sub.s) ? VTrue : VFalse } };
  }});

  // string-find: find first occurrence of substring, returns index or #f
  def("string-find", { tag: "Native", name: "string-find", arity: 2, fn: (args, s) => {
    const str = args[0] as any, sub = args[1] as any;
    if (str.tag !== "Str" || sub.tag !== "Str") throw new Error("string-find: expected strings");
    const idx = str.s.indexOf(sub.s);
    if (idx === -1) {
      return { ...s, control: { tag: "Val", v: VFalse } };
    }
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: idx } } };
  }});

  // string-replace-all: replace all occurrences of substr with replacement
  def("string-replace-all", { tag: "Native", name: "string-replace-all", arity: 3, fn: (args, s) => {
    const str = args[0] as any, sub = args[1] as any, repl = args[2] as any;
    if (str.tag !== "Str" || sub.tag !== "Str" || repl.tag !== "Str") {
      throw new Error("string-replace-all: expected strings");
    }
    // Use split/join for global replace without regex
    const result = str.s.split(sub.s).join(repl.s);
    return { ...s, control: { tag: "Val", v: { tag: "Str", s: result } } };
  }});

  // string-split: split string by delimiter (accepts Str or Char delimiter)
  def("string-split", { tag: "Native", name: "string-split", arity: 2, fn: (args, s) => {
    const str = args[0] as any, delim = args[1] as any;
    if (str.tag !== "Str") throw new Error("string-split: first arg must be string");
    // Accept both Str and Char delimiters
    let delimStr: string;
    if (delim.tag === "Str") delimStr = delim.s;
    else if (delim.tag === "Char") delimStr = delim.c;
    else throw new Error("string-split: delimiter must be string or char");
    const parts = str.s.split(delimStr);
    // Build proper list
    let result: Val = VUnit;
    for (let i = parts.length - 1; i >= 0; i--) {
      result = { tag: "Vector", items: [{ tag: "Str", s: parts[i] }, result] };
    }
    return { ...s, control: { tag: "Val", v: result } };
  }});

  // string-join: join list of strings with delimiter (accepts Str or Char delimiter)
  def("string-join", { tag: "Native", name: "string-join", arity: 2, fn: (args, s) => {
    const lst = args[0] as any, delim = args[1] as any;
    // Accept both Str and Char delimiters
    let delimStr: string;
    if (delim.tag === "Str") delimStr = delim.s;
    else if (delim.tag === "Char") delimStr = delim.c;
    else throw new Error("string-join: delimiter must be string or char");
    // Walk the list
    const parts: string[] = [];
    let cur = lst;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      const elem = cur.items[0] as any;
      if (elem.tag === "Str") parts.push(elem.s);
      cur = cur.items[1];
    }
    return { ...s, control: { tag: "Val", v: { tag: "Str", s: parts.join(delimStr) } } };
  }});

  // string-trim: remove leading/trailing whitespace
  def("string-trim", { tag: "Native", name: "string-trim", arity: 1, fn: (args, s) => {
    const str = args[0] as any;
    if (str.tag !== "Str") throw new Error("string-trim: expected string");
    return { ...s, control: { tag: "Val", v: { tag: "Str", s: str.s.trim() } } };
  }});

  // string-downcase: convert to lowercase
  def("string-downcase", { tag: "Native", name: "string-downcase", arity: 1, fn: (args, s) => {
    const str = args[0] as any;
    if (str.tag !== "Str") throw new Error("string-downcase: expected string");
    return { ...s, control: { tag: "Val", v: { tag: "Str", s: str.s.toLowerCase() } } };
  }});

  // string-upcase: convert to uppercase
  def("string-upcase", { tag: "Native", name: "string-upcase", arity: 1, fn: (args, s) => {
    const str = args[0] as any;
    if (str.tag !== "Str") throw new Error("string-upcase: expected string");
    return { ...s, control: { tag: "Val", v: { tag: "Str", s: str.s.toUpperCase() } } };
  }});

  // string-length: get length of string
  def("string-length", { tag: "Native", name: "string-length", arity: 1, fn: (args, s) => {
    const str = args[0] as any;
    if (str.tag !== "Str") throw new Error("string-length: expected string");
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: str.s.length } } };
  }});

  // symbol->string: convert symbol to string
  def("symbol->string", { tag: "Native", name: "symbol->string", arity: 1, fn: (args, s) => {
    const sym = args[0] as any;
    if (sym.tag !== "Sym") throw new Error("symbol->string: expected symbol");
    return { ...s, control: { tag: "Val", v: { tag: "Str", s: sym.name } } };
  }});

  // string->symbol: convert string to symbol
  def("string->symbol", { tag: "Native", name: "string->symbol", arity: 1, fn: (args, s) => {
    const str = args[0] as any;
    if (str.tag !== "Str") throw new Error("string->symbol: expected string");
    return { ...s, control: { tag: "Val", v: { tag: "Sym", name: str.s } } };
  }});

  // string-append: concatenate strings
  def("string-append", { tag: "Native", name: "string-append", arity: "variadic", fn: (args, s) => {
    let result = "";
    for (const arg of args) {
      if ((arg as any).tag !== "Str") throw new Error("string-append: expected strings");
      result += (arg as any).s;
    }
    return { ...s, control: { tag: "Val", v: { tag: "Str", s: result } } };
  }});

  // substring: extract substring
  def("substring", { tag: "Native", name: "substring", arity: 3, fn: (args, s) => {
    const str = args[0] as any, start = args[1] as any, end = args[2] as any;
    if (str.tag !== "Str") throw new Error("substring: expected string");
    if (start.tag !== "Num" || end.tag !== "Num") throw new Error("substring: expected numbers for indices");
    return { ...s, control: { tag: "Val", v: { tag: "Str", s: str.s.substring(start.n, end.n) } } };
  }});

  // string->number: convert string to number (canonical Lisp primitive)
  def("string->number", { tag: "Native", name: "string->number", arity: 1, fn: (args, s) => {
    const str = args[0] as any;
    if (str.tag !== "Str") throw new Error("string->number: expected string");
    const n = parseFloat(str.s);
    if (isNaN(n)) return { ...s, control: { tag: "Val", v: { tag: "Bool", b: false } } };
    return { ...s, control: { tag: "Val", v: { tag: "Num", n } } };
  }});

  // number->string: convert number to string (canonical Lisp primitive)
  def("number->string", { tag: "Native", name: "number->string", arity: 1, fn: (args, s) => {
    const num = args[0] as any;
    if (num.tag !== "Num") throw new Error("number->string: expected number");
    return { ...s, control: { tag: "Val", v: { tag: "Str", s: String(num.n) } } };
  }});

  // json-escape: escape a string for safe embedding in JSON
  def("json-escape", { tag: "Native", name: "json-escape", arity: 1, fn: (args, s) => {
    const str = args[0] as any;
    if (str.tag !== "Str") throw new Error("json-escape: expected string");
    const escaped = JSON.stringify(str.s).slice(1, -1); // strip outer quotes
    return { ...s, control: { tag: "Val", v: { tag: "Str", s: escaped } } };
  }});

  // ─────────────────────────────────────────────────────────────────
  // Character operations
  // ─────────────────────────────────────────────────────────────────

  // char?: check if value is a character
  def("char?", { tag: "Native", name: "char?", arity: 1, fn: (args, s) => {
    const isChar = (args[0] as any).tag === "Char";
    return { ...s, control: { tag: "Val", v: isChar ? VTrue : VFalse } };
  }});

  // char=?: character equality
  def("char=?", { tag: "Native", name: "char=?", arity: 2, fn: (args, s) => {
    const a = args[0] as any, b = args[1] as any;
    if (a.tag !== "Char" || b.tag !== "Char") {
      throw new Error("char=?: expected characters");
    }
    return { ...s, control: { tag: "Val", v: a.c === b.c ? VTrue : VFalse } };
  }});

  // string-ref: get character at index
  def("string-ref", { tag: "Native", name: "string-ref", arity: 2, fn: (args, s) => {
    const str = args[0] as any, idx = args[1] as any;
    if (str.tag !== "Str") throw new Error("string-ref: expected string");
    if (idx.tag !== "Num") throw new Error("string-ref: expected number index");
    if (idx.n < 0 || idx.n >= str.s.length) {
      throw new Error(`string-ref: index ${idx.n} out of bounds for string of length ${str.s.length}`);
    }
    return { ...s, control: { tag: "Val", v: { tag: "Char", c: str.s[idx.n] } } };
  }});

  // string-index: find first occurrence of character in string (from start position)
  def("string-index", { tag: "Native", name: "string-index", arity: 3, fn: (args, s) => {
    const str = args[0] as any, char = args[1] as any, start = args[2] as any;
    if (str.tag !== "Str") throw new Error("string-index: expected string");
    if (char.tag !== "Char") throw new Error("string-index: expected character");
    if (start.tag !== "Num") throw new Error("string-index: expected number start");
    const idx = str.s.indexOf(char.c, start.n);
    // Return #f if not found, otherwise the index
    if (idx === -1) {
      return { ...s, control: { tag: "Val", v: VFalse } };
    }
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: idx } } };
  }});

  // char->integer: convert character to its Unicode code point
  def("char->integer", { tag: "Native", name: "char->integer", arity: 1, fn: (args, s) => {
    const c = args[0] as any;
    if (c.tag !== "Char") throw new Error("char->integer: expected character");
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: c.c.charCodeAt(0) } } };
  }});

  // integer->char: convert Unicode code point to character
  def("integer->char", { tag: "Native", name: "integer->char", arity: 1, fn: (args, s) => {
    const n = args[0] as any;
    if (n.tag !== "Num") throw new Error("integer->char: expected number");
    return { ...s, control: { tag: "Val", v: { tag: "Char", c: String.fromCharCode(n.n) } } };
  }});

  // ─────────────────────────────────────────────────────────────────
  // S-expression parsing (read)
  // ─────────────────────────────────────────────────────────────────

  // Helper to convert Sexp to Val
  function sexpToVal(sexp: Sexp): Val {
    switch (sexp.tag) {
      case "Sym":
        return { tag: "Sym", name: sexp.name };
      case "Num":
        return { tag: "Num", n: sexp.n };
      case "Str":
        return { tag: "Str", s: sexp.s };
      case "Bool":
        return { tag: "Bool", b: sexp.b };
      case "List":
        return { tag: "Vector", items: sexp.items.map(sexpToVal) };
    }
  }

  // read: parse a string as an s-expression and return the corresponding value
  // This enables homoiconicity - LLM output can become executable code/data
  def("read", { tag: "Native", name: "read", arity: 1, fn: (args, s) => {
    const str = args[0] as any;
    if (str.tag !== "Str") throw new Error("read: expected string");
    try {
      const sexp = parseSexp(str.s);
      const val = sexpToVal(sexp);
      return { ...s, control: { tag: "Val", v: val } };
    } catch (err: any) {
      throw new Error(`read: parse error - ${err.message}`);
    }
  }});

  // read-from-string: explicit alias for read
  // Useful for eval trampolines: (eval (read-from-string "..."))
  def("read-from-string", { tag: "Native", name: "read-from-string", arity: 1, fn: (args, s) => {
    const str = args[0] as any;
    if (str.tag !== "Str") throw new Error("read-from-string: expected string");
    try {
      const sexp = parseSexp(str.s);
      const val = sexpToVal(sexp);
      return { ...s, control: { tag: "Val", v: val } };
    } catch (err: any) {
      throw new Error(`read-from-string: parse error - ${err.message}`);
    }
  }});
  // read-from-string-safe: parse without throwing
  // Returns map {:ok Bool :value Val|#f :error Str|#f}
  def("read-from-string-safe", { tag: "Native", name: "read-from-string-safe", arity: 1, fn: (args, s) => {
    const str = args[0] as any;
    if (str.tag !== "Str") throw new Error("read-from-string-safe: expected string");
    try {
      const sexp = parseSexp(str.s);
      const val = sexpToVal(sexp);
      const out: Val = {
        tag: "Map",
        entries: [
          [{ tag: "Sym", name: ":ok" }, VTrue],
          [{ tag: "Sym", name: ":value" }, val],
          [{ tag: "Sym", name: ":error" }, VFalse],
        ],
      };
      return { ...s, control: { tag: "Val", v: out } };
    } catch (err: any) {
      const out: Val = {
        tag: "Map",
        entries: [
          [{ tag: "Sym", name: ":ok" }, VFalse],
          [{ tag: "Sym", name: ":value" }, VFalse],
          [{ tag: "Sym", name: ":error" }, { tag: "Str", s: String(err?.message ?? err) }],
        ],
      };
      return { ...s, control: { tag: "Val", v: out } };
    }
  }});

  // ─────────────────────────────────────────────────────────────────
  // Symbol and type predicates
  // ─────────────────────────────────────────────────────────────────

  // symbol?: check if value is a symbol
  def("symbol?", { tag: "Native", name: "symbol?", arity: 1, fn: (args, s) => {
    return { ...s, control: { tag: "Val", v: args[0].tag === "Sym" ? VTrue : VFalse } };
  }});

  // string?: check if value is a string
  def("string?", { tag: "Native", name: "string?", arity: 1, fn: (args, s) => {
    return { ...s, control: { tag: "Val", v: args[0].tag === "Str" ? VTrue : VFalse } };
  }});

  // number?: check if value is a number
  def("number?", { tag: "Native", name: "number?", arity: 1, fn: (args, s) => {
    return { ...s, control: { tag: "Val", v: args[0].tag === "Num" ? VTrue : VFalse } };
  }});

  // boolean?: check if value is a boolean
  def("boolean?", { tag: "Native", name: "boolean?", arity: 1, fn: (args, s) => {
    return { ...s, control: { tag: "Val", v: args[0].tag === "Bool" ? VTrue : VFalse } };
  }});

  // procedure?: check if value is callable
  def("procedure?", { tag: "Native", name: "procedure?", arity: 1, fn: (args, s) => {
    const v = args[0];
    const isProc = v.tag === "Closure" || v.tag === "Native" || v.tag === "OracleProc" || v.tag === "Continuation" || v.tag === "Cont";
    return { ...s, control: { tag: "Val", v: isProc ? VTrue : VFalse } };
  }});

  // ─────────────────────────────────────────────────────────────────
  // List utilities (additional helpers for SICP-style programming)
  // ─────────────────────────────────────────────────────────────────

  // length: get length of list
  def("length", { tag: "Native", name: "length", arity: 1, fn: (args, s) => {
    let count = 0;
    let cur = args[0] as any;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      count++;
      cur = cur.items[1];
    }
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: count } } };
  }});

  // list-ref: get nth element (for cons-style lists)
  def("list-ref", { tag: "Native", name: "list-ref", arity: 2, fn: (args, s) => {
    const lst = args[0] as any;
    const n = (args[1] as any).n;
    let cur = lst;
    for (let i = 0; i < n; i++) {
      if (cur.tag !== "Vector" || cur.items.length < 2) throw new Error("list-ref: index out of bounds");
      cur = cur.items[1];
    }
    if (cur.tag !== "Vector" || cur.items.length < 1) throw new Error("list-ref: index out of bounds");
    return { ...s, control: { tag: "Val", v: cur.items[0] } };
  }});

  // vector-ref: get nth element from a flat vector
  def("vector-ref", { tag: "Native", name: "vector-ref", arity: 2, fn: (args, s) => {
    const vec = args[0] as any;
    const n = (args[1] as any).n;
    if (vec.tag !== "Vector") throw new Error("vector-ref: expected vector");
    if (n < 0 || n >= vec.items.length) throw new Error("vector-ref: index out of bounds");
    return { ...s, control: { tag: "Val", v: vec.items[n] } };
  }});

  // vector-length: get length of a flat vector
  def("vector-length", { tag: "Native", name: "vector-length", arity: 1, fn: (args, s) => {
    const vec = args[0] as any;
    if (vec.tag !== "Vector") throw new Error("vector-length: expected vector");
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: vec.items.length } } };
  }});

  // map-ref: get value from Map by key (Str or Sym)
  def("map-ref", { tag: "Native", name: "map-ref", arity: 2, fn: (args, s) => {
    const m = args[0] as any;
    const key = args[1] as any;
    if (m.tag !== "Map") throw new Error("map-ref: expected map as first argument");

    for (const [k, v] of m.entries) {
      if (k.tag === key.tag) {
        if ((k.tag === "Str" && k.s === key.s) ||
            (k.tag === "Sym" && k.name === key.name) ||
            (k.tag === "Num" && k.n === key.n)) {
          return { ...s, control: { tag: "Val", v } };
        }
      }
      // Also match Str key against Sym lookup and vice versa
      if (k.tag === "Str" && key.tag === "Sym" && k.s === key.name) {
        return { ...s, control: { tag: "Val", v } };
      }
      if (k.tag === "Sym" && key.tag === "Str" && k.name === key.s) {
        return { ...s, control: { tag: "Val", v } };
      }
    }

    return { ...s, control: { tag: "Val", v: VFalse } };
  }});

  // ─── Vector constructor: (vector a b c) → flat Vector Val ───
  def("vector", { tag: "Native", name: "vector", arity: "variadic", fn: (args, s) => {
    return { ...s, control: { tag: "Val", v: { tag: "Vector", items: args } } };
  }});

  // vector?: predicate
  def("vector?", { tag: "Native", name: "vector?", arity: 1, fn: (args, s) => {
    return { ...s, control: { tag: "Val", v: { tag: "Bool", b: args[0].tag === "Vector" } } };
  }});

  // ─── Hash-map constructor: (hash-map k1 v1 k2 v2 ...) → Map Val ───
  def("hash-map", { tag: "Native", name: "hash-map", arity: "variadic", fn: (args, s) => {
    if (args.length % 2 !== 0) throw new Error("hash-map: even number of arguments required (key value pairs)");
    const entries: Array<[Val, Val]> = [];
    for (let i = 0; i < args.length; i += 2) {
      entries.push([args[i], args[i + 1]]);
    }
    return { ...s, control: { tag: "Val", v: { tag: "Map", entries } } };
  }});

  // map?: predicate (checks for Map tag, not cons-pair map)
  def("map?", { tag: "Native", name: "map?", arity: 1, fn: (args, s) => {
    return { ...s, control: { tag: "Val", v: { tag: "Bool", b: args[0].tag === "Map" } } };
  }});

  // keyword?: predicate — symbol starting with ":"
  def("keyword?", { tag: "Native", name: "keyword?", arity: 1, fn: (args, s) => {
    const v = args[0];
    const isKw = v.tag === "Sym" && v.name.startsWith(":");
    return { ...s, control: { tag: "Val", v: { tag: "Bool", b: isKw } } };
  }});

  // get: Clojure-style get — works on Map, Vector, and alist
  def("get", { tag: "Native", name: "get", arity: "variadic", fn: (args, s) => {
    if (args.length < 2 || args.length > 3) throw new Error("get: expected (get coll key) or (get coll key default)");
    const coll = args[0];
    const key = args[1];
    const defaultVal: Val = args.length === 3 ? args[2] : VUnit;

    if (coll.tag === "Map") {
      for (const [k, v] of coll.entries) {
        if (valEqual(k, key)) return { ...s, control: { tag: "Val", v } };
      }
      return { ...s, control: { tag: "Val", v: defaultVal } };
    }

    if (coll.tag === "Vector" && key.tag === "Num") {
      const idx = key.n;
      if (idx >= 0 && idx < coll.items.length) {
        return { ...s, control: { tag: "Val", v: coll.items[idx] } };
      }
      return { ...s, control: { tag: "Val", v: defaultVal } };
    }

    return { ...s, control: { tag: "Val", v: defaultVal } };
  }});

  // assoc:
  //   (assoc key alist-or-map)     -> pair or #f
  //   (assoc map key val [k v]...) -> updated map
  def("assoc", { tag: "Native", name: "assoc", arity: "variadic", fn: (args, s) => {
    // Backward-compatible lookup mode used heavily by Lisp packages.
    if (args.length === 2) {
      const key = args[0];
      const coll = args[1] as any;

      if (coll.tag === "Map") {
        for (const [k, v] of coll.entries) {
          if (valEqual(k, key)) {
            return { ...s, control: { tag: "Val", v: { tag: "Vector", items: [k, v] } } };
          }
        }
        return { ...s, control: { tag: "Val", v: VFalse } };
      }

      let alist = coll;
      while (alist.tag === "Vector" && alist.items.length >= 2) {
        const pair = alist.items[0] as any;
        if (pair.tag === "Vector" && pair.items.length >= 2) {
          if (valEqual(key, pair.items[0])) {
            return { ...s, control: { tag: "Val", v: pair } };
          }
        }
        alist = alist.items[1];
      }
      return { ...s, control: { tag: "Val", v: VFalse } };
    }

    if (args.length < 3 || args.length % 2 === 0) {
      throw new Error("assoc: expected (assoc key alist) or (assoc map k v ...)");
    }
    const coll = args[0];

    if (coll.tag === "Map") {
      let entries = [...coll.entries];
      for (let i = 1; i < args.length; i += 2) {
        const key = args[i];
        const val = args[i + 1];
        const idx = entries.findIndex(([k]) => valEqual(k, key));
        if (idx >= 0) {
          entries[idx] = [key, val];
        } else {
          entries.push([key, val]);
        }
      }
      return { ...s, control: { tag: "Val", v: { tag: "Map", entries } } };
    }

    throw new Error("assoc: expected Map as first argument");
  }});

  // dissoc: return new Map without key
  def("dissoc", { tag: "Native", name: "dissoc", arity: "variadic", fn: (args, s) => {
    if (args.length < 2) throw new Error("dissoc: expected (dissoc map key ...)");
    const m = args[0];
    if (m.tag !== "Map") throw new Error("dissoc: expected Map");
    let entries = [...m.entries];
    for (let i = 1; i < args.length; i++) {
      const key = args[i];
      entries = entries.filter(([k]) => !valEqual(k, key));
    }
    return { ...s, control: { tag: "Val", v: { tag: "Map", entries } } };
  }});

  // keys: get list of keys from Map
  def("keys", { tag: "Native", name: "keys", arity: 1, fn: (args, s) => {
    const m = args[0];
    if (m.tag !== "Map") throw new Error("keys: expected Map");
    let result: Val = VUnit;
    for (let i = m.entries.length - 1; i >= 0; i--) {
      result = { tag: "Vector", items: [m.entries[i][0], result] };
    }
    return { ...s, control: { tag: "Val", v: result } };
  }});

  // vals: get list of values from Map
  def("vals", { tag: "Native", name: "vals", arity: 1, fn: (args, s) => {
    const m = args[0];
    if (m.tag !== "Map") throw new Error("vals: expected Map");
    let result: Val = VUnit;
    for (let i = m.entries.length - 1; i >= 0; i--) {
      result = { tag: "Vector", items: [m.entries[i][1], result] };
    }
    return { ...s, control: { tag: "Val", v: result } };
  }});

  // contains?: check if map has key
  def("contains?", { tag: "Native", name: "contains?", arity: 2, fn: (args, s) => {
    const m = args[0];
    const key = args[1];
    if (m.tag !== "Map") throw new Error("contains?: expected Map");
    const found = m.entries.some(([k]) => valEqual(k, key));
    return { ...s, control: { tag: "Val", v: { tag: "Bool", b: found } } };
  }});

  // merge: merge two or more maps (later keys win)
  def("merge", { tag: "Native", name: "merge", arity: "variadic", fn: (args, s) => {
    if (args.length === 0) return { ...s, control: { tag: "Val", v: { tag: "Map", entries: [] } } };
    let entries: Array<[Val, Val]> = [];
    for (const m of args) {
      if (m.tag !== "Map") throw new Error("merge: all arguments must be Maps");
      for (const [k, v] of m.entries) {
        const idx = entries.findIndex(([ek]) => valEqual(ek, k));
        if (idx >= 0) {
          entries[idx] = [k, v];
        } else {
          entries.push([k, v]);
        }
      }
    }
    return { ...s, control: { tag: "Val", v: { tag: "Map", entries } } };
  }});

  // vec: convert list to vector
  def("vec", { tag: "Native", name: "vec", arity: 1, fn: (args, s) => {
    const lst = args[0];
    const items: Val[] = [];
    let cur: Val = lst;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      items.push(cur.items[0]);
      cur = cur.items[1];
    }
    return { ...s, control: { tag: "Val", v: { tag: "Vector", items } } };
  }});

  // conj for vectors and maps
  def("conj", { tag: "Native", name: "conj", arity: "variadic", fn: (args, s) => {
    if (args.length < 2) throw new Error("conj: expected (conj coll item ...)");
    const coll = args[0];
    if (coll.tag === "Vector") {
      // Vector conj: append to end
      const items = [...coll.items];
      for (let i = 1; i < args.length; i++) items.push(args[i]);
      return { ...s, control: { tag: "Val", v: { tag: "Vector", items } } };
    }
    if (coll.tag === "Map") {
      // Map conj: add entries (each arg should be [k v] vector)
      const entries = [...coll.entries];
      for (let i = 1; i < args.length; i++) {
        const item = args[i];
        if (item.tag === "Vector" && item.items.length === 2) {
          const key = item.items[0];
          const val = item.items[1];
          const idx = entries.findIndex(([k]) => valEqual(k, key));
          if (idx >= 0) entries[idx] = [key, val];
          else entries.push([key, val]);
        }
      }
      return { ...s, control: { tag: "Val", v: { tag: "Map", entries } } };
    }
    // List conj: cons to front
    let result: Val = coll;
    for (let i = 1; i < args.length; i++) {
      result = { tag: "Vector", items: [args[i], result] } as Val;
    }
    return { ...s, control: { tag: "Val", v: result } };
  }});

  // number->string: convert number to string
  def("number->string", { tag: "Native", name: "number->string", arity: 1, fn: (args, s) => {
    const v = args[0];
    if (v.tag !== "Num") throw new Error("number->string: expected number");
    return { ...s, control: { tag: "Val", v: { tag: "Str", s: String(v.n) } } };
  }});

  // reverse: reverse a list
  def("reverse", { tag: "Native", name: "reverse", arity: 1, fn: (args, s) => {
    const items: Val[] = [];
    let cur = args[0] as any;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      items.push(cur.items[0]);
      cur = cur.items[1];
    }
    // Build reversed list
    let result: Val = VUnit;
    for (const item of items) {
      result = { tag: "Vector", items: [item, result] };
    }
    return { ...s, control: { tag: "Val", v: result } };
  }});

  // cadr, caddr, etc. (convenience accessors)
  def("cadr", { tag: "Native", name: "cadr", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    if (v.tag !== "Vector" || v.items.length < 2) throw new Error("cadr: expected pair");
    const cdr = v.items[1] as any;
    if (cdr.tag !== "Vector" || cdr.items.length < 1) throw new Error("cadr: expected pair in cdr");
    return { ...s, control: { tag: "Val", v: cdr.items[0] } };
  }});

  def("caddr", { tag: "Native", name: "caddr", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    if (v.tag !== "Vector" || v.items.length < 2) throw new Error("caddr: expected pair");
    const cdr = v.items[1] as any;
    if (cdr.tag !== "Vector" || cdr.items.length < 2) throw new Error("caddr: expected pair in cdr");
    const cddr = cdr.items[1] as any;
    if (cddr.tag !== "Vector" || cddr.items.length < 1) throw new Error("caddr: expected pair in cddr");
    return { ...s, control: { tag: "Val", v: cddr.items[0] } };
  }});

  // cddr: (cdr (cdr x))
  def("cddr", { tag: "Native", name: "cddr", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    if (v.tag !== "Vector" || v.items.length < 2) throw new Error("cddr: expected pair");
    const cdr = v.items[1] as any;
    if (cdr.tag !== "Vector" || cdr.items.length < 2) throw new Error("cddr: expected pair in cdr");
    return { ...s, control: { tag: "Val", v: cdr.items[1] } };
  }});

  // cdddr: (cdr (cdr (cdr x)))
  def("cdddr", { tag: "Native", name: "cdddr", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    if (v.tag !== "Vector" || v.items.length < 2) throw new Error("cdddr: expected pair");
    const cdr1 = v.items[1] as any;
    if (cdr1.tag !== "Vector" || cdr1.items.length < 2) throw new Error("cdddr: expected pair in cdr");
    const cdr2 = cdr1.items[1] as any;
    if (cdr2.tag !== "Vector" || cdr2.items.length < 2) throw new Error("cdddr: expected pair in cddr");
    return { ...s, control: { tag: "Val", v: cdr2.items[1] } };
  }});

  // cadddr: (car (cdr (cdr (cdr x))))
  def("cadddr", { tag: "Native", name: "cadddr", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    if (v.tag !== "Vector" || v.items.length < 2) throw new Error("cadddr: expected pair");
    const cdr1 = v.items[1] as any;
    if (cdr1.tag !== "Vector" || cdr1.items.length < 2) throw new Error("cadddr: expected pair");
    const cdr2 = cdr1.items[1] as any;
    if (cdr2.tag !== "Vector" || cdr2.items.length < 2) throw new Error("cadddr: expected pair");
    const cdr3 = cdr2.items[1] as any;
    if (cdr3.tag !== "Vector" || cdr3.items.length < 1) throw new Error("cadddr: expected pair");
    return { ...s, control: { tag: "Val", v: cdr3.items[0] } };
  }});

  // assoc: find key in association list
  // assoc (alist-style, arity 2): (assoc key alist) → pair or #f
  def("assoc-alist", { tag: "Native", name: "assoc-alist", arity: 2, fn: (args, s) => {
    const key = args[0];
    let alist = args[1] as any;

    while (alist.tag === "Vector" && alist.items.length >= 2) {
      const pair = alist.items[0] as any;
      if (pair.tag === "Vector" && pair.items.length >= 2) {
        if (valEqual(key, pair.items[0])) {
          return { ...s, control: { tag: "Val", v: pair } };
        }
      }
      alist = alist.items[1];
    }
    return { ...s, control: { tag: "Val", v: VFalse } };
  }});

  // ─────────────────────────────────────────────────────────────────
  // Hash tables (mutable)
  // ─────────────────────────────────────────────────────────────────

  // make-table: create a new empty hash table
  def("make-table", { tag: "Native", name: "make-table", arity: 0, fn: (_args, s) => {
    const table: Val = { tag: "Table", entries: new Map<string, Val>() };
    return { ...s, control: { tag: "Val", v: table } };
  }});

  // insert!: insert key-value into table (mutating)
  def("insert!", { tag: "Native", name: "insert!", arity: 3, fn: (args, s) => {
    const key = args[0];
    const value = args[1];
    const table = args[2] as any;

    if (table.tag !== "Table") {
      throw new Error("insert!: expected table as third argument");
    }

    // Convert key to string for Map storage
    let keyStr: string;
    if (key.tag === "Sym") keyStr = `sym:${key.name}`;
    else if (key.tag === "Str") keyStr = `str:${key.s}`;
    else if (key.tag === "Num") keyStr = `num:${key.n}`;
    else keyStr = JSON.stringify(key);

    table.entries.set(keyStr, value);
    return { ...s, control: { tag: "Val", v: VUnit } };
  }});

  // lookup: get value from table by key
  def("lookup", { tag: "Native", name: "lookup", arity: 2, fn: (args, s) => {
    const key = args[0];
    const table = args[1] as any;

    if (table.tag !== "Table") {
      throw new Error("lookup: expected table as second argument");
    }

    let keyStr: string;
    if (key.tag === "Sym") keyStr = `sym:${key.name}`;
    else if (key.tag === "Str") keyStr = `str:${key.s}`;
    else if (key.tag === "Num") keyStr = `num:${key.n}`;
    else keyStr = JSON.stringify(key);

    const val = table.entries.get(keyStr);
    return { ...s, control: { tag: "Val", v: val ?? VFalse } };
  }});

  // table->list: convert table to association list
  def("table->list", { tag: "Native", name: "table->list", arity: 1, fn: (args, s) => {
    const table = args[0] as any;

    if (table.tag !== "Table") {
      throw new Error("table->list: expected table");
    }

    // Build list of (key . value) pairs
    const pairs: Val[] = [];
    for (const [keyStr, value] of table.entries) {
      // Parse key back from string
      let key: Val;
      if (keyStr.startsWith("sym:")) {
        key = { tag: "Sym", name: keyStr.slice(4) };
      } else if (keyStr.startsWith("str:")) {
        key = { tag: "Str", s: keyStr.slice(4) };
      } else if (keyStr.startsWith("num:")) {
        key = { tag: "Num", n: Number(keyStr.slice(4)) };
      } else {
        key = { tag: "Str", s: keyStr };
      }
      pairs.push({ tag: "Vector", items: [key, value] });
    }

    // Build proper list from pairs
    let result: Val = VUnit;
    for (let i = pairs.length - 1; i >= 0; i--) {
      result = { tag: "Vector", items: [pairs[i], result] };
    }
    return { ...s, control: { tag: "Val", v: result } };
  }});

  // equal?: deep structural equality
  def("equal?", { tag: "Native", name: "equal?", arity: 2, fn: (args, s) => {
    function deepEq(a: Val, b: Val): boolean {
      if (a.tag !== b.tag) return false;
      switch (a.tag) {
        case "Unit": return true;
        case "Num": return (b as any).n === a.n;
        case "Bool": return (b as any).b === a.b;
        case "Str": return (b as any).s === a.s;
        case "Sym": return (b as any).name === a.name;
        case "Vector": {
          const bb = b as any;
          if (bb.items.length !== a.items.length) return false;
          for (let i = 0; i < a.items.length; i++) {
            if (!deepEq(a.items[i], bb.items[i])) return false;
          }
          return true;
        }
        default:
          return JSON.stringify(a) === JSON.stringify(b);
      }
    }
    return { ...s, control: { tag: "Val", v: deepEq(args[0], args[1]) ? VTrue : VFalse } };
  }});

  // ─────────────────────────────────────────────────────────────────
  // Higher-Order Function combinators (HOF)
  // These accept Native | Closure | OracleProc as arguments
  // ─────────────────────────────────────────────────────────────────

  // Helper to apply a procedure (Native only for now, Closure/OracleProc need machine stepping)
  function applyNative(proc: Val, procArgs: Val[], state: any): Val | null {
    if (proc.tag === "Native") {
      const result = (proc as any).fn(procArgs, state);
      if (result.control?.tag === "Val") {
        return result.control.v;
      }
    }
    return null;
  }

  // map: apply f to each element of list
  def("map", { tag: "Native", name: "map", arity: 2, fn: (args, s) => {
    const f = args[0];
    const lst = args[1] as any;

    // Collect list elements
    const items: Val[] = [];
    let cur = lst;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      items.push(cur.items[0]);
      cur = cur.items[1];
    }

    // Empty list case
    if (items.length === 0) {
      return { ...s, control: { tag: "Val", v: VUnit } };
    }

    // For Native functions, we can apply directly
    if (f.tag === "Native") {
      const results: Val[] = [];
      for (const item of items) {
        const result = applyNative(f, [item], s);
        if (result === null) {
          throw new Error("map: Native function did not return a value");
        }
        results.push(result);
      }
      let resultList: Val = VUnit;
      for (let i = results.length - 1; i >= 0; i--) {
        resultList = { tag: "Vector", items: [results[i], resultList] };
      }
      return { ...s, control: { tag: "Val", v: resultList } };
    }

    // For Closures, use continuation frames
    if (f.tag === "Closure") {
      const [first, ...rest] = items;
      // Push frame to collect results
      const frame: Frame = { tag: "KMapRest", fn: f, remaining: rest, acc: [], env: s.env };
      const kont = [...s.kont, frame];
      // Apply f to first element
      return applyProcedure(f, [first], { ...s, kont });
    }

    throw new Error("map: expected procedure");
  }});

  // filter: keep elements where predicate returns true
  def("filter", { tag: "Native", name: "filter", arity: 2, fn: (args, s) => {
    const pred = args[0];
    const lst = args[1] as any;

    // Collect list elements
    const items: Val[] = [];
    let cur = lst;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      items.push(cur.items[0]);
      cur = cur.items[1];
    }

    // Empty list case
    if (items.length === 0) {
      return { ...s, control: { tag: "Val", v: VUnit } };
    }

    // For Native functions, we can apply directly
    if (pred.tag === "Native") {
      const results: Val[] = [];
      for (const item of items) {
        const result = applyNative(pred, [item], s);
        if (result === null) {
          throw new Error("filter: Native function did not return a value");
        }
        // Check if truthy (Bool true, or non-false value)
        if (result.tag === "Bool" && result.b) {
          results.push(item);
        } else if (result.tag !== "Bool" && result.tag !== "Unit") {
          results.push(item);
        }
      }
      let resultList: Val = VUnit;
      for (let i = results.length - 1; i >= 0; i--) {
        resultList = { tag: "Vector", items: [results[i], resultList] };
      }
      return { ...s, control: { tag: "Val", v: resultList } };
    }

    // For Closures, use continuation frames
    if (pred.tag === "Closure") {
      const [first, ...rest] = items;
      // Push frame to collect results
      const frame: Frame = { tag: "KFilterRest", fn: pred, remaining: rest, currentItem: first, acc: [], env: s.env };
      const kont = [...s.kont, frame];
      // Apply pred to first element
      return applyProcedure(pred, [first], { ...s, kont });
    }

    throw new Error("filter: expected procedure");
  }});

  // fold (foldl): left fold - (fold f init xs) applies (f acc x) for each x
  def("fold", { tag: "Native", name: "fold", arity: 3, fn: (args, s) => {
    const f = args[0];
    let acc = args[1];
    const lst = args[2] as any;

    // Walk the list
    let cur = lst;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      const item = cur.items[0];
      const result = applyNative(f, [acc, item], s);
      if (result === null) {
        throw new Error("fold: only Native functions supported in primitive; use recursive fold for Closure/OracleProc");
      }
      acc = result;
      cur = cur.items[1];
    }

    return { ...s, control: { tag: "Val", v: acc } };
  }});

  // fold-left: left fold with closure support (Scheme R6RS name)
  def("fold-left", { tag: "Native", name: "fold-left", arity: 3, fn: (args, s) => {
    const f = args[0];
    const init = args[1];
    const lst = args[2] as any;

    // Collect list elements
    const items: Val[] = [];
    let cur = lst;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      items.push(cur.items[0]);
      cur = cur.items[1];
    }

    // Empty list case
    if (items.length === 0) {
      return { ...s, control: { tag: "Val", v: init } };
    }

    // For Native functions, apply directly
    if (f.tag === "Native") {
      let acc = init;
      for (const item of items) {
        const result = applyNative(f, [acc, item], s);
        if (result === null) {
          throw new Error("fold-left: Native function did not return a value");
        }
        acc = result;
      }
      return { ...s, control: { tag: "Val", v: acc } };
    }

    // For Closures, use continuation frames
    if (f.tag === "Closure") {
      const [first, ...rest] = items;
      const frame: Frame = { tag: "KFoldRest", fn: f, remaining: rest, env: s.env };
      const kont = [...s.kont, frame];
      return applyProcedure(f, [init, first], { ...s, kont });
    }

    throw new Error("fold-left: expected procedure");
  }});

  // foldr: right fold - (foldr f init xs) applies (f x acc) from right to left
  def("foldr", { tag: "Native", name: "foldr", arity: 3, fn: (args, s) => {
    const f = args[0];
    const init = args[1];
    const lst = args[2] as any;

    // Collect elements
    const items: Val[] = [];
    let cur = lst;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      items.push(cur.items[0]);
      cur = cur.items[1];
    }

    // Fold from right
    let acc = init;
    for (let i = items.length - 1; i >= 0; i--) {
      const result = applyNative(f, [items[i], acc], s);
      if (result === null) {
        throw new Error("foldr: only Native functions supported in primitive; use recursive foldr for Closure/OracleProc");
      }
      acc = result;
    }

    return { ...s, control: { tag: "Val", v: acc } };
  }});

  // for-each: apply f to each element for side effects, return unit
  def("for-each", { tag: "Native", name: "for-each", arity: 2, fn: (args, s) => {
    const f = args[0];
    const lst = args[1] as any;

    // Collect list elements
    const items: Val[] = [];
    let cur = lst;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      items.push(cur.items[0]);
      cur = cur.items[1];
    }

    // Empty list case
    if (items.length === 0) {
      return { ...s, control: { tag: "Val", v: VUnit } };
    }

    // For Native functions, apply directly
    if (f.tag === "Native") {
      for (const item of items) {
        applyNative(f, [item], s);
      }
      return { ...s, control: { tag: "Val", v: VUnit } };
    }

    // For Closures, use KForEachRest continuation frames (same pattern as map)
    if (f.tag === "Closure") {
      const [first, ...rest] = items;
      const frame: Frame = { tag: "KForEachRest", fn: f, remaining: rest, env: s.env };
      const kont = [...s.kont, frame];
      return applyProcedure(f, [first], { ...s, kont });
    }

    throw new Error("for-each: expected procedure");
  }});

  // compose: (compose f g) => (lambda (x) (f (g x)))
  // Returns a new Native that composes f and g
  def("compose", { tag: "Native", name: "compose", arity: 2, fn: (args, s) => {
    const f = args[0];
    const g = args[1];

    const composed: Val = {
      tag: "Native",
      name: "composed",
      arity: 1,
      fn: (innerArgs: Val[], innerS: any) => {
        // Apply g first
        const gResult = applyNative(g, innerArgs, innerS);
        if (gResult === null) {
          throw new Error("compose: inner function (g) must be Native");
        }
        // Then apply f
        const fResult = applyNative(f, [gResult], innerS);
        if (fResult === null) {
          throw new Error("compose: outer function (f) must be Native");
        }
        return { ...innerS, control: { tag: "Val", v: fResult } };
      }
    };

    return { ...s, control: { tag: "Val", v: composed } };
  }});

  // pipe: (pipe f g) => (lambda (x) (g (f x)))
  // Opposite of compose - f first, then g
  def("pipe", { tag: "Native", name: "pipe", arity: 2, fn: (args, s) => {
    const f = args[0];
    const g = args[1];

    const piped: Val = {
      tag: "Native",
      name: "piped",
      arity: 1,
      fn: (innerArgs: Val[], innerS: any) => {
        // Apply f first
        const fResult = applyNative(f, innerArgs, innerS);
        if (fResult === null) {
          throw new Error("pipe: first function (f) must be Native");
        }
        // Then apply g
        const gResult = applyNative(g, [fResult], innerS);
        if (gResult === null) {
          throw new Error("pipe: second function (g) must be Native");
        }
        return { ...innerS, control: { tag: "Val", v: gResult } };
      }
    };

    return { ...s, control: { tag: "Val", v: piped } };
  }});

  // partial: (partial f a) => (lambda (b ...) (f a b ...))
  // Partially apply first argument
  def("partial", { tag: "Native", name: "partial", arity: 2, fn: (args, s) => {
    const f = args[0];
    const bound = args[1];

    const partialFn: Val = {
      tag: "Native",
      name: "partial",
      arity: "variadic",
      fn: (innerArgs: Val[], innerS: any) => {
        // Prepend bound arg
        const allArgs = [bound, ...innerArgs];
        const result = applyNative(f, allArgs, innerS);
        if (result === null) {
          throw new Error("partial: function must be Native");
        }
        return { ...innerS, control: { tag: "Val", v: result } };
      }
    };

    return { ...s, control: { tag: "Val", v: partialFn } };
  }});

  // apply: (apply f args-list) - call procedure with argument list
  // This is essential for metacircular evaluators
  def("apply", { tag: "Native", name: "apply", arity: 2, fn: (args, s) => {
    const f = args[0];
    const argList = args[1] as any;

    // Convert list to array
    const procArgs: Val[] = [];
    let cur = argList;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      procArgs.push(cur.items[0]);
      cur = cur.items[1];
    }

    if (f.tag === "Continuation") {
      if (procArgs.length !== 1) {
        throw new Error(`apply: continuation expects 1 argument, got ${procArgs.length}`);
      }
      const cont = f as any;
      return {
        ...s,
        control: { tag: "Val", v: procArgs[0] },
        env: cont.env,
        // Preserve current store to keep mutations performed before invoking continuation.
        store: s.store,
        kont: cont.kont.slice(),
        handlers: cont.handlers.slice(),
      };
    }

    // Apply based on procedure type
    if (f.tag === "Native") {
      const result = applyNative(f, procArgs, s);
      if (result === null) {
        throw new Error("apply: Native function did not return a value");
      }
      return { ...s, control: { tag: "Val", v: result } };
    }

    if (f.tag === "Closure") {
      // For Closure, we need to set up the machine state for application
      const closure = f as any;
      // Create a new environment with params bound to args
      const frame = new Map<string, number>();
      let currentStore = s.store;
      for (let i = 0; i < closure.params.length && i < procArgs.length; i++) {
        // alloc returns [Store, Addr] tuple - pass value to allocate
        const [newStore, addr] = currentStore.alloc(procArgs[i]);
        currentStore = newStore;
        frame.set(closure.params[i], addr);
      }
      const envWithArgs = { tag: "Ctx", frame, parent: closure.env };
      // Evaluate the body in the new environment
      return { ...s, store: currentStore, control: { tag: "Expr", e: closure.body }, env: envWithArgs };
    }

    if (f.tag === "OracleProc") {
      // OracleProc application would need effect handling - not directly available here
      throw new Error("apply: OracleProc requires effect handling; use host evaluator");
    }

    throw new Error(`apply: expected procedure, got ${f.tag}`);
  }});

  // identity: (identity x) => x
  def("identity", { tag: "Native", name: "identity", arity: 1, fn: (args, s) => {
    return { ...s, control: { tag: "Val", v: args[0] } };
  }});

  // constantly: (constantly x) => (lambda (y) x)
  def("constantly", { tag: "Native", name: "constantly", arity: 1, fn: (args, s) => {
    const x = args[0];
    const constFn: Val = {
      tag: "Native",
      name: "const",
      arity: 1,
      fn: (_innerArgs: Val[], innerS: any) => {
        return { ...innerS, control: { tag: "Val", v: x } };
      }
    };
    return { ...s, control: { tag: "Val", v: constFn } };
  }});

  // andmap: like all - returns true if predicate is true for all elements
  def("andmap", { tag: "Native", name: "andmap", arity: 2, fn: (args, s) => {
    const pred = args[0];
    const lst = args[1] as any;

    let cur = lst;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      const item = cur.items[0];
      const result = applyNative(pred, [item], s);
      if (result === null) {
        throw new Error("andmap: only Native functions supported");
      }
      if (result.tag === "Bool" && !result.b) {
        return { ...s, control: { tag: "Val", v: VFalse } };
      }
      cur = cur.items[1];
    }
    return { ...s, control: { tag: "Val", v: VTrue } };
  }});

  // ormap: like any - returns true if predicate is true for any element
  def("ormap", { tag: "Native", name: "ormap", arity: 2, fn: (args, s) => {
    const pred = args[0];
    const lst = args[1] as any;

    let cur = lst;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      const item = cur.items[0];
      const result = applyNative(pred, [item], s);
      if (result === null) {
        throw new Error("ormap: only Native functions supported");
      }
      if (result.tag === "Bool" && result.b) {
        return { ...s, control: { tag: "Val", v: VTrue } };
      }
      cur = cur.items[1];
    }
    return { ...s, control: { tag: "Val", v: VFalse } };
  }});

  // ─────────────────────────────────────────────────────────────────
  // Promises (delay/force with memoization)
  // For lazy evaluation and streams
  // ─────────────────────────────────────────────────────────────────

  // make-promise: create a memoizing thunk from a procedure
  // The Promise value stores: {tag: "Promise", thunk, forced: bool, value?: Val}
  def("make-promise", { tag: "Native", name: "make-promise", arity: 1, fn: (args, s) => {
    const thunk = args[0];
    if (thunk.tag !== "Closure" && thunk.tag !== "Native") {
      throw new Error("make-promise: expected procedure");
    }
    const promise: Val = {
      tag: "Promise",
      thunk,
      forced: false,
      value: undefined,
    };
    return { ...s, control: { tag: "Val", v: promise } };
  }});

  // force: evaluate a promise (memoized)
  // For Closure thunks, we return a state that evaluates the closure body.
  // The result will flow to the continuation; memoization happens via KMemoize frame.
  def("force", { tag: "Native", name: "force", arity: 1, fn: (args, s) => {
    const p = args[0] as any;
    if (p.tag !== "Promise") {
      // If not a promise, return as-is (idempotent)
      return { ...s, control: { tag: "Val", v: args[0] } };
    }
    if (p.forced) {
      // Already forced, return cached value
      return { ...s, control: { tag: "Val", v: p.value } };
    }
    // Need to force: call the thunk
    const thunk = p.thunk;
    if (thunk.tag === "Native") {
      const result = (thunk as any).fn([], s);
      if (result.control?.tag === "Val") {
        // Memoize the result
        p.forced = true;
        p.value = result.control.v;
        return result;
      }
    }
    // For Closure thunks, inline the body evaluation
    if (thunk.tag === "Closure") {
      // Add a KMemoize frame to cache the result when body evaluates
      // For now, use a simpler approach: wrap in a continuation that caches
      const memoizeFrame: Frame = {
        tag: "KCall",
        savedEnv: s.env,
        // We hack in memoization by also storing the promise reference
        // Actually, we can use a simpler trick: evaluate body, and the
        // Promise is mutated in-place after first force completes.
        // For simplicity, just evaluate without memoization for now.
      };

      // Return state that evaluates closure body with closure env
      return {
        ...s,
        control: { tag: "Expr", e: thunk.body },
        env: thunk.env,
        kont: [...s.kont, memoizeFrame],
      };
    }
    throw new Error(`force: unsupported thunk type ${thunk.tag}`);
  }});

  // promise?: check if value is a promise
  def("promise?", { tag: "Native", name: "promise?", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    return { ...s, control: { tag: "Val", v: v.tag === "Promise" ? VTrue : VFalse } };
  }});

  // promise-forced?: check if promise has been forced
  def("promise-forced?", { tag: "Native", name: "promise-forced?", arity: 1, fn: (args, s) => {
    const p = args[0] as any;
    if (p.tag !== "Promise") return { ...s, control: { tag: "Val", v: VFalse } };
    return { ...s, control: { tag: "Val", v: p.forced ? VTrue : VFalse } };
  }});

  // ─────────────────────────────────────────────────────────────────
  // Stream primitives
  // Streams are pairs where cdr is a promise
  // ─────────────────────────────────────────────────────────────────

  const isStreamEmptyVal = (v: Val): boolean => v.tag === "Unit" || (v.tag === "Vector" && v.items.length === 0);
  const isStreamVal = (v: Val): boolean => isStreamEmptyVal(v) || (v.tag === "Vector" && v.items.length >= 2);

  function forceStreamTailOutcome(tail: any, state: State): StepOutcome {
    if (tail.tag !== "Promise") {
      return { tag: "State", state: { ...state, control: { tag: "Val", v: tail } } };
    }
    if (tail.forced) {
      return { tag: "State", state: { ...state, control: { tag: "Val", v: tail.value } } };
    }
    const thunk = tail.thunk;
    if (thunk.tag === "Native") {
      const result = (thunk as any).fn([], state);
      if ((result as any).control?.tag === "Val") {
        tail.forced = true;
        tail.value = (result as any).control.v;
        return { tag: "State", state: { ...state, control: { tag: "Val", v: tail.value } } };
      }
      return result as StepOutcome;
    }
    if (thunk.tag === "Closure") {
      const memoizeFrame: Frame = { tag: "KCall", savedEnv: state.env };
      return {
        tag: "State",
        state: {
          ...state,
          control: { tag: "Expr", e: thunk.body },
          env: thunk.env,
          kont: [...state.kont, memoizeFrame],
        },
      };
    }
    throw new Error(`stream: unsupported thunk type ${(thunk as any).tag}`);
  }

  // the-empty-stream: sentinel for empty stream
  def("the-empty-stream", VUnit);

  // stream-cons: create a stream pair (head, lazy tail)
  // (stream-cons head tail-thunk) - tail-thunk is a zero-arg lambda
  def("stream-cons", { tag: "Native", name: "stream-cons", arity: 2, fn: (args, s) => {
    const head = args[0];
    const tailThunk = args[1];
    // Create a promise that wraps the thunk (proper PromiseVal structure)
    const promise: Val = {
      tag: "Promise",
      thunk: tailThunk,
      forced: false,
      value: undefined,
    };
    // Return a cons cell (vector) with head and promised tail
    const streamPair: Val = { tag: "Vector", items: [head, promise] };
    return { ...s, control: { tag: "Val", v: streamPair } };
  }});

  // display: print a value to stdout (side effect, returns unit)
  // Helper to pretty-print values
  function prettyPrint(v: any): string {
    if (v.tag === "Str") return `"${v.s}"`;
    if (v.tag === "Num") return String(v.n);
    if (v.tag === "Bool") return v.b ? "#t" : "#f";
    if (v.tag === "Sym") return v.name;
    if (v.tag === "Unit") return "()";
    if (v.tag === "Vector" && v.items.length === 2) {
      // Check if it's a proper list
      const items: string[] = [];
      let cur = v;
      while (cur.tag === "Vector" && cur.items.length === 2) {
        items.push(prettyPrint(cur.items[0]));
        cur = cur.items[1];
      }
      if (cur.tag === "Unit") {
        return `(${items.join(" ")})`;
      }
      // Dotted pair
      return `(${items.join(" ")} . ${prettyPrint(cur)})`;
    }
    if (v.tag === "Closure") return `<closure>`;
    if (v.tag === "Native") return `<native:${v.name}>`;
    return JSON.stringify(v);
  }

  // display: print a value to stdout (side effect, returns unit)
  def("display", { tag: "Native", name: "display", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    let output = "";
    if (v.tag === "Str") {
      output = v.s;  // Strings without quotes for display
    } else {
      output = prettyPrint(v);
    }
    process.stdout.write(output);
    return { ...s, control: { tag: "Val", v: VUnit } };
  }});

  // newline: print a newline (convenience for display)
  def("newline", { tag: "Native", name: "newline", arity: 0, fn: (args, s) => {
    process.stdout.write("\n");
    return { ...s, control: { tag: "Val", v: VUnit } };
  }});

  // stream-null?: check if stream is empty
  def("stream-null?", { tag: "Native", name: "stream-null?", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    const isEmpty = v.tag === "Unit" || (v.tag === "Vector" && v.items.length === 0);
    return { ...s, control: { tag: "Val", v: isEmpty ? VTrue : VFalse } };
  }});

  // stream-car: get first element (same as car)
  def("stream-car", { tag: "Native", name: "stream-car", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    if (v.tag === "Vector" && v.items.length >= 1) {
      return { ...s, control: { tag: "Val", v: v.items[0] } };
    }
    throw new Error("stream-car: expected stream pair");
  }});

  // stream-cdr: force the tail and return it
  def("stream-cdr", { tag: "Native", name: "stream-cdr", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    if (v.tag !== "Vector" || v.items.length < 2) {
      throw new Error("stream-cdr: expected stream pair");
    }
    const tail = v.items[1] as any;
    // Force the promise
    if (tail.tag === "Promise") {
      if (tail.forced) {
        return { ...s, control: { tag: "Val", v: tail.value } };
      }
      const thunk = tail.thunk;
      // Force native thunk
      if (thunk.tag === "Native") {
        const result = (thunk as any).fn([], s);
        if ((result as StepOutcome).tag) {
          return result as StepOutcome;
        }
        if (result.control?.tag === "Val") {
          tail.forced = true;
          tail.value = result.control.v;
          return { ...s, control: { tag: "Val", v: tail.value } };
        }
      }
      // For Closure thunks, inline the body evaluation
      if (thunk.tag === "Closure") {
        const memoizeFrame: Frame = { tag: "KCall", savedEnv: s.env };
        return {
          ...s,
          control: { tag: "Expr", e: thunk.body },
          env: thunk.env,
          kont: [...s.kont, memoizeFrame],
        };
      }
      throw new Error(`stream-cdr: unsupported thunk type ${thunk.tag}`);
    }
    // If not a promise, return as-is
    return { ...s, control: { tag: "Val", v: tail } };
  }});

  // list->stream: convert list to stream
  def("list->stream", { tag: "Native", name: "list->stream", arity: 1, fn: (args, s) => {
    // Already a list (cons cells), just need to wrap tails as promises
    // For simplicity, return the list directly - streams and lists share structure
    // The difference is conceptual: stream-cdr forces, cdr doesn't
    return { ...s, control: { tag: "Val", v: args[0] } };
  }});

  // stream->list: convert stream to list (eager, forces all)
  // (stream->list stream n) - take n elements from stream as a list
  // Supports StreamMapThunk for closure-based stream-map
  def("stream->list", { tag: "Native", name: "stream->list", arity: 2, fn: (args, s) => {
    let stream = args[0] as any;
    const n = (args[1] as any).n;
    const items: Val[] = [];

    for (let i = 0; i < n; i++) {
      // Check if empty
      if (stream.tag === "Unit" || (stream.tag === "Vector" && stream.items.length === 0)) {
        break;
      }
      if (stream.tag !== "Vector" || stream.items.length < 2) {
        break;
      }
      // Get head
      items.push(stream.items[0]);
      // Force and get tail
      const tail = stream.items[1] as any;
      if (tail.tag === "Promise") {
        if (tail.forced) {
          stream = tail.value;
        } else if (tail.thunk.tag === "Native") {
          const result = (tail.thunk as any).fn([], s);
          if (result.control?.tag === "Val") {
            tail.forced = true;
            tail.value = result.control.v;
            stream = tail.value;
          } else {
            break;
          }
        } else if (tail.thunk.tag === "Closure") {
          // Closure thunk - need to evaluate asynchronously via continuation frames
          const remaining = n - i - 1;
          const thunk = tail.thunk;
          // Create frame to continue collecting stream elements after forcing
          // Pass the promise reference so it can be memoized after forcing
          const frame: Frame = { tag: "KStreamToListRest", remaining, acc: items, env: s.env, pendingPromise: tail };
          const kont = [...s.kont, frame];
          // Evaluate closure body in closure env
          return {
            ...s,
            control: { tag: "Expr", e: thunk.body },
            env: thunk.env,
            kont,
          };
        } else if ((tail.thunk as any).tag === "StreamMapThunk") {
          // StreamMapThunk: need to force underlying stream and apply closure
          const smThunk = tail.thunk as any;
          const fn = smThunk.fn;
          let underlyingTail = smThunk.streamTail as any;

          // Force the underlying stream tail if needed
          if (underlyingTail.tag === "Promise") {
            if (underlyingTail.forced) {
              underlyingTail = underlyingTail.value;
            } else if (underlyingTail.thunk.tag === "Native") {
              const r = (underlyingTail.thunk as any).fn([], s);
              if (r.control?.tag === "Val") {
                underlyingTail.forced = true;
                underlyingTail.value = r.control.v;
                underlyingTail = underlyingTail.value;
              } else {
                break;
              }
            } else {
              // Nested StreamMapThunk - need continuation frames
              const remaining = n - i - 1;
              const frame: Frame = { tag: "KStreamToListRest", remaining, acc: items, env: s.env };
              const kont = [...s.kont, frame];
              // Return state that will force the nested thunk
              return { ...s, kont, control: { tag: "Val", v: tail } };
            }
          }

          // Check if underlying stream is empty
          if (underlyingTail.tag === "Unit") {
            tail.forced = true;
            tail.value = VUnit;
            break;
          }
          if (underlyingTail.tag !== "Vector" || underlyingTail.items.length < 2) {
            tail.forced = true;
            tail.value = VUnit;
            break;
          }

          // Apply fn to head of underlying tail
          const nextHead = underlyingTail.items[0];
          const nextTail = underlyingTail.items[1];

          if (fn.tag === "Native") {
            // Native function - apply directly
            const mappedHead = applyNative(fn, [nextHead], s);
            if (mappedHead === null) {
              break;
            }
            // Build next stream cell
            const nextThunk: Val = {
              tag: "StreamMapThunk",
              fn: fn,
              streamTail: nextTail,
            } as any;
            const nextPromise: Val = { tag: "Promise", thunk: nextThunk, forced: false, value: undefined } as any;
            const nextStream: Val = { tag: "Vector", items: [mappedHead, nextPromise] };
            tail.forced = true;
            tail.value = nextStream;
            stream = nextStream;
          } else if (fn.tag === "Closure") {
            // Closure - need continuation frames
            const remaining = n - i - 1;
            const contFrame: Frame = { tag: "KStreamToListRest", remaining, acc: items, env: s.env };
            const buildFrame: Frame = { tag: "KStreamMapHead", fn, streamTail: nextTail, env: s.env };
            const kont = [...s.kont, contFrame, buildFrame];
            // Apply closure to nextHead
            return applyProcedure(fn, [nextHead], { ...s, kont });
          } else {
            break;
          }
        } else {
          throw new Error("stream->list: unsupported thunk type");
        }
      } else {
        stream = tail;
      }
    }

    // Build result list
    let result: Val = VUnit;
    for (let i = items.length - 1; i >= 0; i--) {
      result = { tag: "Vector", items: [items[i], result] };
    }
    return { ...s, control: { tag: "Val", v: result } };
  }});

  // stream-take: take n elements from stream, returns a new stream
  // (stream-take stream n) - returns stream of first n elements
  def("stream-take", { tag: "Native", name: "stream-take", arity: 2, fn: (args, s) => {
    const stream = args[0];
    const n = (args[1] as any).n;
    // Reuse stream->list logic
    const items: Val[] = [];
    let cur = stream as any;

    for (let i = 0; i < n; i++) {
      if (cur.tag === "Unit") break;
      if (cur.tag !== "Vector" || cur.items.length < 2) break;
      items.push(cur.items[0]);
      const tail = cur.items[1] as any;
      if (tail.tag === "Promise") {
        if (tail.forced) {
          cur = tail.value;
        } else if (tail.thunk.tag === "Native") {
          const result = (tail.thunk as any).fn([], s);
          if (result.control?.tag === "Val") {
            tail.forced = true;
            tail.value = result.control.v;
            cur = tail.value;
          } else break;
        } else {
          throw new Error("stream-take: Closure thunks require machine-level handling");
        }
      } else {
        cur = tail;
      }
    }

    let result: Val = VUnit;
    for (let i = items.length - 1; i >= 0; i--) {
      result = { tag: "Vector", items: [items[i], result] };
    }
    return { ...s, control: { tag: "Val", v: result } };
  }});

  const isEmptyStream = (v: Val): boolean => v.tag === "Unit" || (v.tag === "Vector" && v.items.length === 0);
  const isStreamLike = (v: Val): v is Val & { tag: "Vector"; items: Val[] } =>
    isEmptyStream(v) || (v.tag === "Vector" && v.items.length >= 2);

  const buildInterleave = (a: Val, b: Val, state: State): { v: Val; store: Store } => {
    if (!isStreamLike(a) || !isStreamLike(b)) {
      const tags = `(${(a as any)?.tag ?? "unknown"}, ${(b as any)?.tag ?? "unknown"})`;
      throw new Error(`stream-interleave: expected streams ${tags}`);
    }
    if (isEmptyStream(a)) {
      return { v: b, store: state.store };
    }

    const pair = a as any;
    const head = pair.items[0] as Val;

    // Capture s1/s2 in a closure thunk so existing promise forcing logic can run it later.
    let store = state.store;
    const [sAfterS1, addrS1] = store.alloc(a);
    store = sAfterS1;
    const [sAfterS2, addrS2] = store.alloc(b);
    store = sAfterS2;
    let envLocal = state.env;
    envLocal = envSet(envLocal, "s1", addrS1);
    envLocal = envSet(envLocal, "s2", addrS2);

    const tailExpr: Expr = {
      tag: "App",
      fn: { tag: "Var", name: "stream-interleave" },
      args: [
        { tag: "Var", name: "s2" },
        { tag: "App", fn: { tag: "Var", name: "stream-cdr" }, args: [{ tag: "Var", name: "s1" }] },
      ],
    };

    const thunk: Val = { tag: "Closure", params: [], body: tailExpr, env: envLocal };
    const promise: Val = { tag: "Promise", thunk, forced: false, value: undefined } as any;
    const stream: Val = { tag: "Vector", items: [head, promise] };
    return { v: stream, store };
  };

  // stream-interleave: fairly merge two streams
  def("stream-interleave", { tag: "Native", name: "stream-interleave", arity: 2, fn: (args, s) => {
    const [s1, s2] = args;
    const { v, store } = buildInterleave(s1, s2, s);
    return { ...s, store, control: { tag: "Val", v } };
  }});

  // stream-interleave-lazy: second argument is a thunk returning a stream
  def("stream-interleave-lazy", { tag: "Native", name: "stream-interleave-lazy", arity: 2, lazyArgs: [1], fn: (args, s) => {
    const [s1, thunkVal] = args;
    if (thunkVal.tag !== "Native" && thunkVal.tag !== "Closure") {
      throw new Error("stream-interleave-lazy: expected thunk returning stream");
    }

    const applied = applyProcedure(thunkVal as any, [], s);
    if ((applied as any).control?.tag !== "Val") {
      throw new Error("stream-interleave-lazy: thunk did not return stream");
    }
    const streamVal = (applied as any).control.v as Val;
    const { v, store } = buildInterleave(s1, streamVal, (applied as any).store ? (applied as State) : s);
    const nextState = (applied as any).store ? (applied as State) : s;
    return { ...nextState, store, control: { tag: "Val", v } };
  }});

  // stream-map: apply f to each element of stream (lazy)
  // Supports both Native functions and Closures
  def("stream-map", { tag: "Native", name: "stream-map", arity: 2, fn: (args, s) => {
    const f = args[0];
    const stream = args[1] as any;

    if (stream.tag === "Unit") {
      return { ...s, control: { tag: "Val", v: VUnit } };
    }
    if (stream.tag !== "Vector" || stream.items.length < 2) {
      return { ...s, control: { tag: "Val", v: VUnit } };
    }

    const head = stream.items[0];
    const streamTail = stream.items[1];

    // For Native functions, apply directly
    if (f.tag === "Native") {
      const mappedHead = applyNative(f, [head], s);
      if (mappedHead === null) {
        throw new Error("stream-map: Native function returned null");
      }
      // Create lazy tail thunk (for Native, use simple thunk)
      const tailThunk: Val = {
        tag: "StreamMapThunk",
        fn: f,
        streamTail: streamTail,
      } as any;
      const promise: Val = { tag: "Promise", thunk: tailThunk, forced: false, value: undefined } as any;
      const result: Val = { tag: "Vector", items: [mappedHead, promise] };
      return { ...s, control: { tag: "Val", v: result } };
    }

    // For Closures, use continuation frame
    if (f.tag === "Closure") {
      // Push frame to build stream after head is mapped
      const frame: Frame = {
        tag: "KStreamMapHead",
        fn: f,
        streamTail: streamTail,
        env: s.env,
      };
      const kont = [...s.kont, frame];
      // Apply closure to head
      return applyProcedure(f, [head], { ...s, kont });
    }

    throw new Error("stream-map: expected procedure");
  }});

  // stream-filter: filter stream by predicate (lazy)
  def("stream-filter", { tag: "Native", name: "stream-filter", arity: 2, fn: (args, s) => {
    const pred = args[0];
    const stream = args[1];

    function streamFilterRec(p: Val, str: Val, st: any): any {
      if ((str as any).tag === "Unit") {
        return { ...st, control: { tag: "Val", v: VUnit } };
      }
      const strAny = str as any;
      if (strAny.tag !== "Vector" || strAny.items.length < 2) {
        return { ...st, control: { tag: "Val", v: VUnit } };
      }

      const head = strAny.items[0];
      const testResult = applyNative(p, [head], st);
      if (testResult === null) {
        throw new Error("stream-filter: only Native functions supported");
      }

      // Force tail
      const tail = strAny.items[1] as any;
      let forcedTail: Val;
      if (tail.tag === "Promise") {
        if (tail.forced) {
          forcedTail = tail.value;
        } else if (tail.thunk.tag === "Native") {
          const r = (tail.thunk as any).fn([], st);
          if (r.control?.tag === "Val") {
            tail.forced = true;
            tail.value = r.control.v;
            forcedTail = tail.value;
          } else {
            throw new Error("stream-filter: thunk error");
          }
        } else {
          throw new Error("stream-filter: Closure thunks in stream tail");
        }
      } else {
        forcedTail = tail;
      }

      const keep = testResult.tag === "Bool" && testResult.b;
      if (keep) {
        // Include this element, create lazy filtered tail
        const thunk: Val = {
          tag: "Native",
          name: "filter-thunk",
          arity: 0,
          fn: (_: Val[], is: any) => streamFilterRec(p, forcedTail, is)
        } as any;
        const promise: Val = { tag: "Promise", thunk, forced: false, value: undefined } as any;
        const result: Val = { tag: "Vector", items: [head, promise] };
        return { ...st, control: { tag: "Val", v: result } };
      } else {
        // Skip this element, recurse on tail
        return streamFilterRec(p, forcedTail, st);
      }
    }

    return streamFilterRec(pred, stream, s);
  }});

  // ─────────────────────────────────────────────────────────────────
  // B1: Op-Table Primitives (SICP-style data-directed programming)
  // Operation tables map (op-name, type-tags...) -> procedure
  // ─────────────────────────────────────────────────────────────────

  // make-op-table: create a new operation table (returns a mutable Map-based value)
  def("make-op-table", { tag: "Native", name: "make-op-table", arity: 0, fn: (_args, s) => {
    const table: Val = {
      tag: "OpTable",
      entries: new Map<string, Val>()
    } as any;
    return { ...s, control: { tag: "Val", v: table } };
  }});

  // op-table-put: install a method - (op-table-put table op-name type-tags proc)
  // type-tags is a list of symbols, proc is a procedure
  def("op-table-put", { tag: "Native", name: "op-table-put", arity: 4, fn: (args, s) => {
    const table = args[0] as any;
    const opName = args[1] as any;
    const typeTags = args[2] as any;
    const proc = args[3];

    if (table.tag !== "OpTable") throw new Error("op-table-put: expected OpTable");
    if (opName.tag !== "Sym" && opName.tag !== "Str") throw new Error("op-table-put: op-name must be symbol or string");

    // Convert type-tags list to key string
    const tags: string[] = [];
    let cur = typeTags;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      const tag = cur.items[0] as any;
      tags.push(tag.tag === "Sym" ? tag.name : tag.s ?? String(tag));
      cur = cur.items[1];
    }
    // Handle single symbol
    if (typeTags.tag === "Sym") {
      tags.push(typeTags.name);
    }

    const key = `${opName.tag === "Sym" ? opName.name : opName.s}:${tags.join(",")}`;
    table.entries.set(key, proc);

    return { ...s, control: { tag: "Val", v: VUnit } };
  }});

  // op-table-get: lookup a method - (op-table-get table op-name type-tags)
  def("op-table-get", { tag: "Native", name: "op-table-get", arity: 3, fn: (args, s) => {
    const table = args[0] as any;
    const opName = args[1] as any;
    const typeTags = args[2] as any;

    if (table.tag !== "OpTable") throw new Error("op-table-get: expected OpTable");
    if (opName.tag !== "Sym" && opName.tag !== "Str") throw new Error("op-table-get: op-name must be symbol or string");

    // Convert type-tags list to key string
    const tags: string[] = [];
    let cur = typeTags;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      const tag = cur.items[0] as any;
      tags.push(tag.tag === "Sym" ? tag.name : tag.s ?? String(tag));
      cur = cur.items[1];
    }
    // Handle single symbol
    if (typeTags.tag === "Sym") {
      tags.push(typeTags.name);
    }

    const key = `${opName.tag === "Sym" ? opName.name : opName.s}:${tags.join(",")}`;
    const proc = table.entries.get(key);

    if (proc) {
      return { ...s, control: { tag: "Val", v: proc } };
    }
    return { ...s, control: { tag: "Val", v: VFalse } };
  }});

  // op-table?: check if value is an op-table
  def("op-table?", { tag: "Native", name: "op-table?", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    return { ...s, control: { tag: "Val", v: v.tag === "OpTable" ? VTrue : VFalse } };
  }});

  // ─────────────────────────────────────────────────────────────────
  // B2: Tagged Data Protocol (attach-tag, type-tag, contents)
  // Tagged data: (type-symbol . contents)
  // ─────────────────────────────────────────────────────────────────

  // attach-tag: create tagged datum - (attach-tag type-tag contents)
  def("attach-tag", { tag: "Native", name: "attach-tag", arity: 2, fn: (args, s) => {
    const typeTag = args[0] as any;
    const contents = args[1];

    if (typeTag.tag !== "Sym" && typeTag.tag !== "Str") {
      throw new Error("attach-tag: type-tag must be symbol or string");
    }

    // Tagged datum is a pair: (type-tag . contents)
    const tagged: Val = {
      tag: "Tagged",
      typeTag: typeTag.tag === "Sym" ? typeTag.name : typeTag.s,
      contents
    } as any;
    return { ...s, control: { tag: "Val", v: tagged } };
  }});

  // type-tag: get the type tag of a tagged datum
  def("type-tag", { tag: "Native", name: "type-tag", arity: 1, fn: (args, s) => {
    const datum = args[0] as any;

    if (datum.tag === "Tagged") {
      return { ...s, control: { tag: "Val", v: { tag: "Sym", name: datum.typeTag } } };
    }
    // For primitive types, return their tag as symbol
    return { ...s, control: { tag: "Val", v: { tag: "Sym", name: datum.tag.toLowerCase() } } };
  }});

  // contents: get the contents of a tagged datum
  def("contents", { tag: "Native", name: "contents", arity: 1, fn: (args, s) => {
    const datum = args[0] as any;

    if (datum.tag === "Tagged") {
      return { ...s, control: { tag: "Val", v: datum.contents } };
    }
    // For primitive types, return the value itself
    return { ...s, control: { tag: "Val", v: args[0] } };
  }});

  // tagged?: check if value is a tagged datum
  def("tagged?", { tag: "Native", name: "tagged?", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    return { ...s, control: { tag: "Val", v: v.tag === "Tagged" ? VTrue : VFalse } };
  }});

  // ─────────────────────────────────────────────────────────────────
  // B3: apply-generic (with coercion fallback)
  // ─────────────────────────────────────────────────────────────────

  // apply-generic: dispatch based on type tags
  // (apply-generic op-table op-name . args)
  // First tries exact match, then attempts coercion
  def("apply-generic", { tag: "Native", name: "apply-generic", arity: "variadic", fn: (args, s) => {
    if (args.length < 2) throw new Error("apply-generic: expected at least (table op-name)");

    const table = args[0] as any;
    const opName = args[1] as any;
    const opArgs = args.slice(2);

    if (table.tag !== "OpTable") throw new Error("apply-generic: expected OpTable");

    // Extract type tags from arguments
    const tags: string[] = opArgs.map(arg => {
      const a = arg as any;
      if (a.tag === "Tagged") return a.typeTag;
      return a.tag.toLowerCase();
    });

    // Look up method
    const key = `${opName.tag === "Sym" ? opName.name : opName.s}:${tags.join(",")}`;
    const proc = table.entries.get(key);

    if (proc) {
      // Extract contents for tagged data
      const procArgs = opArgs.map(arg => {
        const a = arg as any;
        return a.tag === "Tagged" ? a.contents : arg;
      });

      // Apply the procedure
      const result = applyNative(proc, procArgs, s);
      if (result === null) {
        throw new Error("apply-generic: method must be Native function");
      }
      return { ...s, control: { tag: "Val", v: result } };
    }

    // No exact match - return false (caller can handle coercion)
    return { ...s, control: { tag: "Val", v: VFalse } };
  }});

  // apply-generic-coerced: dispatch with coercion table
  // (apply-generic-coerced op-table coerce-table op-name . args)
  def("apply-generic-coerced", { tag: "Native", name: "apply-generic-coerced", arity: "variadic", fn: (args, s) => {
    if (args.length < 3) throw new Error("apply-generic-coerced: expected at least (op-table coerce-table op-name)");

    const opTable = args[0] as any;
    const coerceTable = args[1] as any;
    const opName = args[2] as any;
    const opArgs = args.slice(3);

    if (opTable.tag !== "OpTable") throw new Error("apply-generic-coerced: expected OpTable");
    if (coerceTable.tag !== "CoercionTable") throw new Error("apply-generic-coerced: expected CoercionTable");

    // Extract type tags
    const tags: string[] = opArgs.map(arg => {
      const a = arg as any;
      if (a.tag === "Tagged") return a.typeTag;
      return a.tag.toLowerCase();
    });

    // Try exact match first
    const exactKey = `${opName.tag === "Sym" ? opName.name : opName.s}:${tags.join(",")}`;
    const exactProc = opTable.entries.get(exactKey);

    if (exactProc) {
      const procArgs = opArgs.map(arg => {
        const a = arg as any;
        return a.tag === "Tagged" ? a.contents : arg;
      });
      const result = applyNative(exactProc, procArgs, s);
      if (result === null) throw new Error("apply-generic-coerced: method must be Native");
      return { ...s, control: { tag: "Val", v: result } };
    }

    // Try coercion: for 2 args, try coercing arg1 to type of arg2 or vice versa
    if (opArgs.length === 2) {
      const type1 = tags[0], type2 = tags[1];

      // Try coercing type1 to type2
      const coerce1to2Key = `${type1}:${type2}`;
      const coerce1to2 = coerceTable.entries.get(coerce1to2Key);
      if (coerce1to2) {
        // Coerce first arg
        const coercedArg1 = applyNative(coerce1to2, [opArgs[0]], s);
        if (coercedArg1 !== null) {
          // Retry with coerced arg
          const newKey = `${opName.tag === "Sym" ? opName.name : opName.s}:${type2},${type2}`;
          const proc = opTable.entries.get(newKey);
          if (proc) {
            const procArgs = [coercedArg1, opArgs[1]].map(arg => {
              const a = arg as any;
              return a.tag === "Tagged" ? a.contents : arg;
            });
            const result = applyNative(proc, procArgs, s);
            if (result !== null) return { ...s, control: { tag: "Val", v: result } };
          }
        }
      }

      // Try coercing type2 to type1
      const coerce2to1Key = `${type2}:${type1}`;
      const coerce2to1 = coerceTable.entries.get(coerce2to1Key);
      if (coerce2to1) {
        const coercedArg2 = applyNative(coerce2to1, [opArgs[1]], s);
        if (coercedArg2 !== null) {
          const newKey = `${opName.tag === "Sym" ? opName.name : opName.s}:${type1},${type1}`;
          const proc = opTable.entries.get(newKey);
          if (proc) {
            const procArgs = [opArgs[0], coercedArg2].map(arg => {
              const a = arg as any;
              return a.tag === "Tagged" ? a.contents : arg;
            });
            const result = applyNative(proc, procArgs, s);
            if (result !== null) return { ...s, control: { tag: "Val", v: result } };
          }
        }
      }
    }

    // No method found
    throw new Error(`apply-generic-coerced: no method for ${exactKey}`);
  }});

  // ─────────────────────────────────────────────────────────────────
  // B4: Coercion Graph Utilities
  // ─────────────────────────────────────────────────────────────────

  // make-coercion-table: create a new coercion table
  def("make-coercion-table", { tag: "Native", name: "make-coercion-table", arity: 0, fn: (_args, s) => {
    const table: Val = {
      tag: "CoercionTable",
      entries: new Map<string, Val>(),  // "from:to" -> coercion procedure
      edges: new Map<string, string[]>(), // adjacency list for path finding
    } as any;
    return { ...s, control: { tag: "Val", v: table } };
  }});

  // put-coercion: install a coercion - (put-coercion table from-type to-type proc)
  def("put-coercion", { tag: "Native", name: "put-coercion", arity: 4, fn: (args, s) => {
    const table = args[0] as any;
    const fromType = args[1] as any;
    const toType = args[2] as any;
    const proc = args[3];

    if (table.tag !== "CoercionTable") throw new Error("put-coercion: expected CoercionTable");

    const from = fromType.tag === "Sym" ? fromType.name : fromType.s;
    const to = toType.tag === "Sym" ? toType.name : toType.s;
    const key = `${from}:${to}`;

    table.entries.set(key, proc);

    // Update edges for path finding
    if (!table.edges.has(from)) {
      table.edges.set(from, []);
    }
    if (!table.edges.get(from)!.includes(to)) {
      table.edges.get(from)!.push(to);
    }

    return { ...s, control: { tag: "Val", v: VUnit } };
  }});

  // get-coercion: lookup a coercion
  def("get-coercion", { tag: "Native", name: "get-coercion", arity: 3, fn: (args, s) => {
    const table = args[0] as any;
    const fromType = args[1] as any;
    const toType = args[2] as any;

    if (table.tag !== "CoercionTable") throw new Error("get-coercion: expected CoercionTable");

    const from = fromType.tag === "Sym" ? fromType.name : fromType.s;
    const to = toType.tag === "Sym" ? toType.name : toType.s;
    const key = `${from}:${to}`;

    const proc = table.entries.get(key);
    return { ...s, control: { tag: "Val", v: proc ?? VFalse } };
  }});

  // coercion-table?: check if value is a coercion table
  def("coercion-table?", { tag: "Native", name: "coercion-table?", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    return { ...s, control: { tag: "Val", v: v.tag === "CoercionTable" ? VTrue : VFalse } };
  }});

  // find-coercion-path: find a path from one type to another
  // Returns a list of types from source to target, or false if no path
  def("find-coercion-path", { tag: "Native", name: "find-coercion-path", arity: 3, fn: (args, s) => {
    const table = args[0] as any;
    const fromType = args[1] as any;
    const toType = args[2] as any;

    if (table.tag !== "CoercionTable") throw new Error("find-coercion-path: expected CoercionTable");

    const from = fromType.tag === "Sym" ? fromType.name : fromType.s;
    const to = toType.tag === "Sym" ? toType.name : toType.s;

    if (from === to) {
      // No coercion needed
      let result: Val = VUnit;
      result = { tag: "Vector", items: [{ tag: "Sym", name: from }, result] };
      return { ...s, control: { tag: "Val", v: result } };
    }

    // BFS to find shortest path
    const visited = new Set<string>();
    const queue: Array<{ node: string; path: string[] }> = [{ node: from, path: [from] }];

    while (queue.length > 0) {
      const { node, path } = queue.shift()!;
      if (visited.has(node)) continue;
      visited.add(node);

      const neighbors = table.edges.get(node) ?? [];
      for (const neighbor of neighbors) {
        if (neighbor === to) {
          // Found path
          const fullPath = [...path, neighbor];
          let result: Val = VUnit;
          for (let i = fullPath.length - 1; i >= 0; i--) {
            result = { tag: "Vector", items: [{ tag: "Sym", name: fullPath[i] }, result] };
          }
          return { ...s, control: { tag: "Val", v: result } };
        }
        if (!visited.has(neighbor)) {
          queue.push({ node: neighbor, path: [...path, neighbor] });
        }
      }
    }

    // No path found
    return { ...s, control: { tag: "Val", v: VFalse } };
  }});

  // find-all-coercion-paths: find ALL paths from one type to another (for detecting ambiguity)
  // Returns a list of paths, where each path is a list of types
  def("find-all-coercion-paths", { tag: "Native", name: "find-all-coercion-paths", arity: 3, fn: (args, s) => {
    const table = args[0] as any;
    const fromType = args[1] as any;
    const toType = args[2] as any;

    if (table.tag !== "CoercionTable") throw new Error("find-all-coercion-paths: expected CoercionTable");

    const from = fromType.tag === "Sym" ? fromType.name : fromType.s;
    const to = toType.tag === "Sym" ? toType.name : toType.s;

    const allPaths: string[][] = [];

    // DFS to find all paths
    function dfs(node: string, path: string[], visited: Set<string>) {
      if (node === to) {
        allPaths.push([...path]);
        return;
      }
      visited.add(node);
      const neighbors = table.edges.get(node) ?? [];
      for (const neighbor of neighbors) {
        if (!visited.has(neighbor)) {
          path.push(neighbor);
          dfs(neighbor, path, visited);
          path.pop();
        }
      }
      visited.delete(node);
    }

    dfs(from, [from], new Set());

    // Convert to list of lists
    let result: Val = VUnit;
    for (let i = allPaths.length - 1; i >= 0; i--) {
      let pathList: Val = VUnit;
      for (let j = allPaths[i].length - 1; j >= 0; j--) {
        pathList = { tag: "Vector", items: [{ tag: "Sym", name: allPaths[i][j] }, pathList] };
      }
      result = { tag: "Vector", items: [pathList, result] };
    }

    return { ...s, control: { tag: "Val", v: result } };
  }});

  // coerce-value: apply coercion along a path
  // (coerce-value table value from-type to-type)
  def("coerce-value", { tag: "Native", name: "coerce-value", arity: 4, fn: (args, s) => {
    const table = args[0] as any;
    let value = args[1];
    const fromType = args[2] as any;
    const toType = args[3] as any;

    if (table.tag !== "CoercionTable") throw new Error("coerce-value: expected CoercionTable");

    const from = fromType.tag === "Sym" ? fromType.name : fromType.s;
    const to = toType.tag === "Sym" ? toType.name : toType.s;

    if (from === to) {
      return { ...s, control: { tag: "Val", v: value } };
    }

    // Find path
    const visited = new Set<string>();
    const queue: Array<{ node: string; path: string[] }> = [{ node: from, path: [from] }];
    let coercionPath: string[] | null = null;

    while (queue.length > 0) {
      const { node, path } = queue.shift()!;
      if (visited.has(node)) continue;
      visited.add(node);

      const neighbors = table.edges.get(node) ?? [];
      for (const neighbor of neighbors) {
        if (neighbor === to) {
          coercionPath = [...path, neighbor];
          break;
        }
        if (!visited.has(neighbor)) {
          queue.push({ node: neighbor, path: [...path, neighbor] });
        }
      }
      if (coercionPath) break;
    }

    if (!coercionPath) {
      throw new Error(`coerce-value: no coercion path from ${from} to ${to}`);
    }

    // Apply coercions along the path
    for (let i = 0; i < coercionPath.length - 1; i++) {
      const key = `${coercionPath[i]}:${coercionPath[i + 1]}`;
      const coerceProc = table.entries.get(key);
      if (!coerceProc) {
        throw new Error(`coerce-value: missing coercion ${key}`);
      }
      const result = applyNative(coerceProc, [value], s);
      if (result === null) {
        throw new Error("coerce-value: coercion function must be Native");
      }
      value = result;
    }

    return { ...s, control: { tag: "Val", v: value } };
  }});

  // ─────────────────────────────────────────────────────────────────
  // B1 (Prompt 6): Term Rewriting System Primitives
  // Pattern-based AST transformation with strategies
  // ─────────────────────────────────────────────────────────────────

  // Store for rule collections (keyed by name)
  const ruleStore = new Map<string, Rule[]>();

  // make-rule: create a rewrite rule
  // (make-rule name pattern template) -> Rule
  def("make-rule", { tag: "Native", name: "make-rule", arity: 3, fn: (args, s) => {
    const nameVal = args[0] as any;
    const pattern = args[1];
    const template = args[2];

    if (nameVal.tag !== "Sym" && nameVal.tag !== "Str") {
      throw new Error("make-rule: name must be symbol or string");
    }
    const name = nameVal.tag === "Sym" ? nameVal.name : nameVal.s;

    const ruleVal: Val = {
      tag: "Rule",
      name,
      pattern,
      template
    } as any;

    return { ...s, control: { tag: "Val", v: ruleVal } };
  }});

  // make-rule-where: create a rule with a where predicate
  // (make-rule-where name pattern template pred) -> Rule
  // pred is a Native function that takes bindings map and returns bool
  def("make-rule-where", { tag: "Native", name: "make-rule-where", arity: 4, fn: (args, s) => {
    const nameVal = args[0] as any;
    const pattern = args[1];
    const template = args[2];
    const pred = args[3] as any;

    if (nameVal.tag !== "Sym" && nameVal.tag !== "Str") {
      throw new Error("make-rule-where: name must be symbol or string");
    }
    const name = nameVal.tag === "Sym" ? nameVal.name : nameVal.s;

    const ruleVal: Val = {
      tag: "Rule",
      name,
      pattern,
      template,
      wherePred: pred
    } as any;

    return { ...s, control: { tag: "Val", v: ruleVal } };
  }});

  // rule?: check if value is a rule
  def("rule?", { tag: "Native", name: "rule?", arity: 1, fn: (args, s) => {
    const v = args[0] as any;
    return { ...s, control: { tag: "Val", v: v.tag === "Rule" ? VTrue : VFalse } };
  }});

  // Helper to convert our Rule Val to the TRS Rule type
  function valToRule(v: Val): Rule {
    const r = v as any;
    if (r.tag !== "Rule") throw new Error("expected Rule");

    // Create where predicate if present
    let where: ((bindings: Record<string, unknown>) => boolean) | undefined;
    if (r.wherePred && r.wherePred.tag === "Native") {
      where = (bindings: Record<string, unknown>) => {
        // Convert bindings to a Map-like Val
        const entries: Array<[Val, Val]> = [];
        for (const [k, v] of Object.entries(bindings)) {
          entries.push([{ tag: "Sym", name: k }, v as Val]);
        }
        const bindingsVal: Val = { tag: "Map", entries };
        const result = (r.wherePred as any).fn([bindingsVal], {} as any);
        if (result.control?.tag === "Val") {
          const rv = result.control.v as any;
          return rv.tag === "Bool" && rv.b;
        }
        return false;
      };
    }

    return rule(r.name, r.pattern, r.template, where);
  }

  // Helper to convert list of Rule Vals to Rule[]
  function valListToRules(lst: Val): Rule[] {
    const rules: Rule[] = [];
    let cur = lst as any;
    while (cur.tag === "Vector" && cur.items.length >= 2) {
      rules.push(valToRule(cur.items[0]));
      cur = cur.items[1];
    }
    // Handle single rule
    if ((lst as any).tag === "Rule") {
      rules.push(valToRule(lst));
    }
    return rules;
  }

  // rewrite-once: apply rules once at first match
  // (rewrite-once rules expr) -> expr' or #f if no match
  // (rewrite-once rules expr 'topdown) -> with strategy
  def("rewrite-once", { tag: "Native", name: "rewrite-once", arity: "variadic", fn: (args, s) => {
    if (args.length < 2) throw new Error("rewrite-once: expected (rules expr [strategy])");

    const rulesVal = args[0];
    const expr = args[1];
    const strategyVal = args[2] as any;

    const rules = valListToRules(rulesVal);
    const strategy: Strategy = strategyVal?.tag === "Sym" && strategyVal.name === "bottomup"
      ? "bottomup" : "topdown";

    const result = rewriteOnce(rules, expr, strategy);

    if (result.changed) {
      // Return a pair: (expr' . rule-name)
      const resultPair: Val = {
        tag: "Vector",
        items: [
          result.result,
          { tag: "Sym", name: result.ruleName ?? "unknown" }
        ]
      };
      return { ...s, control: { tag: "Val", v: resultPair } };
    }

    return { ...s, control: { tag: "Val", v: VFalse } };
  }});

  // rewrite-fixpoint: apply rules until no more changes or fuel exhausted
  // (rewrite-fixpoint rules expr) -> expr'
  // (rewrite-fixpoint rules expr fuel) -> with fuel limit
  // (rewrite-fixpoint rules expr fuel 'bottomup) -> with strategy
  def("rewrite-fixpoint", { tag: "Native", name: "rewrite-fixpoint", arity: "variadic", fn: (args, s) => {
    if (args.length < 2) throw new Error("rewrite-fixpoint: expected (rules expr [fuel] [strategy])");

    const rulesVal = args[0];
    const expr = args[1];
    const fuelVal = args[2] as any;
    const strategyVal = args[3] as any;

    const rules = valListToRules(rulesVal);
    const fuel = fuelVal?.tag === "Num" ? fuelVal.n : 100;
    const strategy: Strategy = strategyVal?.tag === "Sym" && strategyVal.name === "bottomup"
      ? "bottomup" : "topdown";

    const result = rewriteFixpoint(rules, expr, strategy, fuel);

    // Return a record with result, steps, and whether fixpoint was reached
    const resultVal: Val = {
      tag: "Map",
      entries: [
        [{ tag: "Sym", name: "result" }, result.result],
        [{ tag: "Sym", name: "steps" }, { tag: "Num", n: result.steps }],
        [{ tag: "Sym", name: "fixpoint?" }, result.reachedFixpoint ? VTrue : VFalse]
      ]
    };

    return { ...s, control: { tag: "Val", v: resultVal } };
  }});

  // rewrite-trace: apply rules and return full trace
  // (rewrite-trace rules expr) -> list of intermediate expressions
  def("rewrite-trace", { tag: "Native", name: "rewrite-trace", arity: "variadic", fn: (args, s) => {
    if (args.length < 2) throw new Error("rewrite-trace: expected (rules expr [fuel] [strategy])");

    const rulesVal = args[0];
    const expr = args[1];
    const fuelVal = args[2] as any;
    const strategyVal = args[3] as any;

    const rules = valListToRules(rulesVal);
    const fuel = fuelVal?.tag === "Num" ? fuelVal.n : 100;
    const strategy: Strategy = strategyVal?.tag === "Sym" && strategyVal.name === "bottomup"
      ? "bottomup" : "topdown";

    const result = rewriteTrace(rules, expr, strategy, fuel);

    // Convert trace to list
    let traceList: Val = VUnit;
    for (let i = result.trace.length - 1; i >= 0; i--) {
      const step = result.trace[i];
      const stepVal: Val = {
        tag: "Map",
        entries: [
          [{ tag: "Sym", name: "expr" }, step.expr],
          [{ tag: "Sym", name: "rule" }, { tag: "Sym", name: step.ruleName ?? "initial" }],
          [{ tag: "Sym", name: "position" }, { tag: "Str", s: step.position ?? "" }]
        ]
      };
      traceList = { tag: "Vector", items: [stepVal, traceList] };
    }

    return { ...s, control: { tag: "Val", v: traceList } };
  }});

  // rewrite-conflicts: detect potential non-confluence in rules
  // (rewrite-conflicts rules) -> list of conflict reports
  def("rewrite-conflicts", { tag: "Native", name: "rewrite-conflicts", arity: 1, fn: (args, s) => {
    const rulesVal = args[0];
    const rules = valListToRules(rulesVal);

    const conflicts = detectConflicts(rules);

    // Convert to list of reports
    let conflictList: Val = VUnit;
    for (let i = conflicts.length - 1; i >= 0; i--) {
      const c = conflicts[i];
      const reportVal: Val = {
        tag: "Map",
        entries: [
          [{ tag: "Sym", name: "rule1" }, { tag: "Sym", name: c.rule1 }],
          [{ tag: "Sym", name: "rule2" }, { tag: "Sym", name: c.rule2 }],
          [{ tag: "Sym", name: "overlap" }, { tag: "Sym", name: c.overlap }],
          [{ tag: "Sym", name: "description" }, { tag: "Str", s: c.description }]
        ]
      };
      conflictList = { tag: "Vector", items: [reportVal, conflictList] };
    }

    return { ...s, control: { tag: "Val", v: conflictList } };
  }});

  // match-pattern: test if a pattern matches an expression
  // (match-pattern pattern expr) -> bindings map or #f
  def("match-pattern", { tag: "Native", name: "match-pattern", arity: 2, fn: (args, s) => {
    const pattern = args[0];
    const expr = args[1];

    const result = matchAST(pattern, expr);

    if (result.ok) {
      // Convert bindings to Map Val
      const entries: Array<[Val, Val]> = [];
      for (const [k, v] of Object.entries(result.bindings)) {
        entries.push([{ tag: "Sym", name: k }, v as Val]);
      }
      return { ...s, control: { tag: "Val", v: { tag: "Map", entries } } };
    }

    return { ...s, control: { tag: "Val", v: VFalse } };
  }});

  // substitute-template: apply bindings to a template
  // (substitute-template template bindings) -> expr
  def("substitute-template", { tag: "Native", name: "substitute-template", arity: 2, fn: (args, s) => {
    const template = args[0];
    const bindingsVal = args[1] as any;

    // Convert Map Val to bindings object
    const bindings: Record<string, unknown> = {};
    if (bindingsVal.tag === "Map") {
      for (const [k, v] of bindingsVal.entries) {
        const key = (k as any).tag === "Sym" ? (k as any).name : (k as any).s;
        bindings[key] = v;
      }
    }

    const result = substitute(template, bindings);
    return { ...s, control: { tag: "Val", v: result } };
  }});

  // ─────────────────────────────────────────────────────────────────
  // Evidence primitives
  // ─────────────────────────────────────────────────────────────────
  function extractEvidenceCandidate(value: unknown): Evidence | Evidence[] | undefined {
    if (isMeaning(value as any)) {
      const evs = (value as any).evidence;
      if (Array.isArray(evs) && evs.length > 0) {
        const evidences = evs.filter(isEvidenceVal);
        if (evidences.length === 1) return evidences[0] as Evidence;
        if (evidences.length > 1) return evidences as Evidence[];
      }
    }

    if (Array.isArray(value)) {
      const evidences = (value as any[]).filter(isEvidenceVal);
      if (evidences.length === 1) return evidences[0] as Evidence;
      if (evidences.length > 1) return evidences as Evidence[];
    }

    if (isEvidenceVal(value)) return value as Evidence;
    if (value && typeof value === "object" && typeof (value as any).tag === "string" && (value as any).tag.endsWith("Evidence")) {
      return value as Evidence;
    }

    return undefined;
  }

  def("evidence-id", { tag: "Native", name: "evidence-id", arity: 1, fn: (args, s) => {
    const target = extractEvidenceCandidate(args[0]);
    if (target) {
      const id = computeEvidenceId(target as any);
      return { ...s, control: { tag: "Val", v: { tag: "Str", s: id } } };
    }
    return { ...s, control: { tag: "Val", v: VFalse } };
  }});

  def("verify-evidence", { tag: "Native", name: "verify-evidence", arity: 1, fn: (args, s) => {
    const v = args[0];
    const ok = isMeaning(v) && Array.isArray(v.evidence) && v.evidence.length > 0;
    return { ...s, control: { tag: "Val", v: ok ? VTrue : VFalse } };
  }});

  def("evidence-stale?", { tag: "Native", name: "evidence-stale?", arity: 1, fn: (args, s) => {
    const v = args[0];
    if (isMeaning(v) && v.evidence && v.evidence.length > 0) {
      return { ...s, control: { tag: "Val", v: VFalse } };
    }
    return { ...s, control: { tag: "Val", v: VTrue } };
  }});

  function isEvidenceVal(value: unknown): value is Evidence {
    if (typeof value !== "object" || value === null) return false;
    const tag = (value as any).tag as Evidence["tag"] | string | undefined;
    return typeof tag === "string" && EVIDENCE_TAGS.has(tag as Evidence["tag"]);
  }

  const EVIDENCE_TAGS = new Set<Evidence["tag"]>([
    "TestEvidence",
    "NoMatchEvidence",
    "EqExtEvidence",
    "OracleEvidence",
    "TransformEvidence",
    "DerivedEvidence",
  ]);

  // ─────────────────────────────────────────────────────────────────
  // Machine primitives (Prompt 8): Reified execution state
  // These enable stepping, forking, breakpoints, and time-travel debugging
  // ─────────────────────────────────────────────────────────────────

  // machine-new: Create a reified machine from an expression
  // (machine-new expr) -> Machine
  // (machine-new expr label) -> Machine with label
  def("machine-new", { tag: "Native", name: "machine-new", arity: "variadic", fn: (args, s) => {
    if (args.length < 1) throw new Error("machine-new: expected at least 1 argument");
    const exprVal = args[0];
    const label = args.length > 1 && args[1].tag === "Str" ? args[1].s : undefined;

    // Convert Val to an expression we can evaluate
    // For now, we expect a Syntax value that can be lowered to Expr
    // Or we can create a state that evaluates the value
    const machineId = `m${Date.now().toString(16)}${Math.random().toString(16).slice(2, 6)}`;

    const initialState: any = {
      control: { tag: "Val", v: exprVal },
      env: s.env,
      store: s.store,
      kont: [],
      handlers: s.handlers || [],
    };

    const machineVal: Val = {
      tag: "Machine",
      state: initialState,
      label,
      stepCount: 0,
      isDone: false,
      machineId,
    };

    return { ...s, control: { tag: "Val", v: machineVal } };
  }});

  // machine-step: Single-step a machine
  // (machine-step machine) -> Machine (updated)
  def("machine-step", { tag: "Native", name: "machine-step", arity: 1, fn: (args, s) => {
    const m = args[0] as any;
    if (m.tag !== "Machine") throw new Error("machine-step: expected Machine");
    if (m.isDone) {
      // Already done - return as-is
      return { ...s, control: { tag: "Val", v: m } };
    }

    // stepOnce imported at top of file
    const outcome = stepOnce(m.state);

    const updated: Val = {
      ...m,
      stepCount: m.stepCount + 1,
      lastOutcome: outcome,
      isDone: outcome.tag === "Done",
      state: outcome.tag === "State" ? outcome.state :
             outcome.tag === "Done" ? outcome.state :
             outcome.tag === "Op" ? outcome.state : m.state,
    };

    return { ...s, control: { tag: "Val", v: updated } };
  }});

  // machine-run: Run machine to completion or breakpoint
  // (machine-run machine) -> Machine
  // (machine-run machine max-steps) -> Machine
  def("machine-run", { tag: "Native", name: "machine-run", arity: "variadic", fn: (args, s) => {
    if (args.length < 1) throw new Error("machine-run: expected at least 1 argument");
    const m = args[0] as any;
    if (m.tag !== "Machine") throw new Error("machine-run: expected Machine");
    if (m.isDone) return { ...s, control: { tag: "Val", v: m } };

    const maxSteps = args.length > 1 && args[1].tag === "Num" ? args[1].n : 10000;
    // stepOnce imported at top of file

    let current = m;
    for (let i = 0; i < maxSteps; i++) {
      if (current.isDone) break;

      const outcome = stepOnce(current.state);

      // Check for effect breakpoints
      if (outcome.tag === "Op" && current.breakOnOps?.has(outcome.opcall.op)) {
        current = {
          ...current,
          stepCount: current.stepCount + 1,
          lastOutcome: outcome,
          state: outcome.state,
        };
        break;
      }

      // Update machine
      current = {
        ...current,
        stepCount: current.stepCount + 1,
        lastOutcome: outcome,
        isDone: outcome.tag === "Done",
        state: outcome.tag === "State" ? outcome.state :
               outcome.tag === "Done" ? outcome.state :
               outcome.tag === "Op" ? outcome.state : current.state,
      };

      // Stop on effect emission (for debugging)
      if (outcome.tag === "Op") break;
    }

    return { ...s, control: { tag: "Val", v: current } };
  }});

  // machine-stack: Get the continuation stack as a list
  // (machine-stack machine) -> list of frame descriptions
  def("machine-stack", { tag: "Native", name: "machine-stack", arity: 1, fn: (args, s) => {
    const m = args[0] as any;
    if (m.tag !== "Machine") throw new Error("machine-stack: expected Machine");

    // Convert kont frames to list of symbols describing them
    const frames = m.state.kont || [];
    let result: Val = VUnit;
    for (let i = frames.length - 1; i >= 0; i--) {
      const fr = frames[i];
      const frameDesc: Val = { tag: "Sym", name: fr.tag };
      result = { tag: "Vector", items: [frameDesc, result] };
    }

    return { ...s, control: { tag: "Val", v: result } };
  }});

  // machine-control: Get the current control value
  // (machine-control machine) -> control (Expr or Val)
  def("machine-control", { tag: "Native", name: "machine-control", arity: 1, fn: (args, s) => {
    const m = args[0] as any;
    if (m.tag !== "Machine") throw new Error("machine-control: expected Machine");

    const ctrl = m.state.control;
    if (ctrl.tag === "Val") {
      return { ...s, control: { tag: "Val", v: ctrl.v } };
    }
    // For Expr, return a description
    return { ...s, control: { tag: "Val", v: { tag: "Sym", name: `Expr:${ctrl.e.tag}` } } };
  }});

  // machine-done?: Check if machine is in terminal state
  // (machine-done? machine) -> boolean
  def("machine-done?", { tag: "Native", name: "machine-done?", arity: 1, fn: (args, s) => {
    const m = args[0] as any;
    if (m.tag !== "Machine") throw new Error("machine-done?: expected Machine");
    return { ...s, control: { tag: "Val", v: m.isDone ? VTrue : VFalse } };
  }});

  // machine-value: Get the final value (if done)
  // (machine-value machine) -> value or error
  def("machine-value", { tag: "Native", name: "machine-value", arity: 1, fn: (args, s) => {
    const m = args[0] as any;
    if (m.tag !== "Machine") throw new Error("machine-value: expected Machine");
    if (!m.isDone) throw new Error("machine-value: machine not done");

    if (m.lastOutcome?.tag === "Done") {
      return { ...s, control: { tag: "Val", v: m.lastOutcome.value } };
    }
    if (m.state.control.tag === "Val") {
      return { ...s, control: { tag: "Val", v: m.state.control.v } };
    }
    throw new Error("machine-value: no value available");
  }});

  // machine-fork: Clone a machine for multi-shot exploration
  // (machine-fork machine) -> new Machine
  // (machine-fork machine label) -> new Machine with label
  def("machine-fork", { tag: "Native", name: "machine-fork", arity: "variadic", fn: (args, s) => {
    if (args.length < 1) throw new Error("machine-fork: expected at least 1 argument");
    const m = args[0] as any;
    if (m.tag !== "Machine") throw new Error("machine-fork: expected Machine");
    const label = args.length > 1 && args[1].tag === "Str" ? args[1].s : undefined;

    const newId = `m${Date.now().toString(16)}${Math.random().toString(16).slice(2, 6)}`;

    const forked: Val = {
      tag: "Machine",
      state: JSON.parse(JSON.stringify(m.state)), // Deep clone
      label: label || m.label,
      stepCount: m.stepCount,
      breakOnOps: m.breakOnOps ? new Set(m.breakOnOps) : undefined,
      breakOnPatterns: m.breakOnPatterns?.slice(),
      lastOutcome: m.lastOutcome,
      isDone: m.isDone,
      parentId: m.machineId,
      machineId: newId,
    };

    return { ...s, control: { tag: "Val", v: forked } };
  }});

  // machine-resume: Resume a machine from an effect with a value
  // (machine-resume machine value) -> Machine
  def("machine-resume", { tag: "Native", name: "machine-resume", arity: 2, fn: (args, s) => {
    const m = args[0] as any;
    const value = args[1];
    if (m.tag !== "Machine") throw new Error("machine-resume: expected Machine");

    // Set the control to the provided value and clear effect state
    const resumed: Val = {
      ...m,
      state: {
        ...m.state,
        control: { tag: "Val", v: value },
      },
      lastOutcome: undefined,
    };

    return { ...s, control: { tag: "Val", v: resumed } };
  }});

  // machine-add-breakpoint: Add an effect breakpoint
  // (machine-add-breakpoint machine op-name) -> Machine
  def("machine-add-breakpoint", { tag: "Native", name: "machine-add-breakpoint", arity: 2, fn: (args, s) => {
    const m = args[0] as any;
    const opName = args[1] as any;
    if (m.tag !== "Machine") throw new Error("machine-add-breakpoint: expected Machine");
    if (opName.tag !== "Str" && opName.tag !== "Sym") {
      throw new Error("machine-add-breakpoint: op-name must be string or symbol");
    }

    const name = opName.tag === "Str" ? opName.s : opName.name;
    const breakpoints = m.breakOnOps ? new Set(m.breakOnOps) : new Set<string>();
    breakpoints.add(name);

    const updated: Val = {
      ...m,
      breakOnOps: breakpoints,
    };

    return { ...s, control: { tag: "Val", v: updated } };
  }});

  // machine-step-count: Get the step count
  // (machine-step-count machine) -> number
  def("machine-step-count", { tag: "Native", name: "machine-step-count", arity: 1, fn: (args, s) => {
    const m = args[0] as any;
    if (m.tag !== "Machine") throw new Error("machine-step-count: expected Machine");
    return { ...s, control: { tag: "Val", v: { tag: "Num", n: m.stepCount } } };
  }});

  // machine-last-op: Get the last operation (if any)
  // (machine-last-op machine) -> op-name or #f
  def("machine-last-op", { tag: "Native", name: "machine-last-op", arity: 1, fn: (args, s) => {
    const m = args[0] as any;
    if (m.tag !== "Machine") throw new Error("machine-last-op: expected Machine");

    if (m.lastOutcome?.tag === "Op") {
      return { ...s, control: { tag: "Val", v: { tag: "Sym", name: m.lastOutcome.opcall.op } } };
    }
    return { ...s, control: { tag: "Val", v: VFalse } };
  }});

  // machine?: Check if value is a Machine
  // (machine? x) -> boolean
  def("machine?", { tag: "Native", name: "machine?", arity: 1, fn: (args, s) => {
    return { ...s, control: { tag: "Val", v: args[0].tag === "Machine" ? VTrue : VFalse } };
  }});

  // ═══════════════════════════════════════════════════════════════════
  // LANGUAGE BUILDING PRIMITIVES
  // These make it trivial to build DSLs and new languages on OmegaLLM
  // ═══════════════════════════════════════════════════════════════════

  // eval: Evaluate a quoted expression
  // (eval '(+ 1 2)) -> 3
  // (eval expr env) -> result (with custom environment)
  def("eval", { tag: "Native", name: "eval", arity: "variadic", fn: (args, s) => {
    if (args.length < 1) throw new Error("eval: expected at least 1 argument");
    const quotedExpr = args[0];

    // Convert Val to Sexp for parsing
    function valToSexp(v: Val): any {
      if (v.tag === "Num") return v.n;
      if (v.tag === "Str") return v.s;
      if (v.tag === "Bool") return v.b;
      if (v.tag === "Sym") return { sym: v.name };
      if (v.tag === "Unit") return null;
      if (v.tag === "Vector") {
        // Support both encodings:
        // 1) cons-cell lists: (Vector [head tail]) chains
        // 2) flat vectors from (read "..."): (Vector [e1 e2 ...])
        if (v.items.length !== 2) {
          return v.items.map(valToSexp);
        }

        const items: any[] = [];
        let current: Val = v;
        while (current.tag === "Vector" && current.items.length === 2) {
          items.push(valToSexp(current.items[0]));
          current = current.items[1];
        }
        return items;
      }
      throw new Error(`eval: cannot convert ${v.tag} to expression`);
    }

    try {
      const sexp = valToSexp(quotedExpr);
      const expr = compileTextToExpr(JSON.stringify(sexp)
        .replace(/\{"sym":"([^"]+)"\}/g, '$1')
        .replace(/^\[/, '(').replace(/\]$/, ')')
        .replace(/,/g, ' ')
        .replace(/"/g, '"'));

      // Actually we need to compile from sexp properly - use simpler approach
      // Just reparse the pretty-printed version
      function sexpToString(s: any): string {
        if (s === null) return "()";
        if (typeof s === "number") return String(s);
        if (typeof s === "string") return `"${s}"`;
        if (typeof s === "boolean") return s ? "#t" : "#f";
        if (typeof s === "object" && "sym" in s) return s.sym;
        if (Array.isArray(s)) return `(${s.map(sexpToString).join(" ")})`;
        return String(s);
      }

      const code = sexpToString(sexp);
      if (args.length > 1) {
        // Legacy/custom-env mode remains local+synchronous.
        const compiled = compileTextToExpr(code);
        const subState: any = {
          control: { tag: "Expr", e: compiled },
          env: (args[1] as any).env || s.env,
          store: s.store,
          kont: [],
          handlers: s.handlers || [],
        };

        let current = subState;
        for (let i = 0; i < 10000; i++) {
          const outcome = stepOnce(current);
          if (outcome.tag === "Done") {
            return { ...s, control: { tag: "Val", v: outcome.value }, store: outcome.state?.store || s.store };
          }
          if (outcome.tag === "Op") {
            const syncHandler = getSyncEffectHandler(outcome.opcall.op);
            if (syncHandler) {
              const effectResult = syncHandler(outcome.opcall.args, outcome.state);
              current = outcome.opcall.resumption.invoke(effectResult);
              continue;
            }
            throw new Error(`eval: cannot handle effect ${outcome.opcall.op} in eval (no sync handler registered)`);
          }
          current = outcome.state;
        }
        throw new Error("eval: exceeded step limit");
      }

      // Production path: emit a runtime-managed effect so nested eval can use
      // full async effect dispatch (infer.op, search.op, etc.).
      const suspended: State = { ...s, control: { tag: "Val", v: VUnit } };
      const resumption = captureValueResumption(suspended);
      const opcall: any = {
        op: "eval.run.op",
        args: [{ tag: "Str", s: code } as Val],
        ctxDigest: s.env.cid,
        resumption,
      };
      return { tag: "Op", opcall, state: suspended } as StepOutcome;
    } catch (e: any) {
      throw new Error(`eval: ${e.message}`);
    }
  }});

  // gensym: Generate a unique symbol (for macro hygiene)
  // (gensym) -> fresh symbol
  // (gensym "prefix") -> symbol with prefix
  // Uses the existing gensym helper function defined earlier
  def("gensym", { tag: "Native", name: "gensym", arity: "variadic", fn: (args, s) => {
    const prefix = args.length > 0 && args[0].tag === "Str" ? args[0].s : "g";
    const sym: Val = { tag: "Sym", name: gensym(prefix) };
    return { ...s, control: { tag: "Val", v: sym } };
  }});

  // syntax->datum: Convert syntax object to data (for macro transformers)
  // (syntax->datum stx) -> datum
  def("syntax->datum", { tag: "Native", name: "syntax->datum", arity: 1, fn: (args, s) => {
    // For now, just return the value as-is (syntax objects are values)
    return { ...s, control: { tag: "Val", v: args[0] } };
  }});

  // datum->syntax: Convert datum to syntax object
  // (datum->syntax datum) -> syntax
  def("datum->syntax", { tag: "Native", name: "datum->syntax", arity: 1, fn: (args, s) => {
    // For now, just return the value as-is
    return { ...s, control: { tag: "Val", v: args[0] } };
  }});

  // make-transformer: Create a macro transformer from a procedure
  // (make-transformer proc) -> transformer
  // The proc takes (syntax) and returns (syntax)
  def("make-transformer", { tag: "Native", name: "make-transformer", arity: 1, fn: (args, s) => {
    const proc = args[0];
    if (!isCallable(proc)) throw new Error("make-transformer: expected procedure");

    const transformer: Val = {
      tag: "MacroTransformer",
      proc,
      name: "user-macro",
    };
    return { ...s, control: { tag: "Val", v: transformer } };
  }});

  // make-evaluator: Create a custom evaluator with extended primitives
  // (make-evaluator :base base-eval :extend extensions) -> evaluator
  // Extensions is an alist of (name . procedure) pairs
  def("make-evaluator", { tag: "Native", name: "make-evaluator", arity: "variadic", fn: (args, s) => {
    // Parse keyword args
    let baseEval: Val | undefined = undefined;
    let extensions: Val = VUnit;
    const primitives = new Map<string, Val>();

    for (let i = 0; i < args.length - 1; i += 2) {
      const key = args[i];
      const val = args[i + 1];
      if (key.tag === "Sym" && key.name === ":base") baseEval = val;
      if (key.tag === "Sym" && key.name === ":extend") extensions = val;
    }

    // If extensions is a list, parse it
    if (extensions.tag === "Vector") {
      let current: Val = extensions;
      while (current.tag === "Vector" && current.items.length === 2) {
        const pair = current.items[0];
        if (pair.tag === "Vector" && pair.items.length === 2) {
          const name = pair.items[0];
          const proc = pair.items[1];
          if (name.tag === "Sym" && isCallable(proc)) {
            primitives.set(name.name, proc);
          }
        }
        current = current.items[1];
      }
    }

    // Create evaluator record
    const evaluator: Val = {
      tag: "Evaluator",
      base: baseEval,
      extensions,
      primitives,
    };

    return { ...s, control: { tag: "Val", v: evaluator } };
  }});

  // eval-in: Evaluate expression in a custom evaluator
  // (eval-in evaluator expr) -> result
  def("eval-in", { tag: "Native", name: "eval-in", arity: 2, fn: (args, s) => {
    const evaluator = args[0] as any;
    const expr = args[1];

    if (evaluator.tag !== "Evaluator") {
      throw new Error("eval-in: expected Evaluator");
    }

    // Build environment with custom primitives
    let customEnv = s.env;
    const prims = evaluator.primitives as Map<string, Val>;
    if (prims) {
      for (const [name, proc] of prims) {
        const [st2, addr] = st.alloc(proc);
        st = st2;
        customEnv = envSet(customEnv, name, addr);
      }
    }

    // Evaluate the expression
    function valToSexp(v: Val): any {
      if (v.tag === "Num") return v.n;
      if (v.tag === "Str") return v.s;
      if (v.tag === "Bool") return v.b;
      if (v.tag === "Sym") return { sym: v.name };
      if (v.tag === "Unit") return null;
      if (v.tag === "Vector") {
        if (v.items.length !== 2) {
          return v.items.map(valToSexp);
        }

        const items: any[] = [];
        let current: Val = v;
        while (current.tag === "Vector" && current.items.length === 2) {
          items.push(valToSexp(current.items[0]));
          current = current.items[1];
        }
        return items;
      }
      return null;
    }

    function sexpToString(sx: any): string {
      if (sx === null) return "()";
      if (typeof sx === "number") return String(sx);
      if (typeof sx === "string") return `"${sx}"`;
      if (typeof sx === "boolean") return sx ? "#t" : "#f";
      if (typeof sx === "object" && "sym" in sx) return sx.sym;
      if (Array.isArray(sx)) return `(${sx.map(sexpToString).join(" ")})`;
      return String(sx);
    }

    try {
      const sexp = valToSexp(expr);
      const code = sexpToString(sexp);
      const compiled = compileTextToExpr(code);

      const subState: any = {
        control: { tag: "Expr", e: compiled },
        env: customEnv,
        store: st,
        kont: [],
        handlers: s.handlers || [],
      };

      // stepOnce imported at top of file
      let current = subState;
      for (let i = 0; i < 10000; i++) {
        const outcome = stepOnce(current);
        if (outcome.tag === "Done") {
          return { ...s, control: { tag: "Val", v: outcome.value }, store: outcome.state?.store || st };
        }
        if (outcome.tag === "Op") {
          // Match solve behavior: allow effectful eval only through registered sync handlers.
          const syncHandler = getSyncEffectHandler(outcome.opcall.op);
          if (syncHandler) {
            const effectResult = syncHandler(outcome.opcall.args, outcome.state);
            current = outcome.opcall.resumption.invoke(effectResult);
            continue;
          }
          throw new Error(`eval-in: cannot handle effect ${outcome.opcall.op} (no sync handler registered)`);
        }
        current = outcome.state;
      }
      throw new Error("eval-in: exceeded step limit");
    } catch (e: any) {
      throw new Error(`eval-in: ${e.message}`);
    }
  }});

  // evaluator?: Check if value is an Evaluator
  def("evaluator?", { tag: "Native", name: "evaluator?", arity: 1, fn: (args, s) => {
    return { ...s, control: { tag: "Val", v: (args[0] as any).tag === "Evaluator" ? VTrue : VFalse } };
  }});

  // extend-evaluator: Add primitives to an evaluator
  // (extend-evaluator eval (list (cons 'name proc) ...)) -> new-eval
  def("extend-evaluator", { tag: "Native", name: "extend-evaluator", arity: 2, fn: (args, s) => {
    const evaluator = args[0] as any;
    const extensions = args[1];

    if (evaluator.tag !== "Evaluator") {
      throw new Error("extend-evaluator: expected Evaluator");
    }

    // Clone the evaluator with new primitives
    const newPrims = new Map<string, Val>(evaluator.primitives);

    let current = extensions;
    while (current.tag === "Vector" && current.items.length === 2) {
      const pair = current.items[0];
      if (pair.tag === "Vector" && pair.items.length === 2) {
        const name = pair.items[0];
        const proc = pair.items[1];
        if (name.tag === "Sym" && isCallable(proc)) {
          newPrims.set(name.name, proc);
        }
      }
      current = current.items[1];
    }

    const newEval: Val = {
      tag: "Evaluator",
      base: evaluator.base,
      extensions: evaluator.extensions,
      primitives: newPrims,
    };

    return { ...s, control: { tag: "Val", v: newEval } };
  }});

  // ═══════════════════════════════════════════════════════════════════
  // SIMPLE MACRO SYSTEM (defmacro-style, non-hygienic but easy)
  // ═══════════════════════════════════════════════════════════════════

  // Global macro table for defmacro-style macros
  const macroTable = new Map<string, Val>();

  // register-macro: Register a macro transformer
  // (register-macro 'name transformer) -> 'name
  def("register-macro", { tag: "Native", name: "register-macro", arity: 2, fn: (args, s) => {
    const name = args[0];
    const transformer = args[1];

    if (name.tag !== "Sym") throw new Error("register-macro: name must be a symbol");
    if (!isCallable(transformer)) throw new Error("register-macro: transformer must be a procedure");

    macroTable.set(name.name, transformer);
    return { ...s, control: { tag: "Val", v: name } };
  }});

  // expand-macro: Expand a macro if registered
  // (expand-macro expr) -> expanded-expr or original-expr
  def("expand-macro", { tag: "Native", name: "expand-macro", arity: 1, fn: (args, s) => {
    const expr = args[0];

    // Only expand lists with symbolic head
    if (expr.tag !== "Vector" || expr.items.length < 1) {
      return { ...s, control: { tag: "Val", v: expr } };
    }

    // Get the head
    let head = expr.items[0];
    if (head.tag === "Vector" && head.items.length === 2) {
      head = head.items[0]; // car of cons
    }

    if (head.tag !== "Sym") {
      return { ...s, control: { tag: "Val", v: expr } };
    }

    const transformer = macroTable.get(head.name);
    if (!transformer) {
      return { ...s, control: { tag: "Val", v: expr } };
    }

    // Apply the transformer to the expression
    // For Native functions, apply directly
    if (transformer.tag === "Native") {
      return transformer.fn([expr], s);
    }

    // For Closures, set up application with KAppArg frame
    if (transformer.tag === "Closure") {
      const frame: Frame = {
        tag: "KAppArg",
        fnVal: transformer,
        pending: [],
        acc: [expr],
        env: s.env,
      };
      return {
        ...s,
        control: { tag: "Val", v: transformer },
        kont: [...s.kont, frame],
      };
    }

    // Otherwise just return original expression
    return { ...s, control: { tag: "Val", v: expr } };
  }});

  // macro?: Check if a symbol names a registered macro
  def("macro?", { tag: "Native", name: "macro?", arity: 1, fn: (args, s) => {
    if (args[0].tag !== "Sym") return { ...s, control: { tag: "Val", v: VFalse } };
    return { ...s, control: { tag: "Val", v: macroTable.has(args[0].name) ? VTrue : VFalse } };
  }});

  // list-macros: List all registered macros
  def("list-macros", { tag: "Native", name: "list-macros", arity: 0, fn: (args, s) => {
    let result: Val = VUnit;
    for (const name of macroTable.keys()) {
      result = { tag: "Vector", items: [{ tag: "Sym", name }, result] };
    }
    return { ...s, control: { tag: "Val", v: result } };
  }});

  if (includeExtendedPrims) {
    // Provenance primitives (provenance graph + evidence operations)
    registerProvenancePrims(def);

    // Solver primitives (budgeted search, fixpoints, repair loops, facts)
    registerSolverPrims(def, {
      applyProcedure,
      ensureArity,
      isCallable,
      emitAmb: (choices, state) => emitAmbChoose(choices, state),
    });

    // Solve language primitives (HETA-style recursive decomposition)
    const solveHelpers = {
      applyProcedure,
      evalProcedureToValue: (proc: Val, args: Val[], state: State) => {
        // Apply the procedure to get initial state
        const result = applyProcedure(proc, args, state);

        // If it's already a value (Native function), return it
        if ("control" in result && result.control.tag === "Val") {
          return { value: result.control.v, state: result };
        }

        // For Closures, run the machine to completion synchronously
        let st = result as State;
        const maxSteps = 100000;

        for (let i = 0; i < maxSteps; i++) {
          const out = stepOnce(st);

          if (out.tag === "State") {
            st = out.state;
            continue;
          }

          if (out.tag === "Done") {
            // Return the value and reconstruct a valid state
            const finalState: State = {
              control: { tag: "Val", v: out.value },
              env: st.env,
              kont: [],
              store: st.store,
              handlers: [],
            };
            return { value: out.value, state: finalState };
          }

          if (out.tag === "Op") {
            // Check for registered sync effect handler
            const syncHandler = getSyncEffectHandler(out.opcall.op);
            if (syncHandler) {
              // Handle effect synchronously
              const result = syncHandler(out.opcall.args, st);
              // Resume with the result
              st = out.opcall.resumption.invoke(result);
              continue;
            }
            // No handler - throw
            throw new Error(`Solve algebra procedure used effect '${out.opcall.op}' - effects not supported in solve. Register with registerSyncEffectHandler().`);
          }
        }

        throw new Error("Solve algebra procedure exceeded step limit");
      },
      isCallable,
    };

    registerSolvePrims(def, solveHelpers);

    // Tier B primitives: DAG, repair-until, multi-method selection
    registerTierBPrims(def, solveHelpers);

    // Continuation-calculus primitives (typed continuations, convergence, witnesses)
    registerContinuationPrims(def, {});
  }

  // ─────────────────────────────────────────────────────────────────
  // read-data.v1: Safe S-expression reader for LLM witness payloads
  // Parses a restricted subset: numbers, strings, symbols, booleans, lists
  // NO evaluation, NO macros, NO reader escapes
  // ─────────────────────────────────────────────────────────────────

  type SexpAST =
    | { t: "sym"; v: string }
    | { t: "str"; v: string }
    | { t: "num"; v: number }
    | { t: "bool"; v: boolean }
    | { t: "list"; v: SexpAST[] };

  type SexpTok =
    | { t: "lp" }
    | { t: "rp" }
    | { t: "str"; v: string }
    | { t: "num"; v: number }
    | { t: "bool"; v: boolean }
    | { t: "sym"; v: string };

  function tokenizeData(input: string): SexpTok[] {
    const toks: SexpTok[] = [];
    let i = 0;
    const fail = (msg: string): never => {
      throw new Error(`read-data.v1: ${msg} at index ${i}`);
    };

    while (i < input.length) {
      const ch = input[i];

      // comments: ; to end of line
      if (ch === ";") {
        while (i < input.length && input[i] !== "\n") i++;
        continue;
      }

      // whitespace
      if (ch === " " || ch === "\n" || ch === "\t" || ch === "\r") {
        i++;
        continue;
      }

      if (ch === "(") {
        toks.push({ t: "lp" });
        i++;
        continue;
      }

      if (ch === ")") {
        toks.push({ t: "rp" });
        i++;
        continue;
      }

      if (ch === '"') {
        i++;
        let s = "";
        while (i < input.length) {
          const c = input[i];
          if (c === '"') {
            i++;
            break;
          }
          if (c === "\\") {
            i++;
            if (i >= input.length) fail("unterminated string escape");
            const esc = input[i];
            if (esc === "n") s += "\n";
            else if (esc === "t") s += "\t";
            else if (esc === "r") s += "\r";
            else if (esc === '"' || esc === "\\") s += esc;
            else fail(`unsupported escape \\${esc}`);
            i++;
            continue;
          }
          s += c;
          i++;
        }
        toks.push({ t: "str", v: s });
        continue;
      }

      // atom: read until delimiter
      const isDelimiter = (c: string) => c === " " || c === "\n" || c === "\t" || c === "\r" || c === "(" || c === ")" || c === '"' || c === ";";
      let atom = "";
      while (i < input.length && !isDelimiter(input[i])) {
        atom += input[i];
        i++;
      }

      if (atom === ".") fail("dotted pairs are not permitted");
      if (atom.length === 0) continue;

      // booleans
      if (atom === "#t" || atom.toLowerCase() === "true") {
        toks.push({ t: "bool", v: true });
        continue;
      }
      if (atom === "#f" || atom.toLowerCase() === "false") {
        toks.push({ t: "bool", v: false });
        continue;
      }

      // numeric?
      if (/^[+-]?\d+(\.\d+)?$/.test(atom)) {
        toks.push({ t: "num", v: Number(atom) });
        continue;
      }

      // symbol — restrict to a conservative set
      if (!/^[:A-Za-z0-9_+\-*/?.!]+$/.test(atom)) {
        fail(`illegal symbol token: ${atom}`);
      }
      toks.push({ t: "sym", v: atom });
    }

    return toks;
  }

  function parseDataTokens(toks: SexpTok[]): SexpAST {
    let idx = 0;
    const fail = (msg: string): never => {
      throw new Error(`read-data.v1: ${msg} at token ${idx}`);
    };

    const parseOne = (): SexpAST => {
      if (idx >= toks.length) fail("unexpected EOF");
      const tok = toks[idx++];

      if (tok.t === "lp") {
        const items: SexpAST[] = [];
        while (idx < toks.length && toks[idx].t !== "rp") {
          items.push(parseOne());
        }
        if (idx >= toks.length) fail("unterminated list");
        idx++; // consume rp
        return { t: "list", v: items };
      }

      if (tok.t === "rp") fail("unexpected ')'");
      if (tok.t === "str") return { t: "str", v: tok.v };
      if (tok.t === "num") return { t: "num", v: tok.v };
      if (tok.t === "bool") return { t: "bool", v: tok.v };
      if (tok.t === "sym") return { t: "sym", v: tok.v };
      return fail("unknown token");
    };

    const ast = parseOne();
    if (idx !== toks.length) {
      fail("trailing tokens after first form (must be exactly one form)");
    }
    return ast;
  }

  function astToVal(ast: SexpAST): Val {
    switch (ast.t) {
      case "num": return { tag: "Num", n: ast.v };
      case "str": return { tag: "Str", s: ast.v };
      case "bool": return ast.v ? VTrue : VFalse;
      case "sym": return { tag: "Sym", name: ast.v };
      case "list": {
        // Build proper list structure (cons-cell list)
        let result: Val = VUnit; // () as nil
        for (let i = ast.v.length - 1; i >= 0; i--) {
          result = { tag: "Vector", items: [astToVal(ast.v[i]), result] };
        }
        return result;
      }
    }
  }

  def("read-data.v1", {
    tag: "Native",
    name: "read-data.v1",
    arity: 1,
    fn: (args, s) => {
      const input = args[0];
      if (input.tag !== "Str") {
        throw new Error("read-data.v1: expected string argument");
      }
      const toks = tokenizeData((input as any).s);
      const ast = parseDataTokens(toks);
      const val = astToVal(ast);
      return { ...s, control: { tag: "Val", v: val } };
    },
  });

  return { env, store: st };
}

export function installKernelPrims(store: Store): { env: Env; store: Store } {
  return installPrims(store, {
    includePackageManifest: false,
    includeExtendedPrims: false,
  });
}

// ─────────────────────────────────────────────────────────────────
// IO Primitives (Node.js-dependent) — extracted to prims-io.ts
// We pass registerSyncEffectHandler to avoid circular imports.
// ─────────────────────────────────────────────────────────────────
import { registerIoPrims } from "./prims-io";
registerIoPrims(registerSyncEffectHandler);
