// src/lisp/core/SExprParser.ts
// S-Expression Parser - Convert traditional LISP syntax to JSON wire format
//
// Input:  (define (square x) (* x x))
// Output: ["define", ["square", "x"], ["*", "x", "x"]]
//
// This enables running actual Scheme/LISP code on Matrix.

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

/**
 * Token types for the lexer
 */
type TokenType = 'LPAREN' | 'RPAREN' | 'QUOTE' | 'QUASIQUOTE' | 'UNQUOTE' | 'UNQUOTE_SPLICING' | 'STRING' | 'NUMBER' | 'SYMBOL' | 'BOOLEAN' | 'NIL' | 'EOF';

interface Token {
  type: TokenType;
  value: string | number | boolean | null;
  line: number;
  col: number;
}

/**
 * Lexer - Tokenize S-expression source code
 */
class Lexer {
  private source: string;
  private pos: number = 0;
  private line: number = 1;
  private col: number = 1;

  constructor(source: string) {
    this.source = source;
  }

  private peek(): string {
    return this.source[this.pos] || '\0';
  }

  private peekNext(): string {
    return this.source[this.pos + 1] || '\0';
  }

  private advance(): string {
    const ch = this.source[this.pos++] || '\0';
    if (ch === '\n') {
      this.line++;
      this.col = 1;
    } else {
      this.col++;
    }
    return ch;
  }

  private skipWhitespace(): void {
    while (this.pos < this.source.length) {
      const ch = this.peek();
      if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
        this.advance();
      } else if (ch === ';') {
        // Comment - skip to end of line
        while (this.peek() !== '\n' && this.peek() !== '\0') {
          this.advance();
        }
      } else {
        break;
      }
    }
  }

  private isDigit(ch: string): boolean {
    return ch >= '0' && ch <= '9';
  }

  private isSymbolChar(ch: string): boolean {
    // Symbol can contain letters, digits, and special chars
    // Cannot start with digit (handled in tokenize)
    return /[a-zA-Z0-9_\-+*/<>=!?$%&^@~.#]/.test(ch);
  }

  private readString(): Token {
    const startLine = this.line;
    const startCol = this.col;
    this.advance(); // consume opening quote

    let value = '';
    while (this.peek() !== '"' && this.peek() !== '\0') {
      if (this.peek() === '\\') {
        this.advance();
        const escaped = this.advance();
        switch (escaped) {
          case 'n': value += '\n'; break;
          case 't': value += '\t'; break;
          case 'r': value += '\r'; break;
          case '\\': value += '\\'; break;
          case '"': value += '"'; break;
          default: value += escaped;
        }
      } else {
        value += this.advance();
      }
    }

    if (this.peek() === '\0') {
      throw new Error(`Unterminated string at line ${startLine}, col ${startCol}`);
    }
    this.advance(); // consume closing quote

    return { type: 'STRING', value, line: startLine, col: startCol };
  }

  private readNumber(): Token {
    const startLine = this.line;
    const startCol = this.col;
    let numStr = '';

    // Handle negative numbers
    if (this.peek() === '-' || this.peek() === '+') {
      numStr += this.advance();
    }

    // Integer part
    while (this.isDigit(this.peek())) {
      numStr += this.advance();
    }

    // Decimal part
    if (this.peek() === '.' && this.isDigit(this.peekNext())) {
      numStr += this.advance(); // .
      while (this.isDigit(this.peek())) {
        numStr += this.advance();
      }
    }

    // Exponent
    if (this.peek() === 'e' || this.peek() === 'E') {
      numStr += this.advance();
      if (this.peek() === '+' || this.peek() === '-') {
        numStr += this.advance();
      }
      while (this.isDigit(this.peek())) {
        numStr += this.advance();
      }
    }

    return { type: 'NUMBER', value: parseFloat(numStr), line: startLine, col: startCol };
  }

  private readSymbol(): Token {
    const startLine = this.line;
    const startCol = this.col;
    let symbol = '';

    while (this.isSymbolChar(this.peek())) {
      symbol += this.advance();
    }

    // Check for special symbols
    const lower = symbol.toLowerCase();

    if (lower === '#t' || lower === 'true') {
      return { type: 'BOOLEAN', value: true, line: startLine, col: startCol };
    }
    if (lower === '#f' || lower === 'false') {
      return { type: 'BOOLEAN', value: false, line: startLine, col: startCol };
    }
    if (lower === 'nil' || lower === 'null' || lower === '#nil') {
      return { type: 'NIL', value: null, line: startLine, col: startCol };
    }

    return { type: 'SYMBOL', value: symbol, line: startLine, col: startCol };
  }

  tokenize(): Token[] {
    const tokens: Token[] = [];

    while (this.pos < this.source.length) {
      this.skipWhitespace();

      if (this.pos >= this.source.length) break;

      const ch = this.peek();
      const line = this.line;
      const col = this.col;

      switch (ch) {
        case '(':
          this.advance();
          tokens.push({ type: 'LPAREN', value: '(', line, col });
          break;

        case ')':
          this.advance();
          tokens.push({ type: 'RPAREN', value: ')', line, col });
          break;

        case "'":
          this.advance();
          tokens.push({ type: 'QUOTE', value: "'", line, col });
          break;

        case '`':
          this.advance();
          tokens.push({ type: 'QUASIQUOTE', value: '`', line, col });
          break;

        case ',':
          this.advance();
          if (this.peek() === '@') {
            this.advance();
            tokens.push({ type: 'UNQUOTE_SPLICING', value: ',@', line, col });
          } else {
            tokens.push({ type: 'UNQUOTE', value: ',', line, col });
          }
          break;

        case '"':
          tokens.push(this.readString());
          break;

        case '-':
        case '+':
          // Could be number or symbol
          if (this.isDigit(this.peekNext())) {
            tokens.push(this.readNumber());
          } else {
            tokens.push(this.readSymbol());
          }
          break;

        default:
          if (this.isDigit(ch)) {
            tokens.push(this.readNumber());
          } else if (this.isSymbolChar(ch)) {
            tokens.push(this.readSymbol());
          } else {
            throw new Error(`Unexpected character '${ch}' at line ${line}, col ${col}`);
          }
      }
    }

    tokens.push({ type: 'EOF', value: null, line: this.line, col: this.col });
    return tokens;
  }
}

