"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LockedData = void 0;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const guards_1 = require("./guards");
const printer_1 = require("./printer");
const state_stream_1 = require("./state-stream");
const step_validator_1 = require("./step-validator");
const generate_types_1 = require("./types/generate-types");
class LockedData {
    constructor(baseDir) {
        this.baseDir = baseDir;
        this.state = null;
        this.flows = {};
        this.activeSteps = [];
        this.devSteps = [];
        this.stepsMap = {};
        this.printer = new printer_1.Printer(baseDir);
        this.handlers = {
            'flow-created': [],
            'flow-removed': [],
            'flow-updated': [],
        };
        this.stepHandlers = {
            'step-created': [],
            'step-removed': [],
            'step-updated': [],
        };
        this.streamHandlers = {
            'stream-created': [],
            'stream-removed': [],
            'stream-updated': [],
        };
        this.streams = {};
    }
    applyStreamWrapper(state, streamWrapper) {
        this.streamWrapper = streamWrapper;
        this.state = state;
    }
    saveTypes() {
        const types = (0, generate_types_1.generateTypesFromSteps)(this.activeSteps, this.printer);
        const streams = (0, generate_types_1.generateTypesFromStreams)(this.streams);
        const typesString = (0, generate_types_1.generateTypesString)(types, streams);
        fs_1.default.writeFileSync(path_1.default.join(this.baseDir, 'types.d.ts'), typesString);
    }
    on(event, handler) {
        this.handlers[event].push(handler);
    }
    onStep(event, handler) {
        this.stepHandlers[event].push(handler);
    }
    onStream(event, handler) {
        this.streamHandlers[event].push(handler);
    }
    eventSteps() {
        return this.activeSteps.filter(guards_1.isEventStep);
    }
    apiSteps() {
        return this.activeSteps.filter(guards_1.isApiStep);
    }
    cronSteps() {
        return this.activeSteps.filter(guards_1.isCronStep);
    }
    pythonSteps() {
        return this.activeSteps.filter((step) => step.filePath.endsWith('.py'));
    }
    tsSteps() {
        return this.activeSteps.filter((step) => step.filePath.endsWith('.ts'));
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    getStreams() {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const streams = {};
        for (const [key, value] of Object.entries(this.streams)) {
            const baseConfig = value.config.baseConfig;
            const streamFactory = baseConfig.storageType === 'custom' ? baseConfig.factory : null;
            if (streamFactory) {
                streams[key] = streamFactory;
            }
        }
        return streams;
    }
    findStream(path) {
        return Object.values(this.streams).find((stream) => stream.filePath === path);
    }
    updateStep(oldStep, newStep, options = {}) {
        if (!this.isValidStep(newStep)) {
            this.deleteStep(oldStep);
            return false;
        }
        if (oldStep.config.type !== newStep.config.type) {
            this.activeSteps = this.activeSteps.filter((s) => s.filePath !== oldStep.filePath);
            this.devSteps = this.devSteps.filter((s) => s.filePath !== oldStep.filePath);
            if (newStep.config.type === 'noop') {
                this.devSteps.push(newStep);
            }
            else {
                this.activeSteps.push(newStep);
            }
        }
        const savedStep = this.stepsMap[newStep.filePath];
        const addedFlows = newStep.config.flows?.filter((flowName) => !oldStep.config.flows?.includes(flowName)) ?? [];
        const removedFlows = oldStep.config.flows?.filter((flowName) => !newStep.config.flows?.includes(flowName)) ?? [];
        const untouchedFlows = oldStep.config.flows?.filter((flowName) => newStep.config.flows?.includes(flowName)) ?? [];
        untouchedFlows.forEach((flowName) => this.onFlowUpdated(flowName));
        for (const flowName of addedFlows) {
            if (!this.flows[flowName]) {
                const flow = this.createFlow(flowName);
                flow.steps.push(savedStep);
            }
            else {
                this.flows[flowName].steps.push(savedStep);
                this.onFlowUpdated(flowName);
            }
        }
        for (const flowName of removedFlows) {
            const flowSteps = this.flows[flowName].steps;
            this.flows[flowName].steps = flowSteps.filter(({ filePath }) => filePath !== newStep.filePath);
            if (this.flows[flowName].steps.length === 0) {
                this.removeFlow(flowName);
            }
            else {
                this.onFlowUpdated(flowName);
            }
        }
        savedStep.config = newStep.config;
        if (!options.disableTypeCreation) {
            this.saveTypes();
        }
        this.stepHandlers['step-updated'].forEach((handler) => handler(newStep));
        this.printer.printStepUpdated(newStep);
        return true;
    }
    createStep(step, options = {}) {
        if (!this.isValidStep(step)) {
            return false;
        }
        this.stepsMap[step.filePath] = step;
        if (step.config.type === 'noop') {
            this.devSteps.push(step);
        }
        else {
            this.activeSteps.push(step);
        }
        for (const flowName of step.config.flows ?? []) {
            if (!this.flows[flowName]) {
                const flow = this.createFlow(flowName);
                flow.steps.push(step);
            }
            else {
                this.flows[flowName].steps.push(step);
                this.onFlowUpdated(flowName);
            }
        }
        if (!options.disableTypeCreation) {
            this.saveTypes();
        }
        this.stepHandlers['step-created'].forEach((handler) => handler(step));
        this.printer.printStepCreated(step);
        return true;
    }
    deleteStep(step, options = {}) {
        // Remove step from active and dev steps
        this.activeSteps = this.activeSteps.filter(({ filePath }) => filePath !== step.filePath);
        this.devSteps = this.devSteps.filter(({ filePath }) => filePath !== step.filePath);
        delete this.stepsMap[step.filePath];
        for (const flowName of step.config.flows ?? []) {
            const stepFlows = this.flows[flowName]?.steps;
            if (stepFlows) {
                this.flows[flowName].steps = stepFlows.filter(({ filePath }) => filePath !== step.filePath);
            }
            if (this.flows[flowName].steps.length === 0) {
                this.removeFlow(flowName);
            }
            else {
                this.onFlowUpdated(flowName);
            }
        }
        if (!options.disableTypeCreation) {
            this.saveTypes();
        }
        this.stepHandlers['step-removed'].forEach((handler) => handler(step));
        this.printer.printStepRemoved(step);
    }
    createFactoryWrapper(stream, factory) {
        return () => {
            const streamFactory = this.streamWrapper //
                ? this.streamWrapper(stream.config.name, factory)
                : factory;
            return streamFactory();
        };
    }
    createStream(stream, options = {}) {
        this.streams[stream.config.name] = stream;
        this.streamHandlers['stream-created'].forEach((handler) => handler(stream));
        let factory;
        if (stream.config.baseConfig.storageType === 'state') {
            const property = stream.config.baseConfig.property;
            factory = this.createFactoryWrapper(stream, () => new state_stream_1.InternalStateStream(this.state, property));
        }
        else {
            factory = this.createFactoryWrapper(stream, stream.config.baseConfig.factory);
        }
        stream.config.baseConfig = { storageType: 'custom', factory };
        if (!stream.hidden) {
            this.printer.printStreamCreated(stream);
            if (!options.disableTypeCreation) {
                this.saveTypes();
            }
        }
        return factory;
    }
    deleteStream(stream, options = {}) {
        Object.entries(this.streams).forEach(([streamName, { filePath }]) => {
            if (stream.filePath === filePath) {
                delete this.streams[streamName];
            }
        });
        this.streamHandlers['stream-removed'].forEach((handler) => handler(stream));
        if (!stream.hidden) {
            this.printer.printStreamRemoved(stream);
            if (!options.disableTypeCreation) {
                this.saveTypes();
            }
        }
    }
    updateStream(oldStream, stream, options = {}) {
        if (oldStream.config.name !== stream.config.name) {
            delete this.streams[oldStream.config.name];
        }
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let factory;
        if (stream.config.baseConfig.storageType === 'state') {
            const property = stream.config.baseConfig.property;
            factory = this.createFactoryWrapper(stream, () => new state_stream_1.InternalStateStream(this.state, property));
        }
        else {
            factory = this.createFactoryWrapper(stream, stream.config.baseConfig.factory);
        }
        stream.config.baseConfig = { storageType: 'custom', factory };
        this.streams[stream.config.name] = stream;
        this.streamHandlers['stream-updated'].forEach((handler) => handler(stream));
        if (!stream.hidden) {
            this.printer.printStreamUpdated(stream);
            if (!options.disableTypeCreation) {
                this.saveTypes();
            }
        }
    }
    createFlow(flowName) {
        const flow = { name: flowName, description: '', steps: [] };
        this.flows[flowName] = flow;
        this.handlers['flow-created'].forEach((handler) => handler(flowName));
        this.printer.printFlowCreated(flowName);
        return flow;
    }
    removeFlow(flowName) {
        delete this.flows[flowName];
        this.handlers['flow-removed'].forEach((handler) => handler(flowName));
        this.printer.printFlowRemoved(flowName);
    }
    onFlowUpdated(flowName) {
        this.handlers['flow-updated'].forEach((handler) => handler(flowName));
    }
    isValidStep(step) {
        const validationResult = (0, step_validator_1.validateStep)(step);
        if (!validationResult.success) {
            this.printer.printValidationError(step.filePath, validationResult);
        }
        return validationResult.success;
    }
}
exports.LockedData = LockedData;
