// src/framework/shadowlisp/SlBootstrap.ts
//
// Vision 49: The JavaScript Bootstrap - McCarthy's 7 Primitives
//
// John McCarthy (LISP creator, Turing Award 1971):
//   "These seven operations are sufficient to define all computable functions.
//    Everything else can be derived from them."
//
// This is the ONLY JavaScript evaluation code in metacircular ShadowLisp.
// All other operations are defined using these primitives.

import type { SExpr } from '../../lisp/SExpr';

/**
 * Type for closures created by lambda.
 * [closure, params, body, captured-env]
 *
 * For Matrix integration, closures can also store the body element's mount
 * so they can be evaluated via Matrix transport.
 */
export interface ISlClosure {
  type: 'closure';
  params: string[];
  body: SExpr;
  bodyMount?: string;    // Mount path of body element (for Matrix calls)
  lambdaMount?: string;  // Mount path of the lambda element
  env: Array<[string, SExpr | ISlClosure]>;  // Temporarily any for circular ref
}

/**
 * Runtime value type - extends SExpr to include runtime-only values.
 * Similar to FOLD-Lisp's RuntimeValue pattern.
 *
 * - SExpr: wire format (serializable)
 * - ISlClosure: runtime closures (not serializable)
 */
export type SlRuntimeValue = SExpr | ISlClosure;

/**
 * Type for environment bindings.
 * Association list: [[name, value], [name, value], ...]
 *
 * Environments hold RuntimeValues (not just SExpr) to support closures.
 */
export type SlEnv = Array<[string, SlRuntimeValue]>;

/**
 * Check if a value is a closure.
 */
export function isClosure(val: unknown): val is ISlClosure {
  return (
    typeof val === 'object' &&
    val !== null &&
    (val as ISlClosure).type === 'closure'
  );
}

/**
 * SlBootstrap - McCarthy's 7 Primitives in JavaScript
 *
 * These are the ONLY operations implemented in JavaScript.
 * Everything else (lambda semantics, let, arithmetic, etc.)
 * is defined in ShadowLisp using these primitives.
 */
export class SlBootstrap {
  /**
   * QUOTE - Return the argument unevaluated.
   *
   * (quote x) => x
   * (quote (a b c)) => (a b c)
   *
   * The argument is NOT evaluated. It's returned as-is.
   */
  static quote(expr: SExpr): SExpr {
    return expr;
  }

  /**
   * ATOM - Is this an atomic value (not a list)?
   *
   * (atom 'x) => true
   * (atom '(a b)) => false
   * (atom 5) => true
   * (atom "hello") => true
   * (atom nil) => true
   */
  static atom(expr: SExpr): boolean {
    return !Array.isArray(expr);
  }

  /**
   * EQ - Are two atomic values equal?
   *
   * (eq 'a 'a) => true
   * (eq 'a 'b) => false
   * (eq 5 5) => true
   * (eq '(a) '(a)) => false (lists aren't compared by eq)
   */
  static eq(a: SExpr, b: SExpr): boolean {
    // Only compare atoms - lists are never eq
    if (Array.isArray(a) || Array.isArray(b)) {
      return false;
    }
    return a === b;
  }

  /**
   * CAR - First element of a list.
   *
   * (car '(a b c)) => a
   * (car '(1)) => 1
   * (car '()) => null
   */
  static car(list: SExpr): SExpr {
    if (!Array.isArray(list)) {
      throw new Error(`car: expected list, got ${typeof list}`);
    }
    if (list.length === 0) {
      return null;
    }
    return list[0];
  }

  /**
   * CDR - Rest of a list (everything except first).
   *
   * (cdr '(a b c)) => (b c)
   * (cdr '(a)) => ()
   * (cdr '()) => ()
   */
  static cdr(list: SExpr): SExpr[] {
    if (!Array.isArray(list)) {
      throw new Error(`cdr: expected list, got ${typeof list}`);
    }
    if (list.length === 0) {
      return [];
    }
    return list.slice(1);
  }

  /**
   * CONS - Construct a new list by prepending an element.
   *
   * (cons 'a '(b c)) => (a b c)
   * (cons 'a '()) => (a)
   * (cons 'a 'b) => (a . b) -- we represent as [a, b]
   */
  static cons(head: SExpr, tail: SExpr): SExpr[] {
    if (Array.isArray(tail)) {
      return [head, ...tail];
    }
    // Improper list (dotted pair)
    return [head, tail];
  }

