/**
 * @file StateMachine.ts
 * @description Finite State Machine System with Graphical Editor Support
 * PRO-1.4: Professional Editor Features
 *
 * Features:
 * - Hierarchical state machines (HSM)
 * - Transitions with conditions and actions
 * - Entry/Exit/Update callbacks
 * - Transition triggers and events
 * - Visual editor integration
 * - Debugging and history
 * - Parallel states
 * - Animation state machine support
 */
// ============================================================
// State Machine Runtime
// ============================================================
class StateMachine {
    constructor(definition) {
        this.layerStates = new Map();
        this.activeTransitions = new Map();
        this.parameters = new Map();
        this.triggers = new Set();
        /** Callbacks */
        this.onEnter = new Map();
        this.onExit = new Map();
        this.onUpdate = new Map();
        this.onTransitionStart = [];
        this.onTransitionComplete = [];
        /** Event listeners */
        this.eventListeners = new Map();
        /** Debugging */
        this.history = [];
        this.historyLimit = 100;
        this.debugMode = false;
        this.definition = definition;
        this.initializeParameters();
        this.initializeLayers();
    }
    initializeParameters() {
        for (const param of this.definition.parameters) {
            this.parameters.set(param.name, param.value);
        }
    }
    initializeLayers() {
        for (const layer of this.definition.layers) {
            if (!layer.active)
                continue;
            // Find default state or entry state
            let initialState = layer.states.find((s) => s.isDefault);
            if (!initialState) {
                initialState = layer.states.find((s) => s.type === 'entry');
            }
            if (!initialState && layer.defaultState) {
                initialState = layer.states.find((s) => s.id === layer.defaultState);
            }
            if (!initialState && layer.states.length > 0) {
                initialState = layer.states[0];
            }
            if (initialState) {
                this.layerStates.set(layer.id, {
                    stateId: initialState.id,
                    layerId: layer.id,
                    enterTime: 0,
                    elapsedTime: 0,
                    normalizedTime: 0,
                    isTransitioning: false,
                });
            }
        }
    }
    // ─── Parameter Control ───────────────────────────
    setParameter(name, value) {
        const oldValue = this.parameters.get(name);
        this.parameters.set(name, value);
        if (oldValue !== value) {
            this.emit('parameter-changed', { name, oldValue, newValue: value }, '');
        }
    }
    getParameter(name) {
        return this.parameters.get(name);
    }
    setBool(name, value) {
        this.setParameter(name, value);
    }
    getBool(name) {
        var _a;
        return (_a = this.parameters.get(name)) !== null && _a !== void 0 ? _a : false;
    }
    setFloat(name, value) {
        this.setParameter(name, value);
    }
    getFloat(name) {
        var _a;
        return (_a = this.parameters.get(name)) !== null && _a !== void 0 ? _a : 0;
    }
    setInteger(name, value) {
        this.setParameter(name, Math.floor(value));
    }
    getInteger(name) {
        var _a;
        return Math.floor((_a = this.parameters.get(name)) !== null && _a !== void 0 ? _a : 0);
    }
    setTrigger(name) {
        this.triggers.add(name);
        this.setParameter(name, true);
    }
    resetTrigger(name) {
        this.triggers.delete(name);
        this.setParameter(name, false);
    }
    // ─── Update ──────────────────────────────────────
    update(context) {
        for (const layer of this.definition.layers) {
            if (!layer.active)
                continue;
            this.updateLayer(layer, context);
        }
        // Clear triggers after processing
        this.triggers.clear();
        for (const param of this.definition.parameters) {
            if (param.type === 'trigger') {
                this.parameters.set(param.name, false);
            }
        }
    }
    updateLayer(layer, context) {
        const stateInstance = this.layerStates.get(layer.id);
        if (!stateInstance)
            return;
        const currentState = layer.states.find((s) => s.id === stateInstance.stateId);
        if (!currentState)
            return;
        // Update elapsed time
        stateInstance.elapsedTime += context.dt;
        // Check for transitions
        const activeTransition = this.activeTransitions.get(layer.id);
        if (activeTransition) {
            // Update transition progress
            activeTransition.progress += context.dt / activeTransition.duration;
            if (activeTransition.progress >= 1) {
                // Complete transition
                this.completeTransition(layer, activeTransition, context);
            }
        }
        else {
            // Check for new transitions
            const transition = this.findValidTransition(layer, currentState);
            if (transition) {
                this.startTransition(layer, transition, context);
            }
            else {
                // Update current state
                stateInstance.isTransitioning = false;
                this.callOnUpdate(currentState, context);
            }
        }
    }
    findValidTransition(layer, currentState) {
        // Sort by priority (lower = higher priority)
        const allTransitions = [
            ...layer.anyStateTransitions,
            ...layer.transitions.filter((t) => t.from === currentState.id),
        ].sort((a, b) => a.priority - b.priority);
        for (const transition of allTransitions) {
            if (transition.muted)
                continue;
            // Check if can interrupt
            const activeTransition = this.activeTransitions.get(layer.id);
            if (activeTransition && !transition.canInterrupt)
                continue;
            // Check trigger
            if (transition.trigger && !this.triggers.has(transition.trigger)) {
                continue;
            }
            // Check conditions
            if (this.evaluateConditions(transition.conditions)) {
                return transition;
            }
        }
        return null;
    }
    evaluateConditions(groups) {
        if (groups.length === 0)
            return true;
        for (const group of groups) {
            const groupResult = this.evaluateConditionGroup(group);
            if (!groupResult)
                return false;
        }
        return true;
    }
    evaluateConditionGroup(group) {
        if (group.conditions.length === 0)
            return true;
        const results = group.conditions.map((c) => this.evaluateCondition(c));
        if (group.mode === 'and') {
            return results.every((r) => r);
        }
        else {
            return results.some((r) => r);
        }
    }
    evaluateCondition(condition) {
        const paramValue = this.parameters.get(condition.parameter);
        if (paramValue === undefined)
            return false;
        switch (condition.operator) {
            case 'equals':
                return paramValue === condition.value;
            case 'notEquals':
                return paramValue !== condition.value;
            case 'greater':
                return paramValue > condition.value;
            case 'less':
                return paramValue < condition.value;
            case 'greaterEqual':
                return paramValue >= condition.value;
            case 'lessEqual':
                return paramValue <= condition.value;
            default:
                return false;
        }
    }
    startTransition(layer, transition, context) {
        const stateInstance = this.layerStates.get(layer.id);
        if (!stateInstance)
            return;
        const fromState = layer.states.find((s) => s.id === stateInstance.stateId);
        const toState = layer.states.find((s) => s.id === transition.to);
        if (!toState)
            return;
        // Call exit on current state
        if (fromState) {
            this.callOnExit(fromState, context);
        }
        // Create transition instance
        const transitionInstance = {
            transitionId: transition.id,
            fromState: stateInstance.stateId,
            toState: transition.to,
            startTime: context.time,
            duration: transition.duration,
            progress: 0,
        };
        this.activeTransitions.set(layer.id, transitionInstance);
        stateInstance.isTransitioning = true;
        // Call transition callbacks
        for (const cb of this.onTransitionStart) {
            cb(context, transition);
        }
        this.emit('transition-start', { transition, from: fromState, to: toState }, layer.id);
        // If instant transition, complete immediately
        if (transition.duration === 0) {
            this.completeTransition(layer, transitionInstance, context);
        }
    }
    completeTransition(layer, transitionInstance, context) {
        const stateInstance = this.layerStates.get(layer.id);
        if (!stateInstance)
            return;
        const toState = layer.states.find((s) => s.id === transitionInstance.toState);
        const transition = layer.transitions.find((t) => t.id === transitionInstance.transitionId) ||
            layer.anyStateTransitions.find((t) => t.id === transitionInstance.transitionId);
        if (!toState)
            return;
        // Update state
        stateInstance.stateId = transitionInstance.toState;
        stateInstance.enterTime = context.time;
        stateInstance.elapsedTime = 0;
        stateInstance.normalizedTime = (transition === null || transition === void 0 ? void 0 : transition.targetOffset) || 0;
        stateInstance.isTransitioning = false;
        // Remove active transition
        this.activeTransitions.delete(layer.id);
        // Call enter on new state
        this.callOnEnter(toState, context);
        // Call transition complete callbacks
        if (transition) {
            for (const cb of this.onTransitionComplete) {
                cb(context, transition);
            }
        }
        this.emit('transition-complete', { transition, state: toState }, layer.id);
    }
    // ─── Callbacks ───────────────────────────────────
    callOnEnter(state, context) {
        const callbacks = this.onEnter.get(state.id);
        if (callbacks) {
            for (const cb of callbacks) {
                cb(context, state);
            }
        }
        this.emit('state-enter', { state }, state.id);
    }
    callOnExit(state, context) {
        const callbacks = this.onExit.get(state.id);
        if (callbacks) {
            for (const cb of callbacks) {
                cb(context, state);
            }
        }
        this.emit('state-exit', { state }, state.id);
    }
    callOnUpdate(state, context) {
        const callbacks = this.onUpdate.get(state.id);
        if (callbacks) {
            for (const cb of callbacks) {
                cb(context, state);
            }
        }
        this.emit('state-update', { state }, state.id);
    }
    // ─── Registration ────────────────────────────────
    registerOnEnter(stateId, callback) {
        let callbacks = this.onEnter.get(stateId);
        if (!callbacks) {
            callbacks = [];
            this.onEnter.set(stateId, callbacks);
        }
        callbacks.push(callback);
    }
    registerOnExit(stateId, callback) {
        let callbacks = this.onExit.get(stateId);
        if (!callbacks) {
            callbacks = [];
            this.onExit.set(stateId, callbacks);
        }
        callbacks.push(callback);
    }
    registerOnUpdate(stateId, callback) {
        let callbacks = this.onUpdate.get(stateId);
        if (!callbacks) {
            callbacks = [];
            this.onUpdate.set(stateId, callbacks);
        }
        callbacks.push(callback);
    }
    registerOnTransitionStart(callback) {
        this.onTransitionStart.push(callback);
    }
    registerOnTransitionComplete(callback) {
        this.onTransitionComplete.push(callback);
    }
    // ─── Events ──────────────────────────────────────
    on(event, listener) {
        let listeners = this.eventListeners.get(event);
        if (!listeners) {
            listeners = [];
            this.eventListeners.set(event, listeners);
        }
        listeners.push(listener);
    }
    off(event, listener) {
        const listeners = this.eventListeners.get(event);
        if (listeners) {
            const idx = listeners.indexOf(listener);
            if (idx >= 0) {
                listeners.splice(idx, 1);
            }
        }
    }
    emit(type, data, layerId) {
        const event = {
            type,
            timestamp: Date.now(),
            layerId,
            data,
        };
        // Record history
        if (this.debugMode) {
            this.history.push(event);
            if (this.history.length > this.historyLimit) {
                this.history.shift();
            }
        }
        // Notify listeners
        const listeners = this.eventListeners.get(type);
        if (listeners) {
            for (const listener of listeners) {
                listener(event);
            }
        }
    }
    // ─── State Queries ───────────────────────────────
    getCurrentState(layerId) {
        var _a;
        const targetLayerId = layerId || ((_a = this.definition.layers[0]) === null || _a === void 0 ? void 0 : _a.id);
        if (!targetLayerId)
            return null;
        const stateInstance = this.layerStates.get(targetLayerId);
        if (!stateInstance)
            return null;
        const layer = this.definition.layers.find((l) => l.id === targetLayerId);
        if (!layer)
            return null;
        return layer.states.find((s) => s.id === stateInstance.stateId) || null;
    }
    getCurrentStateId(layerId) {
        const state = this.getCurrentState(layerId);
        return (state === null || state === void 0 ? void 0 : state.id) || null;
    }
    isInState(stateId, layerId) {
        const currentId = this.getCurrentStateId(layerId);
        return currentId === stateId;
    }
    isTransitioning(layerId) {
        var _a;
        const targetLayerId = layerId || ((_a = this.definition.layers[0]) === null || _a === void 0 ? void 0 : _a.id);
        if (!targetLayerId)
            return false;
        return this.activeTransitions.has(targetLayerId);
    }
    getTransitionProgress(layerId) {
        var _a;
        const targetLayerId = layerId || ((_a = this.definition.layers[0]) === null || _a === void 0 ? void 0 : _a.id);
        if (!targetLayerId)
            return 0;
        const transition = this.activeTransitions.get(targetLayerId);
        return (transition === null || transition === void 0 ? void 0 : transition.progress) || 0;
    }
    getStateTime(layerId) {
        var _a;
        const targetLayerId = layerId || ((_a = this.definition.layers[0]) === null || _a === void 0 ? void 0 : _a.id);
        if (!targetLayerId)
            return 0;
        const stateInstance = this.layerStates.get(targetLayerId);
        return (stateInstance === null || stateInstance === void 0 ? void 0 : stateInstance.elapsedTime) || 0;
    }
    // ─── Manual Control ──────────────────────────────
    forceState(stateId, layerId) {
        var _a;
        const targetLayerId = layerId || ((_a = this.definition.layers[0]) === null || _a === void 0 ? void 0 : _a.id);
        if (!targetLayerId)
            return;
        const layer = this.definition.layers.find((l) => l.id === targetLayerId);
        if (!layer)
            return;
        const state = layer.states.find((s) => s.id === stateId);
        if (!state)
            return;
        // Clear any active transition
        this.activeTransitions.delete(targetLayerId);
        // Set state directly
        this.layerStates.set(targetLayerId, {
            stateId,
            layerId: targetLayerId,
            enterTime: 0,
            elapsedTime: 0,
            normalizedTime: 0,
            isTransitioning: false,
        });
    }
    reset() {
        this.layerStates.clear();
        this.activeTransitions.clear();
        this.triggers.clear();
        this.initializeParameters();
        this.initializeLayers();
        this.history = [];
    }
    // ─── Debugging ───────────────────────────────────
    getHistory() {
        return [...this.history];
    }
    clearHistory() {
        this.history = [];
    }
    getDebugInfo() {
        const layers = this.definition.layers.map((layer) => {
            const stateInstance = this.layerStates.get(layer.id);
            const transition = this.activeTransitions.get(layer.id);
            return {
                id: layer.id,
                name: layer.name,
                currentState: (stateInstance === null || stateInstance === void 0 ? void 0 : stateInstance.stateId) || null,
                isTransitioning: !!transition,
                transitionProgress: (transition === null || transition === void 0 ? void 0 : transition.progress) || 0,
            };
        });
        const parameters = {};
        for (const [name, value] of this.parameters) {
            parameters[name] = value;
        }
        return { layers, parameters };
    }
    // ─── Serialization ───────────────────────────────
    getDefinition() {
        return this.definition;
    }
    clone() {
        return new StateMachine(JSON.parse(JSON.stringify(this.definition)));
    }
}
// ============================================================
// Animation State Machine
// ============================================================
/** Extended state machine for animation control */
class AnimatorController extends StateMachine {
    constructor(definition) {
        super(definition);
        this.motionValues = new Map();
        this.blendTrees = new Map();
    }
    /** Get blended motion value for animation sampling */
    getMotionValue(layerId) {
        return this.motionValues.get(layerId) || 0;
    }
    /** Get current motion/clip name */
    getMotionClip(layerId) {
        const state = this.getCurrentState(layerId);
        return (state === null || state === void 0 ? void 0 : state.motion) || null;
    }
    /** Register a blend tree for a state */
    registerBlendTree(stateId, blendTree) {
        this.blendTrees.set(stateId, blendTree);
    }
    /** Sample blend tree for current parameters */
    sampleBlendTree(stateId) {
        const blendTree = this.blendTrees.get(stateId);
        if (!blendTree)
            return null;
        const params = {};
        for (const [name, value] of this.getParameterMap()) {
            if (typeof value === 'number') {
                params[name] = value;
            }
        }
        return blendTree.sample(params);
    }
    getParameterMap() {
        const map = new Map();
        const definition = this.getDefinition();
        for (const param of definition.parameters) {
            const value = this.getParameter(param.name);
            if (value !== undefined) {
                map.set(param.name, value);
            }
        }
        return map;
    }
}
class BlendTree {
    constructor(type, parameterX, parameterY) {
        this.motions = [];
        this.type = type;
        this.parameterX = parameterX;
        this.parameterY = parameterY;
    }
    addMotion(motion) {
        this.motions.push(motion);
    }
    sample(parameters) {
        const paramX = parameters[this.parameterX] || 0;
        const paramY = this.parameterY ? parameters[this.parameterY] || 0 : 0;
        switch (this.type) {
            case '1d':
                return this.sample1D(paramX);
            case '2d-simple':
            case '2d-freeform':
                return this.sample2D(paramX, paramY);
            case 'direct':
                return this.sampleDirect(parameters);
            default:
                return { clips: [] };
        }
    }
    sample1D(value) {
        if (this.motions.length === 0)
            return { clips: [] };
        // Sort by threshold/position
        const sorted = [...this.motions].sort((a, b) => a.position - b.position);
        // Find two motions to blend between
        let lower = sorted[0];
        let upper = sorted[sorted.length - 1];
        for (let i = 0; i < sorted.length - 1; i++) {
            const posLower = sorted[i].position;
            const posUpper = sorted[i + 1].position;
            if (value >= posLower && value <= posUpper) {
                lower = sorted[i];
                upper = sorted[i + 1];
                break;
            }
        }
        const posLower = lower.position;
        const posUpper = upper.position;
        // Calculate blend weight
        let t = 0;
        if (posUpper !== posLower) {
            t = (value - posLower) / (posUpper - posLower);
        }
        t = Math.max(0, Math.min(1, t));
        const clips = [];
        if (t < 1) {
            clips.push({
                clip: lower.clip,
                weight: 1 - t,
                speed: lower.speed || 1,
                mirror: lower.mirror || false,
            });
        }
        if (t > 0 && upper !== lower) {
            clips.push({
                clip: upper.clip,
                weight: t,
                speed: upper.speed || 1,
                mirror: upper.mirror || false,
            });
        }
        return { clips };
    }
    sample2D(x, y) {
        if (this.motions.length === 0)
            return { clips: [] };
        // Calculate weights using inverse distance weighting
        const clips = [];
        const weights = [];
        let totalWeight = 0;
        for (const motion of this.motions) {
            const pos = motion.position;
            const dx = x - pos.x;
            const dy = y - pos.y;
            const dist = Math.sqrt(dx * dx + dy * dy);
            // Inverse distance (with small epsilon to avoid division by zero)
            const weight = 1 / (dist + 0.001);
            weights.push(weight);
            totalWeight += weight;
        }
        // Normalize weights and create result
        for (let i = 0; i < this.motions.length; i++) {
            const motion = this.motions[i];
            const normalizedWeight = weights[i] / totalWeight;
            if (normalizedWeight > 0.001) {
                clips.push({
                    clip: motion.clip,
                    weight: normalizedWeight,
                    speed: motion.speed || 1,
                    mirror: motion.mirror || false,
                });
            }
        }
        return { clips };
    }
    sampleDirect(parameters) {
        const clips = [];
        let totalWeight = 0;
        for (const motion of this.motions) {
            const weight = parameters[motion.clip] || 0;
            if (weight > 0) {
                totalWeight += weight;
                clips.push({
                    clip: motion.clip,
                    weight,
                    speed: motion.speed || 1,
                    mirror: motion.mirror || false,
                });
            }
        }
        // Normalize
        if (totalWeight > 0) {
            for (const clip of clips) {
                clip.weight /= totalWeight;
            }
        }
        return { clips };
    }
}
// ============================================================
// Factory Functions
// ============================================================
function createStateMachine(definition) {
    return new StateMachine(definition);
}
function createAnimatorController(definition) {
    return new AnimatorController(definition);
}
function createBlendTree1D(parameter) {
    return new BlendTree('1d', parameter);
}
function createBlendTree2D(parameterX, parameterY, type = '2d-simple') {
    return new BlendTree(type, parameterX, parameterY);
}
function createSMDefinition(name) {
    return {
        id: `sm_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        name,
        description: '',
        parameters: [],
        layers: [],
    };
}
function createSMLayer(name) {
    return {
        id: `layer_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        name,
        weight: 1,
        blendMode: 'override',
        active: true,
        states: [],
        transitions: [],
        anyStateTransitions: [],
    };
}
function createSMState(name, type = 'normal') {
    return {
        id: `state_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        name,
        type,
        position: { x: 0, y: 0 },
        speed: 1,
    };
}
function createSMTransition(from, to) {
    return {
        id: `trans_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        from,
        to,
        conditions: [],
        duration: 0.1,
        priority: 0,
        canInterrupt: false,
    };
}
function createSMParameter(name, type, value) {
    let defaultValue;
    switch (type) {
        case 'bool':
        case 'trigger':
            defaultValue = value !== undefined ? value : false;
            break;
        case 'int':
            defaultValue = value !== undefined ? Math.floor(value) : 0;
            break;
        case 'float':
            defaultValue = value !== undefined ? value : 0;
            break;
    }
    return {
        name,
        type,
        value: defaultValue,
    };
}
function createSMCondition(parameter, operator, value) {
    return {
        id: `cond_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        parameter,
        operator,
        value,
    };
}
function createSMConditionGroup(mode = 'and') {
    return {
        id: `group_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        mode,
        conditions: [],
    };
}
function createSMContext(params) {
    return {
        ...params,
        services: new Map(),
    };
}

export { AnimatorController, BlendTree, StateMachine, createAnimatorController, createBlendTree1D, createBlendTree2D, createSMCondition, createSMConditionGroup, createSMContext, createSMDefinition, createSMLayer, createSMParameter, createSMState, createSMTransition, createStateMachine };
//# sourceMappingURL=StateMachine.js.map
