'use strict';

var EventBus = require('../core/EventBus.js');
var React = require('react');

/* ────────────────────────────────────────────────────────────────
   Dialogue System — Visual Dialogue Tree Editor
   
   A comprehensive dialogue system with:
   - Visual node-based dialogue tree editor
   - Variables and conditions support
   - Character portraits and emotions
   - Branching conversations
   - Localization support
──────────────────────────────────────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════
   DIALOGUE ENGINE
══════════════════════════════════════════════════════════════════ */
class DialogueEngine {
    constructor(events) {
        this.trees = new Map();
        this.globalVariables = new Map();
        this.currentState = null;
        this.events = events !== null && events !== void 0 ? events : new EventBus.EventBus();
    }
    /* ── Tree Management ──────────────────────────────────────────── */
    registerTree(tree) {
        this.trees.set(tree.id, tree);
    }
    unregisterTree(treeId) {
        this.trees.delete(treeId);
    }
    getTree(treeId) {
        return this.trees.get(treeId);
    }
    getAllTrees() {
        return Array.from(this.trees.values());
    }
    /* ── Variable Management ──────────────────────────────────────── */
    setGlobalVariable(name, value) {
        this.globalVariables.set(name, value);
        this.emitEvent('dialogue:variable-changed', { name, value, scope: 'global' });
    }
    getGlobalVariable(name) {
        return this.globalVariables.get(name);
    }
    getVariable(variableId) {
        var _a;
        if (!this.currentState)
            return this.globalVariables.get(variableId);
        return (_a = this.currentState.variables[variableId]) !== null && _a !== void 0 ? _a : this.globalVariables.get(variableId);
    }
    setVariable(variableId, value, scope) {
        if (scope === 'global') {
            this.globalVariables.set(variableId, value);
        }
        else if (this.currentState) {
            this.currentState.variables[variableId] = value;
        }
        this.emitEvent('dialogue:variable-changed', { variableId, value, scope });
    }
    /* ── Dialogue Flow ────────────────────────────────────────────── */
    startDialogue(treeId, startLabel) {
        const tree = this.trees.get(treeId);
        if (!tree)
            return false;
        // Find start node
        let startNode;
        if (startLabel) {
            startNode = tree.nodes.find((n) => n.type === 'start' && n.label === startLabel);
        }
        if (!startNode) {
            startNode = tree.nodes.find((n) => n.id === tree.startNodeId);
        }
        if (!startNode)
            return false;
        // Initialize state
        const variables = {};
        for (const v of tree.variables) {
            if (v.scope === 'conversation') {
                variables[v.id] = v.defaultValue;
            }
        }
        this.currentState = {
            treeId,
            currentNodeId: startNode.id,
            variables,
            history: [startNode.id],
            choicesMade: [],
            startTime: Date.now(),
            isActive: true,
            isPaused: false,
        };
        this.emitEvent('dialogue:started', { treeId, startNodeId: startNode.id });
        // Advance to first content node
        if (startNode.nextNode) {
            this.goToNode(startNode.nextNode);
        }
        return true;
    }
    endDialogue() {
        if (!this.currentState)
            return;
        const treeId = this.currentState.treeId;
        this.currentState.isActive = false;
        this.emitEvent('dialogue:ended', { treeId, state: { ...this.currentState } });
        this.currentState = null;
    }
    pauseDialogue() {
        if (!this.currentState || !this.currentState.isActive)
            return;
        this.currentState.isPaused = true;
        this.emitEvent('dialogue:paused', {});
    }
    resumeDialogue() {
        if (!this.currentState || !this.currentState.isActive)
            return;
        this.currentState.isPaused = false;
        this.emitEvent('dialogue:resumed', {});
    }
    getState() {
        return this.currentState ? { ...this.currentState } : null;
    }
    isActive() {
        var _a, _b;
        return (_b = (_a = this.currentState) === null || _a === void 0 ? void 0 : _a.isActive) !== null && _b !== void 0 ? _b : false;
    }
    /* ── Node Navigation ──────────────────────────────────────────── */
    goToNode(nodeId) {
        if (!this.currentState)
            return;
        const tree = this.trees.get(this.currentState.treeId);
        if (!tree)
            return;
        const node = tree.nodes.find(n => n.id === nodeId);
        if (!node)
            return;
        this.currentState.currentNodeId = nodeId;
        this.currentState.history.push(nodeId);
        this.emitEvent('dialogue:node-entered', { nodeId, nodeType: node.type });
        this.processNode(node, tree);
    }
    processNode(node, tree) {
        switch (node.type) {
            case 'start':
                if (node.nextNode)
                    this.goToNode(node.nextNode);
                break;
            case 'text':
                this.processTextNode(node, tree);
                break;
            case 'choice':
                this.processChoiceNode(node, tree);
                break;
            case 'branch':
            case 'check_variable':
                this.processBranchNode(node);
                break;
            case 'set_variable':
                this.processSetVariableNode(node, tree);
                break;
            case 'event':
                this.processEventNode(node);
                break;
            case 'random':
                this.processRandomNode(node);
                break;
            case 'end':
                this.processEndNode(node);
                break;
        }
    }
    processTextNode(node, tree) {
        const character = tree.characters.find(c => c.id === node.characterId);
        if (character && this.onTextCallback) {
            this.onTextCallback(node, character);
        }
        this.emitEvent('dialogue:text-displayed', {
            nodeId: node.id,
            characterId: node.characterId,
            text: node.text,
            emotion: node.emotion,
        });
    }
    processChoiceNode(node, tree) {
        // Filter valid options based on conditions
        const validOptions = node.options.filter(opt => {
            if (opt.disabled)
                return false;
            if (!opt.conditions || opt.conditions.length === 0)
                return true;
            return opt.conditions.every(c => this.evaluateCondition(c));
        });
        if (this.onChoiceCallback) {
            this.onChoiceCallback(node, validOptions);
        }
    }
    processBranchNode(node) {
        const result = this.evaluateConditions(node.conditions, node.logic);
        const nextNode = result ? node.trueNode : node.falseNode;
        if (nextNode)
            this.goToNode(nextNode);
    }
    processSetVariableNode(node, tree) {
        var _a;
        const variable = tree.variables.find(v => v.id === node.variableId);
        if (!variable) {
            if (node.nextNode)
                this.goToNode(node.nextNode);
            return;
        }
        const currentValue = (_a = this.getVariable(node.variableId)) !== null && _a !== void 0 ? _a : variable.defaultValue;
        let newValue;
        switch (node.operation) {
            case 'set':
                newValue = node.value;
                break;
            case 'add':
                newValue = currentValue + node.value;
                break;
            case 'subtract':
                newValue = currentValue - node.value;
                break;
            case 'multiply':
                newValue = currentValue * node.value;
                break;
            case 'toggle':
                newValue = !currentValue;
                break;
            case 'append':
                newValue = currentValue + node.value;
                break;
            default:
                newValue = node.value;
        }
        this.setVariable(node.variableId, newValue, variable.scope);
        if (node.nextNode)
            this.goToNode(node.nextNode);
    }
    processEventNode(node) {
        this.events.emit(node.eventType, node.eventData);
        this.emitEvent('dialogue:event-triggered', {
            eventType: node.eventType,
            eventData: node.eventData,
        });
        if (node.nextNode)
            this.goToNode(node.nextNode);
    }
    processRandomNode(node) {
        const totalWeight = node.outputs.reduce((sum, out) => sum + out.weight, 0);
        let random = Math.random() * totalWeight;
        for (const output of node.outputs) {
            random -= output.weight;
            if (random <= 0 && output.nextNode) {
                this.goToNode(output.nextNode);
                return;
            }
        }
        // Fallback to first output
        const first = node.outputs[0];
        if (first === null || first === void 0 ? void 0 : first.nextNode)
            this.goToNode(first.nextNode);
    }
    processEndNode(node) {
        // Apply final variable changes
        if (node.setVariables && this.currentState) {
            const tree = this.trees.get(this.currentState.treeId);
            if (tree) {
                for (const sv of node.setVariables) {
                    const variable = tree.variables.find(v => v.id === sv.variableId);
                    if (variable) {
                        this.setVariable(sv.variableId, sv.value, variable.scope);
                    }
                }
            }
        }
        if (this.onEndCallback && this.currentState) {
            this.onEndCallback(node, this.currentState);
        }
        this.endDialogue();
    }
    /* ── Choice Selection ─────────────────────────────────────────── */
    selectChoice(optionId) {
        if (!this.currentState || !this.currentState.isActive)
            return false;
        const tree = this.trees.get(this.currentState.treeId);
        if (!tree)
            return false;
        const node = tree.nodes.find(n => n.id === this.currentState.currentNodeId);
        if (!node || node.type !== 'choice')
            return false;
        const choice = node.options.find(o => o.id === optionId);
        if (!choice)
            return false;
        // Apply variables from choice
        if (choice.setVariables) {
            for (const sv of choice.setVariables) {
                const variable = tree.variables.find(v => v.id === sv.variableId);
                if (variable) {
                    this.setVariable(sv.variableId, sv.value, variable.scope);
                }
            }
        }
        this.currentState.choicesMade.push({
            nodeId: node.id,
            optionId,
            timestamp: Date.now(),
        });
        this.emitEvent('dialogue:choice-selected', {
            nodeId: node.id,
            optionId,
            text: choice.text,
        });
        if (choice.nextNode) {
            this.goToNode(choice.nextNode);
        }
        return true;
    }
    /** Advance to next node (for text nodes) */
    advance() {
        if (!this.currentState || !this.currentState.isActive)
            return false;
        const tree = this.trees.get(this.currentState.treeId);
        if (!tree)
            return false;
        const node = tree.nodes.find(n => n.id === this.currentState.currentNodeId);
        if (!node)
            return false;
        if (node.type === 'text' && node.nextNode) {
            this.goToNode(node.nextNode);
            return true;
        }
        return false;
    }
    /* ── Condition Evaluation ─────────────────────────────────────── */
    evaluateConditions(conditions, logic) {
        if (conditions.length === 0)
            return true;
        if (logic === 'and') {
            return conditions.every(c => this.evaluateCondition(c));
        }
        else {
            return conditions.some(c => this.evaluateCondition(c));
        }
    }
    evaluateCondition(condition) {
        const varValue = this.getVariable(condition.variableId);
        if (varValue === undefined)
            return false;
        const { operator, value } = condition;
        switch (operator) {
            case 'eq':
                return varValue === value;
            case 'neq':
                return varValue !== value;
            case 'gt':
                return varValue > value;
            case 'lt':
                return varValue < value;
            case 'gte':
                return varValue >= value;
            case 'lte':
                return varValue <= value;
            case 'contains':
                return String(varValue).includes(String(value));
            case 'startsWith':
                return String(varValue).startsWith(String(value));
            case 'endsWith':
                return String(varValue).endsWith(String(value));
            default:
                return false;
        }
    }
    /* ── Callbacks ────────────────────────────────────────────────── */
    onText(callback) {
        this.onTextCallback = callback;
    }
    onChoice(callback) {
        this.onChoiceCallback = callback;
    }
    onEnd(callback) {
        this.onEndCallback = callback;
    }
    /* ── Events ───────────────────────────────────────────────────── */
    emitEvent(type, data) {
        var _a, _b, _c, _d;
        this.events.emit(type, {
            type,
            treeId: (_b = (_a = this.currentState) === null || _a === void 0 ? void 0 : _a.treeId) !== null && _b !== void 0 ? _b : '',
            nodeId: (_d = (_c = this.currentState) === null || _c === void 0 ? void 0 : _c.currentNodeId) !== null && _d !== void 0 ? _d : undefined,
            data,
            timestamp: Date.now(),
        });
    }
    subscribe(eventType, handler) {
        return this.events.on(eventType, handler);
    }
}
/* ═══════════════════════════════════════════════════════════════
   DIALOGUE TREE BUILDER (Fluent API)
══════════════════════════════════════════════════════════════════ */
class DialogueTreeBuilder {
    constructor(id, name) {
        this.nodeCounter = 0;
        this.tree = {
            id,
            name,
            version: '1.0.0',
            characters: [],
            variables: [],
            nodes: [],
            startNodeId: '',
            metadata: {
                created: new Date().toISOString(),
                modified: new Date().toISOString(),
                locale: 'en',
            },
        };
    }
    generateId() {
        return `node_${++this.nodeCounter}`;
    }
    /* ── Metadata ─────────────────────────────────────────────────── */
    description(desc) {
        this.tree.description = desc;
        return this;
    }
    author(author) {
        this.tree.metadata.author = author;
        return this;
    }
    locale(locale) {
        this.tree.metadata.locale = locale;
        return this;
    }
    tags(tags) {
        this.tree.metadata.tags = tags;
        return this;
    }
    /* ── Characters ───────────────────────────────────────────────── */
    addCharacter(character) {
        this.tree.characters.push(character);
        return this;
    }
    /* ── Variables ────────────────────────────────────────────────── */
    addVariable(variable) {
        this.tree.variables.push(variable);
        return this;
    }
    boolVar(id, name, defaultValue = false, scope = 'conversation') {
        return this.addVariable({ id, name, type: 'boolean', defaultValue, scope });
    }
    numVar(id, name, defaultValue = 0, scope = 'conversation') {
        return this.addVariable({ id, name, type: 'number', defaultValue, scope });
    }
    strVar(id, name, defaultValue = '', scope = 'conversation') {
        return this.addVariable({ id, name, type: 'string', defaultValue, scope });
    }
    /* ── Nodes ────────────────────────────────────────────────────── */
    addStart(label, nextNode = null, position = { x: 0, y: 0 }) {
        const id = this.generateId();
        const node = { id, type: 'start', label, nextNode, position };
        this.tree.nodes.push(node);
        if (!this.tree.startNodeId)
            this.tree.startNodeId = id;
        return this;
    }
    addText(characterId, text, nextNode = null, options = {}) {
        var _a;
        const id = this.generateId();
        const node = {
            id,
            type: 'text',
            characterId,
            text,
            nextNode,
            position: (_a = options.position) !== null && _a !== void 0 ? _a : { x: 100, y: 0 },
            ...options,
        };
        this.tree.nodes.push(node);
        return this;
    }
    addChoice(options, settings = {}) {
        var _a;
        const id = this.generateId();
        const node = {
            id,
            type: 'choice',
            options,
            position: (_a = settings.position) !== null && _a !== void 0 ? _a : { x: 200, y: 0 },
            ...settings,
        };
        this.tree.nodes.push(node);
        return this;
    }
    addBranch(conditions, trueNode, falseNode, logic = 'and', position = { x: 150, y: 0 }) {
        const id = this.generateId();
        const node = {
            id,
            type: 'branch',
            conditions,
            logic,
            trueNode,
            falseNode,
            position,
        };
        this.tree.nodes.push(node);
        return this;
    }
    addSetVariable(variableId, operation, value, nextNode = null, position = { x: 150, y: 0 }) {
        const id = this.generateId();
        const node = {
            id,
            type: 'set_variable',
            variableId,
            operation,
            value,
            nextNode,
            position,
        };
        this.tree.nodes.push(node);
        return this;
    }
    addEvent(eventType, eventData, nextNode = null, position = { x: 150, y: 0 }) {
        const id = this.generateId();
        const node = {
            id,
            type: 'event',
            eventType,
            eventData,
            nextNode,
            position,
        };
        this.tree.nodes.push(node);
        return this;
    }
    addRandom(outputs, position = { x: 150, y: 0 }) {
        const id = this.generateId();
        const node = {
            id,
            type: 'random',
            outputs: outputs.map((o, i) => ({ ...o, id: `out_${i}` })),
            position,
        };
        this.tree.nodes.push(node);
        return this;
    }
    addEnd(result = 'success', setVariables, position = { x: 300, y: 0 }) {
        const id = this.generateId();
        const node = {
            id,
            type: 'end',
            result,
            setVariables,
            position,
        };
        this.tree.nodes.push(node);
        return this;
    }
    /* ── Build ────────────────────────────────────────────────────── */
    build() {
        this.tree.metadata.modified = new Date().toISOString();
        return { ...this.tree };
    }
    /** Get the ID of the last added node */
    lastNodeId() {
        var _a;
        const lastNode = this.tree.nodes[this.tree.nodes.length - 1];
        return (_a = lastNode === null || lastNode === void 0 ? void 0 : lastNode.id) !== null && _a !== void 0 ? _a : '';
    }
    /** Get node by index (1-based for readability) */
    nodeId(index) {
        return `node_${index}`;
    }
}
/**
 * React hook for typewriter text effect
 */
