#!/usr/bin/env node

// src/cli.ts
import { resolve, dirname } from "path";
import { fileURLToPath, pathToFileURL } from "url";
import { existsSync, readFileSync, unlinkSync, mkdirSync } from "fs";
import { spawn } from "child_process";
import { buildSync } from "esbuild";
import { tmpdir } from "os";
import { randomBytes } from "crypto";
import { config as loadDotenv } from "dotenv";
var __dirname = dirname(fileURLToPath(import.meta.url));
function getVersion() {
  try {
    const pkgPath = resolve(__dirname, "../package.json");
    const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
    return pkg.version || "unknown";
  } catch {
    return "unknown";
  }
}
function showHelp() {
  console.log(`
Notty Server CLI v${getVersion()}

Usage:
  notty-server [options]

Options:
  -c, --config <file>  Use custom config file (default: notty.config.ts)
  -d, --dev            Run in development mode (watch & auto-restart)
  -v, --version        Show version number
  -h, --help           Show this help message

Examples:
  notty-server                     Start server (production mode)
  notty-server -c config.ts        Use custom config file
  notty-server --dev               Development mode with auto-restart

Documentation: https://github.com/AniTeleWorker/notty
`);
}
var CONFIG_FILES = ["notty.config.ts", "notty.config.js", "notty.config.mjs", "notty.config.cjs"];
var DEFAULT_JWT_SECRET = "dev-secret-change-in-production";
var DEFAULT_JWT_EXPIRES_IN = "7d";
var DEFAULT_STORAGE_TYPE = "local";
var DEFAULT_STORAGE_UPLOAD_DIR = "uploads";
var DEFAULT_STORAGE_BASE_URL = "/api/uploads";
var DEFAULT_SERVER_PORT = 2102;
var DEFAULT_SERVER_HOST = "localhost";
var DEFAULT_SCHEMAS_DIR = "./schemas";
var DEFAULT_DATA_DIR = "./data";
var DEFAULT_CONFIG = {
  jwt: {
    secret: DEFAULT_JWT_SECRET,
    expiresIn: DEFAULT_JWT_EXPIRES_IN
  },
  storage: {
    type: DEFAULT_STORAGE_TYPE,
    uploadDir: DEFAULT_STORAGE_UPLOAD_DIR,
    baseUrl: DEFAULT_STORAGE_BASE_URL
  },
  server: {
    port: DEFAULT_SERVER_PORT,
    host: DEFAULT_SERVER_HOST
  },
  schemasDir: DEFAULT_SCHEMAS_DIR,
  dataDir: DEFAULT_DATA_DIR
};
function setEnvIfMissing(key, value) {
  if (value !== void 0 && process.env[key] === void 0) {
    process.env[key] = value;
  }
}
function setEnv(key, value) {
  if (value !== void 0) {
    process.env[key] = value;
  }
}
function resolveEnvValue(key, configValue) {
  return process.env[key] ?? configValue;
}
function resolveScalarEnvValue(key, configValue) {
  if (process.env[key] !== void 0) {
    return process.env[key];
  }
  if (configValue === void 0) {
    return void 0;
  }
  return String(configValue);
}
function resolveListEnvValue(key, configValue) {
  if (process.env[key] !== void 0) {
    return process.env[key];
  }
  if (Array.isArray(configValue)) {
    return configValue.join(",");
  }
  return configValue;
}
function loadProjectEnv(cwd) {
  loadDotenv({
    path: resolve(cwd, ".env"),
    override: false
  });
}
function applyConfigToEnv(config, rootDir) {
  const schemasDir = resolve(
    rootDir,
    process.env.NOTTY_SCHEMAS_DIR || config.schemasDir || DEFAULT_SCHEMAS_DIR
  );
  const dataDir = resolve(
    rootDir,
    process.env.NOTTY_DATA_DIR || config.dataDir || DEFAULT_DATA_DIR
  );
  const storageConfig = config.storage;
  const legacyS3Config = storageConfig?.s3;
  const legacyGoogleCloudConfig = storageConfig?.googleCloud;
  const databaseUrl = resolveEnvValue("DATABASE_URL", config.database.url);
  setEnvIfMissing("DATABASE_URL", databaseUrl);
  setEnv("NITRO_DATABASE_URL", databaseUrl);
  const isProduction = process.env.NODE_ENV === "production";
  const jwtFallback = isProduction ? void 0 : DEFAULT_JWT_SECRET;
  const jwtSecret = resolveEnvValue("JWT_SECRET", config.jwt?.secret || jwtFallback);
  const jwtExpiresIn = resolveEnvValue(
    "JWT_EXPIRES_IN",
    config.jwt?.expiresIn || DEFAULT_JWT_EXPIRES_IN
  );
  setEnvIfMissing("JWT_SECRET", jwtSecret);
  setEnvIfMissing("JWT_EXPIRES_IN", jwtExpiresIn);
  if (jwtSecret !== void 0) {
    setEnv("NITRO_JWT_SECRET", jwtSecret);
  }
  setEnv("NITRO_JWT_EXPIRES_IN", jwtExpiresIn);
  const storageType = resolveEnvValue("STORAGE_TYPE", config.storage?.type || DEFAULT_STORAGE_TYPE);
  const storageUploadDir = resolveEnvValue(
    "STORAGE_UPLOAD_DIR",
    config.storage?.uploadDir || DEFAULT_STORAGE_UPLOAD_DIR
  );
  const storageBaseUrl = resolveEnvValue(
    "STORAGE_BASE_URL",
    config.storage?.baseUrl || DEFAULT_STORAGE_BASE_URL
  );
  setEnvIfMissing("STORAGE_TYPE", storageType);
  setEnvIfMissing("STORAGE_UPLOAD_DIR", storageUploadDir);
  setEnvIfMissing("STORAGE_BASE_URL", storageBaseUrl);
  setEnv("NITRO_STORAGE_TYPE", storageType);
  setEnv("NITRO_STORAGE_UPLOAD_DIR", storageUploadDir);
  setEnv("NITRO_STORAGE_BASE_URL", storageBaseUrl);
  const storageBucket = resolveEnvValue(
    "STORAGE_BUCKET",
    storageConfig?.bucket ?? legacyS3Config?.bucket ?? legacyGoogleCloudConfig?.bucket
  );
  const storageRegion = resolveEnvValue(
    "STORAGE_REGION",
    storageConfig?.region ?? legacyS3Config?.region
  );
  const storageEndpoint = resolveEnvValue(
    "STORAGE_ENDPOINT",
    storageConfig?.endpoint ?? legacyS3Config?.endpoint
  );
  const storageAccessKeyId = resolveEnvValue(
    "STORAGE_ACCESS_KEY_ID",
    storageConfig?.accessKeyId ?? legacyS3Config?.accessKeyId
  );
  const storageSecretAccessKey = resolveEnvValue(
    "STORAGE_SECRET_ACCESS_KEY",
    storageConfig?.secretAccessKey ?? legacyS3Config?.secretAccessKey
  );
  const storageForcePathStyle = resolveScalarEnvValue(
    "STORAGE_FORCE_PATH_STYLE",
    storageConfig?.forcePathStyle ?? legacyS3Config?.forcePathStyle
  );
  const storageProjectId = resolveEnvValue(
    "STORAGE_PROJECT_ID",
    storageConfig?.projectId ?? legacyGoogleCloudConfig?.projectId
  );
  const storageKeyFilename = resolveEnvValue(
    "STORAGE_KEY_FILENAME",
    storageConfig?.keyFilename ?? legacyGoogleCloudConfig?.keyFilename
  );
  const storageCredentials = resolveEnvValue(
    "STORAGE_CREDENTIALS_JSON",
    storageConfig?.credentials ? JSON.stringify(storageConfig.credentials) : legacyGoogleCloudConfig?.credentials ? JSON.stringify(legacyGoogleCloudConfig.credentials) : void 0
  );
  const storagePublicUrl = resolveEnvValue(
    "STORAGE_PUBLIC_URL",
    storageConfig?.publicUrl ?? legacyS3Config?.publicUrl ?? legacyGoogleCloudConfig?.publicUrl
  );
  const storageSignedUrlExpiry = resolveScalarEnvValue(
    "STORAGE_SIGNED_URL_EXPIRY",
    storageConfig?.signedUrlExpiry ?? legacyS3Config?.signedUrlExpiry ?? legacyGoogleCloudConfig?.signedUrlExpiry
  );
  setEnvIfMissing("STORAGE_BUCKET", storageBucket);
  setEnvIfMissing("STORAGE_REGION", storageRegion);
  setEnvIfMissing("STORAGE_ENDPOINT", storageEndpoint);
  setEnvIfMissing("STORAGE_ACCESS_KEY_ID", storageAccessKeyId);
  setEnvIfMissing("STORAGE_SECRET_ACCESS_KEY", storageSecretAccessKey);
  setEnvIfMissing("STORAGE_FORCE_PATH_STYLE", storageForcePathStyle);
  setEnvIfMissing("STORAGE_PROJECT_ID", storageProjectId);
  setEnvIfMissing("STORAGE_KEY_FILENAME", storageKeyFilename);
  setEnvIfMissing("STORAGE_CREDENTIALS_JSON", storageCredentials);
  setEnvIfMissing("STORAGE_PUBLIC_URL", storagePublicUrl);
  setEnvIfMissing("STORAGE_SIGNED_URL_EXPIRY", storageSignedUrlExpiry);
  setEnv("NITRO_STORAGE_BUCKET", storageBucket);
  setEnv("NITRO_STORAGE_REGION", storageRegion);
  setEnv("NITRO_STORAGE_ENDPOINT", storageEndpoint);
  setEnv("NITRO_STORAGE_ACCESS_KEY_ID", storageAccessKeyId);
  setEnv("NITRO_STORAGE_SECRET_ACCESS_KEY", storageSecretAccessKey);
  setEnv("NITRO_STORAGE_FORCE_PATH_STYLE", storageForcePathStyle);
  setEnv("NITRO_STORAGE_PROJECT_ID", storageProjectId);
  setEnv("NITRO_STORAGE_KEY_FILENAME", storageKeyFilename);
  setEnv("NITRO_STORAGE_CREDENTIALS", storageCredentials);
  setEnv("NITRO_STORAGE_PUBLIC_URL", storagePublicUrl);
  setEnv("NITRO_STORAGE_SIGNED_URL_EXPIRY", storageSignedUrlExpiry);
  setEnvIfMissing("NOTTY_SCHEMAS_DIR", schemasDir);
  setEnvIfMissing("NOTTY_DATA_DIR", dataDir);
  setEnvIfMissing("NOTTY_PROJECT_ROOT", rootDir);
  setEnvIfMissing("NOTTY_EDITION", config.edition);
  setEnvIfMissing("NOTTY_LICENSE_KEY", config.license?.key);
  setEnvIfMissing("NOTTY_LICENSE_ENFORCEMENT", config.license?.enforcement);
  setEnvIfMissing("NOTTY_LICENSE_PUBLIC_KEY", config.license?.publicKey);
  setEnvIfMissing("NOTTY_LICENSE_TRUST_MODE", config.license?.trustMode);
  setEnvIfMissing("NOTTY_INSTALLATION_ID", config.license?.installationId);
  setEnvIfMissing(
    "NOTTY_LICENSE_REQUIRE_SIGNATURE",
    resolveScalarEnvValue("NOTTY_LICENSE_REQUIRE_SIGNATURE", config.license?.requireSignature)
  );
  setEnvIfMissing(
    "NOTTY_ENABLED_MODULES",
    resolveListEnvValue("NOTTY_ENABLED_MODULES", config.license?.enabledModules)
  );
  setEnvIfMissing(
    "NOTTY_DISABLED_MODULES",
    resolveListEnvValue("NOTTY_DISABLED_MODULES", config.license?.disabledModules)
  );
  const port = resolveEnvValue("PORT", String(config.server?.port || DEFAULT_SERVER_PORT));
  const host = resolveEnvValue("HOST", config.server?.host || DEFAULT_SERVER_HOST);
  setEnvIfMissing("PORT", port);
  setEnvIfMissing("HOST", host);
  setEnv("NITRO_PORT", port);
  setEnv("NITRO_HOST", host);
  if (config.oauth?.google) {
    const googleClientId = resolveEnvValue("GOOGLE_CLIENT_ID", config.oauth.google.clientId);
    const googleClientSecret = resolveEnvValue(
      "GOOGLE_CLIENT_SECRET",
      config.oauth.google.clientSecret
    );
    setEnvIfMissing("GOOGLE_CLIENT_ID", googleClientId);
    setEnvIfMissing("GOOGLE_CLIENT_SECRET", googleClientSecret);
    setEnv("NITRO_OAUTH_GOOGLE_CLIENT_ID", googleClientId);
    setEnv("NITRO_OAUTH_GOOGLE_CLIENT_SECRET", googleClientSecret);
  }
  if (config.oauth?.github) {
    const githubClientId = resolveEnvValue("GITHUB_CLIENT_ID", config.oauth.github.clientId);
    const githubClientSecret = resolveEnvValue(
      "GITHUB_CLIENT_SECRET",
      config.oauth.github.clientSecret
    );
    setEnvIfMissing("GITHUB_CLIENT_ID", githubClientId);
    setEnvIfMissing("GITHUB_CLIENT_SECRET", githubClientSecret);
    setEnv("NITRO_OAUTH_GITHUB_CLIENT_ID", githubClientId);
    setEnv("NITRO_OAUTH_GITHUB_CLIENT_SECRET", githubClientSecret);
  }
  if (config.oauth?.facebook) {
    const facebookAppId = resolveEnvValue("FACEBOOK_APP_ID", config.oauth.facebook.appId);
    const facebookAppSecret = resolveEnvValue(
      "FACEBOOK_APP_SECRET",
      config.oauth.facebook.appSecret
    );
    setEnvIfMissing("FACEBOOK_APP_ID", facebookAppId);
    setEnvIfMissing("FACEBOOK_APP_SECRET", facebookAppSecret);
    setEnv("NITRO_OAUTH_FACEBOOK_APP_ID", facebookAppId);
    setEnv("NITRO_OAUTH_FACEBOOK_APP_SECRET", facebookAppSecret);
  }
  if (config.oauth?.vk) {
    const vkAppId = resolveEnvValue("VK_APP_ID", config.oauth.vk.appId);
    const vkAppSecret = resolveEnvValue("VK_APP_SECRET", config.oauth.vk.appSecret);
    setEnvIfMissing("VK_APP_ID", vkAppId);
    setEnvIfMissing("VK_APP_SECRET", vkAppSecret);
    setEnv("NITRO_OAUTH_VK_APP_ID", vkAppId);
    setEnv("NITRO_OAUTH_VK_APP_SECRET", vkAppSecret);
  }
  if (config.admin?.enabledLocales?.length) {
    setEnvIfMissing("ADMIN_ENABLED_LOCALES", JSON.stringify(config.admin.enabledLocales));
  }
  if (config.admin?.customLocales && Object.keys(config.admin.customLocales).length > 0) {
    setEnvIfMissing("ADMIN_CUSTOM_LOCALES", JSON.stringify(config.admin.customLocales));
  }
}
function cleanupConfigBundle(bundlePath) {
  if (!bundlePath) {
    return;
  }
  try {
    unlinkSync(bundlePath);
  } catch {
  }
}
async function loadBundledConfig(configPath) {
  const tempDir = resolve(tmpdir(), "notty-config");
  mkdirSync(tempDir, { recursive: true });
  const tempFile = resolve(tempDir, `config-${randomBytes(8).toString("hex")}.mjs`);
  try {
    buildSync({
      entryPoints: [configPath],
      bundle: true,
      format: "esm",
      platform: "node",
      target: "node18",
      outfile: tempFile,
      external: ["node:*"],
      write: true,
      minify: false
    });
    const configModule = await import(`${pathToFileURL(tempFile).href}?t=${Date.now()}`);
    return {
      config: configModule.default || configModule,
      bundlePath: tempFile
    };
  } catch (error) {
    cleanupConfigBundle(tempFile);
    throw error;
  }
}
function findServerPath() {
  const outputPath = resolve(__dirname, "../.output/server/index.mjs");
  const devOutputPath = resolve(__dirname, "../../.output/server/index.mjs");
  if (existsSync(outputPath)) {
    return outputPath;
  }
  if (existsSync(devOutputPath)) {
    return devOutputPath;
  }
  return null;
}
function startServer(serverPath, cwd) {
  const child = spawn("node", [serverPath], {
    stdio: "inherit",
    env: process.env,
    cwd
  });
  child.on("error", (err) => {
    console.error("\u274C Failed to start server:", err);
  });
  return child;
}
async function runDevMode(serverPath, cwd, configPath, initialConfig, initialBundlePath) {
  const { watch } = await import("chokidar");
  let child = null;
  let isRestarting = false;
  let currentConfig = initialConfig;
  let currentBundlePath = initialBundlePath;
  const schemasDir = resolve(cwd, currentConfig.schemasDir || "./schemas");
  const serverExtensionPaths = [
    resolve(cwd, "src/plugins"),
    resolve(cwd, "src/routes"),
    resolve(cwd, "src/controllers"),
    resolve(cwd, "src/services"),
    resolve(cwd, "src/policies"),
    resolve(cwd, "src/hooks"),
    resolve(cwd, "src/middlewares"),
    resolve(cwd, "middlewares")
  ];
  const watchPaths = [
    ...CONFIG_FILES.map((f) => resolve(cwd, f)),
    schemasDir,
    ...serverExtensionPaths
  ];
  const existingPaths = watchPaths.filter(
    (p) => existsSync(p) || CONFIG_FILES.some((f) => p.endsWith(f))
  );
  const reloadConfig = async () => {
    const previousBundle = currentBundlePath;
    loadProjectEnv(cwd);
    const loaded = await loadBundledConfig(configPath);
    currentConfig = {
      ...DEFAULT_CONFIG,
      ...loaded.config,
      jwt: { ...DEFAULT_CONFIG.jwt, ...loaded.config.jwt },
      storage: { ...DEFAULT_CONFIG.storage, ...loaded.config.storage },
      server: { ...DEFAULT_CONFIG.server, ...loaded.config.server }
    };
    process.env.NOTTY_CONFIG_BUNDLE_PATH = loaded.bundlePath;
    applyConfigToEnv(currentConfig, cwd);
    currentBundlePath = loaded.bundlePath;
    cleanupConfigBundle(previousBundle);
  };
  const restart = async () => {
    if (isRestarting) {
      return;
    }
    isRestarting = true;
    try {
      if (child) {
        console.log("\n\u{1F504} Restarting server...");
        const oldChild = child;
        oldChild.kill("SIGTERM");
        await new Promise((resolve2) => {
          oldChild.on("close", () => resolve2());
          setTimeout(resolve2, 2e3);
        });
      }
      await reloadConfig();
      child = startServer(serverPath, cwd);
    } catch (error) {
      console.error("\u274C Failed to restart server:", error);
    } finally {
      isRestarting = false;
    }
  };
  const watcher = watch(existingPaths, {
    ignoreInitial: true,
    ignored: /(^|[\/\\])\../
    // ignore dotfiles
  });
  watcher.on("all", (event, path) => {
    console.log(`
\u{1F4C1} File ${event}: ${path}`);
    restart();
  });
  console.log("\u{1F440} Watching for changes in:");
  existingPaths.forEach((p) => console.log(`   - ${p}`));
  console.log("");
  child = startServer(serverPath, cwd);
  const shutdown = () => {
    console.log("\n\u{1F6D1} Shutting down...");
    watcher.close();
    if (child) {
      child.kill("SIGTERM");
    }
    cleanupConfigBundle(currentBundlePath);
    process.exit(0);
  };
  process.on("SIGINT", shutdown);
  process.on("SIGTERM", shutdown);
  await new Promise(() => {
  });
}
async function runProdMode(serverPath, cwd) {
  const child = startServer(serverPath, cwd);
  const shutdown = () => {
    console.log("\n\u{1F6D1} Shutting down...");
    child.kill("SIGTERM");
  };
  process.on("SIGINT", shutdown);
  process.on("SIGTERM", shutdown);
  await new Promise((resolve2) => {
    child.on("close", (code) => {
      if (code !== 0 && code !== null) {
        console.error(`\u274C Server exited with code ${code}`);
      }
      resolve2();
    });
  });
}
async function main() {
  const args = process.argv.slice(2);
  let configPath;
  let isDev = false;
  for (let i = 0; i < args.length; i++) {
    if (args[i] === "-v" || args[i] === "--version") {
      console.log(`notty-server v${getVersion()}`);
      process.exit(0);
    }
    if (args[i] === "-h" || args[i] === "--help") {
      showHelp();
      process.exit(0);
    }
    if (args[i] === "-c" || args[i] === "--config") {
      configPath = args[i + 1];
      i++;
    }
    if (args[i] === "--dev" || args[i] === "-d") {
      isDev = true;
    }
  }
  const cwd = process.cwd();
  loadProjectEnv(cwd);
  if (configPath) {
    configPath = resolve(cwd, configPath);
    if (!existsSync(configPath)) {
      console.error(`\u274C Config file not found: ${configPath}`);
      process.exit(1);
    }
  } else {
    for (const file of CONFIG_FILES) {
      const fullPath = resolve(cwd, file);
      if (existsSync(fullPath)) {
        configPath = fullPath;
        break;
      }
    }
  }
  if (!configPath) {
    console.error("\u274C No config file found. Create notty.config.ts or use -c flag.");
    console.error("");
    console.error("Example notty.config.ts:");
    console.error(`
import { defineConfig } from '@notty/core/config';

export default defineConfig({
  database: {
    url: 'sqlite://./data/notty.db'
  }
});
`);
    process.exit(1);
  }
  console.log(`\u{1F4C4} Loading config from: ${configPath}`);
  let configBundlePath;
  try {
    const loadedConfig = await loadBundledConfig(configPath);
    configBundlePath = loadedConfig.bundlePath;
    const userConfig = loadedConfig.config;
    if (!userConfig.database?.url) {
      console.error("\u274C Invalid config: database.url is required");
      process.exit(1);
    }
    const config = {
      ...DEFAULT_CONFIG,
      ...userConfig,
      jwt: { ...DEFAULT_CONFIG.jwt, ...userConfig.jwt },
      storage: { ...DEFAULT_CONFIG.storage, ...userConfig.storage },
      server: { ...DEFAULT_CONFIG.server, ...userConfig.server }
    };
    applyConfigToEnv(config, cwd);
    process.env.NOTTY_CONFIG_BUNDLE_PATH = configBundlePath;
    const serverPath = findServerPath();
    if (!serverPath) {
      console.error("\u274C Pre-built server not found. Package may be corrupted.");
      console.error("   Try reinstalling @notty/server");
      process.exit(1);
    }
    const port = config.server?.port || 2102;
    const host = config.server?.host || "localhost";
    console.log("\u{1F680} Starting Notty server...");
    if (isDev) {
      console.log("\u{1F527} Development mode enabled (watch & auto-restart)");
    } else {
      console.log("\u{1F3ED} Production mode");
    }
    console.log(`\u{1F310} Server will be available at: http://${host}:${port}`);
    console.log(`\u{1F4CA} Admin panel: http://${host}:${port}/admin`);
    console.log(`\u{1F4DA} API docs: http://${host}:${port}/api/_docs`);
    if (isDev) {
      await runDevMode(serverPath, cwd, configPath, config, configBundlePath);
    } else {
      await runProdMode(serverPath, cwd);
    }
  } catch (error) {
    console.error("\u274C Failed to start Notty:", error);
    process.exit(1);
  } finally {
    if (!isDev) {
      cleanupConfigBundle(configBundlePath);
    }
  }
}
main();
