// src/runtime.ts
// OmegaRuntime - Clean API for external integrations
//
// Usage:
//   import { OmegaRuntime } from "omega-llm";
//
//   const omega = new OmegaRuntime();
//   const result = await omega.eval("(+ 1 2)");
//   console.log(result.value); // 3

import type { Val } from "./core/eval/values";
import type { OracleAdapter } from "../packages/oracle/core/src/adapter";
import type { MeaningVal } from "./core/meaning";
import type { Ctx } from "./core/ctx/ctx";
import type { CapSet } from "./core/governance/caps";
import type { BudgetLimits } from "./core/governance/budgets";
import type { Profile } from "./core/governance/profile";
import type { State } from "./core/eval/machine";
import type { Expr } from "./core/ast";
import type { DistVal } from "./core/eval/dist";

import { ctxRootFromProfile, ctxSeal, ctxApplyProfile } from "./core/ctx/ctx";
import { DEFAULT_PROFILE } from "./core/governance/profile";
import { COWStore } from "./core/eval/store";
import { installPrims } from "./core/prims";
import { runToCompletion } from "./core/eval/run";
import { RuntimeImpl } from "./core/effects/runtimeImpl";
import { compileTextToExpr } from "./core/pipeline/compileText";
import { DepthTrackingAdapter } from "../packages/oracle/core/src/adapters/types";
import { SnapshotRepo } from "../packages/oracle/core/src/snapshots";
import { InMemoryReceiptStore } from "../packages/oracle/core/src/receipts";
import type { DriverRegistry } from "@open-matrix/inference";

/**
 * Configuration for OmegaRuntime
 */
export type OmegaConfig = {
  /** Oracle adapter for LLM calls (optional - auto-detected from API keys if not provided) */
  adapter?: OracleAdapter;

  /** Maximum Oracle recursion depth (default: 4) */
  maxOracleDepth?: number;

  /** Default capability set for new contexts */
  defaultCaps?: CapSet;

  /** Default budget limits for new contexts */
  defaultBudgets?: BudgetLimits;

  /** Truth regime profile (default: DEFAULT_PROFILE) */
  profile?: Profile;

  /** Maximum evaluation steps (default: 500000) */
  maxSteps?: number;

  /** Driver registry for named-driver routing (VISION Section 3.6) */
  registry?: DriverRegistry;

  /** Default inference-driver name when payload has no :driver (VISION 3.8) */
  defaultDriver?: string;
};

/**
 * Result of an eval operation
 */
export type EvalResult = {
  /** Whether evaluation succeeded */
  ok: boolean;

  /** The result value (if ok) */
  value?: Val;

  /** Error message (if not ok) */
  error?: string;
};

/**
 * Result of an infer operation
 */
export type InferResult = {
  /** Whether inference succeeded */
  ok: boolean;

  /** The Meaning returned by Oracle */
  meaning?: MeaningVal;

  /** Just the denotation (convenience accessor) */
  value?: Val;

  /** Confidence score (0-1) */
  confidence?: number;

  /** Error message (if not ok) */
  error?: string;
};

/**
 * Auto-populate a DriverRegistry from environment variables (VISION 3.8).
 * Uses dynamic imports — silently skips any driver package not installed.
 * Call this at the application boundary (REPL, CLI) before creating OmegaRuntime.
 */
export async function populateRegistryFromEnv(registry: DriverRegistry): Promise<void> {
  // Helper: dynamic import that TypeScript won't resolve at compile time.
  // This is intentional — driver packages are optional peer dependencies.
  // Vite/Vitest must also leave these unresolved until runtime.
  const tryImport = async (...pkgs: string[]): Promise<any> => {
    for (const pkg of pkgs) {
      try { return await import(/* @vite-ignore */ /* webpackIgnore: true */ pkg); }
      catch { /* try next */ }
    }
    return undefined;
  };

  // Anthropic — register if API key present
  if (process.env.ANTHROPIC_API_KEY) {
    const mod = await tryImport("@open-matrix/driver-anthropic", "@omega/driver-anthropic");
    if (mod?.AnthropicDriver) {
      const driver = new mod.AnthropicDriver({
        apiKey: process.env.ANTHROPIC_API_KEY,
        defaultModel: "claude-sonnet-4-20250514",
      });
      registry.register("inference-reentrant", driver);
    }
  }

  // OpenAI — register if API key present
  if (process.env.OPENAI_API_KEY) {
    const mod = await tryImport("@open-matrix/driver-openai", "@omega/driver-openai");
    if (mod?.OpenAIDriver) {
      const driver = new mod.OpenAIDriver({
        apiKey: process.env.OPENAI_API_KEY,
        defaultModel: "gpt-4o",
      });
      registry.register("inference-tools", driver);
    }
  }

  // Ollama — always register (local, no key needed)
  {
    const mod = await tryImport("@open-matrix/driver-ollama", "@omega/driver-ollama");
    if (mod?.OllamaDriver) {
      registry.register("inference-local", new mod.OllamaDriver({ model: "llama3.1" }));
    }
  }

  // ChatGPT browser automation — register if Chrome debug port configured
  if (process.env.CHATGPT_CHROME_PORT) {
    const mod = await tryImport("@omega/driver-chatgpt");
    if (mod?.ChatGPTDriver) {
      const driver = new mod.ChatGPTDriver({ port: +process.env.CHATGPT_CHROME_PORT });
      registry.register("browser-agent-fast", driver);
    }
  }

  // Claude CLI subprocess — always register
  {
    const mod = await tryImport("@omega/driver-claude-cli");
    if (mod?.ClaudeCLIDriver) {
      registry.register("claude-code-cli", new mod.ClaudeCLIDriver());
    }
  }

  // Codex CLI subprocess — always register
  {
    const mod = await tryImport("@omega/driver-codex-cli");
    if (mod?.CodexCliDriver) {
      registry.register("codex-cli", new mod.CodexCliDriver());
    }
  }
}