function useTypewriter(text, speed = 50, enabled = true) {
    const [displayedText, setDisplayedText] = React.useState('');
    const [isComplete, setIsComplete] = React.useState(!enabled);
    const indexRef = React.useRef(0);
    React.useEffect(() => {
        if (!enabled) {
            setDisplayedText(text);
            setIsComplete(true);
            return;
        }
        setDisplayedText('');
        setIsComplete(false);
        indexRef.current = 0;
        const interval = setInterval(() => {
            if (indexRef.current >= text.length) {
                setIsComplete(true);
                clearInterval(interval);
                return;
            }
            indexRef.current++;
            setDisplayedText(text.slice(0, indexRef.current));
        }, 1000 / speed);
        return () => clearInterval(interval);
    }, [text, speed, enabled]);
    const skip = React.useCallback(() => {
        setDisplayedText(text);
        setIsComplete(true);
    }, [text]);
    return { displayedText, isComplete, skip };
}
/**
 * DialogueBox component for displaying dialogue text
 */
const DialogueBox = ({ text, characterName, portrait, emotion = 'neutral', textEffect = 'typewriter', textSpeed = 50, onComplete, onAdvance, className = '', style, }) => {
    const { displayedText, isComplete, skip } = useTypewriter(text, textSpeed, textEffect === 'typewriter');
    React.useEffect(() => {
        if (isComplete && onComplete) {
            onComplete();
        }
    }, [isComplete, onComplete]);
    const handleClick = () => {
        if (!isComplete) {
            skip();
        }
        else if (onAdvance) {
            onAdvance();
        }
    };
    return React.createElement('div', {
        className: `nice-dialogue-box ${className}`,
        style: {
            display: 'flex',
            gap: '16px',
            padding: '16px',
            backgroundColor: 'rgba(0, 0, 0, 0.8)',
            borderRadius: '8px',
            color: 'white',
            cursor: 'pointer',
            ...style,
        },
        onClick: handleClick,
        'data-emotion': emotion,
    }, [
        portrait && React.createElement('div', {
            key: 'portrait',
            className: 'nice-dialogue-portrait',
            style: {
                width: '80px',
                height: '80px',
                flexShrink: 0,
                borderRadius: '8px',
                overflow: 'hidden',
            },
        }, React.createElement('img', {
            src: portrait,
            alt: characterName !== null && characterName !== void 0 ? characterName : 'Character',
            style: { width: '100%', height: '100%', objectFit: 'cover' },
        })),
        React.createElement('div', {
            key: 'content',
            className: 'nice-dialogue-content',
            style: { flex: 1 },
        }, [
            characterName && React.createElement('div', {
                key: 'name',
                className: 'nice-dialogue-name',
                style: {
                    fontWeight: 'bold',
                    marginBottom: '8px',
                    color: '#64b5f6',
                },
            }, characterName),
            React.createElement('div', {
                key: 'text',
                className: 'nice-dialogue-text',
                style: { lineHeight: 1.6 },
            }, displayedText),
            React.createElement('div', {
                key: 'indicator',
                className: 'nice-dialogue-indicator',
                style: {
                    marginTop: '8px',
                    textAlign: 'right',
                    opacity: isComplete ? 1 : 0.5,
                    fontSize: '12px',
                },
            }, isComplete ? '▼ Click to continue' : '...'),
        ]),
    ]);
};
/**
 * ChoiceList component for displaying dialogue choices
 */
