#!/usr/bin/env node
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-env node */
const commander_1 = require("commander");
const express_1 = __importDefault(require("express"));
const http_1 = __importDefault(require("http"));
const ws_1 = require("ws");
const logger_1 = require("./server/utils/logger");
const path_1 = __importDefault(require("path"));
const util_1 = require("util");
const child_process_1 = require("child_process");
const db_1 = require("./utils/db");
const client_1 = require("@prisma/client");
const exec = (0, util_1.promisify)(child_process_1.exec);
const projectRootDir = path_1.default.resolve(__dirname, '..', '..');
async function initDb(opts) {
    const envVars = (0, db_1.loadDbUrlEnvVar)();
    if (!envVars) {
        process.exit(1);
    }
    const u = new URL(envVars.DATABASE_URL);
    const dbName = u.pathname.replace('/', '');
    const client = new client_1.PrismaClient({
        datasourceUrl: process.env.DATABASE_URL,
    });
    try {
        // TODO - look into whether we even need to set the schema for self hosting customers who need to run initdb
        const schema = u.searchParams.get('schema');
        if (schema) {
            logger_1.logger.info('Creating schema...');
            await client.$queryRaw `
        CREATE SCHEMA IF NOT EXISTS "${schema}";
      `;
            await client.$queryRaw `
        SET search_path TO "${schema}",public;
      `;
        }
        if (!opts.skipCreate) {
            logger_1.logger.info(`Will create database ${dbName} on host ${u.hostname}...`);
            // Check if the database already exists
            const dbExistsResult = await client.$queryRaw `SELECT 1 FROM pg_database WHERE datname='${dbName}'`;
            if (dbExistsResult.length > 0) {
                logger_1.logger.error(`The database "${dbName}" already exists. You can run \`forge-server db-init --skip-create\` to run the initialization script against the existing "${dbName}" database.`);
                process.exit(1);
            }
            // Create the database
            await client.$queryRaw `CREATE DATABASE "${dbName}";`;
        }
        // Create the extension
        await client.$queryRaw `
      CREATE EXTENSION IF NOT EXISTS pgcrypto SCHEMA public;
    `;
        // Create the function
        await client.$queryRaw `
      CREATE OR REPLACE FUNCTION nanoid(size int DEFAULT 21)
      RETURNS text AS $$
      DECLARE
        id text := '';
        i int := 0;
        urlAlphabet char(64) := 'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW';
        bytes bytea := public.gen_random_bytes(size);
        byte int;
        pos int;
      BEGIN
        WHILE i < size LOOP
          byte := get_byte(bytes, i);
          pos := (byte & 63) + 1; -- + 1 because substr starts at 1 for some reason
          id := id || substr(urlAlphabet, pos, 1);
          i = i + 1;
        END LOOP;
        RETURN id;
      END
      $$ LANGUAGE PLPGSQL STABLE;
    `;
        // Push Prisma changes
        await exec(`npx -y prisma db push`, { cwd: projectRootDir });
    }
    catch (err) {
        logger_1.logger.error('Database initialization failed:', err);
        process.exit(1);
    }
}
const program = new commander_1.Command();
program.showHelpAfterError();
program
    .name('forge-server')
    .description('Forge Server is the central server for Forge apps')
    .option('-v, --verbose', 'verbose output')
    .addCommand(new commander_1.Command('start').description('starts Forge Server'))
    .addCommand(new commander_1.Command('db-init').addOption(new commander_1.Option('--skip-create', 'for when a database already exists, skip creating one')));
const [cmd, ...args] = program.parse().args;
async function main() {
    if (cmd === 'start') {
        // TODO - instead of calling initDb give users ability to setup a local sqlite db for self hosting
        await initDb({ skipCreate: true }).catch(() => {
            logger_1.logger.error(`Failed to initialize database.`);
            process.exit(1);
        });
        const envVars = (await Promise.resolve().then(() => __importStar(require('./env')))).default;
        // start the internal web socket server
        Promise.resolve().then(() => __importStar(require('./wss/index')));
        const app = (0, express_1.default)();
        const mainAppServer = (await Promise.resolve().then(() => __importStar(require('./server/index')))).default;
        app.use(mainAppServer);
        const server = http_1.default.createServer(app);
        const wss = new ws_1.WebSocketServer({ server, path: '/websocket' });
        const { setupWebSocketServer } = await Promise.resolve().then(() => __importStar(require('./wss/wss')));
        setupWebSocketServer(wss);
        server.listen(Number(envVars.PORT), () => {
            logger_1.logger.info(`📡 Forge Server listening at http://localhost:${envVars.PORT}`);
        });
    }
    else if (cmd === 'db-init') {
        logger_1.logger.info('Initializing a database...');
        initDb({ skipCreate: args.includes('--skip-create') }).catch(() => {
            logger_1.logger.error(`Failed to initialize database.`);
            process.exit(1);
        });
    }
}
main();