// Mock commit adapter for standalone runtime
const mockCommitAdapter = {
  async commit(_payload: Val, _ctxDigest: string): Promise<Val> {
    return { tag: "Str", s: "commit:" + Math.random().toString(16).slice(2) } as Val;
  }
};

// Note: Sync effect handlers for solve are registered in core/prims.ts

// installPrims is now imported from ./core/prims (117 production primitives)

/**
 * OmegaRuntime - Main entry point for external integrations
 *
 * Provides a clean, promise-based API for:
 * - Evaluating Lisp expressions
 * - Running Oracle-powered inference
 * - Managing contexts with governance
 * - Integrating with VS Code, CLI tools, etc.
 */
export class OmegaRuntime {
  private adapter?: OracleAdapter;
  private config: OmegaConfig;
  private rootCtx: Ctx;
  private maxSteps: number;

  constructor(config: OmegaConfig = {}) {
    this.config = config;
    this.maxSteps = config.maxSteps ?? 500_000;

    const profile = config.profile ?? DEFAULT_PROFILE;
    this.rootCtx = ctxRootFromProfile(profile);

    // Determine which adapter to use
    let baseAdapter: OracleAdapter | undefined = config.adapter;
    // If no adapter provided, infer.op will fail gracefully at runtime.
    // Use DriverRegistry-based adapters or provide an adapter explicitly.

    // Wrap adapter with depth tracking if we have one
    if (baseAdapter) {
      this.adapter = new DepthTrackingAdapter(
        baseAdapter,
        config.maxOracleDepth ?? 4
      );
    }

    // Apply profile restrictions if non-default caps/budgets specified
    if (config.defaultCaps || config.defaultBudgets) {
      const restrictProfile: Profile = {
        name: "custom",
        caps: config.defaultCaps ?? profile.caps,
        budgets: config.defaultBudgets ?? profile.budgets,
        truth: profile.truth,
      };
      this.rootCtx = ctxApplyProfile(this.rootCtx, restrictProfile);
    }
  }

  /**
   * Create initial machine state from expression
   */
  private initialState(expr: Expr): State {
    const store0 = new COWStore();
    const prim = installPrims(store0);
    return {
      control: { tag: "Expr", e: expr },
      env: prim.env,
      store: prim.store,
      kont: [],
      handlers: [],
    };
  }

  /**
   * Evaluate a Lisp expression
   *
   * @param code - Lisp source code
   * @param options - Optional context overrides
   * @returns EvalResult with value or error
   *
   * @example
   * const result = await omega.eval("(+ 1 2 3)");
   * console.log(result.value); // 6
   */
  async eval(
    code: string,
    options?: { sealed?: boolean }
  ): Promise<EvalResult> {
    try {
      // Compile text to expression
      const expr = compileTextToExpr(code);

      // Set up context
      let ctx = this.rootCtx;
      if (options?.sealed) {
        ctx = ctxSeal(ctx);
      }

      // Create runtime with Oracle support
      const snapshots = new SnapshotRepo();
      const receipts = new InMemoryReceiptStore("off");
      // Compute defaultDriver: explicit config, or first reentrant driver in registry
      const defaultDriver = this.config.defaultDriver
        ?? this.config.registry?.query(c => c.oracleProtocol.reentrant)[0]?.name;

      const runtime = new RuntimeImpl(
        this.adapter,  // May be undefined — legacy infer.op will error, driver-routed path works
        snapshots,
        receipts,
        mockCommitAdapter,
        this.config.profile,
        undefined,  // budgetTracker — managed by run loop
        this.config.registry,
        defaultDriver,
      );

      // Run to completion
      const value = await runToCompletion(runtime, this.initialState(expr), this.maxSteps);

      return { ok: true, value };
    } catch (e) {
      return { ok: false, error: String(e) };
    }
  }

