function tokenize(expression) {
  const tokens = [];
  let i = 0;
  const src = expression;

  while (i < src.length) {
    if (/\s/.test(src[i])) {
      i++;
      continue;
    }

    if (src[i] === '[') {
      let j = i + 1;
      while (j < src.length && src[j] !== ']') j++;
      tokens.push({ type: 'FIELD_REF', value: src.slice(i, j + 1) });
      i = j + 1;
      continue;
    }

    if (/[0-9]/.test(src[i]) || (src[i] === '.' && /[0-9]/.test(src[i + 1] || ''))) {
      let j = i;
      while (j < src.length && /[0-9.]/.test(src[j])) j++;
      tokens.push({ type: 'NUMBER', value: src.slice(i, j) });
      i = j;
      continue;
    }

    if (src[i] === '"' || src[i] === "'") {
      const quote = src[i];
      let j = i + 1;
      while (j < src.length && src[j] !== quote) j++;
      tokens.push({ type: 'STRING', value: src.slice(i + 1, j) });
      i = j + 1;
      continue;
    }

    if (/[A-Za-z_]/.test(src[i])) {
      let j = i;
      while (j < src.length && /[A-Za-z0-9_]/.test(src[j])) j++;
      const word = src.slice(i, j);
      tokens.push({ type: 'IDENT', value: word });
      i = j;
      continue;
    }

    if (src[i] === '!' && src[i + 1] === '=') {
      tokens.push({ type: 'CMP', value: '!=' });
      i += 2;
      continue;
    }
    if (src[i] === '>' && src[i + 1] === '=') {
      tokens.push({ type: 'CMP', value: '>=' });
      i += 2;
      continue;
    }
    if (src[i] === '<' && src[i + 1] === '=') {
      tokens.push({ type: 'CMP', value: '<=' });
      i += 2;
      continue;
    }
    if (src[i] === '=') { tokens.push({ type: 'CMP', value: '=' }); i++; continue; }
    if (src[i] === '>') { tokens.push({ type: 'CMP', value: '>' }); i++; continue; }
    if (src[i] === '<') { tokens.push({ type: 'CMP', value: '<' }); i++; continue; }

    if (src[i] === '+' || src[i] === '-' || src[i] === '*' || src[i] === '/') {
      tokens.push({ type: 'OP', value: src[i] });
      i++;
      continue;
    }

    if (src[i] === '(') { tokens.push({ type: 'LPAREN', value: '(' }); i++; continue; }
    if (src[i] === ')') { tokens.push({ type: 'RPAREN', value: ')' }); i++; continue; }
    if (src[i] === ',') { tokens.push({ type: 'COMMA', value: ',' }); i++; continue; }

    i++;
  }

  tokens.push({ type: 'EOF', value: '' });
  return tokens;
}

function parse(tokens) {
  let pos = 0;

  const peek = () => tokens[pos];
  const consume = () => tokens[pos++];

  function parseExpression() {
    return parseComparison();
  }

  function parseComparison() {
    const left = parseAdditive();
    if (peek().type === 'CMP') {
      const op = consume().value;
      const right = parseAdditive();
      return { kind: 'cmp', op, left, right };
    }
    return left;
  }

  function parseAdditive() {
    let node = parseMultiplicative();
    while (peek().type === 'OP' && (peek().value === '+' || peek().value === '-')) {
      const op = consume().value;
      const right = parseMultiplicative();
      node = { kind: 'binop', op, left: node, right };
    }
    return node;
  }

  function parseMultiplicative() {
    let node = parseUnary();
    while (peek().type === 'OP' && (peek().value === '*' || peek().value === '/')) {
      const op = consume().value;
      const right = parseUnary();
      node = { kind: 'binop', op, left: node, right };
    }
    return node;
  }

  function parseUnary() {
    if (peek().type === 'OP' && peek().value === '-') {
      consume();
      const operand = parseUnary();
      return { kind: 'unary', op: '-', operand };
    }
    return parsePrimary();
  }

  function parsePrimary() {
    const tok = peek();

    if (tok.type === 'NUMBER') {
      consume();
      return { kind: 'number', value: parseFloat(tok.value) };
    }

    if (tok.type === 'STRING') {
      consume();
      return { kind: 'string', value: tok.value };
    }

    if (tok.type === 'FIELD_REF') {
      consume();
      return { kind: 'field', ref: tok.value };
    }

    if (tok.type === 'IDENT' && tok.value.toUpperCase() === 'IF') {
      consume();
      if (peek().type !== 'LPAREN') throw new Error('Expected ( after IF');
      consume();
      const cond = parseExpression();
      if (peek().type !== 'COMMA') throw new Error('Expected , in IF');
      consume();
      const then = parseExpression();
      if (peek().type !== 'COMMA') throw new Error('Expected , in IF');
      consume();
      const els = parseExpression();
      if (peek().type !== 'RPAREN') throw new Error('Expected ) after IF');
      consume();
      return { kind: 'if', cond, then, els };
    }

    if (tok.type === 'LPAREN') {
      consume();
      const node = parseExpression();
      if (peek().type !== 'RPAREN') throw new Error('Expected )');
      consume();
      return node;
    }

    throw new Error(`Unexpected token: ${tok.type} "${tok.value}"`);
  }

  return parseExpression();
}

function evalNode(node, resolveField) {
  switch (node.kind) {
    case 'number':
      return node.value;

    case 'string':
      return node.value;

    case 'field': {
      const raw = resolveField(node.ref);
      return raw;
    }

    case 'unary': {
      const val = evalNode(node.operand, resolveField);
      const n = Number(val);
      return isNaN(n) ? 0 : -n;
    }

    case 'binop': {
      const l = evalNode(node.left, resolveField);
      const r = evalNode(node.right, resolveField);
      const ln = Number(l);
      const rn = Number(r);
      const lv = isNaN(ln) ? 0 : ln;
      const rv = isNaN(rn) ? 0 : rn;
      if (node.op === '+') return lv + rv;
      if (node.op === '-') return lv - rv;
      if (node.op === '*') return lv * rv;
      if (node.op === '/') {
        if (rv === 0) throw { divByZero: true };
        return lv / rv;
      }
      return 0;
    }

    case 'cmp': {
      const l = evalNode(node.left, resolveField);
      const r = evalNode(node.right, resolveField);
      const ln = Number(l);
      const rn = Number(r);
      const useNum = !isNaN(ln) && !isNaN(rn);
      const lv = useNum ? ln : String(l);
      const rv = useNum ? rn : String(r);
      if (node.op === '=') return lv == rv;
      if (node.op === '!=') return lv != rv;
      if (node.op === '>') return lv > rv;
      if (node.op === '<') return lv < rv;
      if (node.op === '>=') return lv >= rv;
      if (node.op === '<=') return lv <= rv;
      return false;
    }

    case 'if': {
      const condition = evalNode(node.cond, resolveField);
      if (condition) {
        return evalNode(node.then, resolveField);
      } else {
        return evalNode(node.els, resolveField);
      }
    }

    default:
      throw new Error(`Unknown node kind: ${node.kind}`);
  }
}

export function evaluateExpression(expression, resolveField) {
  if (!expression || !expression.trim()) return '';
  try {
    const tokens = tokenize(expression);
    const ast = parse(tokens);
    const result = evalNode(ast, resolveField);
    return result !== undefined && result !== null ? String(result) : '';
  } catch (e) {
    if (e && e.divByZero) return '#DIV/0';
    return '#ERR';
  }
}
