// src/framework/shadowlisp/SlMetaEval.ts
//
// Vision 49: The Metacircular Evaluator
//
// Gerald Sussman (SICP co-author, Scheme designer):
//   "A metacircular evaluator is the crown jewel of language implementation.
//    It defines the language in terms of itself."
//
// This is the evaluator that makes ShadowLisp metacircular.
// It's a DOM element that interprets other DOM elements.

import type { SExpr } from '../../lisp/SExpr';
import type { SlEnv } from './SlBootstrap';
import { SlBootstrap, isClosure, type ISlClosure } from './SlBootstrap';
import {
  type ISlEvaluable,
  type ISlEvalContext,
  isSlEvaluable,
} from './primitives/ISlEvaluable';
import { applySlClosure } from './primitives/SlLambda';

// Import all primitives to ensure they're registered
import './primitives';

const SlMetaEvalElementBase = HTMLElement;

function warnUnknownElement(tagName: string): void {
  // eslint-disable-next-line no-console -- kernel metacircular evaluator diagnostics have no actor context
  console.warn(`[SlMetaEval] Unknown element: ${tagName}`);
}

/**
 * SlMetaEval - The Metacircular Evaluator element.
 *
 * This element provides the `evaluate` function that interprets
 * ShadowLisp DOM elements. It's the "CPU" of the ShadowLisp machine.
 *
 * @example
 * <sl-meta-eval id="cpu">
 *   <!-- Child elements are evaluated on demand -->
 * </sl-meta-eval>
 */
export class SlMetaEval extends SlMetaEvalElementBase {
  private _env: SlEnv = [];

  constructor() {
    super();
  }

  connectedCallback(): void {
    // Initialize with standard bindings
    this._initStandardEnv();
  }

  /**
   * Get the current environment.
   */
  get env(): SlEnv {
    return this._env;
  }

  /**
   * Initialize standard environment with built-in values.
   */
  private _initStandardEnv(): void {
    // Boolean constants
    this._env.push(['true', true]);
    this._env.push(['false', false]);
    this._env.push(['nil', null]);
  }

  /**
   * Create an evaluation context.
   */
  private _createContext(env: SlEnv): ISlEvalContext {
    return {
      env,
      runtime: this,
      evaluate: (expr, overrideEnv) => this.evaluate(expr, overrideEnv ?? env),
    };
  }

  /**
   * Evaluate a ShadowLisp expression.
   *
   * This is the main entry point for evaluation.
   * Accepts either a DOM element or an S-expression.
   *
   * @param expr - The expression to evaluate (DOM element or S-expr)
   * @param env - Optional environment (defaults to global)
   * @returns The result of evaluation
   */
  async evaluate(expr: SExpr | HTMLElement, env?: SlEnv): Promise<SExpr> {
    const currentEnv = env ?? this._env;
    const ctx = this._createContext(currentEnv);

    // If it's a DOM element, evaluate it
    if (expr instanceof HTMLElement) {
      return this._evalElement(expr, ctx);
    }

    // Otherwise it's an S-expression - evaluate it
    return this._evalSExpr(expr, ctx);
  }

  /**
   * Evaluate a DOM element.
   */
  private async _evalElement(el: HTMLElement, ctx: ISlEvalContext): Promise<SExpr> {
    // If the element is evaluable, use its slEval method
    if (isSlEvaluable(el)) {
      return el.slEval(ctx);
    }

    // Unknown element - return null
    warnUnknownElement(el.tagName);
    return null;
  }