const ChoiceList = ({ options, prompt, onSelect, timeout, className = '', style, }) => {
    const [timeLeft, setTimeLeft] = React.useState(timeout !== null && timeout !== void 0 ? timeout : 0);
    const [selectedIndex, setSelectedIndex] = React.useState(0);
    // Timeout countdown
    React.useEffect(() => {
        if (!timeout)
            return;
        setTimeLeft(timeout);
        const interval = setInterval(() => {
            setTimeLeft(prev => {
                if (prev <= 100) {
                    // Auto-select first option
                    const firstValid = options.find(o => !o.disabled);
                    if (firstValid)
                        onSelect(firstValid.id);
                    return 0;
                }
                return prev - 100;
            });
        }, 100);
        return () => clearInterval(interval);
    }, [timeout, options, onSelect]);
    // Keyboard navigation
    React.useEffect(() => {
        const handleKeyDown = (e) => {
            const validOptions = options.filter(o => !o.disabled);
            switch (e.key) {
                case 'ArrowUp':
                    e.preventDefault();
                    setSelectedIndex(prev => (prev - 1 + validOptions.length) % validOptions.length);
                    break;
                case 'ArrowDown':
                    e.preventDefault();
                    setSelectedIndex(prev => (prev + 1) % validOptions.length);
                    break;
                case 'Enter':
                case ' ':
                    e.preventDefault();
                    if (validOptions[selectedIndex]) {
                        onSelect(validOptions[selectedIndex].id);
                    }
                    break;
            }
        };
        window.addEventListener('keydown', handleKeyDown);
        return () => window.removeEventListener('keydown', handleKeyDown);
    }, [options, selectedIndex, onSelect]);
    const validOptions = options.filter(o => !o.disabled);
    return React.createElement('div', {
        className: `nice-choice-list ${className}`,
        style: {
            padding: '16px',
            backgroundColor: 'rgba(0, 0, 0, 0.8)',
            borderRadius: '8px',
            color: 'white',
            ...style,
        },
    }, [
        prompt && React.createElement('div', {
            key: 'prompt',
            className: 'nice-choice-prompt',
            style: {
                marginBottom: '16px',
                fontStyle: 'italic',
                opacity: 0.9,
            },
        }, prompt),
        timeout && timeLeft > 0 && React.createElement('div', {
            key: 'timer',
            className: 'nice-choice-timer',
            style: {
                marginBottom: '8px',
                height: '4px',
                backgroundColor: 'rgba(255, 255, 255, 0.2)',
                borderRadius: '2px',
                overflow: 'hidden',
            },
        }, React.createElement('div', {
            style: {
                width: `${(timeLeft / timeout) * 100}%`,
                height: '100%',
                backgroundColor: timeLeft < 3000 ? '#f44336' : '#4caf50',
                transition: 'width 0.1s linear',
            },
        })),
        React.createElement('div', {
            key: 'options',
            className: 'nice-choice-options',
            style: { display: 'flex', flexDirection: 'column', gap: '8px' },
        }, validOptions.map((option, index) => React.createElement('button', {
            key: option.id,
            className: `nice-choice-option ${index === selectedIndex ? 'selected' : ''}`,
            onClick: () => onSelect(option.id),
            title: option.tooltip,
            style: {
                padding: '12px 16px',
                backgroundColor: index === selectedIndex ? 'rgba(100, 181, 246, 0.3)' : 'rgba(255, 255, 255, 0.1)',
                border: index === selectedIndex ? '2px solid #64b5f6' : '2px solid transparent',
                borderRadius: '4px',
                color: 'white',
                cursor: 'pointer',
                textAlign: 'left',
                transition: 'all 0.2s ease',
            },
        }, option.text))),
    ]);
};
/* ═══════════════════════════════════════════════════════════════
   SERIALIZATION & VALIDATION
══════════════════════════════════════════════════════════════════ */
/**
 * Serialize dialogue tree to JSON string
 */