/**
 * Parser - Build AST from tokens
 */
class Parser {
  private tokens: Token[];
  private pos: number = 0;

  constructor(tokens: Token[]) {
    this.tokens = tokens;
  }

  private peek(): Token {
    return this.tokens[this.pos] || { type: 'EOF', value: null, line: 0, col: 0 };
  }

  private advance(): Token {
    return this.tokens[this.pos++];
  }

  private parseExpr(): SExpr {
    const token = this.peek();

    switch (token.type) {
      case 'LPAREN':
        return this.parseList();

      case 'QUOTE':
        this.advance();
        return ['quote', this.parseExpr()];

      case 'QUASIQUOTE':
        this.advance();
        return ['quasiquote', this.parseExpr()];

      case 'UNQUOTE':
        this.advance();
        return ['unquote', this.parseExpr()];

      case 'UNQUOTE_SPLICING':
        this.advance();
        return ['unquote-splicing', this.parseExpr()];

      case 'STRING':
        this.advance();
        return token.value as string;

      case 'NUMBER':
        this.advance();
        return token.value as number;

      case 'BOOLEAN':
        this.advance();
        return token.value as boolean;

      case 'NIL':
        this.advance();
        return null;

      case 'SYMBOL':
        this.advance();
        return token.value as string;

      case 'RPAREN':
        throw new Error(`Unexpected ')' at line ${token.line}, col ${token.col}`);

      case 'EOF':
        throw new Error('Unexpected end of input');

      default:
        throw new Error(`Unexpected token type: ${token.type}`);
    }
  }

  private parseList(): SExpr[] {
    const lparen = this.advance(); // consume (
    if (lparen.type !== 'LPAREN') {
      throw new Error(`Expected '(' at line ${lparen.line}, col ${lparen.col}`);
    }

    const elements: SExpr[] = [];

    while (this.peek().type !== 'RPAREN' && this.peek().type !== 'EOF') {
      elements.push(this.parseExpr());
    }

    if (this.peek().type !== 'RPAREN') {
      throw new Error(`Expected ')' at line ${lparen.line}, col ${lparen.col}`);
    }
    this.advance(); // consume )

    return elements;
  }