  /**
   * Run Oracle-powered inference
   *
   * @param query - Natural language or structured query
   * @param options - Optional context and confidence threshold
   * @returns InferResult with meaning or error
   *
   * @example
   * const result = await omega.infer("Simplify: 2x + 3x");
   * if (result.ok && result.confidence > 0.8) {
   *   console.log(result.value);
   * }
   */
  async infer(
    query: string | Record<string, unknown>,
    options?: { minConfidence?: number; sealed?: boolean }
  ): Promise<InferResult> {
    if (!this.adapter) {
      return { ok: false, error: "No Oracle adapter configured" };
    }

    // Build the infer expression
    const payload = typeof query === "string"
      ? `"${query.replace(/"/g, '\\"')}"`
      : JSON.stringify(query);
    const code = `(effect int.op ${payload})`;

    try {
      const result = await this.eval(code, { sealed: options?.sealed });

      if (!result.ok) {
        return { ok: false, error: result.error };
      }

      const val = result.value;
      if (val && typeof val === "object" && "tag" in val && val.tag === "Meaning") {
        const meaning = val as MeaningVal;
        const confidence = meaning.confidence ?? 0;

        if (options?.minConfidence !== undefined && confidence < options.minConfidence) {
          return {
            ok: false,
            error: `Confidence ${confidence} below threshold ${options.minConfidence}`,
            meaning,
            confidence,
          };
        }

        return {
          ok: true,
          meaning,
          value: meaning.denotation as Val | undefined,
          confidence,
        };
      }

      return { ok: false, error: "Inference did not return a Meaning" };
    } catch (e) {
      return { ok: false, error: String(e) };
    }
  }

  /**
   * Multi-shot search returning a distribution of solutions
   *
   * @param query - Query for the Oracle
   * @param options - Optional sample count and threshold
   * @returns Array of candidate solutions with probabilities
   */
  async search(
    query: string | Record<string, unknown>,
    options?: { maxCandidates?: number; minProb?: number }
  ): Promise<{ candidates: Array<{ value: Val | undefined; prob: number; confidence: number }> }> {
    if (!this.adapter) {
      return { candidates: [] };
    }

    const payload = typeof query === "string"
      ? `"${query.replace(/"/g, '\\"')}"`
      : JSON.stringify(query);
    const code = `(effect search.op ${payload})`;

    const result = await this.eval(code);
    if (!result.ok || !result.value) {
      return { candidates: [] };
    }

    const val = result.value;
    if (typeof val === "object" && "tag" in val && val.tag === "Dist") {
      const distVal = val as DistVal;
      const candidates = distVal.support
        .filter((item) => options?.minProb === undefined || item.w >= options.minProb)
        .slice(0, options?.maxCandidates ?? 10)
        .map((item) => {
          const meaning = item.v as MeaningVal;
          return {
            value: meaning.denotation as Val | undefined,
            prob: item.w,
            confidence: meaning.confidence ?? 0,
          };
        });
      return { candidates };
    }

    return { candidates: [] };
  }

  /**
   * Create a sealed sandbox for untrusted inference
   *
   * @param caps - Restricted capability set
   * @returns A new OmegaRuntime with sealed context
   */
  sandbox(caps: CapSet): OmegaRuntime {
    const sandboxed = new OmegaRuntime({
      ...this.config,
      defaultCaps: caps,
    });
    sandboxed.rootCtx = ctxSeal(sandboxed.rootCtx);
    return sandboxed;
  }

  /**
   * Get the underlying adapter (for advanced use)
   */
  getAdapter(): OracleAdapter | undefined {
    return this.adapter;
  }

  /**
   * Check if Oracle is available
   */
  hasOracle(): boolean {
    return !!this.adapter;
  }
}

/**
 * Quick eval helper - creates a runtime and evaluates code
 *
 * @example
 * const result = await evalOmegaCode("(+ 1 2)");
 * if (result.ok) console.log(result.value);
 */
export async function evalOmegaCode(code: string): Promise<EvalResult> {
  const runtime = new OmegaRuntime();
  return runtime.eval(code);
}

// Re-export package resolution system
export { RequireResolver, createResolver, PackageRegistry, parseDefinePackage } from "./core/require";
export type { ResolverConfig, ResolvedPackage, PackageManifest } from "./core/require";
