/**
 * `when` mini-language evaluator (ticket #43, carnet §7.2) — deliberately
 * limited, evaluated by IdeA itself (never handed to the plugin as code):
 * boolean variables (`projectOpen`, `gitRepository`, `agentSelected`,
 * `terminalFocused`, `layoutCellFocused`), `&&`/`||`/`!`, and parentheses.
 *
 * An invalid expression never throws past this module: the contribution using
 * it is disabled with a diagnostic reason instead (carnet: "Toute expression
 * invalide => contribution disabled + raison diagnostic, pas crash").
 */

export type WhenVariable =
  | "projectOpen"
  | "gitRepository"
  | "agentSelected"
  | "terminalFocused"
  | "layoutCellFocused";

const VARIABLES: ReadonlySet<string> = new Set([
  "projectOpen",
  "gitRepository",
  "agentSelected",
  "terminalFocused",
  "layoutCellFocused",
]);

export type WhenContext = Record<WhenVariable, boolean>;

export type WhenEvalResult =
  | { ok: true; value: boolean }
  | { ok: false; reason: string };

type Token =
  | { kind: "var"; name: string }
  | { kind: "and" | "or" | "not" | "lparen" | "rparen" };

function tokenize(expr: string): Token[] | null {
  const tokens: Token[] = [];
  let i = 0;
  while (i < expr.length) {
    const ch = expr[i];
    if (ch === " " || ch === "\t") {
      i += 1;
      continue;
    }
    if (ch === "(") {
      tokens.push({ kind: "lparen" });
      i += 1;
      continue;
    }
    if (ch === ")") {
      tokens.push({ kind: "rparen" });
      i += 1;
      continue;
    }
    if (ch === "!") {
      tokens.push({ kind: "not" });
      i += 1;
      continue;
    }
    if (expr.startsWith("&&", i)) {
      tokens.push({ kind: "and" });
      i += 2;
      continue;
    }
    if (expr.startsWith("||", i)) {
      tokens.push({ kind: "or" });
      i += 2;
      continue;
    }
    const match = /^[A-Za-z][A-Za-z0-9]*/.exec(expr.slice(i));
    if (match) {
      tokens.push({ kind: "var", name: match[0] });
      i += match[0].length;
      continue;
    }
    return null;
  }
  return tokens;
}

/** Recursive-descent parser/evaluator, precedence: `!` > `&&` > `||`. */
class Parser {
  private pos = 0;
  constructor(private readonly tokens: Token[]) {}

  private peek(): Token | undefined {
    return this.tokens[this.pos];
  }

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

  parseOr(ctx: WhenContext): boolean {
    let value = this.parseAnd(ctx);
    while (this.peek()?.kind === "or") {
      this.next();
      const rhs = this.parseAnd(ctx);
      value = value || rhs;
    }
    return value;
  }

  private parseAnd(ctx: WhenContext): boolean {
    let value = this.parseUnary(ctx);
    while (this.peek()?.kind === "and") {
      this.next();
      const rhs = this.parseUnary(ctx);
      value = value && rhs;
    }
    return value;
  }

  private parseUnary(ctx: WhenContext): boolean {
    if (this.peek()?.kind === "not") {
      this.next();
      return !this.parseUnary(ctx);
    }
    return this.parseAtom(ctx);
  }

  private parseAtom(ctx: WhenContext): boolean {
    const tok = this.next();
    if (!tok) throw new Error("unexpected end of expression");
    if (tok.kind === "lparen") {
      const value = this.parseOr(ctx);
      const close = this.next();
      if (close?.kind !== "rparen") throw new Error("expected \")\"");
      return value;
    }
    if (tok.kind === "var") {
      if (!VARIABLES.has(tok.name)) throw new Error(`unknown variable "${tok.name}"`);
      return ctx[tok.name as WhenVariable];
    }
    throw new Error("expected a variable, \"!\" or \"(\"");
  }

  finished(): boolean {
    return this.pos >= this.tokens.length;
  }
}

/** Evaluates a `when` expression; `undefined` means "no condition" ⇒ always true. */
export function evaluateWhen(expr: string | undefined, ctx: WhenContext): WhenEvalResult {
  if (expr === undefined || expr.trim() === "") return { ok: true, value: true };
  const tokens = tokenize(expr);
  if (!tokens) return { ok: false, reason: `invalid "when" expression: ${expr}` };
  try {
    const parser = new Parser(tokens);
    const value = parser.parseOr(ctx);
    if (!parser.finished()) throw new Error("unexpected trailing tokens");
    return { ok: true, value };
  } catch (e) {
    const reason = e instanceof Error ? e.message : String(e);
    return { ok: false, reason: `invalid "when" expression "${expr}": ${reason}` };
  }
}