function serializeDialogueTree(tree) {
    return JSON.stringify(tree, null, 2);
}
/**
 * Deserialize dialogue tree from JSON string
 */
function deserializeDialogueTree(json) {
    const parsed = JSON.parse(json);
    return validateDialogueTree(parsed);
}
/**
 * Validate dialogue tree structure
 */
function validateDialogueTree(tree) {
    if (!tree || typeof tree !== 'object') {
        throw new Error('Invalid dialogue tree: must be an object');
    }
    const t = tree;
    if (typeof t.id !== 'string' || !t.id) {
        throw new Error('Invalid dialogue tree: missing id');
    }
    if (typeof t.name !== 'string' || !t.name) {
        throw new Error('Invalid dialogue tree: missing name');
    }
    if (!Array.isArray(t.nodes)) {
        throw new Error('Invalid dialogue tree: nodes must be an array');
    }
    if (!Array.isArray(t.characters)) {
        throw new Error('Invalid dialogue tree: characters must be an array');
    }
    if (!Array.isArray(t.variables)) {
        throw new Error('Invalid dialogue tree: variables must be an array');
    }
    if (typeof t.startNodeId !== 'string') {
        throw new Error('Invalid dialogue tree: missing startNodeId');
    }
    // Validate all node connections
    const nodeIds = new Set(t.nodes.map(n => n.id));
    for (const node of t.nodes) {
        validateNodeConnections(node, nodeIds);
    }
    return t;
}
function validateNodeConnections(node, validIds) {
    const checkId = (id, context) => {
        if (id !== null && !validIds.has(id)) {
            throw new Error(`Invalid node reference "${id}" in ${context} of node "${node.id}"`);
        }
    };
    switch (node.type) {
        case 'start':
        case 'text':
        case 'set_variable':
        case 'event':
            checkId(node.nextNode, 'nextNode');
            break;
        case 'choice':
            for (const opt of node.options) {
                checkId(opt.nextNode, `option "${opt.id}"`);
            }
            break;
        case 'branch':
        case 'check_variable':
            checkId(node.trueNode, 'trueNode');
            checkId(node.falseNode, 'falseNode');
            break;
        case 'random':
            for (const out of node.outputs) {
                checkId(out.nextNode, `output "${out.id}"`);
            }
            break;
    }
}
/**
 * Find unreachable nodes in dialogue tree
 */
