// src/lisp/primitives/meta.ts
// Meta Primitives: eval, apply, lambda, define, let, quasiquote, macros, etc.
//
// These are the primitives for metaprogramming and language extension.

import type { SExpr } from '../SExpr';
import type { ILambdaClosure } from '../ILambdaClosure';
import type { IMacroDef } from '../IMacroDef';
import type { IPrimitiveDefinition } from './mccarthy';

/**
 * EVAL - Evaluate a quoted expression
 * (eval '(+ 1 2)) → 3
 */
export const evalPrimitive: IPrimitiveDefinition = {
  name: 'eval',
  handler: async (args, evaluate) => {
    const toEval = await evaluate(args[0]);
    return await evaluate(toEval as SExpr);
  },
};

/**
 * APPLY - Apply a function to a list of arguments
 * (apply + '(1 2 3)) → 6
 */
export const apply: IPrimitiveDefinition = {
  name: 'apply',
  handler: async (args, evaluate) => {
    // Requires interpreter support for lambda application
    throw new Error('apply must be handled by interpreter with lambda support');
  },
};

/**
 * LAMBDA - Create a function
 * (lambda (x y) (+ x y))
 */
export const lambda: IPrimitiveDefinition = {
  name: 'lambda',
  aliases: ['fn'],
  handler: async (args, evaluate) => {
    // Note: This is a special form - args are NOT evaluated
    // The interpreter handles this specially
    throw new Error('lambda must be handled by interpreter as special form');
  },
};

/**
 * DEFINE - Create a global binding
 * (define name value)
 * (define (fn args...) body) ; shorthand for function
 */
export const define: IPrimitiveDefinition = {
  name: 'define',
  aliases: ['def'],
  handler: async (args, evaluate) => {
    throw new Error('define must be handled by interpreter as special form');
  },
};

/**
 * LET - Local bindings
 * (let ((x 1) (y 2)) (+ x y)) → 3
 */
export const letPrimitive: IPrimitiveDefinition = {
  name: 'let',
  handler: async (args, evaluate) => {
    throw new Error('let must be handled by interpreter as special form');
  },
};

/**
 * LET* - Sequential local bindings
 * (let* ((x 1) (y (+ x 1))) y) → 2
 */
export const letStar: IPrimitiveDefinition = {
  name: 'let*',
  handler: async (args, evaluate) => {
    throw new Error('let* must be handled by interpreter as special form');
  },
};

/**
 * LETREC - Recursive local bindings
 * (letrec ((fact (lambda (n) (if (< n 2) 1 (* n (fact (- n 1))))))) (fact 5))
 */
export const letrec: IPrimitiveDefinition = {
  name: 'letrec',
  handler: async (args, evaluate) => {
    throw new Error('letrec must be handled by interpreter as special form');
  },
};

/**
 * IF - Conditional expression
 * (if condition then-expr else-expr)
 */
export const ifPrimitive: IPrimitiveDefinition = {
  name: 'if',
  handler: async (args, evaluate) => {
    throw new Error('if must be handled by interpreter as special form');
  },
};

/**
 * WHEN - One-armed if
 * (when condition expr)
 */
export const when: IPrimitiveDefinition = {
  name: 'when',
  handler: async (args, evaluate) => {
    const condition = await evaluate(args[0]);
    if (condition) {
      return await evaluate(args[1]);
    }
    return null;
  },
};

/**
 * UNLESS - Negated when
 * (unless condition expr)
 */
export const unless: IPrimitiveDefinition = {
  name: 'unless',
  handler: async (args, evaluate) => {
    const condition = await evaluate(args[0]);
    if (!condition) {
      return await evaluate(args[1]);
    }
    return null;
  },
};

/**
 * CASE - Value-based dispatch
 * (case value ((val1) result1) ((val2) result2) (else default))
 */
export const casePrimitive: IPrimitiveDefinition = {
  name: 'case',
  handler: async (args, evaluate) => {
    const val = await evaluate(args[0]);
    for (let i = 1; i < args.length; i++) {
      const clause = args[i] as SExpr[];
      const [testVal, result] = clause;
      if (testVal === 'else' || await evaluate(testVal) === val) {
        return await evaluate(result);
      }
    }
    return null;
  },
};

/**
 * DO/PROGN - Sequential evaluation
 * (do expr1 expr2 expr3) → result of expr3
 */
export const doPrimitive: IPrimitiveDefinition = {
  name: 'do',
  aliases: ['progn', 'begin'],
  handler: async (args, evaluate) => {
    let result: unknown = null;
    for (const arg of args) {
      result = await evaluate(arg);
    }
    return result;
  },
};

/**
 * QUASIQUOTE - Template with unquote
 * `(a b ,x ,@y)
 */
export const quasiquote: IPrimitiveDefinition = {
  name: 'quasiquote',
  aliases: ['`'],
  handler: async (args, evaluate) => {
    throw new Error('quasiquote must be handled by interpreter with unquote support');
  },
};

/**
 * DEFMACRO - Define a macro
 * (defmacro unless (test body) `(if (not ,test) ,body))
 */
export const defmacro: IPrimitiveDefinition = {
  name: 'defmacro',
  handler: async (args, evaluate) => {
    throw new Error('defmacro must be handled by interpreter as special form');
  },
};

/**
 * MACROEXPAND - Expand a macro call
 * (macroexpand '(unless condition body))
 */
