export class WorldState {
    values = new Map();
    constructor(initial = {}) {
        for (const [key, value] of Object.entries(initial))
            this.values.set(key, value);
    }
    static from(initial) {
        return new WorldState(initial);
    }
    set(key, value) {
        this.values.set(key, value);
    }
    get(key) {
        return this.values.get(key);
    }
    satisfies(goal) {
        return goal.entries().every(([key, value]) => this.values.get(key) === value);
    }
    matches(shape) {
        return Object.entries(shape).every(([key, value]) => this.values.get(key) === value);
    }
    apply(shape) {
        const next = this.clone();
        for (const [key, value] of Object.entries(shape))
            next.set(key, value);
        return next;
    }
    clone() {
        return new WorldState(this.toObject());
    }
    entries() {
        return [...this.values.entries()].sort(([left], [right]) => left.localeCompare(right));
    }
    toObject() {
        return Object.fromEntries(this.entries());
    }
    key() {
        return this.entries().map(([key, value]) => `${key}:${String(value)}`).join("|");
    }
}
export class GOAPAction {
    name;
    cost;
    preconditions;
    effects;
    enabled;
    constructor(options) {
        this.name = options.name;
        this.cost = options.cost ?? 1;
        this.preconditions = options.preconditions ?? {};
        this.effects = options.effects;
        this.enabled = options.enabled ?? true;
        if (!Number.isFinite(this.cost) || this.cost < 0)
            throw new RangeError("GOAP action cost must be a finite non-negative number.");
    }
    canRun(state) {
        return this.enabled && state.matches(this.preconditions);
    }
    apply(state) {
        return state.apply(this.effects);
    }
}
export class GOAPPlanner {
    maxIterations;
    maxPlanLength;
    constructor(options = {}) {
        this.maxIterations = options.maxIterations ?? 128;
        this.maxPlanLength = options.maxPlanLength ?? 8;
    }
    plan(current, goal, actions) {
        if (current.satisfies(goal)) {
            return { valid: true, actions: [], cost: 0, nodesExplored: 0, finalState: current.toObject() };
        }
        const enabledActions = actions.filter((action) => action.enabled).sort((left, right) => left.cost - right.cost || left.name.localeCompare(right.name));
        const open = [{ state: current, actions: [], cost: 0 }];
        const bestCostByState = new Map([[current.key(), 0]]);
        let nodesExplored = 0;
        while (open.length > 0 && nodesExplored < this.maxIterations) {
            open.sort((left, right) => left.cost - right.cost || left.actions.join(",").localeCompare(right.actions.join(",")));
            const node = open.shift();
            nodesExplored += 1;
            if (node.state.satisfies(goal)) {
                return { valid: true, actions: node.actions, cost: round3(node.cost), nodesExplored, finalState: node.state.toObject() };
            }
            if (node.actions.length >= this.maxPlanLength)
                continue;
            for (const action of enabledActions) {
                if (!action.canRun(node.state))
                    continue;
                const nextState = action.apply(node.state);
                const nextCost = round3(node.cost + action.cost);
                const key = nextState.key();
                if ((bestCostByState.get(key) ?? Number.POSITIVE_INFINITY) <= nextCost)
                    continue;
                bestCostByState.set(key, nextCost);
                open.push({ state: nextState, actions: [...node.actions, action.name], cost: nextCost });
            }
        }
        return { valid: false, actions: [], cost: 0, nodesExplored, finalState: current.toObject() };
    }
}
function round3(value) {
    return Number(value.toFixed(3));
}
//# sourceMappingURL=GOAP.js.map