// src/framework/shadowlisp/SlMacro.ts
//
// Vision 48: ShadowLisp Macro System
//
// Guy Steele (Scheme co-designer, "Lambda the Ultimate"):
//   "Macros are the key to language extension. They transform syntax
//    before evaluation - code that writes code."
//
// John McCarthy (LISP creator, Turing Award 1971):
//   "The ability to represent programs as data structures and manipulate
//    them as such is LISP's greatest strength. Macros are the embodiment."
//
// This implements a hygienic macro system for ShadowLisp with:
// - define-macro: Simple macro definition
// - syntax-rules: Pattern-based macro definition
// - Macro expansion before evaluation

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

/**
 * Macro transformer function type.
 * Takes the macro form and returns the expanded code.
 */
export type MacroTransformer = (form: SExpr[]) => SExpr;

/**
 * Pattern matching result.
 */
interface IPatternMatch {
  matched: boolean;
  bindings: Map<string, SExpr>;
}

/**
 * Syntax rule definition.
 */
interface ISyntaxRule {
  pattern: SExpr;
  template: SExpr;
  literals: Set<string>;
}

/**
 * SlMacroExpander - Expand macros in S-expressions.
 *
 * Rich Hickey (Clojure creator):
 *   "Macros run at compile time. They see code as data and return
 *    transformed code. The evaluator never sees the macro call."
 *
 * @example
 * ```typescript
 * const expander = new SlMacroExpander();
 *
 * // Define a simple macro
 * expander.defineMacro('when', (form) => {
 *   const [_, test, ...body] = form;
 *   return ['if', test, ['begin', ...body], null];
 * });
 *
 * // Expand
 * const expanded = expander.expand(['when', ['>', 'x', 0], ['print', 'positive']]);
 * // → ['if', ['>', 'x', 0], ['begin', ['print', 'positive']], null]
 * ```
 */
export class SlMacroExpander {
  private readonly macros = new Map<string, MacroTransformer>();
  private readonly syntaxRules = new Map<string, ISyntaxRule[]>();

  /**
   * Define a simple macro with a transformer function.
   *
   * @param name - Macro name
   * @param transformer - Function that transforms the macro form
   */
  defineMacro(name: string, transformer: MacroTransformer): void {
    this.macros.set(name, transformer);
  }

  /**
   * Define a macro using syntax-rules (pattern-based).
   *
   * @param name - Macro name
   * @param literals - Literal keywords that must match exactly
   * @param rules - Array of [pattern, template] pairs
   *
   * @example
   * ```typescript
   * expander.defineSyntaxRules('cond', [], [
   *   [['cond'], null],
   *   [['cond', ['else', '...body']], ['begin', '...body']],
   *   [['cond', ['test', '...body'], '...rest'],
   *    ['if', 'test', ['begin', '...body'], ['cond', '...rest']]]
   * ]);
   * ```
   */
  defineSyntaxRules(
    name: string,
    literals: string[],
    rules: Array<[SExpr, SExpr]>
  ): void {
    const literalSet = new Set(literals);
    const syntaxRules: ISyntaxRule[] = rules.map(([pattern, template]) => ({
      pattern,
      template,
      literals: literalSet
    }));
    this.syntaxRules.set(name, syntaxRules);
  }

  /**
   * Check if a name is a defined macro.
   */
  isMacro(name: string): boolean {
    return this.macros.has(name) || this.syntaxRules.has(name);
  }

  /**
   * Expand all macros in an S-expression (recursive).
   *
   * @param sexp - S-expression to expand
   * @returns Fully expanded S-expression
   */
  expand(sexp: SExpr): SExpr {
    // Atoms don't expand
    if (!Array.isArray(sexp)) {
      return sexp;
    }

    // Empty list
    if (sexp.length === 0) {
      return sexp;
    }

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

    // Check for special forms that should NOT expand their bodies yet
    if (head === 'quote') {
      return sexp; // Don't expand quoted forms
    }

    // Check if head is a macro name
    if (typeof head === 'string' && this.isMacro(head)) {
      // Expand the macro
      const expanded = this._expandMacro(head, sexp);
      // Recursively expand the result
      return this.expand(expanded);
    }

    // Not a macro - expand children
    return sexp.map(child => this.expand(child)) as SExpr[];
  }

