// src/core/lisp/ShadowLispDispatchStrategy.ts
//
// Vision 48: ShadowLisp Handler Dispatch Strategy
//
// Gerald Sussman (SICP co-author):
//   "The dispatch strategy bridges syntax (DOM AST) and semantics (actor behavior).
//    Handlers stored as AST, evaluated when messages arrive."
//
// Carl Hewitt (Actor Model creator):
//   "An actor processes one message at a time. Whether the handler is
//    TypeScript or ShadowLisp doesn't matter - the mailbox serializes."
//
// This implements IDispatchStrategy for ShadowLisp AST handlers.
// Unlike LispDispatchStrategy (which stores handlers as strings),
// this stores handlers as S-expression AST directly.

import type { IDispatchStrategy } from '../actor/IDispatchStrategy';
import type { IMessageMeta } from '../actor/IMessageMeta';
import type { SExpr } from '../../lisp/SExpr';
import type { IMatrixContext } from '../../engine/core/IMatrixContext';
import { TopicRouter } from '../../engine/messaging/TopicRouter';
import { SlMacroExpander } from '../../framework/shadowlisp/SlMacro';
import { SlEffectContext, createHandler, type EffectHandler } from '../../framework/shadowlisp/SlEffects';
import { SlContinuationContext, DEFAULT_PROMPT, makePromptTag } from '../../framework/shadowlisp/SlContinuation';

/**
 * Simple environment for variable bindings in handler evaluation.
 * Mirrors the Environment class from SlRuntime but standalone for dispatch.
 */
class HandlerEnvironment {
  private bindings = new Map<string, unknown>();
  private parent?: HandlerEnvironment;

  constructor(parent?: HandlerEnvironment) {
    this.parent = parent;
  }

  define(name: string, value: unknown): void {
    this.bindings.set(name, value);
  }

  lookup(name: string): unknown {
    if (this.bindings.has(name)) {
      return this.bindings.get(name);
    }
    if (this.parent) {
      return this.parent.lookup(name);
    }
    return undefined;
  }

  has(name: string): boolean {
    if (this.bindings.has(name)) return true;
    if (this.parent) return this.parent.has(name);
    return false;
  }

  set(name: string, value: unknown): void {
    if (this.bindings.has(name)) {
      this.bindings.set(name, value);
    } else if (this.parent) {
      this.parent.set(name, value);
    } else {
      // Define if not found (permissive for handler use)
      this.bindings.set(name, value);
    }
  }

  extend(): HandlerEnvironment {
    return new HandlerEnvironment(this);
  }
}

/**
 * Handler definition - either AST or compiled function.
 */
interface IHandlerDef {
  /** Handler body as S-expression AST */
  ast: SExpr[];
  /** Optional parameter names */
  params?: string[];
}

/**
 * ShadowLispDispatchStrategy - Handle commands via ShadowLisp AST.
 *
 * Unlike LispDispatchStrategy (which stores handlers as strings),
 * this strategy stores handlers as DOM AST references directly.
 * The DOM IS the program - no parsing needed at dispatch time.
 *
 * @example
 * ```typescript
 * const strategy = new ShadowLispDispatchStrategy(context);
 * strategy.registerHandler('increment', [
 *   ['set!', 'count', ['+', 'count', ['get', 'payload', 'amount']]],
 *   ['emit', 'changed', ['hash', 'count', 'count']]
 * ]);
 * actor.addDispatchStrategy(strategy);
 * ```
 */
export class ShadowLispDispatchStrategy implements IDispatchStrategy {
  readonly name = 'shadowlisp';
  readonly priority = 10; // Same as LispDispatchStrategy

  private readonly handlers = new Map<string, IHandlerDef>();
  private readonly baseEnv: HandlerEnvironment;
  private readonly context: IMatrixContext;

  /** Component state - shared across all handlers */
  private readonly state: Record<string, unknown> = {};

  /** Emit callback - set by component to publish events */
  private _emitFn?: (event: string, payload: unknown) => void;

  /** Phase 4: Macro expander for syntax transformation */
  private readonly _macroExpander: SlMacroExpander;

  /** Phase 4: Effect context for algebraic effects */
  private readonly _effectContext: SlEffectContext;

