// SOURCE: ARCHITECTURE/32-LANGUAGE-OFFICIAL-13-IMPLEMENTATION-16.md
// Refactored: EffectBackend-based dispatch (VISION Section 4)
// Original if/else chain → Map<string, EffectBackend>

import type { Runtime, DispatchResult } from "../eval/runtime";
import type { State, HandlerFrame } from "../eval/machine";
import type { OpCall } from "./opcall";
import type { Val } from "../eval/values";
import { VUnit } from "../eval/values";
import { envSet } from "../eval/env";
import type { Resumption } from "./opcall";

// Oracle Protocol imports (still needed for constructor compatibility)
import type { OracleAdapter, SnapshotRepo, ReceiptStore } from "../../package-bridge/oracle-runtime";
import {
  createAmbRuntimeBackend,
  createCacheRuntimeBackend,
  createCommitRuntimeBackend,
  createEmacsRuntimeBackend,
  createOracleRuntimeBackend,
  type AmbStrategy,
  type AmbBackendLike,
  type OracleBackendLike,
} from "../../package-bridge/lazy-runtime-backends";

// Governance imports
import type { Profile } from "../governance/profile";
import { DEFAULT_PROFILE } from "../governance/profile";
import type { BudgetTracker } from "../governance/budgets";

// Backend imports
import type { EffectBackend } from "./backend";
import { SystemIOBackend } from "./backends/systemio";
import { SleepBackend } from "./backends/sleep";

// Driver system imports (JOB-04)
import type { DriverRegistry } from "@open-matrix/inference";

export type { AmbStrategy };

export interface CommitAdapter {
  commit(payload: Val, ctxDigest: string): Promise<Val>;
}

type LazyBackendGroup = "oracle" | "amb" | "commit" | "cache" | "emacs";

type OracleBudgetAwareBackend = OracleBackendLike;
type AmbRuntimeBackend = AmbBackendLike;

const ORACLE_OPS = new Set<string>([
  "infer.op",
  "int.op",
  "rewrite.op",
  "search.op",
  "oracle.apply.op",
  "infer-fire.op",
  "infer-await.op",
  "infer-status.op",
  "session-create.op",
  "session-list.op",
  "session-delete.op",
  "session-reset.op",
  "chatgpt.infer.op",
]);

const AMB_OPS = new Set<string>(["amb.op", "amb.choose", "amb.fail"]);
const COMMIT_OPS = new Set<string>(["commit.op", "commit-tested.op", "commit/rewrite.op"]);
const CACHE_OPS = new Set<string>(["cache.get.op", "cache.set.op", "cache.has.op", "cache.stats.op", "cache.clear.op"]);
const EMACS_OPS = new Set<string>([
  "emacs.health.op",
  "emacs.ensure.op",
  "emacs.eval.op",
  "emacs.search.op",
  "emacs.edit.op",
  "emacs.replace-all.op",
  "emacs.restart.op",
  "emacs.buffer-count.op",
]);

const BACKEND_NAME_TO_GROUP = new Map<string, LazyBackendGroup>([
  ["oracle", "oracle"],
  ["oracle-core", "oracle"],
  ["oracle/core", "oracle"],
  ["cache", "cache"],
  ["cached-infer", "cache"],
  ["oracle/cached-infer", "cache"],
  ["commit", "commit"],
  ["self-healing", "commit"],
  ["emacs", "emacs"],
  ["amb", "amb"],
  ["nondeterministic", "amb"],
  ["strategies/nondeterministic", "amb"],
]);

function findBoundaryIndex(kont: State["kont"], hid: string): number {
  for (let i = kont.length - 1; i >= 0; i--) {
    const fr = kont[i];
    if (fr.tag === "KHandleBoundary" && fr.hid === hid) return i;
  }
  return -1;
}

export class RuntimeImpl implements Runtime {
  private profile: Profile;
  private budget?: BudgetTracker;

  // Backend dispatch map: op name → backend
  private backendMap = new Map<string, EffectBackend>();

