import type { City, General, Nation, TriggerValue } from '@sammo-ts/logic/domain/entities.js';
import type { ConstraintContext, ConstraintResult, RequirementKey, StateView } from './types.js';

export const allow = (): ConstraintResult => ({ kind: 'allow' });

export const unknownOrDeny = (ctx: ConstraintContext, missing: RequirementKey[], reason: string): ConstraintResult =>
    ctx.mode === 'precheck' ? { kind: 'unknown', missing } : { kind: 'deny', reason };

export const readGeneral = (ctx: ConstraintContext, view: StateView): General | null => {
    const req: RequirementKey = { kind: 'general', id: ctx.actorId };
    if (!view.has(req)) {
        return null;
    }
    return view.get(req) as General | null;
};

export const readCity = (view: StateView, id?: number): City | null => {
    if (id === undefined) {
        return null;
    }
    const req: RequirementKey = { kind: 'city', id };
    if (!view.has(req)) {
        return null;
    }
    return view.get(req) as City | null;
};

export const resolveDestGeneralId = (ctx: ConstraintContext): number | undefined => {
    if (ctx.destGeneralId !== undefined) {
        return ctx.destGeneralId;
    }
    const raw = ctx.args?.destGeneralId;
    return typeof raw === 'number' ? raw : undefined;
};

export const readDestGeneral = (ctx: ConstraintContext, view: StateView): General | null => {
    const destGeneralId = resolveDestGeneralId(ctx);
    if (destGeneralId === undefined) {
        return null;
    }
    const req: RequirementKey = { kind: 'destGeneral', id: destGeneralId };
    if (!view.has(req)) {
        return null;
    }
    return view.get(req) as General | null;
};

export const resolveDestCityId = (ctx: ConstraintContext): number | undefined => {
    if (ctx.destCityId !== undefined) {
        return ctx.destCityId;
    }
    const raw = ctx.args?.destCityId;
    return typeof raw === 'number' ? raw : undefined;
};

export const resolveDestNationId = (ctx: ConstraintContext): number | undefined => {
    if (ctx.destNationId !== undefined) {
        return ctx.destNationId;
    }
    const raw = ctx.args?.destNationId;
    return typeof raw === 'number' ? raw : undefined;
};

export const readDestCity = (ctx: ConstraintContext, view: StateView): City | null => {
    const destCityId = resolveDestCityId(ctx);
    return readCity(view, destCityId);
};

export const readNation = (view: StateView, id?: number): Nation | null => {
    if (id === undefined) {
        return null;
    }
    const req: RequirementKey = { kind: 'nation', id };
    if (!view.has(req)) {
        return null;
    }
    return view.get(req) as Nation | null;
};

export const readMetaNumber = (meta: Record<string, TriggerValue>, key: string): number | null => {
    const value = meta[key];
    return typeof value === 'number' ? value : null;
};

export const readDiplomacyState = (view: StateView, srcNationId: number, destNationId: number): number | null => {
    const req: RequirementKey = {
        kind: 'diplomacy',
        srcNationId,
        destNationId,
    };
    if (!view.has(req)) {
        return null;
    }
    const value = view.get(req);
    if (typeof value === 'number') {
        return value;
    }
    if (value && typeof value === 'object') {
        const state = (value as { state?: number; stateCode?: number }).state;
        const stateCode = (value as { state?: number; stateCode?: number }).stateCode;
        if (typeof state === 'number') {
            return state;
        }
        if (typeof stateCode === 'number') {
            return stateCode;
        }
    }
    return null;
};

export const readMetaNumberFromUnknown = (meta: Record<string, unknown>, key: string): number | null => {
    const value = meta[key];
    return typeof value === 'number' ? value : null;
};

export const parsePercent = (value: string): number | null => {
    const match = /^(\d+(?:\.\d+)?)%$/.exec(value);
    if (!match) {
        return null;
    }
    return Number(match[1]) / 100;
};

export type CompareOperator = '>' | '>=' | '==' | '<=' | '<' | '!=' | '===' | '!==';

export const compareValues = (target: unknown, op: CompareOperator, source: unknown): boolean => {
    // The cast is type-only: JavaScript still applies its native relational
    // coercion rules to the original runtime values, matching the legacy
    // constraint evaluator without opting the whole comparison into `any`.
    const lhs = target as number;
    const rhs = source as number;
    switch (op) {
        case '<':
            return lhs < rhs;
        case '<=':
            return lhs <= rhs;
        case '==':
            return lhs == rhs;
        case '!=':
            return lhs != rhs;
        case '===':
            return lhs === rhs;
        case '!==':
            return lhs !== rhs;
        case '>=':
            return lhs >= rhs;
        case '>':
            return lhs > rhs;
    }
};