  parse(): SExpr[] {
    const expressions: SExpr[] = [];

    while (this.peek().type !== 'EOF') {
      expressions.push(this.parseExpr());
    }

    return expressions;
  }
}

/**
 * Parse S-expression source code to JSON wire format
 *
 * @param source - S-expression source code
 * @returns Array of parsed expressions
 *
 * @example
 * parseSExpr("(+ 1 2)")
 * // => [["+", 1, 2]]
 *
 * @example
 * parseSExpr("(define (square x) (* x x))")
 * // => [["define", ["square", "x"], ["*", "x", "x"]]]
 *
 * @example
 * parseSExpr("'(1 2 3)")
 * // => [["quote", [1, 2, 3]]]
 */
export function parseSExpr(source: string): SExpr[] {
  const lexer = new Lexer(source);
  const tokens = lexer.tokenize();
  const parser = new Parser(tokens);
  return parser.parse();
}

/**
 * Parse a single S-expression
 * Throws if more than one expression is provided
 */
export function parseSExprSingle(source: string): SExpr {
  const exprs = parseSExpr(source);
  if (exprs.length === 0) {
    throw new Error('No expression found');
  }
  if (exprs.length > 1) {
    throw new Error(`Expected single expression, got ${exprs.length}`);
  }
  return exprs[0];
}

/**
 * Convert JSON wire format back to S-expression string
 *
 * @param expr - JSON wire format expression
 * @returns S-expression string
 *
 * @example
 * toSExpr(["+", 1, 2])
 * // => "(+ 1 2)"
 */