  // Constructor resources retained for lazy backend loading.
  private readonly oracle: OracleAdapter | undefined;
  private readonly snapshots: SnapshotRepo;
  private readonly receipts: ReceiptStore;
  private readonly commit: CommitAdapter;
  private readonly registry: DriverRegistry | undefined;
  private readonly defaultDriver: string | undefined;

  // References to lazily loaded backends that expose public methods.
  private ambBackend: AmbRuntimeBackend | undefined;
  private oracleBackend: OracleBudgetAwareBackend | undefined;
  private pendingAmbStrategy: AmbStrategy = "DFS";

  private readonly lazyLoadPromises = new Map<LazyBackendGroup, Promise<void>>();
  private readonly loadedLazyBackends = new Set<LazyBackendGroup>();

  constructor(
    oracle: OracleAdapter | undefined,
    snapshots: SnapshotRepo,
    receipts: ReceiptStore,
    commit: CommitAdapter,
    profile?: Profile,
    budget?: BudgetTracker,
    registry?: DriverRegistry,
    defaultDriver?: string,
  ) {
    this.oracle = oracle;
    this.snapshots = snapshots;
    this.receipts = receipts;
    this.commit = commit;
    this.registry = registry;
    this.defaultDriver = defaultDriver;
    this.profile = profile ?? DEFAULT_PROFILE;
    this.budget = budget;

    // Minimal kernel backends are always present.
    const backends: EffectBackend[] = [
      new SystemIOBackend(this.profile, this),
      new SleepBackend(),
    ];

    for (const b of backends) {
      this.registerBackend(b);
    }
  }

  /** Register an effect backend — adds all its ops to the dispatch map */
  registerBackend(backend: EffectBackend): void {
    for (const op of backend.ops) {
      this.backendMap.set(op, backend);
    }
  }

  /** Set the budget tracker (allows sharing with run loop). */
  setBudget(budget: BudgetTracker): void {
    this.budget = budget;
    this.oracleBackend?.setBudget(budget);
    this.ambBackend?.setBudget(budget);
  }

  /** Set the amb search strategy (DFS or FAIR). */
  setAmbStrategy(strategy: AmbStrategy): void {
    this.pendingAmbStrategy = strategy;
    this.ambBackend?.setStrategy(strategy);
  }

  /** Reset amb state (clears all pending alternatives). */
  resetAmb(): void {
    this.ambBackend?.reset();
  }

  /** Check if there are pending amb alternatives. */
  hasAmbAlternatives(): boolean {
    return this.ambBackend?.hasAlternatives() ?? false;
  }

  private getLazyBackendGroup(op: string): LazyBackendGroup | null {
    if (ORACLE_OPS.has(op)) return "oracle";
    if (AMB_OPS.has(op)) return "amb";
    if (COMMIT_OPS.has(op)) return "commit";
    if (CACHE_OPS.has(op)) return "cache";
    if (EMACS_OPS.has(op)) return "emacs";
    return null;
  }

  async preloadBackendByName(name: string): Promise<void> {
    const normalized = name.trim().toLowerCase();
    if (!normalized) {
      throw new Error("Backend name cannot be empty");
    }

    const group = BACKEND_NAME_TO_GROUP.get(normalized);
    if (!group) {
      throw new Error(`Unknown backend: ${name}`);
    }

    await this.ensureLazyBackendGroup(group);
  }

  private async ensureLazyBackendForOp(op: string): Promise<void> {
    await this.ensureLazyBackendGroup(this.getLazyBackendGroup(op));
  }

  private async ensureLazyBackendGroup(group: LazyBackendGroup | null): Promise<void> {
    if (!group || this.loadedLazyBackends.has(group)) {
      return;
    }

    const existing = this.lazyLoadPromises.get(group);
    if (existing) {
      await existing;
      return;
    }

    const pending = this.loadLazyBackendGroup(group)
      .then(() => {
        this.loadedLazyBackends.add(group);
      })
      .finally(() => {
        this.lazyLoadPromises.delete(group);
      });

    this.lazyLoadPromises.set(group, pending);
    await pending;
  }