export const macroexpand: IPrimitiveDefinition = {
  name: 'macroexpand',
  aliases: ['macroexpand-1'],
  handler: async (args, evaluate) => {
    throw new Error('macroexpand must be handled by interpreter with macro support');
  },
};

/**
 * GENSYM - Generate unique symbol
 * (gensym) → G__1234
 * (gensym "prefix") → prefix__1234
 */
export const gensym: IPrimitiveDefinition = {
  name: 'gensym',
  handler: async (args, evaluate) => {
    const prefix = args.length > 0 ? await evaluate(args[0]) : 'G';
    return `${prefix}__${Date.now()}__${Math.random().toString(36).slice(2)}`;
  },
};

/**
 * TRY/CATCH - Error handling
 * (try expr (catch e handler))
 */
export const tryPrimitive: IPrimitiveDefinition = {
  name: 'try',
  handler: async (args, evaluate) => {
    throw new Error('try must be handled by interpreter as special form');
  },
};

/**
 * THROW/ERROR - Raise an error
 * (throw "something went wrong")
 */
export const throwPrimitive: IPrimitiveDefinition = {
  name: 'throw',
  aliases: ['error'],
  handler: async (args, evaluate) => {
    const msg = await evaluate(args[0]);
    throw new Error(String(msg));
  },
};

/**
 * TYPE PREDICATES
 */
export const numberp: IPrimitiveDefinition = {
  name: 'number?',
  handler: async (args, evaluate) => {
    const val = await evaluate(args[0]);
    return typeof val === 'number' && !isNaN(val);
  },
};

export const booleanp: IPrimitiveDefinition = {
  name: 'boolean?',
  handler: async (args, evaluate) => {
    const val = await evaluate(args[0]);
    return typeof val === 'boolean';
  },
};

export const functionp: IPrimitiveDefinition = {
  name: 'function?',
  aliases: ['procedure?', 'lambda?'],
  handler: async (args, evaluate) => {
    const val = await evaluate(args[0]) as ILambdaClosure;
    return val && typeof val === 'object' && val.__type === 'lambda';
  },
};

/**
 * TYPE CONVERSION
 */
export const toNumber: IPrimitiveDefinition = {
  name: 'number',
  aliases: ['parse-int', 'int'],
  handler: async (args, evaluate) => {
    const val = await evaluate(args[0]);
    return parseInt(String(val), 10);
  },
};

export const toFloat: IPrimitiveDefinition = {
  name: 'float',
  aliases: ['parse-float'],
  handler: async (args, evaluate) => {
    const val = await evaluate(args[0]);
    return parseFloat(String(val));
  },
};

export const toString: IPrimitiveDefinition = {
  name: 'string',
  aliases: ['to-string'],
  handler: async (args, evaluate) => {
    const val = await evaluate(args[0]);
    return String(val);
  },
};

export const toBoolean: IPrimitiveDefinition = {
  name: 'boolean',
  aliases: ['bool'],
  handler: async (args, evaluate) => {
    const val = await evaluate(args[0]);
    return Boolean(val);
  },
};

/**
 * JSON
 */
export const jsonParse: IPrimitiveDefinition = {
  name: 'json-parse',
  handler: async (args, evaluate) => {
    const s = await evaluate(args[0]) as string;
    return JSON.parse(s);
  },
};

export const jsonStringify: IPrimitiveDefinition = {
  name: 'json-stringify',
  handler: async (args, evaluate) => {
    const val = await evaluate(args[0]);
    return JSON.stringify(val);
  },
};

/**
 * TIME
 */
export const now: IPrimitiveDefinition = {
  name: 'now',
  aliases: ['current-time'],
  handler: async () => Date.now(),
};

export const sleep: IPrimitiveDefinition = {
  name: 'sleep',
  aliases: ['delay'],
  handler: async (args, evaluate) => {
    const ms = await evaluate(args[0]) as number;
    await new Promise(resolve => setTimeout(resolve, ms));
    return null;
  },
};

/**
 * DEBUG
 */
export const print: IPrimitiveDefinition = {
  name: 'print',
  aliases: ['println', 'log'],
  handler: async (args, evaluate) => {
    const values = await Promise.all(args.map(a => evaluate(a)));
    // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
    console.log('[LISP]', ...values);
    return values.length === 1 ? values[0] : values;
  },
};

export const debug: IPrimitiveDefinition = {
  name: 'debug',
  handler: async (args, evaluate) => {
    const val = await evaluate(args[0]);
    // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
    console.log('[LISP DEBUG]', JSON.stringify(val, null, 2));
    return val;
  },
};

/**
 * THREAD-FIRST - Threading macro
 * (-> x (f a) (g b)) = (g (f x a) b)
 */
export const threadFirst: IPrimitiveDefinition = {
  name: '->',
  aliases: ['thread-first'],
  handler: async (args, evaluate) => {
    throw new Error('-> must be handled by interpreter as special form');
  },
};

/**
 * All meta primitives
 */
export const metaPrimitives: IPrimitiveDefinition[] = [
  evalPrimitive,
  apply,
  lambda,
  define,
  letPrimitive,
  letStar,
  letrec,
  ifPrimitive,
  when,
  unless,
  casePrimitive,
  doPrimitive,
  quasiquote,
  defmacro,
  macroexpand,
  gensym,
  tryPrimitive,
  throwPrimitive,
  numberp,
  booleanp,
  functionp,
  toNumber,
  toFloat,
  toString,
  toBoolean,
  jsonParse,
  jsonStringify,
  now,
  sleep,
  print,
  debug,
  threadFirst,
];
