"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createServer = void 0;
const cron_handler_1 = require("./cron-handler");
const body_parser_1 = __importDefault(require("body-parser"));
const express_1 = __importDefault(require("express"));
const http_1 = __importDefault(require("http"));
const multer_1 = __importDefault(require("multer"));
const cors_1 = __importDefault(require("cors"));
const flows_endpoint_1 = require("./flows-endpoint");
const guards_1 = require("./guards");
const logger_1 = require("./logger");
const steps_1 = require("./steps");
const call_step_file_1 = require("./call-step-file");
const logger_factory_1 = require("./logger-factory");
const generate_trace_id_1 = require("./generate-trace-id");
const flows_config_endpoint_1 = require("./flows-config-endpoint");
const api_endpoints_1 = require("./streams/api-endpoints");
const socket_server_1 = require("./socket-server");
const logs_stream_1 = require("./streams/logs-stream");
const createServer = async (lockedData, eventManager, state, config) => {
    const printer = lockedData.printer;
    const app = (0, express_1.default)();
    const server = http_1.default.createServer(app);
    const upload = (0, multer_1.default)();
    const { pushEvent, socketServer } = (0, socket_server_1.createSocketServer)({
        server,
        onJoin: async (streamName, id) => {
            const streams = lockedData.getStreams();
            const stream = streams[streamName];
            if (stream) {
                const result = await stream().get(id);
                delete result.__motia; // deleting because we don't need it in the socket
                return result;
            }
        },
        onJoinGroup: async (streamName, groupId) => {
            const streams = lockedData.getStreams();
            const stream = streams[streamName];
            if (stream) {
                const result = stream ? await stream().getList(groupId) : [];
                // eslint-disable-next-line @typescript-eslint/no-unused-vars
                return result.map(({ __motia, ...rest }) => rest);
            }
        },
    });
    lockedData.applyStreamWrapper(state, (streamName, stream) => {
        return () => {
            const suuper = stream();
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const wrapObject = (id, object) => ({
                ...object,
                __motia: { type: 'state-stream', streamName, id },
            });
            const wrapper = {
                ...suuper,
                async send(channel, event) {
                    pushEvent({ streamName, ...channel, event: { type: 'event', event } });
                },
                async get(id) {
                    const result = await suuper.get.apply(wrapper, [id]);
                    return wrapObject(id, result);
                },
                async update(id, data) {
                    if (!data) {
                        return null;
                    }
                    const updated = await suuper.update.apply(wrapper, [id, data]);
                    const result = updated ?? data;
                    const wrappedResult = wrapObject(id, result);
                    const groupId = suuper.getGroupId(result);
                    pushEvent({ streamName, id, event: { type: 'update', data: result } });
                    if (groupId) {
                        pushEvent({ streamName, groupId, event: { type: 'update', data: result } });
                    }
                    return wrappedResult;
                },
                async delete(id) {
                    const result = await suuper.delete.apply(wrapper, [id]);
                    const groupId = result && suuper.getGroupId(result);
                    pushEvent({ streamName, id, event: { type: 'delete', data: result } });
                    if (groupId) {
                        pushEvent({ streamName, groupId, event: { type: 'delete', data: result } });
                    }
                    return wrapObject(id, result);
                },
                async create(id, data) {
                    const created = await suuper.create.apply(wrapper, [id, data]);
                    const result = created ?? data;
                    const wrappedResult = wrapObject(id, result);
                    const groupId = suuper.getGroupId(result);
                    pushEvent({ streamName, id, event: { type: 'create', data: result } });
                    if (groupId) {
                        pushEvent({ streamName, groupId, event: { type: 'create', data: result } });
                    }
                    return wrappedResult;
                },
                getList: async (groupId) => {
                    const list = await suuper.getList.apply(wrapper, [groupId]);
                    return list.map((object) => wrapObject(object.id, object));
                },
            };
            return wrapper;
        };
    });
    const logStream = lockedData.createStream({
        filePath: '__motia.logs',
        hidden: true,
        config: {
            name: '__motia.logs',
            baseConfig: { storageType: 'custom', factory: () => new logs_stream_1.LogsStream() },
            schema: null,
        },
    })();
    const allSteps = [...steps_1.systemSteps, ...lockedData.activeSteps];
    const loggerFactory = new logger_factory_1.LoggerFactory(config.isVerbose, logStream);
    const cronManager = (0, cron_handler_1.setupCronHandlers)(lockedData, eventManager, state, loggerFactory);
    const asyncHandler = (step) => {
        return async (req, res) => {
            const traceId = (0, generate_trace_id_1.generateTraceId)();
            const { name: stepName, flows } = step.config;
            const logger = loggerFactory.create({ traceId, flows, stepName });
            logger.debug('[API] Received request, processing step', { path: req.path });
            const request = {
                body: req.body,
                headers: req.headers,
                pathParams: req.params,
                queryParams: req.query,
                files: req.files,
            };
            try {
                const data = request;
                const result = await (0, call_step_file_1.callStepFile)({
                    contextInFirstArg: false,
                    lockedData,
                    data,
                    step,
                    printer,
                    logger,
                    eventManager,
                    state,
                    traceId,
                });
                if (!result) {
                    res.status(500).json({ error: 'Internal server error' });
                    return;
                }
                if (result.headers) {
                    Object.entries(result.headers).forEach(([key, value]) => res.setHeader(key, value));
                }
                res.status(result.status);
                res.json(result.body);
            }
            catch (error) {
                logger.error('[API] Internal server error', { error });
                console.log(error);
                res.status(500).json({ error: 'Internal server error' });
            }
        };
    };
    app.use(body_parser_1.default.json());
    app.use(body_parser_1.default.urlencoded({ extended: true }));
    const router = express_1.default.Router();
    const addRoute = (step) => {
        const { method, path } = step.config;
        logger_1.globalLogger.debug('[API] Registering route', step.config);
        const handler = asyncHandler(step);
        const methods = {
            GET: () => router.get(path, handler),
            POST: () => router.post(path, upload.any(), handler),
            PUT: () => router.put(path, upload.any(), handler),
            DELETE: () => router.delete(path, handler),
            PATCH: () => router.patch(path, upload.any(), handler),
            OPTIONS: () => router.options(path, handler),
            HEAD: () => router.head(path, handler),
        };
        const methodHandler = methods[method];
        if (!methodHandler) {
            throw new Error(`Unsupported method: ${method}`);
        }
        methodHandler();
    };
    const removeRoute = (step) => {
        const { path, method } = step.config;
        const routerStack = router.stack;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const filteredStack = routerStack.filter((layer) => {
            if (layer.route) {
                const match = layer.route.path === path && layer.route.methods[method.toLowerCase()];
                return !match;
            }
            return true;
        });
        router.stack = filteredStack;
    };
    allSteps.filter(guards_1.isApiStep).forEach(addRoute);
    app.use((0, cors_1.default)());
    app.use(router);
    (0, api_endpoints_1.apiEndpoints)(lockedData);
    (0, flows_endpoint_1.flowsEndpoint)(lockedData, app);
    (0, flows_config_endpoint_1.flowsConfigEndpoint)(app, process.cwd());
    server.on('error', (error) => {
        console.error('Server error:', error);
    });
    const close = async () => {
        cronManager.close();
        socketServer.close();
    };
    return { app, server, socketServer, close, removeRoute, addRoute, cronManager };
};
exports.createServer = createServer;
