export type MiniScriptErrorPhase = 'lex' | 'parse' | 'runtime' | 'register';

export type MiniScriptErrorDetails = {
  phase?: MiniScriptErrorPhase;
  /** 原始脚本，用于计算行列与摘取片段 */
  source?: string;
  /** 在 source 中的字符偏移（从 0 起） */
  offset?: number;
  /** 附加上下文（如属性名、被调用目标描述） */
  extra?: string;
};

function positionFromOffset(source: string, offset: number): { line: number; column: number } {
  const o = Math.max(0, Math.min(offset, source.length));
  let line = 1;
  let column = 1;
  for (let i = 0; i < o; i++) {
    const ch = source[i];
    if (ch === '\n') {
      line++;
      column = 1;
    } else if (ch === '\r') {
      /* 忽略，与 \n 配对时由 \n 换行 */
    } else {
      column++;
    }
  }
  return { line, column };
}

/** 取 offset 所在行文本，并在对应列下方输出 ^ */
function lineSnippetWithCaret(source: string, offset: number, maxLineLen = 200): string {
  const len = source.length;
  const o = Math.max(0, Math.min(offset, len));
  let lineStart = 0;
  for (let i = o - 1; i >= 0; i--) {
    if (source[i] === '\n') {
      lineStart = i + 1;
      break;
    }
    if (i === 0) lineStart = 0;
  }
  let lineEnd = source.indexOf('\n', lineStart);
  if (lineEnd === -1) lineEnd = len;
  let text = source.slice(lineStart, lineEnd);
  const col = o - lineStart;
  if (text.length > maxLineLen) {
    const half = Math.floor(maxLineLen / 2);
    const start = Math.max(0, col - half);
    const end = Math.min(text.length, start + maxLineLen);
    const prefix = start > 0 ? '…' : '';
    const suffix = end < text.length ? '…' : '';
    text = prefix + text.slice(start, end) + suffix;
    const adjCol = col - start + prefix.length;
    return `${text}\n${' '.repeat(Math.max(0, adjCol))}^`;
  }
  return `${text}\n${' '.repeat(Math.max(0, col))}^`;
}

function formatMiniScriptError(base: string, d: MiniScriptErrorDetails): string {
  const parts: string[] = [base.trimEnd()];
  if (d.phase) {
    const label =
      d.phase === 'lex'
        ? '词法'
        : d.phase === 'parse'
          ? '解析'
          : d.phase === 'runtime'
            ? '运行'
            : '注册';
    parts.push(`[${label}]`);
  }
  if (d.source != null && d.source.length > 0 && d.offset !== undefined) {
    const { line, column } = positionFromOffset(d.source, d.offset);
    parts.push(`第 ${line} 行，第 ${column} 列（偏移 ${d.offset}）`);
    try {
      parts.push('\n' + lineSnippetWithCaret(d.source, d.offset));
    } catch {
      /* 忽略片段生成失败 */
    }
  }
  if (d.extra) parts.push(d.extra);
  return parts.join(' ');
}

type Token =
  | { type: 'num'; value: number; start: number }
  | { type: 'str'; value: string; start: number }
  | { type: 'id'; value: string; start: number }
  | {
      type: 'kw';
      value: 'return' | 'new' | 'true' | 'false' | 'null' | 'undefined' | 'if' | 'else';
      start: number;
    }
  | { type: 'op'; value: string; start: number }
  | { type: 'punc'; value: string; start: number }
  | { type: 'eof'; start: number };

type Expr =
  | { type: 'Literal'; value: unknown }
  | { type: 'Identifier'; name: string }
  | { type: 'Binary'; op: string; left: Expr; right: Expr }
  | { type: 'Unary'; op: string; argument: Expr }
  | { type: 'Call'; callee: Expr; args: Expr[] }
  | { type: 'Member'; object: Expr; property: string; computed: boolean; keyExpr?: Expr }
  | { type: 'ArrayLiteral'; elements: Expr[] }
  | { type: 'ObjectLiteral'; props: { key: string; value: Expr }[] }
  | { type: 'Assign'; left: Expr; right: Expr }
  | { type: 'ArrowFunction'; params: string[]; body: Expr | Stmt[] }
  | { type: 'New'; callee: Expr; args: Expr[] };

type Stmt =
  | { type: 'ExpressionStatement'; expr: Expr }
  | { type: 'ReturnStatement'; argument?: Expr }
  | { type: 'BlockStatement'; body: Stmt[] }
  | { type: 'IfStatement'; test: Expr; consequent: Stmt; alternate?: Stmt };

type Program = { type: 'Program'; body: Stmt[] };

function formatTokenBrief(t: Token): string {
  if (t.type === 'eof') return '文件结束';
  if (t.type === 'kw') return `关键字 "${t.value}"`;
  if (t.type === 'id') return `标识符 "${t.value}"`;
  if (t.type === 'num') return `数字 ${t.value}`;
  if (t.type === 'str') return `字符串字面量`;
  if (t.type === 'op') return `运算符 "${t.value}"`;
  if (t.type === 'punc') return `符号 "${t.value}"`;
  return '未知 token';
}

