/// <reference types="vite/client" />
import { AsyncLocalStorage } from "node:async_hooks";
import { Alepha } from "./Alepha.ts";
import type { RunOptions } from "./interfaces/Run.ts";
import type { Service } from "./interfaces/Service.ts";
import { $module } from "./primitives/$module.ts";
import { AlsProvider } from "./providers/AlsProvider.ts";
import { CodecManager } from "./providers/CodecManager.ts";
import { EventManager } from "./providers/EventManager.ts";
import { Json } from "./providers/Json.ts";
import { JsonSchemaCodec } from "./providers/JsonSchemaCodec.ts";
import { KeylessJsonSchemaCodec } from "./providers/KeylessJsonSchemaCodec.ts";
import { SchemaCodec } from "./providers/SchemaCodec.ts";
import { SchemaValidator } from "./providers/SchemaValidator.ts";
import { StateManager } from "./providers/StateManager.ts";

// ---------------------------------------------------------------------------------------------------------------------

export * from "./index.shared.ts";

// ---------------------------------------------------------------------------------------------------------------------

/**
 * Foundation of the entire framework with dependency injection and lifecycle management.
 *
 * **Features:**
 * - Dependency injection for services
 * - Service substitution/mocking
 * - Type-safe environment variable loading with TypeBox schemas
 * - Lifecycle hooks (start, stop, log, etc.)
 * - Module definitions and composition
 * - Request-scoped context access via Async Local Storage (ALS)
 * - Reactive state management with atoms
 * - Full TypeScript generics and type inference
 *
 * @module alepha.core
 */
export const AlephaCore = $module({
  name: "alepha.core",
  // These services are instantiated directly by Alepha's constructor in a strict order.
  // Declared as `variants` (semantically: not really alternatives, but the framework
  // owns their wiring) so the module tag is attached without auto-injection.
  variants: [
    StateManager,
    CodecManager,
    EventManager,
    AlsProvider,
    Json,
    JsonSchemaCodec,
    KeylessJsonSchemaCodec,
    SchemaCodec,
    SchemaValidator,
  ],
});

// ---------------------------------------------------------------------------------------------------------------------

/**
 * Run Alepha application, trigger start lifecycle.
 *
 * ```ts
 * import { Alepha, run } from "alepha";
 * import { MyService } from "./services/MyService.ts";
 *
 * const alepha = new Alepha({ env: { APP_NAME: "MyAlephaApp" } });
 *
 * alepha.with(MyService);
 *
 * export default run(alepha);
 * ```
 */
export const run = (
  entry: Alepha | Service | Array<Service>,
  opts?: RunOptions,
): Alepha => {
  const env: Record<string, string | undefined> =
    typeof process === "object" ? process.env : {};

  const alepha =
    entry instanceof Alepha
      ? entry
      : Alepha.create({ env: { ...env, ...opts?.env } });

  if (!(entry instanceof Alepha)) {
    const entries = Array.isArray(entry) ? entry : [entry];
    for (const e of entries) {
      alepha.with(e);
    }
  }

  // make alepha globally accessible (for serverless functions, etc...)
  // it's not recommended, we should force 'export default run(alepha)'
  (globalThis as any).__alepha = alepha;

  // when alepha instance is imported via CLI, use a different global variable
  if (env.ALEPHA_CLI_IMPORT) {
    (globalThis as any).__cli_alepha = alepha;
  }

  if (alepha.isServerless() || alepha.isViteDev() || env.ALEPHA_CLI_IMPORT) {
    return alepha;
  }

  setTimeout(async () => {
    try {
      await opts?.configure?.(alepha);

      await alepha.start();

      if (opts?.ready) {
        await opts.ready(alepha);
      }

      if (opts?.once) {
        await alepha.stop();
        return alepha;
      }

      if (typeof process === "object") {
        const traps = ["SIGTERM", "SIGINT", "SIGUSR2", "uncaughtException"];

        for (const trap of traps) {
          process.once(trap, async (err) => {
            if (trap === "uncaughtException") {
              alepha.log?.error("Uncaught Exception", err);
            } else {
              alepha.log?.info("Received signal", { trap });
            }
            try {
              await alepha.stop();
              console.log(" ");
              process.exit(0);
            } catch (error) {
              alepha.log?.error("Alepha failed to stop", error);
              process.exit(1);
            }
          });
        }
      }
    } catch (error) {
      alepha.log?.error("Alepha failed to start", error);
      if (typeof process === "object") {
        process.exit(1);
      }
    }
  });

  return alepha;
};

// ---------------------------------------------------------------------------------------------------------------------

// only for node.js environment
AlsProvider.create = () => new AsyncLocalStorage();