  /** Phase 4: Continuation context for delimited continuations */
  private readonly _continuationContext: SlContinuationContext;

  constructor(context: IMatrixContext) {
    this.context = context;
    this.baseEnv = this._createBaseEnvironment();
    this._macroExpander = SlMacroExpander.withStandardMacros();
    this._effectContext = new SlEffectContext();
    this._continuationContext = new SlContinuationContext();
  }

  /**
   * Set the emit function for this strategy.
   * Called by the component to wire up event publishing.
   */
  setEmitFn(fn: (event: string, payload: unknown) => void): void {
    this._emitFn = fn;
  }

  /**
   * Set initial state values.
   */
  setState(initialState: Record<string, unknown>): void {
    Object.assign(this.state, initialState);
    // Also define in environment
    for (const [key, value] of Object.entries(initialState)) {
      this.baseEnv.define(key, value);
    }
  }

  /**
   * Get current state.
   */
  getState(): Record<string, unknown> {
    return { ...this.state };
  }

  /**
   * Register a handler from ShadowLisp AST.
   *
   * @param command - Command name (e.g., 'increment')
   * @param bodyAst - Handler body as array of S-expressions
   * @param params - Optional parameter names from command definition
   */
  registerHandler(command: string, bodyAst: SExpr[], params?: string[]): void {
    this.handlers.set(command, { ast: bodyAst, params });
  }

  /**
   * Check if this strategy can handle the command.
   */
  canHandle(command: string): boolean {
    return this.handlers.has(command);
  }

  /**
   * Handle the command by evaluating the ShadowLisp AST.
   */
  async handle(
    command: string,
    payload: unknown,
    meta: IMessageMeta
  ): Promise<unknown> {
    const handler = this.handlers.get(command);
    if (!handler) {
      throw new Error(`[ShadowLispDispatchStrategy] No handler for: ${command}`);
    }

    // Create handler environment with payload bound
    const handlerEnv = this.baseEnv.extend();
    handlerEnv.define('payload', payload);
    handlerEnv.define('meta', meta);
    handlerEnv.define('command', command);

    // Bind payload fields directly if params specified
    if (handler.params && payload && typeof payload === 'object') {
      for (const param of handler.params) {
        if (param in (payload as Record<string, unknown>)) {
          handlerEnv.define(param, (payload as Record<string, unknown>)[param]);
        }
      }
    }

    // Phase 4: Expand macros before evaluation
    const expandedAst = handler.ast.map(expr => this._macroExpander.expand(expr));

    // Evaluate each expression in handler body
    let result: unknown = null;
    for (const expr of expandedAst) {
      result = await this._evalSExpr(expr, handlerEnv);
    }

    return result;
  }