function describeCalleeExpr(expr: Expr): string {
  switch (expr.type) {
    case 'Identifier':
      return expr.name;
    case 'Member':
      if (!expr.computed) {
        return `${describeCalleeExpr(expr.object)}.${expr.property}`;
      }
      return `${describeCalleeExpr(expr.object)}[…]`;
    case 'Literal':
      return JSON.stringify(expr.value);
    default:
      return `(${expr.type})`;
  }
}

export class MiniScriptError extends Error {
  public readonly details: MiniScriptErrorDetails;

  constructor(message: string, details: MiniScriptErrorDetails = {}) {
    super(formatMiniScriptError(message, details));
    this.name = 'MiniScriptError';
    this.details = { ...details };
  }
}

const KW = new Set([
  'return',
  'new',
  'true',
  'false',
  'null',
  'undefined',
  'if',
  'else',
]);

function tokenize(source: string): Token[] {
  const tokens: Token[] = [];
  let i = 0;
  const len = source.length;

  const pushOp = (value: string, start: number) => {
    tokens.push({ type: 'op', value, start });
  };

  while (i < len) {
    const c = source[i];
    const start = i;

    if (/\s/.test(c)) {
      i++;
      continue;
    }

    if (c === '/' && source[i + 1] === '/') {
      i += 2;
      while (i < len && source[i] !== '\n' && source[i] !== '\r') i++;
      continue;
    }

    if (c === '/' && source[i + 1] === '*') {
      i += 2;
      while (i + 1 < len && !(source[i] === '*' && source[i + 1] === '/')) i++;
      i = Math.min(i + 2, len);
      continue;
    }

    if (c === '"' || c === "'") {
      const quote = c;
      i++;
      let s = '';
      while (i < len) {
        const ch = source[i];
        if (ch === quote) {
          i++;
          break;
        }
        if (ch === '\\' && i + 1 < len) {
          const n = source[i + 1];
          if (n === 'n') s += '\n';
          else if (n === 't') s += '\t';
          else if (n === 'r') s += '\r';
          else if (n === '\\' || n === quote) s += n;
          else s += n;
          i += 2;
          continue;
        }
        s += ch;
        i++;
      }
      tokens.push({ type: 'str', value: s, start });
      continue;
    }

    if (/[0-9]/.test(c) || (c === '.' && /[0-9]/.test(source[i + 1] ?? ''))) {
      let numStr = '';
      if (c === '.') {
        numStr += c;
        i++;
      }
      while (i < len && /[0-9]/.test(source[i])) {
        numStr += source[i];
        i++;
      }
      if (source[i] === '.' && !numStr.includes('.')) {
        numStr += source[i];
        i++;
        while (i < len && /[0-9]/.test(source[i])) {
          numStr += source[i];
          i++;
        }
      }
      if (source[i] === 'e' || source[i] === 'E') {
        numStr += source[i];
        i++;
        if (source[i] === '+' || source[i] === '-') {
          numStr += source[i];
          i++;
        }
        while (i < len && /[0-9]/.test(source[i])) {
          numStr += source[i];
          i++;
        }
      }
      tokens.push({ type: 'num', value: Number(numStr), start });
      continue;
    }

    if (/[a-zA-Z_$]/.test(c)) {
      let name = '';
      while (i < len && /[a-zA-Z0-9_$]/.test(source[i])) {
        name += source[i];
        i++;
      }
      if (KW.has(name)) {
        tokens.push({
          type: 'kw',
          value: name as Extract<Token, { type: 'kw' }>['value'],
          start,
        });
      } else {
        tokens.push({ type: 'id', value: name, start });
      }
      continue;
    }

    const two = source.slice(i, i + 2);
    const three = source.slice(i, i + 3);

    if (three === '===') {
      pushOp('===', start);
      i += 3;
      continue;
    }
    if (three === '!==') {
      pushOp('!==', start);
      i += 3;
      continue;
    }
    if (two === '==') {
      pushOp('==', start);
      i += 2;
      continue;
    }
    if (two === '!=') {
      pushOp('!=', start);
      i += 2;
      continue;
    }
    if (two === '&&') {
      pushOp('&&', start);
      i += 2;
      continue;
    }
    if (two === '||') {
      pushOp('||', start);
      i += 2;
      continue;
    }
    if (two === '<=') {
      pushOp('<=', start);
      i += 2;
      continue;
    }
    if (two === '>=') {
      pushOp('>=', start);
      i += 2;
      continue;
    }
    if (two === '=>') {
      pushOp('=>', start);
      i += 2;
      continue;
    }

    if (c === '<' || c === '>') {
      tokens.push({ type: 'op', value: c, start });
      i++;
      continue;
    }

    if ('+-*/%!<>(){}[],;:=.'.includes(c)) {
      tokens.push({ type: 'punc', value: c, start });
      i++;
      continue;
    }

    throw new MiniScriptError(`无法识别的字符: ${JSON.stringify(c)}`, {
      phase: 'lex',
      source,
      offset: start,
    });
  }

  tokens.push({ type: 'eof', start: len });
  return tokens;
}

