import { Serializer } from './serializer.js';
export class StateManager {
    executionId;
    clientId;
    cache;
    logger;
    state;
    resumeMode = false;
    serializer;
    captureInterval;
    statementCounter = 0;
    constructor(executionId, clientId, cache, logger, options) {
        this.executionId = executionId;
        this.clientId = clientId;
        this.cache = cache;
        this.logger = logger;
        this.serializer = new Serializer();
        this.captureInterval = options?.captureInterval || 10;
        this.state = {
            executionId,
            clientId,
            currentStatementId: -1,
            statements: [],
            variables: [],
            controlFlow: [],
        };
    }
    /**
     * Capture state at a statement boundary
     * In resume mode, skip already-executed statements
     */
    async capture(statementId, getVars) {
        this.logger.debug('State capture', { statementId, resumeMode: this.resumeMode });
        if (this.resumeMode) {
            const cached = this.findStatement(statementId);
            if (cached) {
                this.logger.debug('Skipping cached statement', { statementId });
                return;
            }
        }
        const vars = getVars();
        const serializedVars = {};
        for (const [name, value] of Object.entries(vars)) {
            try {
                serializedVars[name] = this.serializer.serialize(value, vars);
            }
            catch (e) {
                this.logger.warn('Failed to serialize variable', { name, error: e });
            }
        }
        const statementState = {
            id: statementId,
            executed: true,
            variables: serializedVars,
            timestamp: Date.now(),
        };
        this.state.statements.push([statementId, statementState]);
        this.state.currentStatementId = statementId;
        this.statementCounter++;
        if (this.cache && this.statementCounter % this.captureInterval === 0) {
            await this.persist();
        }
    }
    /**
     * Wrap external calls with caching
     */
    async call(statementId, fn) {
        if (this.resumeMode) {
            const cached = this.findStatement(statementId);
            if (cached && cached.result !== undefined) {
                this.logger.debug('Using cached call result', { statementId });
                return this.serializer.deserialize(cached.result);
            }
        }
        this.logger.debug('Executing call', { statementId });
        const result = await fn();
        try {
            const serialized = this.serializer.serialize(result);
            const statement = this.findStatement(statementId);
            if (statement) {
                statement.result = serialized;
            }
        }
        catch (e) {
            this.logger.warn('Failed to serialize call result', { statementId });
        }
        if (this.cache) {
            await this.persist();
        }
        return result;
    }
    /**
     * Check if a call result is cached (for use from isolate)
     */
    getCached(statementId) {
        if (this.resumeMode) {
            const cached = this.findStatement(statementId);
            if (cached && cached.result !== undefined) {
                return this.serializer.deserialize(cached.result);
            }
        }
        return null;
    }
    /**
     * Store a call result (for use from isolate)
     */
    async storeResult(statementId, result) {
        try {
            const serialized = this.serializer.serialize(result);
            let statement = this.findStatement(statementId);
            if (!statement) {
                statement = {
                    id: statementId,
                    executed: true,
                    variables: {},
                    timestamp: Date.now(),
                };
                this.state.statements.push([statementId, statement]);
            }
            statement.result = serialized;
            if (this.cache) {
                await this.persist();
            }
        }
        catch (e) {
            this.logger.warn('Failed to serialize and store call result', { statementId, error: e });
        }
    }
    /**
     * Track control flow branches
     */
    branch(statementId, condition) {
        this.logger.debug('Branch decision', { statementId, condition });
        if (this.resumeMode) {
            const cached = this.state.controlFlow.find((b) => b.statementId === statementId);
            if (cached) {
                this.logger.debug('Using cached branch decision', {
                    statementId,
                    decision: cached.taken,
                });
                return cached.taken;
            }
        }
        const decision = {
            statementId,
            taken: condition,
            timestamp: Date.now(),
        };
        this.state.controlFlow.push(decision);
        return condition;
    }
    /**
     * Persist state to cache
     */
    /**
     * Persist current execution state to cache
     */
    async persist() {
        if (!this.cache)
            return;
        const key = this.getCacheKey();
        try {
            await this.cache.set(key, this.state, 3600);
            this.logger.debug('State persisted', {
                executionId: this.executionId,
                statements: this.state.statements.length,
            });
        }
        catch (e) {
            this.logger.error('Failed to persist state', { error: e });
        }
    }
    /**
     * Load state for resume
     */
    async loadForResume(executionId) {
        if (!this.cache) {
            this.logger.warn('Cannot load state: no cache provider');
            return false;
        }
        const key = `execution-state:${this.clientId}:${executionId}`;
        try {
            const persisted = await this.cache.get(key);
            if (!persisted) {
                this.logger.debug('No persisted state found (expected for new executions)', {
                    executionId,
                });
                return false;
            }
            this.state = persisted;
            this.resumeMode = true;
            this.logger.info('State loaded for resume', {
                executionId,
                statements: this.state.statements.length,
                currentStatement: this.state.currentStatementId,
            });
            return true;
        }
        catch (e) {
            this.logger.error('Failed to load state', { error: e });
            return false;
        }
    }
    /**
     * Cleanup state from cache
     */
    async cleanup() {
        if (!this.cache)
            return;
        const key = this.getCacheKey();
        try {
            await this.cache.delete(key);
            this.logger.debug('State cleaned up', { executionId: this.executionId });
        }
        catch (e) {
            this.logger.warn('Failed to cleanup state', { error: e });
        }
    }
    /**
     * Get execution progress
     */
    getProgress() {
        if (this.state.statements.length === 0)
            return 0;
        return (this.state.currentStatementId / this.state.statements.length) * 100;
    }
    /**
     * Get statistics
     */
    getStats() {
        return {
            statementsExecuted: this.state.statements.length,
            statementsCached: this.resumeMode ? this.state.statements.length : 0,
        };
    }
    findStatement(id) {
        const entry = this.state.statements.find(([sid]) => sid === id);
        return entry ? entry[1] : undefined;
    }
    getCacheKey() {
        return `execution-state:${this.clientId}:${this.executionId}`;
    }
}
//# sourceMappingURL=state-manager.js.map