  /**
   * COND - Conditional expression.
   *
   * (cond
   *   (test1 result1)
   *   (test2 result2)
   *   (true default))
   *
   * Evaluates tests in order. Returns the result of the first true test.
   * The evalFn is used to evaluate tests and results.
   */
  static async cond(
    clauses: Array<[SExpr, SExpr]>,
    evalFn: (expr: SExpr, env: SlEnv) => Promise<SExpr>,
    env: SlEnv
  ): Promise<SExpr> {
    for (const [test, result] of clauses) {
      // Evaluate the test
      const testResult = await evalFn(test, env);

      // If test is truthy, evaluate and return the result
      if (testResult !== false && testResult !== null) {
        return evalFn(result, env);
      }
    }
    // No clause matched
    return null;
  }

  // =========================================================================
  // Environment Operations (Required for evaluation)
  // =========================================================================

  /**
   * LOOKUP - Find a variable in the environment.
   *
   * Environment is an association list: [[name, value], ...]
   */
  static lookup(name: string, env: SlEnv): SlRuntimeValue {
    for (const [varName, value] of env) {
      if (varName === name) {
        return value;
      }
    }
    throw new Error(`Undefined variable: ${name}`);
  }

  /**
   * EXTEND - Add bindings to an environment.
   *
   * Returns a new environment with the bindings prepended.
   */
  static extend(names: string[], values: SExpr[], env: SlEnv): SlEnv {
    const newBindings: SlEnv = names.map((name, i) => [name, values[i]]);
    return [...newBindings, ...env];
  }

  // =========================================================================
  // Convenience Accessors (Built from car/cdr)
  // =========================================================================

  /**
   * CADR - (car (cdr x)) - Second element.
   */
  static cadr(list: SExpr): SExpr {
    return SlBootstrap.car(SlBootstrap.cdr(list as SExpr[]));
  }

  /**
   * CADDR - (car (cdr (cdr x))) - Third element.
   */
  static caddr(list: SExpr): SExpr {
    return SlBootstrap.car(SlBootstrap.cdr(SlBootstrap.cdr(list as SExpr[])));
  }

  /**
   * CADDDR - Fourth element.
   */
  static cadddr(list: SExpr): SExpr {
    return SlBootstrap.car(
      SlBootstrap.cdr(SlBootstrap.cdr(SlBootstrap.cdr(list as SExpr[])))
    );
  }

  // =========================================================================
  // Type Predicates (Required for the metacircular evaluator)
  // =========================================================================

  /**
   * NULL? - Is this the empty list or null?
   */
  static isNull(expr: SExpr): boolean {
    if (expr === null) return true;
    if (Array.isArray(expr) && expr.length === 0) return true;
    return false;
  }

  /**
   * PAIR? - Is this a non-empty list?
   */
  static isPair(expr: SExpr): boolean {
    return Array.isArray(expr) && expr.length > 0;
  }

  /**
   * NUMBER? - Is this a number?
   */
  static isNumber(expr: SExpr): boolean {
    return typeof expr === 'number';
  }

  /**
   * STRING? - Is this a string?
   */
  static isString(expr: SExpr): boolean {
    return typeof expr === 'string';
  }

  /**
   * BOOLEAN? - Is this a boolean?
   */
  static isBoolean(expr: SExpr): boolean {
    return typeof expr === 'boolean';
  }

  /**
   * SYMBOL? - Is this a symbol (string that's not quoted)?
   * In our representation, symbols are strings without surrounding quotes.
   */
  static isSymbol(expr: SExpr): boolean {
    if (typeof expr !== 'string') return false;
    // A symbol is a string that doesn't start/end with quotes
    return !expr.startsWith('"');
  }

  // =========================================================================
  // Arithmetic Operations (Bridge to JavaScript)
  // These could be defined in ShadowLisp, but JS is more efficient
  // =========================================================================

  static add(a: number, b: number): number {
    return a + b;
  }

  static sub(a: number, b: number): number {
    return a - b;
  }

  static mul(a: number, b: number): number {
    return a * b;
  }

  static div(a: number, b: number): number {
    if (b === 0) throw new Error('Division by zero');
    return a / b;
  }

  static mod(a: number, b: number): number {
    return a % b;
  }

  static gt(a: number, b: number): boolean {
    return a > b;
  }

  static lt(a: number, b: number): boolean {
    return a < b;
  }

  static gte(a: number, b: number): boolean {
    return a >= b;
  }

  static lte(a: number, b: number): boolean {
    return a <= b;
  }
}
