// lib/process/stages/policy-dsl-legacy.js — DEPRECATED string-expression DSL.
//
// The hand-rolled tokenizer/parser kept for ONE release as a back-compat path
// while string rules migrate to JSON predicates (`../policy-predicates.js`).
// New workflows must author predicate JSON; the spec-validator accepts both
// forms for now and the policy_gate handler routes per-rule. Do not extend
// this grammar — it is on a removal track.
//
// Rule grammar:
//   - intake.<field>
//   - prior_stage('<slug>').<field>
//   - Comparison: ==, !=, <, <=, >, >=
//   - Logical: && ||, parens
//   - String literals: 'foo'
//   - Number literals: 1, 0.5
//   - Membership: <left> IN [<lit>, <lit>, ...]

function readPath(obj, path) {
  if (!obj || !path) return undefined;
  return path.split('.').reduce((acc, key) => (acc == null ? acc : acc[key]), obj);
}

function tokenize(expr) {
  const tokens = [];
  let i = 0;
  while (i < expr.length) {
    const ch = expr[i];
    if (/\s/.test(ch)) { i++; continue; }
    if (ch === '(' || ch === ')' || ch === '[' || ch === ']' || ch === ',') {
      tokens.push({ type: ch }); i++; continue;
    }
    if (ch === '&' && expr[i + 1] === '&') { tokens.push({ type: '&&' }); i += 2; continue; }
    if (ch === '|' && expr[i + 1] === '|') { tokens.push({ type: '||' }); i += 2; continue; }
    if (ch === '=' && expr[i + 1] === '=') { tokens.push({ type: 'op', value: '==' }); i += 2; continue; }
    if (ch === '!' && expr[i + 1] === '=') { tokens.push({ type: 'op', value: '!=' }); i += 2; continue; }
    if (ch === '<' && expr[i + 1] === '=') { tokens.push({ type: 'op', value: '<=' }); i += 2; continue; }
    if (ch === '>' && expr[i + 1] === '=') { tokens.push({ type: 'op', value: '>=' }); i += 2; continue; }
    if (ch === '<' || ch === '>') { tokens.push({ type: 'op', value: ch }); i++; continue; }
    if (ch === "'" || ch === '"') {
      const end = expr.indexOf(ch, i + 1);
      if (end === -1) throw new Error(`policy-gate: unterminated string at ${i}`);
      tokens.push({ type: 'string', value: expr.slice(i + 1, end) });
      i = end + 1; continue;
    }
    if (/[0-9.]/.test(ch)) {
      let j = i;
      while (j < expr.length && /[0-9.]/.test(expr[j])) j++;
      tokens.push({ type: 'number', value: Number(expr.slice(i, j)) });
      i = j; continue;
    }
    if (/[A-Za-z_]/.test(ch)) {
      let j = i;
      while (j < expr.length && /[A-Za-z0-9_.]/.test(expr[j])) j++;
      const word = expr.slice(i, j);
      if (word === 'IN' || word === 'in') { tokens.push({ type: 'IN' }); }
      else if (word === 'true') { tokens.push({ type: 'boolean', value: true }); }
      else if (word === 'false') { tokens.push({ type: 'boolean', value: false }); }
      else if (word === 'prior_stage' && expr[j] === '(') {
        // Parse prior_stage('slug').field
        const closeQuote = expr.indexOf(')', j);
        const inner = expr.slice(j + 1, closeQuote).trim().replace(/^['"]|['"]$/g, '');
        let k = closeQuote + 1;
        // Expect .field
        if (expr[k] !== '.') throw new Error("policy-gate: expected '.' after prior_stage(...)");
        let m = k + 1;
        while (m < expr.length && /[A-Za-z0-9_.]/.test(expr[m])) m++;
        tokens.push({ type: 'ref', kind: 'prior_stage', stage: inner, path: expr.slice(k + 1, m) });
        i = m; continue;
      } else {
        tokens.push({ type: 'ref', kind: 'path', path: word });
      }
      i = j; continue;
    }
    throw new Error(`policy-gate: unexpected character "${ch}" at ${i}`);
  }
  return tokens;
}

function resolveRef(token, ctx) {
  if (token.kind === 'prior_stage') {
    return readPath(ctx.context?.[token.stage], token.path);
  }
  // path like intake.email or stage_output.foo.bar
  if (token.path.startsWith('intake.')) {
    return readPath(ctx.intake, token.path.slice('intake.'.length));
  }
  if (token.path.startsWith('context.')) {
    return readPath(ctx.context, token.path.slice('context.'.length));
  }
  return readPath(ctx, token.path);
}

function evalTokens(tokens, ctx) {
  // Pratt-ish recursive descent. Grammar:
  //   expr := or
  //   or := and ('||' and)*
  //   and := cmp ('&&' cmp)*
  //   cmp := value (op value)?
  //        | value IN '[' values ']'
  //   value := ref | number | string | boolean | '(' or ')'
  let pos = 0;
  function peek() { return tokens[pos]; }
  function consume(type) {
    const t = tokens[pos];
    if (!t || t.type !== type) throw new Error(`policy-gate: expected ${type}, got ${t?.type || 'EOF'}`);
    pos++;
    return t;
  }
  function value() {
    const t = peek();
    if (!t) throw new Error('policy-gate: unexpected end of expression');
    if (t.type === '(') { pos++; const e = or(); consume(')'); return e; }
    if (t.type === 'number' || t.type === 'string' || t.type === 'boolean') {
      pos++;
      return t.value;
    }
    if (t.type === 'ref') {
      pos++;
      return resolveRef(t, ctx);
    }
    throw new Error(`policy-gate: unexpected token ${t.type}`);
  }
  function cmp() {
    const left = value();
    const t = peek();
    if (!t) return Boolean(left);
    if (t.type === 'op') {
      pos++;
      const right = value();
      switch (t.value) {
        case '==': return left == right; // eslint-disable-line eqeqeq
        case '!=': return left != right; // eslint-disable-line eqeqeq
        case '<': return Number(left) < Number(right);
        case '<=': return Number(left) <= Number(right);
        case '>': return Number(left) > Number(right);
        case '>=': return Number(left) >= Number(right);
      }
    }
    if (t.type === 'IN') {
      pos++;
      consume('[');
      const list = [];
      while (peek() && peek().type !== ']') {
        const v = value();
        list.push(v);
        if (peek() && peek().type === ',') pos++;
      }
      consume(']');
      return list.includes(left);
    }
    return Boolean(left);
  }
  function and() {
    let left = cmp();
    while (peek() && peek().type === '&&') { pos++; const right = cmp(); left = Boolean(left) && Boolean(right); }
    return left;
  }
  function or() {
    let left = and();
    while (peek() && peek().type === '||') { pos++; const right = and(); left = Boolean(left) || Boolean(right); }
    return left;
  }
  const result = or();
  if (pos !== tokens.length) throw new Error(`policy-gate: unexpected trailing token at ${pos}`);
  return result;
}

// DEPRECATED — evaluate a legacy string-DSL rule. Returns { result, error }.
export function evaluateRule(rule, { intake, context }) {
  if (!rule || typeof rule !== 'string') return { result: true, error: null };
  try {
    const tokens = tokenize(rule);
    const result = Boolean(evalTokens(tokens, { intake, context }));
    return { result, error: null };
  } catch (err) {
    return { result: false, error: err?.message || String(err) };
  }
}