function findUnreachableNodes(tree) {
    const reachable = new Set();
    const toVisit = [tree.startNodeId];
    while (toVisit.length > 0) {
        const nodeId = toVisit.pop();
        if (reachable.has(nodeId))
            continue;
        reachable.add(nodeId);
        const node = tree.nodes.find(n => n.id === nodeId);
        if (!node)
            continue;
        const nextIds = getNextNodeIds(node);
        for (const id of nextIds) {
            if (id && !reachable.has(id)) {
                toVisit.push(id);
            }
        }
    }
    return tree.nodes
        .filter(n => !reachable.has(n.id) && n.type !== 'start')
        .map(n => n.id);
}
function getNextNodeIds(node) {
    switch (node.type) {
        case 'start':
        case 'text':
        case 'set_variable':
        case 'event':
            return [node.nextNode];
        case 'choice':
            return node.options.map(o => o.nextNode);
        case 'branch':
        case 'check_variable':
            return [node.trueNode, node.falseNode];
        case 'random':
            return node.outputs.map(o => o.nextNode);
        case 'end':
            return [];
        default:
            return [];
    }
}
/**
 * Extract all translatable strings from a dialogue tree
 */
function extractTranslatableStrings(tree) {
    const nodeTexts = {};
    const characterNames = {};
    for (const node of tree.nodes) {
        if (node.type === 'text') {
            nodeTexts[node.id] = node.text;
        }
        else if (node.type === 'choice') {
            const choiceNode = node;
            if (choiceNode.prompt) {
                nodeTexts[`${node.id}_prompt`] = choiceNode.prompt;
            }
            for (const opt of choiceNode.options) {
                nodeTexts[`${node.id}_${opt.id}`] = opt.text;
                if (opt.tooltip) {
                    nodeTexts[`${node.id}_${opt.id}_tooltip`] = opt.tooltip;
                }
            }
        }
    }
    for (const char of tree.characters) {
        characterNames[char.id] = char.displayName;
    }
    return { nodeTexts, characterNames };
}
/**
 * Apply localization to a dialogue tree (creates a copy)
 */