class Parser {
  private i = 0;

  constructor(
    private readonly tokens: Token[],
    private readonly source: string,
  ) {}

  private peek(): Token {
    return this.tokens[this.i] ?? { type: 'eof', start: 0 };
  }

  private eat(): Token {
    return this.tokens[this.i++] ?? { type: 'eof', start: 0 };
  }

  private matchPunc(p: string): boolean {
    const t = this.peek();
    return t.type === 'punc' && t.value === p;
  }

  private expectPunc(p: string): void {
    const t = this.peek();
    if (t.type !== 'punc' || t.value !== p) {
      throw new MiniScriptError(`期望符号 "${p}"，实际为 ${formatTokenBrief(t)}`, {
        phase: 'parse',
        source: this.source,
        offset: t.start,
        extra: `当前 token 详情: ${JSON.stringify(t)}`,
      });
    }
    this.eat();
  }

  private matchOp(op: string): boolean {
    const t = this.peek();
    return t.type === 'op' && t.value === op;
  }

  parseProgram(): Program {
    const body: Stmt[] = [];
    while (this.peek().type !== 'eof') {
      body.push(this.parseStatement());
      if (this.matchPunc(';')) this.eat();
    }
    return { type: 'Program', body };
  }

  private parseStatement(): Stmt {
    const t = this.peek();
    if (t.type === 'punc' && t.value === '{') return this.parseBlockStatement();
    if (t.type === 'kw' && t.value === 'return') {
      this.eat();
      if (this.peek().type === 'eof' || this.matchPunc(';') || this.matchPunc('}')) {
        return { type: 'ReturnStatement' };
      }
      const arg = this.parseExpression();
      return { type: 'ReturnStatement', argument: arg };
    }
    if (t.type === 'kw' && t.value === 'if') {
      return this.parseIfStatement();
    }
    if (t.type === 'kw' && t.value === 'else') {
      throw new MiniScriptError('此处出现孤立的 else（没有对应的 if）', {
        phase: 'parse',
        source: this.source,
        offset: t.start,
      });
    }
    const expr = this.parseExpression();
    return { type: 'ExpressionStatement', expr };
  }

  private parseIfStatement(): Stmt {
    this.eat(); // if
    this.expectPunc('(');
    const test = this.parseExpression();
    this.expectPunc(')');
    const consequent = this.parseStatement();
    let alternate: Stmt | undefined;
    const nt = this.peek();
    if (nt.type === 'kw' && nt.value === 'else') {
      this.eat();
      alternate = this.parseStatement();
    }
    return { type: 'IfStatement', test, consequent, alternate };
  }

  private parseBlockStatement(): Stmt {
    this.expectPunc('{');
    const body: Stmt[] = [];
    while (!this.matchPunc('}') && this.peek().type !== 'eof') {
      body.push(this.parseStatement());
      if (this.matchPunc(';')) this.eat();
    }
    this.expectPunc('}');
    return { type: 'BlockStatement', body };
  }

  private parseExpression(): Expr {
    return this.parseAssign();
  }

  private parseAssign(): Expr {
    const left = this.parseLogicalOr();
    if (this.matchPunc('=')) {
      this.eat();
      if (left.type !== 'Identifier' && left.type !== 'Member') {
        throw new MiniScriptError(
          `赋值左侧必须是标识符或成员表达式，实际为 ${left.type} 表达式`,
          {
            phase: 'parse',
            source: this.source,
            offset: this.peek().start,
          },
        );
      }
      const right = this.parseAssign();
      return { type: 'Assign', left, right };
    }
    return left;
  }

  private parseLogicalOr(): Expr {
    let left = this.parseLogicalAnd();
    while (this.matchOp('||')) {
      this.eat();
      const right = this.parseLogicalAnd();
      left = { type: 'Binary', op: '||', left, right };
    }
    return left;
  }

  private parseLogicalAnd(): Expr {
    let left = this.parseEquality();
    while (this.matchOp('&&')) {
      this.eat();
      const right = this.parseEquality();
      left = { type: 'Binary', op: '&&', left, right };
    }
    return left;
  }

  private parseEquality(): Expr {
    let left = this.parseRelational();
    while (true) {
      const t = this.peek();
      if (t.type !== 'op') break;
      const op = t.value;
      if (op !== '==' && op !== '!=' && op !== '===' && op !== '!==') break;
      this.eat();
      const right = this.parseRelational();
      left = { type: 'Binary', op, left, right };
    }
    return left;
  }