export function toSExpr(expr: SExpr): string {
  if (expr === null) {
    return 'nil';
  }
  if (typeof expr === 'boolean') {
    return expr ? '#t' : '#f';
  }
  if (typeof expr === 'number') {
    return String(expr);
  }
  if (typeof expr === 'string') {
    // Check if it needs quoting (has spaces, special chars, etc.)
    if (/^[a-zA-Z_\-+*/<>=!?$%&^@~.][a-zA-Z0-9_\-+*/<>=!?$%&^@~.]*$/.test(expr)) {
      return expr; // Valid symbol
    }
    // String literal - escape and quote
    const escaped = expr
      .replace(/\\/g, '\\\\')
      .replace(/"/g, '\\"')
      .replace(/\n/g, '\\n')
      .replace(/\t/g, '\\t')
      .replace(/\r/g, '\\r');
    return `"${escaped}"`;
  }
  if (Array.isArray(expr)) {
    // Handle special forms
    if (expr.length === 2 && expr[0] === 'quote') {
      return `'${toSExpr(expr[1])}`;
    }
    if (expr.length === 2 && expr[0] === 'quasiquote') {
      return `\`${toSExpr(expr[1])}`;
    }
    if (expr.length === 2 && expr[0] === 'unquote') {
      return `,${toSExpr(expr[1])}`;
    }
    if (expr.length === 2 && expr[0] === 'unquote-splicing') {
      return `,@${toSExpr(expr[1])}`;
    }
    // Regular list
    return `(${expr.map(toSExpr).join(' ')})`;
  }
  return String(expr);
}

/**
 * Pretty-print S-expression with indentation
 */
export function prettySExpr(expr: SExpr, indent: number = 0): string {
  const spaces = '  '.repeat(indent);

  if (!Array.isArray(expr)) {
    return spaces + toSExpr(expr);
  }

  // Short lists on one line
  const simple = toSExpr(expr);
  if (simple.length < 60 && !simple.includes('\n')) {
    return spaces + simple;
  }

  // Long lists - one element per line
  const [head, ...rest] = expr;
  if (rest.length === 0) {
    return `${spaces}(${toSExpr(head)})`;
  }

  const lines = [
    `${spaces}(${toSExpr(head)}`,
    ...rest.map(e => prettySExpr(e, indent + 1)),
    `${spaces})`
  ];

  return lines.join('\n');
}

// ============================================================================
// REPL and evaluation helpers
// ============================================================================

/**
 * Wrap parsed expressions in a do block if multiple
 */
export function wrapInDo(exprs: SExpr[]): SExpr {
  if (exprs.length === 0) return null;
  if (exprs.length === 1) return exprs[0];
  return ['do', ...exprs];
}

/**
 * Parse and prepare for evaluation
 * Handles multi-expression input by wrapping in do block
 */
export function parseForEval(source: string): SExpr {
  return wrapInDo(parseSExpr(source));
}

// ============================================================================
// Example library - Classic LISP/Scheme programs
// ============================================================================

export const SCHEME_EXAMPLES = {
  // Factorial
  factorial: `
(define (factorial n)
  (if (<= n 1)
      1
      (* n (factorial (- n 1)))))

(factorial 5)
`,

  // Fibonacci
  fibonacci: `
(define (fib n)
  (cond ((<= n 0) 0)
        ((= n 1) 1)
        (else (+ (fib (- n 1)) (fib (- n 2))))))

(fib 10)
`,

  // Map implementation
  mapImpl: `
(define (my-map f lst)
  (if (null? lst)
      '()
      (cons (f (car lst))
            (my-map f (cdr lst)))))

(my-map (lambda (x) (* x x)) '(1 2 3 4 5))
`,

  // Filter implementation
  filterImpl: `
(define (my-filter pred lst)
  (cond ((null? lst) '())
        ((pred (car lst))
         (cons (car lst) (my-filter pred (cdr lst))))
        (else (my-filter pred (cdr lst)))))

(my-filter (lambda (x) (> x 2)) '(1 2 3 4 5))
`,

  // Reduce/fold implementation
  reduceImpl: `
(define (my-reduce f init lst)
  (if (null? lst)
      init
      (my-reduce f (f init (car lst)) (cdr lst))))

(my-reduce + 0 '(1 2 3 4 5))
`,

  // Quicksort
  quicksort: `
(define (quicksort lst)
  (if (null? lst)
      '()
      (let ((pivot (car lst))
            (rest (cdr lst)))
        (append
          (quicksort (filter (lambda (x) (< x pivot)) rest))
          (list pivot)
          (quicksort (filter (lambda (x) (>= x pivot)) rest))))))

(quicksort '(3 1 4 1 5 9 2 6 5 3 5))
`,

  // Y combinator (mind-bending recursion without define)
  yCombinator: `
; The Y combinator enables recursion without naming
(define Y
  (lambda (f)
    ((lambda (x) (f (lambda (v) ((x x) v))))
     (lambda (x) (f (lambda (v) ((x x) v)))))))

; Factorial using Y (no explicit recursion!)
(define fact-gen
  (lambda (f)
    (lambda (n)
      (if (<= n 1)
          1
          (* n (f (- n 1)))))))

((Y fact-gen) 5)
`,

  // Church numerals
  churchNumerals: `
; Church encoding - numbers as functions
(define zero (lambda (f) (lambda (x) x)))
(define one (lambda (f) (lambda (x) (f x))))
(define two (lambda (f) (lambda (x) (f (f x)))))

(define succ
  (lambda (n)
    (lambda (f)
      (lambda (x)
        (f ((n f) x))))))

(define add
  (lambda (m)
    (lambda (n)
      (lambda (f)
        (lambda (x)
          ((m f) ((n f) x)))))))

; Convert Church numeral to integer
(define church->int
  (lambda (n)
    ((n (lambda (x) (+ x 1))) 0)))

(church->int ((add two) two))
`,

  // Macro example (when we have defmacro)
  macroExample: `
; Define 'unless' as a macro
(defmacro unless (test body)
  (quasiquote
    (if (not (unquote test))
        (unquote body)
        nil)))

(unless (= 1 2) "one is not two!")
`,
};

/**
 * Parse and convert a Scheme example to JSON wire format
 */
export function parseSchemeExample(name: keyof typeof SCHEME_EXAMPLES): SExpr {
  const source = SCHEME_EXAMPLES[name];
  return parseForEval(source);
}