  /**
   * Create base environment with primitives.
   */
  private _createBaseEnvironment(): HandlerEnvironment {
    const env = new HandlerEnvironment();

    // Arithmetic
    env.define('+', (...args: number[]) => args.reduce((a, b) => a + b, 0));
    env.define('-', (a: number, b: number) => a - b);
    env.define('*', (...args: number[]) => args.reduce((a, b) => a * b, 1));
    env.define('/', (a: number, b: number) => a / b);
    env.define('%', (a: number, b: number) => a % b);

    // Comparison
    env.define('=', (a: unknown, b: unknown) => a === b);
    env.define('<', (a: number, b: number) => a < b);
    env.define('>', (a: number, b: number) => a > b);
    env.define('<=', (a: number, b: number) => a <= b);
    env.define('>=', (a: number, b: number) => a >= b);
    env.define('not', (x: unknown) => !x);
    env.define('and', (...args: unknown[]) => args.every(Boolean));
    env.define('or', (...args: unknown[]) => args.some(Boolean));

    // List operations
    env.define('list', (...args: unknown[]) => args);
    env.define('car', (list: unknown[]) => list?.[0]);
    env.define('cdr', (list: unknown[]) => list?.slice(1));
    env.define('cons', (head: unknown, tail: unknown[]) => [head, ...(tail ?? [])]);
    env.define('null?', (x: unknown) => x === null || (Array.isArray(x) && x.length === 0));
    env.define('atom?', (x: unknown) => !Array.isArray(x));
    env.define('length', (x: unknown[]) => x?.length ?? 0);

    // Object/hash operations
    env.define('hash', (...pairs: unknown[]) => {
      const obj: Record<string, unknown> = {};
      for (let i = 0; i < pairs.length; i += 2) {
        obj[String(pairs[i])] = pairs[i + 1];
      }
      return obj;
    });
    env.define('get', (obj: Record<string, unknown>, key: string) => obj?.[key]);
    env.define('put', (obj: Record<string, unknown>, key: string, val: unknown) => {
      if (obj) obj[key] = val;
      return obj;
    });

    // String operations
    env.define('concat', (...args: string[]) => args.join(''));
    env.define('format', (template: string, ...args: unknown[]) => {
      let result = template;
      for (const arg of args) {
        result = result.replace(/~[asd]/, String(arg));
      }
      return result;
    });

    // Type predicates
    env.define('number?', (x: unknown) => typeof x === 'number');
    env.define('string?', (x: unknown) => typeof x === 'string');
    env.define('boolean?', (x: unknown) => typeof x === 'boolean');

    // Debug
    env.define('print', (...args: unknown[]) => {
      // eslint-disable-next-line no-console -- lisp print primitive intentionally writes to the host console
      console.log(`[ShadowLisp:${this.context.mount}]`, ...args);
      return args[args.length - 1];
    });

    // Context access
    env.define('__context', this.context);
    env.define('__mount', this.context.mount);

    return env;
  }