  /**
   * Evaluate an S-expression.
   *
   * This handles the case where we have raw S-expr data
   * (e.g., from closures or external sources).
   */
  private async _evalSExpr(expr: SExpr, ctx: ISlEvalContext): Promise<SExpr> {
    // Self-evaluating: numbers
    if (typeof expr === 'number') {
      return expr;
    }

    // Self-evaluating: booleans
    if (typeof expr === 'boolean') {
      return expr;
    }

    // Self-evaluating: null
    if (expr === null) {
      return null;
    }

    // Strings that start with " are string literals
    if (typeof expr === 'string') {
      if (expr.startsWith('"') && expr.endsWith('"')) {
        // String literal - return without quotes
        return expr.slice(1, -1);
      }
      // Symbol - look up in environment (cast to SExpr, closures handled by _apply)
      return SlBootstrap.lookup(expr, ctx.env) as SExpr;
    }

    // List - evaluate as function application or special form
    if (Array.isArray(expr)) {
      if (expr.length === 0) {
        return [];
      }

      const [op, ...args] = expr;

      // Handle special forms
      if (typeof op === 'string') {
        switch (op) {
          case 'quote':
            return args[0];

          case 'if': {
            const test = await this._evalSExpr(args[0], ctx);
            if (test !== false && test !== null) {
              return this._evalSExpr(args[1], ctx);
            }
            return args[2] !== undefined ? this._evalSExpr(args[2], ctx) : null;
          }

          case 'lambda': {
            const params = args[0] as string[];
            const body = args[1];
            const closure: ISlClosure = {
              type: 'closure',
              params: Array.isArray(params) ? params : [params],
              body,
              env: [...ctx.env],
            };
            return closure as unknown as SExpr;
          }

          case 'define': {
            const name = args[0] as string;
            const value = await this._evalSExpr(args[1], ctx);
            ctx.env.unshift([name, value]);
            return value;
          }

          case 'set!': {
            const name = args[0] as string;
            const value = await this._evalSExpr(args[1], ctx);
            for (const binding of ctx.env) {
              if (binding[0] === name) {
                binding[1] = value;
                return value;
              }
            }
            throw new Error(`Cannot set undefined: ${name}`);
          }

          case 'begin': {
            let result: SExpr = null;
            for (const subExpr of args) {
              result = await this._evalSExpr(subExpr, ctx);
            }
            return result;
          }

          case 'cond': {
            for (const clause of args) {
              const [test, result] = clause as [SExpr, SExpr];
              const testResult = await this._evalSExpr(test, ctx);
              if (testResult !== false && testResult !== null) {
                return this._evalSExpr(result, ctx);
              }
            }
            return null;
          }

          case 'list': {
            const results: SExpr[] = [];
            for (const arg of args) {
              results.push(await this._evalSExpr(arg, ctx));
            }
            return results;
          }

          // McCarthy primitives that need special handling
          case 'car':
            return SlBootstrap.car(await this._evalSExpr(args[0], ctx));

          case 'cdr':
            return SlBootstrap.cdr(await this._evalSExpr(args[0], ctx) as SExpr[]);

          case 'cons': {
            const head = await this._evalSExpr(args[0], ctx);
            const tail = await this._evalSExpr(args[1], ctx);
            return SlBootstrap.cons(head, tail);
          }

          case 'atom?':
          case 'atom':
            return SlBootstrap.atom(await this._evalSExpr(args[0], ctx));

          case 'eq?':
          case 'eq': {
            const a = await this._evalSExpr(args[0], ctx);
            const b = await this._evalSExpr(args[1], ctx);
            return SlBootstrap.eq(a, b);
          }

          case 'null?':
            return SlBootstrap.isNull(await this._evalSExpr(args[0], ctx));

          case 'pair?':
            return SlBootstrap.isPair(await this._evalSExpr(args[0], ctx));

          case 'number?':
            return SlBootstrap.isNumber(await this._evalSExpr(args[0], ctx));

          case 'string?':
            return SlBootstrap.isString(await this._evalSExpr(args[0], ctx));

          case 'boolean?':
            return SlBootstrap.isBoolean(await this._evalSExpr(args[0], ctx));

          // Arithmetic (bridge to JS)
          case '+': {
            let sum = 0;
            for (const arg of args) {
              sum = SlBootstrap.add(sum, await this._evalSExpr(arg, ctx) as number);
            }
            return sum;
          }

          case '-': {
            if (args.length === 0) return 0;
            let result = await this._evalSExpr(args[0], ctx) as number;
            if (args.length === 1) return -result;
            for (let i = 1; i < args.length; i++) {
              result = SlBootstrap.sub(result, await this._evalSExpr(args[i], ctx) as number);
            }
            return result;
          }

          case '*': {
            let product = 1;
            for (const arg of args) {
              product = SlBootstrap.mul(product, await this._evalSExpr(arg, ctx) as number);
            }
            return product;
          }

          case '/': {
            if (args.length === 0) return 1;
            let result = await this._evalSExpr(args[0], ctx) as number;
            for (let i = 1; i < args.length; i++) {
              result = SlBootstrap.div(result, await this._evalSExpr(args[i], ctx) as number);
            }
            return result;
          }

          case '>': {
            const a = await this._evalSExpr(args[0], ctx) as number;
            const b = await this._evalSExpr(args[1], ctx) as number;
            return SlBootstrap.gt(a, b);
          }

          case '<': {
            const a = await this._evalSExpr(args[0], ctx) as number;
            const b = await this._evalSExpr(args[1], ctx) as number;
            return SlBootstrap.lt(a, b);
          }

          case '>=': {
            const a = await this._evalSExpr(args[0], ctx) as number;
            const b = await this._evalSExpr(args[1], ctx) as number;
            return SlBootstrap.gte(a, b);
          }

          case '<=': {
            const a = await this._evalSExpr(args[0], ctx) as number;
            const b = await this._evalSExpr(args[1], ctx) as number;
            return SlBootstrap.lte(a, b);
          }

          case '=': {
            const a = await this._evalSExpr(args[0], ctx);
            const b = await this._evalSExpr(args[1], ctx);
            return a === b;
          }

          // Accessor shortcuts
          case 'cadr':
            return SlBootstrap.cadr(await this._evalSExpr(args[0], ctx));

          case 'caddr':
            return SlBootstrap.caddr(await this._evalSExpr(args[0], ctx));

          case 'cadddr':
            return SlBootstrap.cadddr(await this._evalSExpr(args[0], ctx));
        }
      }

      // Not a special form - function application
      const fn = await this._evalSExpr(op, ctx);

      // Evaluate arguments
      const evaledArgs: SExpr[] = [];
      for (const arg of args) {
        evaledArgs.push(await this._evalSExpr(arg, ctx));
      }

      // Apply the function
      return this._apply(fn, evaledArgs, ctx);
    }

    // Unknown expression type
    throw new Error(`Cannot evaluate: ${JSON.stringify(expr)}`);
  }

  /**
   * Apply a function to arguments.
   */
  private async _apply(fn: SExpr, args: SExpr[], ctx: ISlEvalContext): Promise<SExpr> {
    // If it's a closure, apply it
    if (isClosure(fn)) {
      // Pass runtime context which may have transport
      return applySlClosure(fn, args, ctx.runtime as { transport?: unknown } | undefined);
    }

    // Unknown function
    throw new Error(`Cannot apply: ${JSON.stringify(fn)}`);
  }

  /**
   * Evaluate all child elements and return results.
   */
  async evalChildren(): Promise<SExpr[]> {
    const results: SExpr[] = [];
    const ctx = this._createContext(this._env);

    for (const child of this.children) {
      if (child instanceof HTMLElement && isSlEvaluable(child)) {
        results.push(await child.slEval(ctx));
      }
    }

    return results;
  }

  /**
   * Run evaluation on all child elements.
   * Called automatically when the element becomes visible.
   */
  async run(): Promise<SExpr> {
    const results = await this.evalChildren();
    return results.length > 0 ? results[results.length - 1] : null;
  }
}

// Register the custom element
if (!customElements.get('sl-meta-eval')) {
  customElements.define('sl-meta-eval', SlMetaEval);
}