  private parseRelational(): Expr {
    let left = this.parseAdditive();
    while (true) {
      const t = this.peek();
      if (t.type !== 'op') break;
      const op = t.value;
      if (op !== '<' && op !== '>' && op !== '<=' && op !== '>=') break;
      this.eat();
      const right = this.parseAdditive();
      left = { type: 'Binary', op, left, right };
    }
    return left;
  }

  private parseAdditive(): Expr {
    let left = this.parseMultiplicative();
    while (true) {
      const t = this.peek();
      if (t.type !== 'punc' && t.type !== 'op') break;
      const op = t.type === 'punc' ? t.value : t.value;
      if (op !== '+' && op !== '-') break;
      if (t.type === 'punc') this.eat();
      else this.eat();
      const right = this.parseMultiplicative();
      left = { type: 'Binary', op, left, right };
    }
    return left;
  }

  private parseMultiplicative(): Expr {
    let left = this.parseUnary();
    while (true) {
      const t = this.peek();
      if (t.type !== 'punc') break;
      const op = t.value;
      if (op !== '*' && op !== '/' && op !== '%') break;
      this.eat();
      const right = this.parseUnary();
      left = { type: 'Binary', op, left, right };
    }
    return left;
  }

  private parseUnary(): Expr {
    const t = this.peek();
    if (t.type === 'kw' && t.value === 'new') {
      const created = this.parseNewExpression();
      return this.parsePostfixTail(created);
    }
    if ((t.type === 'op' || t.type === 'punc') && t.value === '!') {
      this.eat();
      return { type: 'Unary', op: '!', argument: this.parseUnary() };
    }
    if (t.type === 'punc' && (t.value === '+' || t.value === '-')) {
      const op = t.value;
      this.eat();
      return { type: 'Unary', op, argument: this.parseUnary() };
    }
    return this.parsePostfix();
  }

  private parseNewExpression(): Expr {
    this.eat(); // new
    const t0 = this.peek();
    if (t0.type !== 'id') {
      throw new MiniScriptError(
        `new 后须为构造函数名（或成员访问链），实际为 ${formatTokenBrief(t0)}`,
        {
          phase: 'parse',
          source: this.source,
          offset: t0.start,
        },
      );
    }
    this.eat();
    let callee: Expr = { type: 'Identifier', name: t0.value };
    while (this.matchPunc('.')) {
      this.eat();
      const t = this.peek();
      if (t.type !== 'id')
        throw new MiniScriptError(`new 的成员链中期望属性名（标识符），实际为 ${formatTokenBrief(t)}`, {
          phase: 'parse',
          source: this.source,
          offset: t.start,
        });
      this.eat();
      callee = { type: 'Member', object: callee, property: t.value, computed: false };
    }
    while (this.matchPunc('[')) {
      this.eat();
      const key = this.parseExpression();
      this.expectPunc(']');
      callee = {
        type: 'Member',
        object: callee,
        property: '',
        computed: true,
        keyExpr: key,
      };
    }
    const args: Expr[] = [];
    if (this.matchPunc('(')) {
      this.expectPunc('(');
      if (!this.matchPunc(')')) {
        do {
          args.push(this.parseExpression());
        } while (this.matchPunc(',') && (this.eat(), true));
      }
      this.expectPunc(')');
    }
    return { type: 'New', callee, args };
  }

  private parsePostfixTail(expr: Expr): Expr {
    while (true) {
      if (this.matchPunc('.')) {
        this.eat();
        const t = this.peek();
        if (t.type !== 'id')
        throw new MiniScriptError(`成员访问（.）后期望属性名（标识符），实际为 ${formatTokenBrief(t)}`, {
          phase: 'parse',
          source: this.source,
          offset: t.start,
        });
        this.eat();
        expr = { type: 'Member', object: expr, property: t.value, computed: false };
        continue;
      }
      if (this.matchPunc('[')) {
        this.eat();
        const key = this.parseExpression();
        this.expectPunc(']');
        expr = {
          type: 'Member',
          object: expr,
          property: '',
          computed: true,
          keyExpr: key,
        };
        continue;
      }
      if (this.matchPunc('(')) {
        this.eat();
        const args: Expr[] = [];
        if (!this.matchPunc(')')) {
          do {
            args.push(this.parseExpression());
          } while (this.matchPunc(',') && (this.eat(), true));
        }
        this.expectPunc(')');
        expr = { type: 'Call', callee: expr, args };
        continue;
      }
      break;
    }
    return expr;
  }

  private parsePostfix(): Expr {
    const expr = this.parsePrimary();
    return this.parsePostfixTail(expr);
  }

  private tryParseArrowFromParen(): Expr | null {
    const save = this.i;
    if (!this.matchPunc('(')) return null;
    this.eat();
    const params: string[] = [];
    if (this.matchPunc(')')) {
      this.eat();
    } else {
      while (true) {
        const t = this.peek();
        if (t.type !== 'id') {
          this.i = save;
          return null;
        }
        this.eat();
        params.push(t.value);
        if (this.matchPunc(',')) {
          this.eat();
          continue;
        }
        if (this.matchPunc(')')) {
          this.eat();
          break;
        }
        this.i = save;
        return null;
      }
    }
    if (!this.matchOp('=>')) {
      this.i = save;
      return null;
    }
    this.eat();
    const body = this.parseArrowBody();
    return { type: 'ArrowFunction', params, body };
  }