  /**
   * Evaluate an S-expression in the handler environment.
   */
  private async _evalSExpr(sexp: SExpr, env: HandlerEnvironment): Promise<unknown> {
    // Atoms
    if (!Array.isArray(sexp)) {
      if (sexp === null) return null;
      if (typeof sexp === 'number') return sexp;
      if (typeof sexp === 'boolean') return sexp;
      if (typeof sexp === 'string') {
        // Quoted strings are literals - strip quotes and return
        if (sexp.startsWith('"') && sexp.endsWith('"')) {
          return sexp.slice(1, -1);
        }
        // Keywords self-evaluate
        if (sexp.startsWith(':')) return sexp;
        // Look up in environment, then in state
        if (env.has(sexp)) return env.lookup(sexp);
        if (sexp in this.state) return this.state[sexp];
        return sexp;
      }
      return sexp;
    }

    if (sexp.length === 0) return [];

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

    // Special forms
    switch (op) {
      case 'quote':
        return args[0];

      case 'if': {
        const test = await this._evalSExpr(args[0], env);
        if (test) {
          return this._evalSExpr(args[1], env);
        } else if (args.length > 2) {
          return this._evalSExpr(args[2], env);
        }
        return null;
      }

      case 'begin': {
        let result: unknown = null;
        for (const expr of args) {
          result = await this._evalSExpr(expr, env);
        }
        return result;
      }

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

      case 'set!': {
        const name = args[0] as string;
        const value = await this._evalSExpr(args[1], env);
        // Update both env and state
        env.set(name, value);
        if (name in this.state) {
          this.state[name] = value;
        }
        return value;
      }

      case 'lambda': {
        const params = args[0] as string[];
        const body = args.slice(1);
        return { type: 'closure', params, body, env };
      }

      case 'let': {
        const bindings = args[0] as [string, SExpr][];
        const body = args.slice(1);
        const letEnv = env.extend();

        for (const [name, expr] of bindings) {
          const value = await this._evalSExpr(expr, env);
          letEnv.define(name, value);
        }

        let result: unknown = null;
        for (const expr of body) {
          result = await this._evalSExpr(expr, letEnv);
        }
        return result;
      }

      // ═══════════════════════════════════════════════════════════════════
      // ACTOR FORMS - These are the key Matrix integrations
      // ═══════════════════════════════════════════════════════════════════

      case 'emit': {
        const eventName = await this._evalSExpr(args[0], env) as string;
        const eventPayload = args[1] ? await this._evalSExpr(args[1], env) : undefined;

        if (this._emitFn) {
          this._emitFn(eventName, eventPayload);
        } else {
          // Publish to $events topic with MxEnvelope (FP-21 fix)
          const topic = TopicRouter.events(this.context.mount);
          this.context.transport.publish(topic, {
            op: eventName,
            payload: eventPayload,
            from: this.context.mount,
          });
        }
        return null;
      }

      case 'send': {
        const target = await this._evalSExpr(args[0], env) as string;
        const cmd = await this._evalSExpr(args[1], env) as string;
        const cmdPayload = args[2] ? await this._evalSExpr(args[2], env) : undefined;

        // Publish to target's $inbox with MxEnvelope (FP-21 fix)
        const topic = TopicRouter.inbox(target);
        this.context.transport.publish(topic, {
          op: cmd,
          payload: cmdPayload,
          from: this.context.mount,
        });
        return null;
      }

      case 'send-self': {
        const cmd = await this._evalSExpr(args[0], env) as string;
        const cmdPayload = args[1] ? await this._evalSExpr(args[1], env) : undefined;

        // Publish to own $inbox with MxEnvelope (FP-21 fix)
        const topic = TopicRouter.inbox(this.context.mount);
        this.context.transport.publish(topic, {
          op: cmd,
          payload: cmdPayload,
          from: this.context.mount,
        });
        return null;
      }

      // ═══════════════════════════════════════════════════════════════════
      // PHASE 4: DELIMITED CONTINUATIONS (reset/shift)
      // ═══════════════════════════════════════════════════════════════════

      case 'reset': {
        // (reset body) or (reset tag body)
        let tag = DEFAULT_PROMPT;
        let body = args[0];

        if (args.length > 1 && typeof args[0] === 'string') {
          tag = makePromptTag(args[0] as string);
          body = args[1];
        }

        // Execute body within reset boundary
        return this._continuationContext.reset(tag, async () => {
          return await this._evalSExpr(body, env);
        });
      }

      case 'shift': {
        // (shift k body) or (shift tag k body)
        let tag = DEFAULT_PROMPT;
        let kName: string;
        let body: SExpr;

        if (args.length === 2) {
          kName = args[0] as string;
          body = args[1];
        } else if (args.length === 3) {
          tag = makePromptTag(args[0] as string);
          kName = args[1] as string;
          body = args[2];
        } else {
          throw new Error('[ShadowLispDispatchStrategy] shift requires 2 or 3 arguments');
        }

        // Capture continuation up to reset
        return this._continuationContext.shift(tag, async (capturedK) => {
          // Bind k in environment
          const shiftEnv = env.extend();
          shiftEnv.define(kName, capturedK);
          return await this._evalSExpr(body, shiftEnv);
        });
      }

      // ═══════════════════════════════════════════════════════════════════
      // PHASE 4: ALGEBRAIC EFFECTS (perform/handle)
      // ═══════════════════════════════════════════════════════════════════

      case 'perform': {
        // (perform effect-name operation payload?)
        const effectName = await this._evalSExpr(args[0], env) as string;
        const operation = await this._evalSExpr(args[1], env) as string;
        const payload = args[2] ? await this._evalSExpr(args[2], env) : undefined;

        return this._effectContext.perform(effectName, operation, payload);
      }

      case 'handle': {
        // (handle handler-spec body)
        // handler-spec: (effect-name (op1 (params) body1) (op2 (params) body2) ...)
        const handlerSpec = args[0] as SExpr[];
        const body = args[1];

        if (!Array.isArray(handlerSpec) || handlerSpec.length < 1) {
          throw new Error('[ShadowLispDispatchStrategy] handle requires handler specification');
        }

        const effectName = handlerSpec[0] as string;
        const opSpecs = handlerSpec.slice(1) as SExpr[][];

        // Build operations map for handler
        const operationsMap: Record<string, EffectHandler> = {};

        for (const opDef of opSpecs) {
          if (!Array.isArray(opDef) || opDef.length < 3) continue;
          const [opName, opParams, ...opBody] = opDef;

          // Create handler function for this operation
          operationsMap[opName as string] = (payload: unknown, resume: (v: unknown) => unknown) => {
            const opEnv = env.extend();
            // Bind parameters from handler spec
            if (Array.isArray(opParams)) {
              for (let i = 0; i < opParams.length; i++) {
                const paramName = opParams[i] as string;
                if (paramName === 'payload') {
                  opEnv.define(paramName, payload);
                } else if (paramName === 'resume') {
                  opEnv.define(paramName, resume);
                }
              }
            }
            opEnv.define('payload', payload);
            opEnv.define('resume', resume);

            // Evaluate handler body synchronously (effects are sync in this context)
            let result: unknown = null;
            for (const expr of opBody) {
              // Note: This is a simplified sync version
              result = this._evalSExprSync(expr as SExpr, opEnv);
            }
            return result;
          };
        }

        // Create handler definition
        const handlerDef = createHandler(effectName, operationsMap);

        // Execute body with handler installed
        return this._effectContext.handle(handlerDef, () => {
          // Effects need sync evaluation, so we return a promise wrapper
          return this._evalSExprSync(body, env);
        });
      }

      // ═══════════════════════════════════════════════════════════════════
      // APPLICATION - Evaluate operator and apply
      // ═══════════════════════════════════════════════════════════════════

      default: {
        const proc = await this._evalSExpr(op, env);
        const evaledArgs = await Promise.all(
          args.map(arg => this._evalSExpr(arg, env))
        );

        if (typeof proc === 'function') {
          return proc(...evaledArgs);
        }

        // Closure application
        if (proc && typeof proc === 'object' && (proc as Record<string, unknown>).type === 'closure') {
          const closure = proc as {
            params: string[];
            body: SExpr[];
            env: HandlerEnvironment;
          };
          const callEnv = closure.env.extend();

          for (let i = 0; i < closure.params.length; i++) {
            callEnv.define(closure.params[i], evaledArgs[i]);
          }

          let result: unknown = null;
          for (const expr of closure.body) {
            result = await this._evalSExpr(expr, callEnv);
          }
          return result;
        }

        throw new Error(`[ShadowLispDispatchStrategy] Cannot apply: ${op}`);
      }
    }
  }