  /**
   * Expand a single macro call.
   */
  private _expandMacro(name: string, form: SExpr[]): SExpr {
    // Try simple macro first
    const transformer = this.macros.get(name);
    if (transformer) {
      return transformer(form);
    }

    // Try syntax-rules
    const rules = this.syntaxRules.get(name);
    if (rules) {
      for (const rule of rules) {
        const match = this._matchPattern(rule.pattern, form, rule.literals);
        if (match.matched) {
          return this._substituteTemplate(rule.template, match.bindings);
        }
      }
      throw new Error(`[SlMacro] No syntax-rules pattern matched for: ${name}`);
    }

    // Should not reach here
    throw new Error(`[SlMacro] Unknown macro: ${name}`);
  }

  /**
   * Match a pattern against a form.
   *
   * Pattern syntax:
   * - Literals match themselves
   * - Symbols become bindings
   * - (pattern ...) matches zero or more
   * - _ matches anything (ignored)
   */
  private _matchPattern(
    pattern: SExpr,
    form: SExpr,
    literals: Set<string>
  ): IPatternMatch {
    const bindings = new Map<string, SExpr>();

    const match = (pat: SExpr, frm: SExpr): boolean => {
      // Null pattern matches null
      if (pat === null) {
        return frm === null;
      }

      // Number/boolean match exactly
      if (typeof pat === 'number' || typeof pat === 'boolean') {
        return pat === frm;
      }

      // String patterns
      if (typeof pat === 'string') {
        // Underscore matches anything
        if (pat === '_') {
          return true;
        }

        // Literal must match exactly
        if (literals.has(pat)) {
          return pat === frm;
        }

        // Ellipsis pattern handled separately
        if (pat.startsWith('...')) {
          // Should be handled in array matching
          return false;
        }

        // Pattern variable - bind it
        if (bindings.has(pat)) {
          // Already bound - must match
          return this._sexpEqual(bindings.get(pat)!, frm);
        }

        // New binding
        bindings.set(pat, frm);
        return true;
      }

      // Array patterns
      if (Array.isArray(pat)) {
        if (!Array.isArray(frm)) {
          return false;
        }

        // Check for ellipsis
        const ellipsisIdx = pat.findIndex(
          p => typeof p === 'string' && p.startsWith('...')
        );

        if (ellipsisIdx >= 0) {
          // Pattern has ellipsis - match prefix and suffix
          const prefix = pat.slice(0, ellipsisIdx);
          const suffix = pat.slice(ellipsisIdx + 1);
          const ellipsisVar = (pat[ellipsisIdx] as string).slice(3); // Remove ...

          // Match prefix
          for (let i = 0; i < prefix.length; i++) {
            if (!match(prefix[i], frm[i])) return false;
          }

          // Match suffix from end
          for (let i = 0; i < suffix.length; i++) {
            if (!match(suffix[i], frm[frm.length - suffix.length + i])) {
              return false;
            }
          }

          // Capture middle as ellipsis binding
          const middle = frm.slice(prefix.length, frm.length - suffix.length);
          if (ellipsisVar) {
            bindings.set(ellipsisVar, middle);
          }

          return true;
        }

        // No ellipsis - exact length match
        if (pat.length !== frm.length) {
          return false;
        }

        return pat.every((p, i) => match(p, frm[i]));
      }

      return false;
    };

    const matched = match(pattern, form);
    return { matched, bindings };
  }

  /**
   * Substitute bindings into a template.
   */
  private _substituteTemplate(
    template: SExpr,
    bindings: Map<string, SExpr>
  ): SExpr {
    // Null passes through
    if (template === null) {
      return null;
    }

    // Numbers/booleans pass through
    if (typeof template === 'number' || typeof template === 'boolean') {
      return template;
    }

    // String - check for binding or ellipsis splice
    if (typeof template === 'string') {
      // Ellipsis splice reference
      if (template.startsWith('...')) {
        const varName = template.slice(3);
        const value = bindings.get(varName);
        return value ?? template;
      }

      // Regular variable
      if (bindings.has(template)) {
        return bindings.get(template)!;
      }

      return template;
    }

    // Array - substitute each element
    if (Array.isArray(template)) {
      const result: SExpr[] = [];

      for (const elem of template) {
        // Check for ellipsis splice
        if (typeof elem === 'string' && elem.startsWith('...')) {
          const varName = elem.slice(3);
          const value = bindings.get(varName);
          if (Array.isArray(value)) {
            result.push(...value);
          } else if (value !== undefined) {
            result.push(value);
          }
        } else {
          result.push(this._substituteTemplate(elem, bindings));
        }
      }

      return result;
    }

    return template;
  }