  private parseArrowBody(): Expr | Stmt[] {
    if (this.matchPunc('{')) {
      const block = this.parseBlockStatement();
      if (block.type !== 'BlockStatement') {
        throw new MiniScriptError(`箭头函数块体解析异常：期望 BlockStatement，实际为 ${block.type}`, {
          phase: 'parse',
          source: this.source,
          offset: this.peek().start,
        });
      }
      return block.body;
    }
    return this.parseExpression();
  }

  private parsePrimary(): Expr {
    const parenArrow = this.tryParseArrowFromParen();
    if (parenArrow) return parenArrow;

    const t = this.peek();

    if (t.type === 'punc' && t.value === '(') {
      this.eat();
      const inner = this.parseExpression();
      this.expectPunc(')');
      return inner;
    }

    if (t.type === 'num') {
      this.eat();
      return { type: 'Literal', value: t.value };
    }
    if (t.type === 'str') {
      this.eat();
      return { type: 'Literal', value: t.value };
    }
    if (t.type === 'kw') {
      if (t.value === 'true' || t.value === 'false' || t.value === 'null' || t.value === 'undefined') {
        this.eat();
        const v =
          t.value === 'true'
            ? true
            : t.value === 'false'
              ? false
              : t.value === 'null'
                ? null
                : undefined;
        return { type: 'Literal', value: v };
      }
    }

    if (t.type === 'id') {
      this.eat();
      if (this.matchOp('=>')) {
        this.eat();
        const body = this.parseArrowBody();
        return { type: 'ArrowFunction', params: [t.value], body };
      }
      return { type: 'Identifier', name: t.value };
    }

    if (t.type === 'punc' && t.value === '[') {
      return this.parseArrayLiteral();
    }
    if (t.type === 'punc' && t.value === '{') {
      return this.parseObjectLiteral();
    }

    throw new MiniScriptError(`此处不能出现 ${formatTokenBrief(t)}（表达式起始位置不合法）`, {
      phase: 'parse',
      source: this.source,
      offset: t.start,
      extra: `原始 token: ${JSON.stringify(t)}`,
    });
  }

  private parseArrayLiteral(): Expr {
    this.expectPunc('[');
    const elements: Expr[] = [];
    if (!this.matchPunc(']')) {
      do {
        elements.push(this.parseExpression());
      } while (this.matchPunc(',') && (this.eat(), true));
    }
    this.expectPunc(']');
    return { type: 'ArrayLiteral', elements };
  }

  private parseObjectLiteral(): Expr {
    this.expectPunc('{');
    const props: { key: string; value: Expr }[] = [];
    while (!this.matchPunc('}') && this.peek().type !== 'eof') {
      const kt = this.peek();
      let key: string;
      if (kt.type === 'id') {
        this.eat();
        key = kt.value;
      } else if (kt.type === 'str') {
        this.eat();
        key = kt.value;
      } else {
        throw new MiniScriptError(`对象字面量的键须为标识符或字符串，实际为 ${formatTokenBrief(kt)}`, {
          phase: 'parse',
          source: this.source,
          offset: kt.start,
        });
      }
      this.expectPunc(':');
      props.push({ key, value: this.parseExpression() });
      if (this.matchPunc(',')) this.eat();
    }
    this.expectPunc('}');
    return { type: 'ObjectLiteral', props };
  }
}

type ExecDiag = { readonly source: string };

class Env {
  private readonly context: Record<string, unknown> | null;

  constructor(
    readonly parent: Env | null,
    private readonly vars: Map<string, unknown> = new Map(),
    context?: Record<string, unknown> | null,
    private readonly execDiag: ExecDiag | null = null,
  ) {
    this.context = context === undefined ? null : context;
  }

  child(): Env {
    return new Env(this, new Map(), undefined, this.execDiag);
  }

  /** 用于运行期错误的脚本定位（沿父链查找） */
  private getExecSource(): string | undefined {
    if (this.execDiag) return this.execDiag.source;
    return this.parent?.getExecSource();
  }

  /** 供解释器构造运行期 MiniScriptError 的附加上下文 */
  buildRuntimeErrorDetails(extra?: string): MiniScriptErrorDetails {
    return {
      phase: 'runtime',
      source: this.getExecSource(),
      extra,
    };
  }

  private contextHas(name: string): boolean {
    return (
      this.context !== null && Object.prototype.hasOwnProperty.call(this.context, name)
    );
  }

  /** 标识符是否在本层或祖先中已有定义（用于赋值解析） */
  private definesInChain(name: string): boolean {
    if (this.vars.has(name)) return true;
    if (this.contextHas(name)) return true;
    return this.parent?.definesInChain(name) ?? false;
  }