function applyLocalization(tree, localization) {
    const localized = JSON.parse(JSON.stringify(tree));
    localized.metadata.locale = localization.locale;
    for (const node of localized.nodes) {
        if (node.type === 'text' && localization.texts[node.id]) {
            node.text = localization.texts[node.id];
        }
        else if (node.type === 'choice') {
            const choiceNode = node;
            if (localization.texts[`${node.id}_prompt`]) {
                choiceNode.prompt = localization.texts[`${node.id}_prompt`];
            }
            for (const opt of choiceNode.options) {
                if (localization.texts[`${node.id}_${opt.id}`]) {
                    opt.text = localization.texts[`${node.id}_${opt.id}`];
                }
                if (localization.texts[`${node.id}_${opt.id}_tooltip`]) {
                    opt.tooltip = localization.texts[`${node.id}_${opt.id}_tooltip`];
                }
            }
        }
    }
    for (const char of localized.characters) {
        if (localization.characterNames[char.id]) {
            char.displayName = localization.characterNames[char.id];
        }
    }
    return localized;
}
/* ═══════════════════════════════════════════════════════════════
   DEFAULT EXPORTS
══════════════════════════════════════════════════════════════════ */
const DEFAULT_TEXT_SPEED = 50; // characters per second
const EMOTION_COLORS = {
    neutral: '#9e9e9e',
    happy: '#4caf50',
    sad: '#2196f3',
    angry: '#f44336',
    surprised: '#ff9800',
    scared: '#9c27b0',
    disgusted: '#795548',
    confused: '#607d8b',
    thoughtful: '#3f51b5',
    excited: '#ffeb3b',
    tired: '#455a64',
    love: '#e91e63',
    custom: '#ffffff',
};
function createEmptyDialogueTree(id, name) {
    return new DialogueTreeBuilder(id, name)
        .addStart('main', null)
        .addEnd('success')
        .build();
}

exports.ChoiceList = ChoiceList;
exports.DEFAULT_TEXT_SPEED = DEFAULT_TEXT_SPEED;
exports.DialogueBox = DialogueBox;
exports.DialogueEngine = DialogueEngine;
exports.DialogueTreeBuilder = DialogueTreeBuilder;
exports.EMOTION_COLORS = EMOTION_COLORS;
exports.applyLocalization = applyLocalization;
exports.createEmptyDialogueTree = createEmptyDialogueTree;
exports.deserializeDialogueTree = deserializeDialogueTree;
exports.extractTranslatableStrings = extractTranslatableStrings;
exports.findUnreachableNodes = findUnreachableNodes;
exports.serializeDialogueTree = serializeDialogueTree;
exports.useTypewriter = useTypewriter;
exports.validateDialogueTree = validateDialogueTree;
//# sourceMappingURL=DialogueSystem.js.map
