'use strict';

/* ────────────────────────────────────────────────────────────────
   EPIC E1 — AI Integration
   
   AI NPC behaviors, procedural content, AI playtesting
──────────────────────────────────────────────────────────────── */
class BTAction {
    constructor(name, fn) {
        this.name = name;
        this.fn = fn;
    }
    tick(ctx) { return this.fn(ctx); }
    reset() { }
}
class BTCondition {
    constructor(name, condition) {
        this.name = name;
        this.condition = condition;
    }
    tick(ctx) { return this.condition(ctx) ? 'success' : 'failure'; }
    reset() { }
}
class BTSequence {
    constructor(name, children) {
        this.currentIndex = 0;
        this.name = name;
        this.children = children;
    }
    tick(ctx) {
        for (; this.currentIndex < this.children.length; this.currentIndex++) {
            const result = this.children[this.currentIndex].tick(ctx);
            if (result === 'running')
                return 'running';
            if (result === 'failure') {
                this.currentIndex = 0;
                return 'failure';
            }
        }
        this.currentIndex = 0;
        return 'success';
    }
    reset() {
        this.currentIndex = 0;
        for (const child of this.children)
            child.reset();
    }
}
class BTSelector {
    constructor(name, children) {
        this.currentIndex = 0;
        this.name = name;
        this.children = children;
    }
    tick(ctx) {
        for (; this.currentIndex < this.children.length; this.currentIndex++) {
            const result = this.children[this.currentIndex].tick(ctx);
            if (result === 'running')
                return 'running';
            if (result === 'success') {
                this.currentIndex = 0;
                return 'success';
            }
        }
        this.currentIndex = 0;
        return 'failure';
    }
    reset() {
        this.currentIndex = 0;
        for (const child of this.children)
            child.reset();
    }
}
class BTParallel {
    constructor(name, children, successThreshold = -1) {
        this.name = name;
        this.children = children;
        this.successThreshold = successThreshold < 0 ? children.length : successThreshold;
    }
    tick(ctx) {
        let successes = 0;
        let failures = 0;
        for (const child of this.children) {
            const result = child.tick(ctx);
            if (result === 'success')
                successes++;
            if (result === 'failure')
                failures++;
        }
        if (successes >= this.successThreshold)
            return 'success';
        if (failures > this.children.length - this.successThreshold)
            return 'failure';
        return 'running';
    }
    reset() { for (const child of this.children)
        child.reset(); }
}
class BTDecorator {
    constructor(name, child, decorator) {
        this.name = name;
        this.child = child;
        this.decorator = decorator;
    }
    tick(ctx) {
        return this.decorator(this.child.tick(ctx), ctx);
    }
    reset() { this.child.reset(); }
}
/** Inverter: success→failure, failure→success. */
function btInvert(child) {
    return new BTDecorator(`invert(${child.name})`, child, (result) => {
        if (result === 'success')
            return 'failure';
        if (result === 'failure')
            return 'success';
        return 'running';
    });
}
/** Repeat N times or until failure. */
class BTRepeat {
    constructor(name, child, maxRepeats) {
        this.count = 0;
        this.name = name;
        this.child = child;
        this.maxRepeats = maxRepeats;
    }
    tick(ctx) {
        while (this.count < this.maxRepeats) {
            const result = this.child.tick(ctx);
            if (result === 'running')
                return 'running';
            if (result === 'failure') {
                this.count = 0;
                return 'failure';
            }
            this.count++;
            this.child.reset();
        }
        this.count = 0;
        return 'success';
    }
    reset() { this.count = 0; this.child.reset(); }
}
function steerSeek(agent, target) {
    const dx = target.x - agent.position.x;
    const dy = target.y - agent.position.y;
    const len = Math.sqrt(dx * dx + dy * dy) || 1;
    const desiredX = (dx / len) * agent.maxSpeed;
    const desiredY = (dy / len) * agent.maxSpeed;
    return clampForce({ x: desiredX - agent.velocity.x, y: desiredY - agent.velocity.y }, agent.maxForce);
}
function steerFlee(agent, target) {
    const dx = agent.position.x - target.x;
    const dy = agent.position.y - target.y;
    const len = Math.sqrt(dx * dx + dy * dy) || 1;
    const desiredX = (dx / len) * agent.maxSpeed;
    const desiredY = (dy / len) * agent.maxSpeed;
    return clampForce({ x: desiredX - agent.velocity.x, y: desiredY - agent.velocity.y }, agent.maxForce);
}
function steerArrive(agent, target, slowRadius) {
    const dx = target.x - agent.position.x;
    const dy = target.y - agent.position.y;
    const dist = Math.sqrt(dx * dx + dy * dy);
    if (dist < 0.01)
        return { x: 0, y: 0 };
    const speed = dist < slowRadius ? agent.maxSpeed * (dist / slowRadius) : agent.maxSpeed;
    const desiredX = (dx / dist) * speed;
    const desiredY = (dy / dist) * speed;
    return clampForce({ x: desiredX - agent.velocity.x, y: desiredY - agent.velocity.y }, agent.maxForce);
}
function steerWander(agent, wanderDistance, wanderRadius, wanderAngle) {
    const angle = wanderAngle + (Math.random() - 0.5) * 1;
    const vLen = Math.sqrt(agent.velocity.x * agent.velocity.x + agent.velocity.y * agent.velocity.y) || 1;
    const nx = agent.velocity.x / vLen;
    const ny = agent.velocity.y / vLen;
    const cx = agent.position.x + nx * wanderDistance;
    const cy = agent.position.y + ny * wanderDistance;
    const tx = cx + Math.cos(angle) * wanderRadius;
    const ty = cy + Math.sin(angle) * wanderRadius;
    return { force: steerSeek(agent, { x: tx, y: ty }), newAngle: angle };
}
function steerSeparation(agent, neighbors, radius) {
    let fx = 0, fy = 0;
    let count = 0;
    for (const n of neighbors) {
        const dx = agent.position.x - n.x;
        const dy = agent.position.y - n.y;
        const dist = Math.sqrt(dx * dx + dy * dy);
        if (dist > 0 && dist < radius) {
            fx += dx / dist / dist;
            fy += dy / dist / dist;
            count++;
        }
    }
    if (count > 0) {
        fx /= count;
        fy /= count;
        const len = Math.sqrt(fx * fx + fy * fy) || 1;
        fx = (fx / len) * agent.maxSpeed - agent.velocity.x;
        fy = (fy / len) * agent.maxSpeed - agent.velocity.y;
    }
    return clampForce({ x: fx, y: fy }, agent.maxForce);
}
function clampForce(force, maxForce) {
    const len = Math.sqrt(force.x * force.x + force.y * force.y);
    if (len > maxForce && len > 0) {
        return { x: (force.x / len) * maxForce, y: (force.y / len) * maxForce };
    }
    return force;
}
class FiniteStateMachine {
    constructor(context) {
        this.states = new Map();
        this.currentState = null;
        this.context = context;
    }
    addState(state) {
        this.states.set(state.name, state);
    }
    setState(name) {
        var _a, _b, _c, _d;
        const next = this.states.get(name);
        if (!next || next === this.currentState)
            return;
        (_b = (_a = this.currentState) === null || _a === void 0 ? void 0 : _a.exit) === null || _b === void 0 ? void 0 : _b.call(_a, this.context);
        this.currentState = next;
        (_d = (_c = this.currentState).enter) === null || _d === void 0 ? void 0 : _d.call(_c, this.context);
    }
    update(dt) {
        var _a, _b;
        (_b = (_a = this.currentState) === null || _a === void 0 ? void 0 : _a.update) === null || _b === void 0 ? void 0 : _b.call(_a, this.context, dt);
    }
    getCurrentState() {
        var _a, _b;
        return (_b = (_a = this.currentState) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : null;
    }
}
function selectBestAction(actions, ctx) {
    let best = null;
    let bestScore = -Infinity;
    for (const action of actions) {
        const s = action.score(ctx);
        if (s > bestScore) {
            bestScore = s;
            best = action;
        }
    }
    return best;
}
function planGOAP(worldState, goal, actions, maxDepth = 10) {
    function goalMet(state) {
        for (const [key, val] of Object.entries(goal.conditions)) {
            if (state[key] !== val)
                return false;
        }
        return true;
    }
    function actionValid(action, state) {
        for (const [key, val] of Object.entries(action.preconditions)) {
            if (state[key] !== val)
                return false;
        }
        return true;
    }
    if (goalMet(worldState))
        return [];
    const queue = [{ state: { ...worldState }, actions: [], cost: 0 }];
    while (queue.length > 0) {
        queue.sort((a, b) => a.cost - b.cost);
        const current = queue.shift();
        if (current.actions.length >= maxDepth)
            continue;
        for (const action of actions) {
            if (!actionValid(action, current.state))
                continue;
            const newState = { ...current.state, ...action.effects };
            const newPlan = [...current.actions, action];
            if (goalMet(newState))
                return newPlan;
            queue.push({ state: newState, actions: newPlan, cost: current.cost + action.cost });
        }
    }
    return null; // No plan found
}
function createDialogTree(id, nodes) {
    var _a, _b;
    const nodeMap = new Map();
    for (const n of nodes)
        nodeMap.set(n.id, n);
    return { id, startNodeId: (_b = (_a = nodes[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : '', nodes: nodeMap };
}
/** Get available responses (filter by conditions using evaluator). */
function getAvailableResponses(tree, nodeId, evaluateCondition) {
    const node = tree.nodes.get(nodeId);
    if (!node)
        return [];
    if (!evaluateCondition)
        return node.responses;
    return node.responses.filter(r => !r.condition || evaluateCondition(r.condition));
}

exports.BTAction = BTAction;
exports.BTCondition = BTCondition;
exports.BTDecorator = BTDecorator;
exports.BTParallel = BTParallel;
exports.BTRepeat = BTRepeat;
exports.BTSelector = BTSelector;
exports.BTSequence = BTSequence;
exports.FiniteStateMachine = FiniteStateMachine;
exports.btInvert = btInvert;
exports.createDialogTree = createDialogTree;
exports.getAvailableResponses = getAvailableResponses;
exports.planGOAP = planGOAP;
exports.selectBestAction = selectBestAction;
exports.steerArrive = steerArrive;
exports.steerFlee = steerFlee;
exports.steerSeek = steerSeek;
exports.steerSeparation = steerSeparation;
exports.steerWander = steerWander;
//# sourceMappingURL=AI.js.map