  /**
   * Check S-expression equality.
   */
  private _sexpEqual(a: SExpr, b: SExpr): boolean {
    if (a === b) return true;
    if (typeof a !== typeof b) return false;
    if (Array.isArray(a) && Array.isArray(b)) {
      if (a.length !== b.length) return false;
      return a.every((elem, i) => this._sexpEqual(elem, b[i]));
    }
    return false;
  }

  /**
   * Get list of defined macros.
   */
  listMacros(): string[] {
    return [
      ...this.macros.keys(),
      ...this.syntaxRules.keys()
    ];
  }

  /**
   * Create standard library macros.
   */
  static withStandardMacros(): SlMacroExpander {
    const expander = new SlMacroExpander();

    // when - conditional execution
    expander.defineMacro('when', (form) => {
      const [, test, ...body] = form;
      return ['if', test, ['begin', ...body], null];
    });

    // unless - negated conditional
    expander.defineMacro('unless', (form) => {
      const [, test, ...body] = form;
      return ['if', ['not', test], ['begin', ...body], null];
    });

    // and - short-circuit and
    expander.defineMacro('and', (form) => {
      const [, ...args] = form;
      if (args.length === 0) return true;
      if (args.length === 1) return args[0];
      return ['if', args[0], ['and', ...args.slice(1)], false];
    });

    // or - short-circuit or
    expander.defineMacro('or', (form) => {
      const [, ...args] = form;
      if (args.length === 0) return false;
      if (args.length === 1) return args[0];
      const temp = `__or_temp_${Date.now()}`;
      return ['let', [[temp, args[0]]], ['if', temp, temp, ['or', ...args.slice(1)]]];
    });

    // cond - multi-branch conditional
    expander.defineMacro('cond', (form) => {
      const [, ...clauses] = form;
      if (clauses.length === 0) return null;

      const [first, ...rest] = clauses;
      if (!Array.isArray(first)) return null;

      const [test, ...body] = first as SExpr[];

      if (test === 'else') {
        return ['begin', ...body];
      }

      return ['if', test, ['begin', ...body], ['cond', ...rest]];
    });

    // let* - sequential let
    expander.defineMacro('let*', (form) => {
      const [, bindings, ...body] = form;
      if (!Array.isArray(bindings) || bindings.length === 0) {
        return ['begin', ...body];
      }
      const [first, ...rest] = bindings as [string, SExpr][];
      if (rest.length === 0) {
        return ['let', [first], ...body];
      }
      return ['let', [first], ['let*', rest, ...body]];
    });

    // do - simple loop (while)
    expander.defineMacro('do-while', (form) => {
      const [, test, ...body] = form;
      const loop = `__loop_${Date.now()}`;
      return ['begin',
        ['define', loop, ['lambda', [],
          ['when', test,
            ...body,
            [loop]]]],
        [loop]];
    });

    // -> threading macro (like Clojure)
    expander.defineMacro('->', (form) => {
      const [, first, ...forms] = form;
      if (forms.length === 0) return first;

      let result = first;
      for (const f of forms) {
        if (Array.isArray(f)) {
          result = [f[0], result, ...f.slice(1)];
        } else {
          result = [f, result];
        }
      }
      return result;
    });

    // ->> threading macro (insert at end)
    expander.defineMacro('->>', (form) => {
      const [, first, ...forms] = form;
      if (forms.length === 0) return first;

      let result = first;
      for (const f of forms) {
        if (Array.isArray(f)) {
          result = [...f, result] as SExpr;
        } else {
          result = [f, result];
        }
      }
      return result;
    });

    // dotimes - repeat N times
    expander.defineMacro('dotimes', (form) => {
      const [, binding, ...body] = form;
      if (!Array.isArray(binding) || binding.length < 2) return null;
      const [varName, count] = binding as [string, SExpr];
      const loop = `__dotimes_${Date.now()}`;
      return ['begin',
        ['define', loop, ['lambda', [varName],
          ['when', ['<', varName, count],
            ...body,
            [loop, ['+', varName, 1]]]]],
        [loop, 0]];
    });

    return expander;
  }
}