  /**
   * Synchronous S-expression evaluator for effect handlers.
   * Effects require synchronous execution for proper stack capture.
   */
  private _evalSExprSync(sexp: SExpr, env: HandlerEnvironment): unknown {
    // Atoms
    if (!Array.isArray(sexp)) {
      if (sexp === null) return null;
      if (typeof sexp === 'number') return sexp;
      if (typeof sexp === 'boolean') return sexp;
      if (typeof sexp === 'string') {
        if (sexp.startsWith('"') && sexp.endsWith('"')) {
          return sexp.slice(1, -1);
        }
        if (sexp.startsWith(':')) return sexp;
        if (env.has(sexp)) return env.lookup(sexp);
        if (sexp in this.state) return this.state[sexp];
        return sexp;
      }
      return sexp;
    }

    if (sexp.length === 0) return [];

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

    // Special forms (subset for effects)
    switch (op) {
      case 'quote':
        return args[0];

      case 'if': {
        const test = this._evalSExprSync(args[0], env);
        if (test) {
          return this._evalSExprSync(args[1], env);
        } else if (args.length > 2) {
          return this._evalSExprSync(args[2], env);
        }
        return null;
      }

      case 'begin': {
        let result: unknown = null;
        for (const expr of args) {
          result = this._evalSExprSync(expr, env);
        }
        return result;
      }

      case 'perform': {
        const effectName = this._evalSExprSync(args[0], env) as string;
        const operation = this._evalSExprSync(args[1], env) as string;
        const payload = args[2] ? this._evalSExprSync(args[2], env) : undefined;
        return this._effectContext.perform(effectName, operation, payload);
      }

      default: {
        const proc = this._evalSExprSync(op, env);
        const evaledArgs = args.map(arg => this._evalSExprSync(arg, env));

        if (typeof proc === 'function') {
          return proc(...evaledArgs);
        }

        throw new Error(`[ShadowLispDispatchStrategy] Cannot apply sync: ${op}`);
      }
    }
  }

  /**
   * List registered handlers.
   */
  listHandlers(): string[] {
    return Array.from(this.handlers.keys());
  }

  /**
   * Dispose and clean up.
   */
  dispose(): void {
    this.handlers.clear();
  }
}