  private async loadLazyBackendGroup(group: LazyBackendGroup): Promise<void> {
    switch (group) {
      case "oracle": {
        const backend = await createOracleRuntimeBackend({
          oracle: this.oracle,
          snapshots: this.snapshots,
          receipts: this.receipts,
          profile: this.profile,
          budget: this.budget,
          runtime: this,
          registry: this.registry,
          defaultDriver: this.defaultDriver,
        });
        this.oracleBackend = backend;
        this.registerBackend(backend);
        return;
      }
      case "amb": {
        const backend = await createAmbRuntimeBackend(this.budget);
        backend.setStrategy(this.pendingAmbStrategy);
        this.ambBackend = backend;
        this.registerBackend(backend);
        return;
      }
      case "commit": {
        const backend = await createCommitRuntimeBackend({
          commit: this.commit,
          profile: this.profile,
          runtime: this,
          snapshots: this.snapshots,
          receipts: this.receipts,
        });
        this.registerBackend(backend);
        return;
      }
      case "cache": {
        const backend = await createCacheRuntimeBackend();
        this.registerBackend(backend);
        return;
      }
      case "emacs": {
        const backend = await createEmacsRuntimeBackend();
        this.registerBackend(backend);
        return;
      }
    }
  }

  async dispatch(state: State, opcall: OpCall): Promise<DispatchResult> {
    // 1) Try language-level handlers (deep handlers; nearest enclosing clause wins).
    for (let hi = state.handlers.length - 1; hi >= 0; hi--) {
      const hf = state.handlers[hi];
      const clause = hf.on.get(opcall.op);
      if (!clause) continue;

      // The handler for opcall.op is hf at index hi.
      // Clause executes *outside* any inner handlers above hf (those are inside the suspended continuation).
      const handlersTrunc = state.handlers.slice(0, hi + 1);

      // Compute delimiter boundary location in the suspended continuation.
      const boundaryIndex = findBoundaryIndex(state.kont, hf.hid);
      if (boundaryIndex < 0) throw new Error(`dispatch: boundary not found for handler ${hf.hid}`);

      // Truncate continuation to delimiter boundary (discard frames inside handle body).
      // Keep the boundary itself so clause return aborts to it and triggers return clause logic.
      const kontTrunc = state.kont.slice(0, boundaryIndex + 1);

      // Construct k as a ContVal.
      const kVal: Val = { tag: "Cont", hid: hf.hid, boundaryIndex, resumption: opcall.resumption };

      // Bind params and k into handler lexical env.
      let store = state.store;
      let env = hf.env;

      // Bind operation parameters
      if (clause.params.length !== opcall.args.length) {
        throw new Error(`dispatch: op arity mismatch for ${clause.op}`);
      }
      for (let i = 0; i < clause.params.length; i++) {
        const [store2, addr] = store.alloc(opcall.args[i]);
        store = store2;
        env = envSet(env, clause.params[i], addr);
      }

      // Bind k
      {
        const [store2, addr] = store.alloc(kVal);
        store = store2;
        env = envSet(env, clause.k, addr);
      }

      // Evaluate handler clause body.
      const st2: State = {
        ...state,
        control: { tag: "Expr", e: clause.body },
        env,
        store,
        kont: kontTrunc,
        handlers: handlersTrunc,
      };
      return st2;
    }

    // 2) Find registered backend by exact op name
    const backend = this.backendMap.get(opcall.op);
    if (backend) {
      return backend.handle(state, opcall);
    }

    // 3) Lazily load package-owned backends on first use.
    await this.ensureLazyBackendForOp(opcall.op);
    const lazyBackend = this.backendMap.get(opcall.op);
    if (lazyBackend) {
      return lazyBackend.handle(state, opcall);
    }

    // 4) Uncaught
    return "Uncaught";
  }
}
