import type { RPGCondition, RPGVariables } from "../types";
import { isEvalContext, type EvalContext } from "../core/world";
import { resolveVariable } from "./variables";

type ConditionOperator = '==' | '!=' | '>' | '<' | '>=' | '<=' | 'null' | '~null';
type ConditionValue = string | number | boolean | null;

export interface ParsedCondition {
    variable: string;
    negate: boolean;
    operator?: ConditionOperator;
    value?: ConditionValue;
}

const parseCache = new Map<string, ParsedCondition>();

export function parseCondition(s: RPGCondition): ParsedCondition {
    const cached = parseCache.get(s);
    if (cached) return cached;
    let result: ParsedCondition;

    // ~variable — falsy check, nothing else allowed
    if (s.startsWith('~') && !s.includes(' ')) {
        result = { variable: s.slice(1), negate: true };
    } else {
        const spaceIdx = s.indexOf(' ');
        if (spaceIdx === -1) {
            result = { variable: s, negate: false };
        } else {
            const variable = s.slice(0, spaceIdx);
            const rest = s.slice(spaceIdx + 1).trim();

            if (rest === 'null') {
                result = { variable, negate: false, operator: 'null' };
            } else if (rest === '~null') {
                result = { variable, negate: false, operator: '~null' };
            } else {
                const opMatch = rest.match(/^(==|!=|>=|<=|>|<)\s*(.+)$/);
                if (!opMatch) throw new Error(`Invalid condition: "${s}"`);

                const [, operator, rawValue] = opMatch;
                let value: ConditionValue;
                if (rawValue === 'null') value = null;
                else if (rawValue === 'true') value = true;
                else if (rawValue === 'false') value = false;
                else if (!isNaN(Number(rawValue))) value = Number(rawValue);
                else value = rawValue.replace(/^['"]|['"]$/g, '');

                result = { variable, negate: false, operator: operator as ConditionOperator, value };
            }
        }
    }

    parseCache.set(s, result);
    return result;
}

function evalParsed({ negate, operator, value }: ParsedCondition, val: RPGVariables[string]): boolean {
    if (operator === 'null') return val == null;
    if (operator === '~null') return val != null;

    if (operator === undefined)
        return negate ? !Boolean(val) : Boolean(val);

    if (operator === '==') return val === value;
    if (operator === '!=') return val !== value;

    if (typeof val !== 'number' || typeof value !== 'number') return false;
    if (operator === '<') return val < value;
    if (operator === '>') return val > value;
    if (operator === '<=') return val <= value;
    if (operator === '>=') return val >= value;

    return false;
}

type Cond = ParsedCondition | RPGCondition;

export function evaluateCondition(condition: Cond, variables: RPGVariables): boolean;
export function evaluateCondition(condition: Cond, ctx: EvalContext): boolean;
export function evaluateCondition(condition: Cond, variablesOrCtx: RPGVariables | EvalContext): boolean {
    const parsed = typeof condition === 'string' ? parseCondition(condition) : condition;
    const val = isEvalContext(variablesOrCtx)
        ? resolveVariable(parsed.variable, variablesOrCtx)
        : variablesOrCtx[parsed.variable];

    return evalParsed(parsed, val);
}


export function evaluateConditions(conditions: Cond[], variables: RPGVariables): boolean;
export function evaluateConditions(conditions: Cond[], ctx: EvalContext): boolean;
export function evaluateConditions(conditions: Cond[], variablesOrCtx: RPGVariables | EvalContext): boolean {
    return conditions.every(c => evaluateCondition(c, variablesOrCtx as RPGVariables));
}