  get(name: string): unknown {
    if (this.vars.has(name)) return this.vars.get(name);
    if (this.contextHas(name)) return this.context![name];
    if (this.parent) return this.parent.get(name);
    throw new MiniScriptError(
      `未定义的标识符: ${name}`,
      this.buildRuntimeErrorDetails(
        `请通过 execute(script, context) 的 context 传入 "${name}"，或使用 registerFunction / registerGlobal 注册后再使用。`,
      ),
    );
  }

  set(name: string, value: unknown): void {
    if (this.vars.has(name)) {
      this.vars.set(name, value);
      return;
    }
    if (this.contextHas(name)) {
      this.context![name] = value;
      return;
    }
    if (this.parent?.definesInChain(name)) {
      this.parent.set(name, value);
      return;
    }
    if (this.context !== null) {
      this.context[name] = value;
      return;
    }
    this.vars.set(name, value);
  }

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

function defaultBuiltins(): Record<string, unknown> {
  return {
    Math,
    Date,
    String,
    Array,
    Object,
    Number,
    Boolean,
    console,
    JSON,
    parseInt,
    parseFloat,
    isNaN,
    isFinite,
    Infinity,
    NaN,
    undefined,
  };
}

type ArrowCallable = (...args: unknown[]) => unknown;

function runStatement(st: Stmt, env: Env): { value: unknown; returned: boolean } {
  switch (st.type) {
    case 'ReturnStatement':
      return {
        value: st.argument === undefined ? undefined : evaluate(st.argument, env),
        returned: true,
      };
    case 'ExpressionStatement':
      return { value: evaluate(st.expr, env), returned: false };
    case 'BlockStatement': {
      const inner = env.child();
      return runBlock(st.body, inner);
    }
    case 'IfStatement': {
      const cond = evaluate(st.test, env);
      const branch = cond ? st.consequent : st.alternate;
      if (!branch) return { value: undefined, returned: false };
      return runStatement(branch, env);
    }
    default:
      throw new MiniScriptError(`未知语句类型: ${(st as { type: string }).type}`, env.buildRuntimeErrorDetails());
  }
}

function runBlock(body: Stmt[], env: Env): { value: unknown; returned: boolean } {
  let last: unknown = undefined;
  for (const st of body) {
    const r = runStatement(st, env);
    if (r.returned) return r;
    last = r.value;
  }
  return { value: last, returned: false };
}

function evaluate(expr: Expr, env: Env): unknown {
  switch (expr.type) {
    case 'Literal':
      return expr.value;
    case 'Identifier':
      return env.get(expr.name);
    case 'Unary': {
      const v = evaluate(expr.argument, env);
      if (expr.op === '!') return !v;
      if (expr.op === '+') return +Number(v);
      if (expr.op === '-') return -Number(v);
      throw new MiniScriptError(`未知一元运算符: ${expr.op}`, env.buildRuntimeErrorDetails());
    }
    case 'Binary': {
      const l = evaluate(expr.left, env);
      if (expr.op === '&&') return l && evaluate(expr.right, env);
      if (expr.op === '||') return l || evaluate(expr.right, env);
      const r = evaluate(expr.right, env);
      switch (expr.op) {
        case '+':
          if (typeof l === 'string' || typeof r === 'string') return String(l) + String(r);
          return (l as number) + (r as number);
        case '-':
          return (l as number) - (r as number);
        case '*':
          return (l as number) * (r as number);
        case '/':
          return (l as number) / (r as number);
        case '%':
          return (l as number) % (r as number);
        case '==':
          return l == r;
        case '!=':
          return l != r;
        case '===':
          return l === r;
        case '!==':
          return l !== r;
        case '<':
          return (l as number) < (r as number);
        case '>':
          return (l as number) > (r as number);
        case '<=':
          return (l as number) <= (r as number);
        case '>=':
          return (l as number) >= (r as number);
        default:
          throw new MiniScriptError(`未知二元运算符: ${expr.op}`, env.buildRuntimeErrorDetails());
      }
    }
    case 'Member': {
      const obj = evaluate(expr.object, env);
      const key = expr.computed
        ? evaluate(expr.keyExpr!, env)
        : expr.property;
      if (obj === null || obj === undefined) {
        const keyDesc = expr.computed ? `[${String(key)}]` : `.${expr.property}`;
        throw new MiniScriptError(
          `无法读取 null 或 undefined 的属性 ${keyDesc}（对象为 ${String(obj)}）`,
          env.buildRuntimeErrorDetails(`访问链: ${describeCalleeExpr(expr)}`),
        );
      }
      return (obj as Record<string | number, unknown>)[key as string | number];
    }
    case 'Assign': {
      const rhs = evaluate(expr.right, env);
      if (expr.left.type === 'Identifier') {
        env.set(expr.left.name, rhs);
        return rhs;
      }
      if (expr.left.type === 'Member') {
        const obj = evaluate(expr.left.object, env);
        const key = expr.left.computed
          ? evaluate(expr.left.keyExpr!, env)
          : expr.left.property;
        if (obj === null || obj === undefined) {
          const keyDesc = expr.left.computed ? `[${String(key)}]` : `.${expr.left.property}`;
          throw new MiniScriptError(
            `无法对 null 或 undefined 的成员 ${keyDesc} 赋值`,
            env.buildRuntimeErrorDetails(`左值为成员表达式: ${describeCalleeExpr(expr.left)}`),
          );
        }
        (obj as Record<string | number, unknown>)[key as string | number] = rhs;
        return rhs;
      }
      throw new MiniScriptError(
        `非法赋值目标（类型 ${expr.left.type}）`,
        env.buildRuntimeErrorDetails(),
      );
    }
    case 'ArrayLiteral':
      return expr.elements.map((e) => evaluate(e, env));
    case 'ObjectLiteral': {
      const o: Record<string, unknown> = {};
      for (const p of expr.props) {
        o[p.key] = evaluate(p.value, env);
      }
      return o;
    }
    case 'Call': {
      const args = expr.args.map((a) => evaluate(a, env));
      if (expr.callee.type === 'Member') {
        const thisArg = evaluate(expr.callee.object, env);
        const key = expr.callee.computed
          ? evaluate(expr.callee.keyExpr!, env)
          : expr.callee.property;
        if (thisArg === null || thisArg === undefined) {
          const keyDesc = expr.callee.computed ? `[${String(key)}]` : `.${expr.callee.property}`;
          throw new MiniScriptError(
            `无法在 null 或 undefined 上调用方法 ${keyDesc}`,
            env.buildRuntimeErrorDetails(`调用目标: ${describeCalleeExpr(expr.callee)}`),
          );
        }
        const fn = (thisArg as Record<string | number, unknown>)[key as string | number];
        if (typeof fn !== 'function') {
          const keyDesc = expr.callee.computed ? `[${String(key)}]` : `.${expr.callee.property}`;
          throw new MiniScriptError(
            `属性 ${keyDesc} 不是函数（实际类型: ${fn === null ? 'null' : typeof fn}）`,
            env.buildRuntimeErrorDetails(`调用: ${describeCalleeExpr(expr.callee)}(...)`),
          );
        }
        return (fn as (...a: unknown[]) => unknown).apply(thisArg, args);
      }
      const callee = evaluate(expr.callee, env);
      if (typeof callee !== 'function') {
        throw new MiniScriptError(
          `调用目标不是函数: ${describeCalleeExpr(expr.callee)}（实际类型: ${callee === null ? 'null' : typeof callee}）`,
          env.buildRuntimeErrorDetails(),
        );
      }
      return (callee as (...a: unknown[]) => unknown)(...args);
    }
    case 'New': {
      const Ctor = evaluate(expr.callee, env);
      if (typeof Ctor !== 'function') {
        throw new MiniScriptError(
          `new 的目标不是函数: ${describeCalleeExpr(expr.callee)}（实际类型: ${Ctor === null ? 'null' : typeof Ctor}）`,
          env.buildRuntimeErrorDetails(),
        );
      }
      const args = expr.args.map((a) => evaluate(a, env));
      return new (Ctor as new (...a: unknown[]) => unknown)(...args);
    }
    case 'ArrowFunction': {
      const captured = env;
      const params = expr.params;
      const body = expr.body;
      const fn: ArrowCallable = (...args: unknown[]) => {
        const local = captured.child();
        for (let i = 0; i < params.length; i++) {
          local.define(params[i], args[i]);
        }
        if (Array.isArray(body)) {
          const r = runBlock(body, local);
          return r.value;
        }
        return evaluate(body, local);
      };
      Object.defineProperty(fn, 'length', { value: params.length });
      return fn;
    }
    default:
      throw new MiniScriptError(
        `未知表达式类型: ${(expr as { type: string }).type}`,
        env.buildRuntimeErrorDetails(),
      );
  }
}

function runProgram(prog: Program, env: Env): unknown {
  let last: unknown = undefined;
  for (const st of prog.body) {
    const r = runStatement(st, env);
    if (r.returned) return r.value;
    last = r.value;
  }
  return last;
}

function isExtensionNamespaceNode(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

/**
 * MiniScript — 轻量脚本执行器（词法 / 递归下降解析 / 解释执行），语法为 JavaScript 子集。

  ## 支持的语法概要

  - 语句：return、表达式语句、{ ... } 块、if (条件) 语句 [else 语句]（含 else if 链）；多条语句可用分号分隔。无 return 时，结果为最后一条表达式语句的值。
  - 字面量：数字；单/双引号字符串（含常用转义）；true / false / null / undefined；数组 [a, b]；对象 { key: value, "k2": v }。
  - 运算符：+ - * / %；&& || !（&&、|| 短路）；== != === !==；< > <= >=；一元 + -。任一侧为字符串时 + 做拼接。
  - 访问与调用：.、[]、f(a, b)；方法调用会绑定 this（如 [1,2].map(x => x * 2)）。
  - new：new Constructor 或 new Constructor(a, b)；左侧可为标识符及 .、[ 成员链（如 new Date(2020,0,1).getFullYear()）。
  - 箭头函数：x => expr、(a, b) => expr、块体 (x) => { return x + 1 }，可闭包捕获外层环境。
  - 赋值：=，左侧为标识符或成员表达式；顶层新变量写入 execute 的 context 对象。
  - 注释：行注释 //，以及成对的块注释（斜杠星号 … 星号斜杠）。

  ## 内置全局（脚本中可直接使用）

  Math、Date、String、Array、Object、Number、Boolean、JSON、parseInt、parseFloat、isNaN、isFinite、Infinity、NaN、undefined。

  ## 扩展与上下文

  - registerFunction(name, fn)：注册全局函数；与内置同名时覆盖内置绑定。name 含点号时按路径挂到嵌套对象上（如 uni.getSystemInfo → 全局 uni.getSystemInfo）。
  - execute(script, context)：context 的自有属性作为顶层变量；赋值会回写到该对象（含脚本内新建的顶层变量）。

  ## 注意事项与限制

  - 非完整 JS：无 var/let/const、function 声明、模板字符串、可选链、空值合并、class、async/await、import 等。
  - new 限制：构造函数路径不支持任意括号包裹表达式（例如 new (getCtor())()）。
  - 安全：脚本可使用 context 与注册函数暴露的能力及宿主 API；请在可信环境或对 API 做白名单后再交给不可信用户脚本。
 */
export class MiniScript {
  /** 注册的扩展：顶层键对应函数或命名空间对象（由带点号的 name 合并而成） */
  private readonly extensions: Record<string, unknown> = {};

  /**
   * 注册可在脚本中直接调用的函数。
   * - 无点号：作为顶层全局名注册，如 `double` → `double()`。
   * - 含点号：按路径挂到嵌套普通对象上，如 `uni.getSystemInfo` → `uni.getSystemInfo()`（自动创建 `uni` 对象）。
   * 若路径前缀已被非对象占用（如已为函数），将抛出错误。
   */
  registerFunction(name: string, func: Function): void {
    const parts = name
      .split('.')
      .map((s) => s.trim())
      .filter((s) => s.length > 0);
    if (parts.length === 0) {
      throw new MiniScriptError('registerFunction: 名称不能为空', { phase: 'register' });
    }
    if (parts.length === 1) {
      this.extensions[parts[0]] = func;
      return;
    }
    let cur = this.extensions;
    for (let i = 0; i < parts.length - 1; i++) {
      const key = parts[i];
      const prefix = parts.slice(0, i + 1).join('.');
      const existing = cur[key];
      if (existing === undefined) {
        const nested: Record<string, unknown> = {};
        cur[key] = nested;
        cur = nested;
      } else if (isExtensionNamespaceNode(existing)) {
        cur = existing;
      } else {
        throw new MiniScriptError(
          `registerFunction: 路径前缀 "${prefix}" 已存在且不是命名空间对象，无法继续注册 "${name}"`,
          { phase: 'register', extra: `冲突键: "${prefix}"` },
        );
      }
    }
    cur[parts[parts.length - 1]] = func;
  }

  /**
   * 注册全局变量
   */
  registerGlobal(name: string, value: unknown): void {
    if (!name)
      throw new MiniScriptError('registerGlobal: 名称不能为空', { phase: 'register' });
    if (name.includes('.'))
      throw new MiniScriptError('registerGlobal: 名称不能包含点号', {
        phase: 'register',
        extra: `非法名称: "${name}"`,
      });
    if (typeof value === 'undefined')
      throw new MiniScriptError('registerGlobal: 值不能为 undefined', {
        phase: 'register',
        extra: `名称 "${name}"`,
      });
    if (typeof value !== 'object')
      throw new MiniScriptError('registerGlobal: 值必须为对象', {
        phase: 'register',
        extra: `名称 "${name}"，实际类型: ${value === null ? 'null' : typeof value}`,
      });
    this.extensions[name] = value;
  }

  /**
   * 执行脚本。`context` 中的键可作为顶层变量；`registerFunction` 注册的函数优先于同名内置。
   * 支持 `return`；若无 `return`，取最后一条表达式语句的值。
   */
  execute(script: string, context: Record<string, unknown> = {}): unknown {
    const tokens = tokenize(script);
    const parser = new Parser(tokens, script);
    const prog = parser.parseProgram();
    const globals = new Env(null);
    for (const [k, v] of Object.entries(defaultBuiltins()))
      globals.define(k, v);
    for (const [k, v] of Object.entries(this.extensions))
      globals.define(k, v);
    const root = new Env(globals, new Map(), context, { source: script });
    return runProgram(prog, root);
  }
}
