import { spawnSync } from 'node:child_process';
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
import { mkdirSync, readFileSync, writeFileSync, chmodSync, existsSync, statSync } from 'node:fs';
import { basename, dirname, resolve, join } from 'node:path';
import { pathToFileURL } from 'node:url';
import {
  InMemoryBroker,
  InMemoryTransport,
  MatrixActor,
  MatrixRuntime,
  TopicRouter,
  type Unsub,
} from '@open-matrix/core';

type CommandOptions = Record<string, string | boolean>;
type ActorConstructor = new () => MatrixActor;
type PackageBootstrapFunction = (...args: readonly unknown[]) => Promise<unknown> | unknown;

interface ParsedArgs {
  readonly positional: string[];
  readonly options: CommandOptions;
}

interface RunStatus {
  readonly pid: number;
  readonly host: string;
  readonly port: number;
  readonly mount: string;
  readonly entry: string;
  readonly exportName: string;
  readonly startedAt: string;
}

interface PackageRunSpec {
  readonly packageDir: string;
  readonly entryPath: string;
  readonly exportName: string;
  readonly mount: string;
}

function parseArgs(args: string[]): ParsedArgs {
  const positional: string[] = [];
  const options: CommandOptions = {};

  for (let i = 0; i < args.length; i += 1) {
    const arg = args[i];
    if (!arg.startsWith('--')) {
      positional.push(arg);
      continue;
    }

    const withoutPrefix = arg.slice(2);
    const [rawKey, inlineValue] = withoutPrefix.split(/=(.*)/s, 2);
    const key = rawKey.trim();
    if (!key) throw new Error(`Invalid option: ${arg}`);
    if (inlineValue !== undefined) {
      options[key] = inlineValue;
      continue;
    }

    const next = args[i + 1];
    if (next && !next.startsWith('--')) {
      options[key] = next;
      i += 1;
    } else {
      options[key] = true;
    }
  }

  return { positional, options };
}

function stringOption(options: CommandOptions, key: string): string | undefined {
  const value = options[key];
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
}

function numberOption(options: CommandOptions, key: string, defaultValue: number): number {
  const raw = stringOption(options, key);
  if (!raw) return defaultValue;
  const parsed = Number.parseInt(raw, 10);
  if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(`--${key} must be a positive integer`);
  return parsed;
}

function workspaceDir(options: CommandOptions): string {
  return resolve(stringOption(options, 'dir') ?? process.cwd());
}

function matrixDir(root: string): string {
  return join(root, '.matrix');
}

function ensureDir(path: string): void {
  mkdirSync(path, { recursive: true });
}

function readJsonFile(path: string): unknown {
  return JSON.parse(readFileSync(path, 'utf8'));
}

function writeJsonFile(path: string, value: unknown): void {
  ensureDir(dirname(path));
  writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
}

function normalizePackageName(name: string): string {
  return name
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9._-]+/g, '-')
    .replace(/^-+|-+$/g, '') || 'matrix-package';
}

function toPascalCase(input: string): string {
  const parts = input.trim().split(/[^a-zA-Z0-9]+/).filter(Boolean);
  const name = parts.map((part) => `${part[0]?.toUpperCase() ?? ''}${part.slice(1)}`).join('');
  return name || 'Demo';
}

function toActorClassName(input: string): string {
  const className = toPascalCase(input);
  return className.endsWith('Actor') ? className : `${className}Actor`;
}

function writeIfMissing(path: string, contents: string): void {
  if (!existsSync(path)) writeFileSync(path, contents, 'utf8');
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

function optionalString(value: unknown): string | undefined {
  if (typeof value !== 'string') return undefined;
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : undefined;
}

async function initCommand(options: CommandOptions, positional: string[]): Promise<void> {
  const root = resolve(positional[0] ?? stringOption(options, 'dir') ?? process.cwd());
  const packageName = normalizePackageName(stringOption(options, 'name') ?? basename(root));

  ensureDir(root);
  ensureDir(join(root, 'src'));
  ensureDir(matrixDir(root));

  writeIfMissing(join(root, 'package.json'), `${JSON.stringify({
    name: packageName,
    version: '0.0.0',
    type: 'module',
    scripts: {
      build: 'tsc -p tsconfig.json',
    },
    dependencies: {
      '@open-matrix/sdk': '^0.1.0',
    },
    devDependencies: {
      typescript: '^5.7.0',
    },
  }, null, 2)}\n`);

  writeIfMissing(join(root, 'tsconfig.json'), `${JSON.stringify({
    compilerOptions: {
      target: 'ES2022',
      module: 'NodeNext',
      moduleResolution: 'NodeNext',
      strict: true,
      skipLibCheck: true,
      outDir: 'dist',
    },
    include: ['src/**/*.ts'],
  }, null, 2)}\n`);

  writeIfMissing(join(root, 'src', 'index.ts'), '// Matrix package exports are added here.\n');
  writeIfMissing(join(root, 'matrix.json'), `${JSON.stringify({
    name: packageName,
    version: '0.0.0',
    runtime: {
      language: 'typescript',
      entry: 'dist/index.js',
    },
    components: [],
  }, null, 2)}\n`);

  console.log(JSON.stringify({ ok: true, command: 'init', dir: root, name: packageName }, null, 2));
}

function readMatrixManifest(root: string): Record<string, unknown> {
  const manifestPath = join(root, 'matrix.json');
  if (!existsSync(manifestPath)) throw new Error(`matrix.json not found in ${root}; run matrix init first`);
  const value = readJsonFile(manifestPath);
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    throw new Error(`matrix.json must be a JSON object: ${manifestPath}`);
  }
  return value as Record<string, unknown>;
}

function appendExport(indexPath: string, className: string): void {
  const line = `export { ${className} } from './${className}.js';`;
  const current = existsSync(indexPath) ? readFileSync(indexPath, 'utf8') : '';
  if (!current.includes(line)) {
    writeFileSync(indexPath, `${current.trimEnd()}\n${line}\n`, 'utf8');
  }
}

function appendFactoryExport(indexPath: string, className: string): void {
  const line = `export { createStandalone${className} } from './create-standalone-${className}.js';`;
  const current = existsSync(indexPath) ? readFileSync(indexPath, 'utf8') : '';
  if (!current.includes(line)) {
    writeFileSync(indexPath, `${current.trimEnd()}\n${line}\n`, 'utf8');
  }
}

function runtimeIdToken(value: string): string {
  const token = value
    .toUpperCase()
    .replace(/[^A-Z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .replace(/-+/g, '-');
  return token || 'ACTOR';
}

function actorFactoryTemplate(className: string, mount: string): string {
  const factoryName = `createStandalone${className}`;
  return `import { InMemoryBroker, InMemoryTransport, MatrixRuntime } from '@open-matrix/sdk';

import { ${className} } from './${className}.js';

interface StandaloneBootContext {
  readonly root?: string;
  readonly mount?: string;
}

interface StandaloneRunnerServices {
  readonly runtime?: MatrixRuntime;
}

function readTrimmedString(value: unknown): string | undefined {
  if (typeof value !== 'string') return undefined;
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : undefined;
}

function requiredStandaloneRoot(requestedRoot: string | undefined, runtime: MatrixRuntime): string {
  const runtimeRoot = readTrimmedString(runtime.transport.root);
  const root = requestedRoot ?? runtimeRoot;
  if (!root) throw new Error('${factoryName} requires bootContext.root or a runtime transport root');
  return root;
}

export async function ${factoryName}(
  _overrides: Record<string, unknown> = {},
  services?: StandaloneRunnerServices,
  _config?: unknown,
  bootContext: StandaloneBootContext = {},
): Promise<{
  runtime: MatrixRuntime;
  actor: ${className};
  root: string;
  mount: string;
}> {
  const requestedRoot = readTrimmedString(bootContext.root);
  const runtime = services?.runtime ?? new MatrixRuntime({
    transport: new InMemoryTransport(new InMemoryBroker(), {
      name: '${mount}-standalone',
      ...(requestedRoot ? { root: requestedRoot } : {}),
    }),
    logging: false,
  });
  const root = requiredStandaloneRoot(requestedRoot, runtime);
  const mount = readTrimmedString(bootContext.mount) ?? '${mount}';
  const actor = await runtime.createSupervised(${className}, mount);
  return { runtime, actor, root, mount };
}
`;
}

async function addActorCommand(options: CommandOptions, name: string): Promise<void> {
  const root = workspaceDir(options);
  const normalized = normalizePackageName(name);
  const className = toActorClassName(name);
  const mount = stringOption(options, 'mount') ?? normalized;
  const sourcePath = join(root, 'src', `${className}.ts`);
  const factoryPath = join(root, 'src', `create-standalone-${className}.ts`);

  readMatrixManifest(root);
  ensureDir(join(root, 'src'));
  writeIfMissing(sourcePath, `import { MatrixActor } from '@open-matrix/sdk';

export class ${className} extends MatrixActor {
  static accepts = {
    echo: {},
    getStatus: {},
  };

  onEcho(payload: { message?: string }) {
    return {
      ok: true,
      message: payload.message ?? null,
      actor: '${className}',
    };
  }

  onGetStatus() {
    return {
      ok: true,
      actor: '${className}',
      mount: '${mount}',
    };
  }
}
`);
  appendExport(join(root, 'src', 'index.ts'), className);
  writeIfMissing(factoryPath, actorFactoryTemplate(className, mount));
  appendFactoryExport(join(root, 'src', 'index.ts'), className);

  const manifest = readMatrixManifest(root);
  const components = Array.isArray(manifest.components)
    ? manifest.components.filter(isRecord)
    : [];
  const nextComponents = components.filter((entry) => entry.type !== className && entry.mount !== mount);
  nextComponents.push({
    type: className,
    export: className,
    mount,
    surface: 'headless',
    accepts: ['echo', 'getStatus'],
  });
  writeJsonFile(join(root, 'matrix.json'), {
    ...manifest,
    runtime: {
      ...(isRecord(manifest.runtime) ? manifest.runtime : {}),
      language: 'typescript',
      entry: './dist/index.js',
      factory: {
        kind: 'factory',
        export: `createStandalone${className}`,
        instance: {
          id: `RUNTIME-HOST-${runtimeIdToken(mount)}`,
          class: 'service',
          rootKind: 'component',
          componentType: className,
          autoStart: true,
        },
      },
    },
    components: nextComponents,
  });

  console.log(JSON.stringify({ ok: true, command: 'add actor', dir: root, actor: className, mount }, null, 2));
}

async function configureCommand(options: CommandOptions): Promise<void> {
  const root = workspaceDir(options);
  const key = stringOption(options, 'key') ?? process.env.MATRIX_API_KEY ?? process.env.HIVECAST_API_KEY;
  const cloud = stringOption(options, 'cloud') ?? process.env.MATRIX_CLOUD ?? process.env.HIVECAST_CLOUD;
  const space = stringOption(options, 'space') ?? process.env.MATRIX_SPACE ?? process.env.HIVECAST_SPACE;
  const runtimeHostBin = stringOption(options, 'runtime-host-bin') ?? process.env.MATRIX_RUNTIME_HOST_BIN;
  const runtimeHostRouting = stringOption(options, 'runtime-host-routing') ?? process.env.MATRIX_RUNTIME_HOST_ROUTING;
  if (!key) throw new Error('MATRIX_API_KEY_REQUIRED: matrix configure requires --key or MATRIX_API_KEY');
  if (!cloud) throw new Error('MATRIX_CLOUD_REQUIRED: matrix configure requires --cloud or MATRIX_CLOUD');
  if (!space) throw new Error('MATRIX_SPACE_REQUIRED: matrix configure requires --space or MATRIX_SPACE');
  if (runtimeHostRouting && !['release', 'workspace-dist', 'workspace-source'].includes(runtimeHostRouting)) {
    throw new Error(`--runtime-host-routing must be release, workspace-dist, or workspace-source, got ${runtimeHostRouting}`);
  }

  const configPath = join(matrixDir(root), 'config.json');
  const credentialPath = join(matrixDir(root), 'credentials', 'matrix-api-key.json');
  writeJsonFile(configPath, {
    cloud,
    space,
    ...(runtimeHostBin ? { runtimeHostBin } : {}),
    ...(runtimeHostRouting ? { runtimeHostRouting } : {}),
  });
  writeJsonFile(credentialPath, { key, cloud, space });
  chmodSync(credentialPath, 0o600);

  console.log(JSON.stringify({ ok: true, command: 'configure', dir: root, cloud, space }, null, 2));
}

function actorConstructorFromExport(value: unknown): ActorConstructor | null {
  if (typeof value !== 'function') return null;
  const candidate = value as { prototype?: { initialize?: unknown } };
  if (typeof candidate.prototype?.initialize !== 'function') return null;
  return value as ActorConstructor;
}

async function importActor(entry: string, exportName: string): Promise<ActorConstructor> {
  const moduleUrl = `${pathToFileURL(resolve(entry)).href}?matrix_cli=${Date.now()}`;
  const mod = await import(moduleUrl) as Record<string, unknown>;
  const requested = actorConstructorFromExport(mod[exportName]);
  if (requested) return requested;
  const available = Object.entries(mod).filter(([, value]) => actorConstructorFromExport(value)).map(([key]) => key);
  if (available.length === 1) return actorConstructorFromExport(mod[available[0]])!;
  throw new Error(`No actor class found as export "${exportName}". Available actor exports: ${available.join(', ') || '(none)'}`);
}

async function importPackageBootstrap(entry: string, exportName: string): Promise<PackageBootstrapFunction> {
  const moduleUrl = `${pathToFileURL(resolve(entry)).href}?matrix_cli=${Date.now()}`;
  const mod = await import(moduleUrl) as Record<string, unknown>;
  const value = mod[exportName];
  if (typeof value !== 'function') {
    throw new Error(`Package runtime factory export "${exportName}" was not found in ${entry}`);
  }
  return value as PackageBootstrapFunction;
}

function readRequestBody(req: IncomingMessage): Promise<unknown> {
  return new Promise((resolveBody, reject) => {
    const chunks: Buffer[] = [];
    req.on('data', (chunk: Buffer) => chunks.push(chunk));
    req.on('error', reject);
    req.on('end', () => {
      const raw = Buffer.concat(chunks).toString('utf8');
      try {
        resolveBody(raw.trim() ? JSON.parse(raw) : {});
      } catch (error) {
        reject(error);
      }
    });
  });
}

function sendJson(res: ServerResponse, status: number, body: unknown): void {
  const text = JSON.stringify(body);
  res.writeHead(status, {
    'content-type': 'application/json',
    'content-length': Buffer.byteLength(text),
  });
  res.end(text);
}

async function requestActor(
  transport: InMemoryTransport,
  mount: string,
  op: string,
  payload: unknown,
): Promise<unknown> {
  const correlationId = `matrix-cli-${Date.now()}-${Math.random().toString(16).slice(2)}`;
  const replyTo = `$replies.${correlationId}`;
  let unsubscribe: Unsub = () => {};
  const response = new Promise<unknown>((resolveResponse, reject) => {
    const timeout = setTimeout(() => {
      unsubscribe();
      reject(new Error(`REQUEST_TIMEOUT: ${mount}.${op}`));
    }, 5000);
    unsubscribe = transport.subscribe(replyTo, (value) => {
      clearTimeout(timeout);
      unsubscribe();
      resolveResponse(value);
    });
  });

  transport.publish(TopicRouter.inbox(mount), {
    op,
    payload,
    replyTo,
    correlationId,
  });

  return response;
}

async function serveRuntime(input: {
  readonly runtime: MatrixRuntime;
  readonly transport: InMemoryTransport;
  readonly root: string;
  readonly mount: string;
  readonly entry: string;
  readonly exportName: string;
  readonly host: string;
  readonly port: number;
  readonly workspaceRoot: string;
}): Promise<void> {
  const { runtime, transport, root, mount, entry, exportName, host, port, workspaceRoot } = input;
  const server = createServer(async (req, res) => {
    try {
      if (req.method === 'GET' && req.url === '/healthz') {
        sendJson(res, 200, { ok: true, mount, root, pid: process.pid });
        return;
      }
      if (req.method !== 'POST' || req.url !== '/invoke') {
        sendJson(res, 404, { ok: false, error: 'NOT_FOUND' });
        return;
      }
      const body = await readRequestBody(req) as { mount?: unknown; op?: unknown; payload?: unknown };
      const targetMount = typeof body.mount === 'string' && body.mount.trim() ? body.mount.trim() : mount;
      const op = typeof body.op === 'string' && body.op.trim() ? body.op.trim() : '';
      if (!op) throw new Error('matrix invoke requires an op');
      const result = await requestActor(transport, targetMount, op, body.payload);
      sendJson(res, 200, result);
    } catch (error) {
      sendJson(res, 500, { ok: false, error: error instanceof Error ? error.message : String(error) });
    }
  });

  await new Promise<void>((resolveListen) => server.listen(port, host, resolveListen));
  const status: RunStatus = {
    pid: process.pid,
    host,
    port,
    mount,
    entry: resolve(entry),
    exportName,
    startedAt: new Date().toISOString(),
  };
  writeJsonFile(join(matrixDir(workspaceRoot), 'run.json'), status);
  console.log(JSON.stringify({ ok: true, command: 'run', root, ...status }, null, 2));

  const stop = async () => {
    await runtime.shutdown();
    server.close();
  };
  process.once('SIGINT', () => { void stop().finally(() => process.exit(0)); });
  process.once('SIGTERM', () => { void stop().finally(() => process.exit(0)); });
}

async function runActorEntryCommand(options: CommandOptions, entry: string): Promise<void> {
  const mount = stringOption(options, 'mount');
  if (!mount) throw new Error('matrix run <entry> requires --mount <mount>');
  const exportName = stringOption(options, 'export') ?? 'default';
  const host = stringOption(options, 'host') ?? '127.0.0.1';
  const port = numberOption(options, 'port', 3920);
  const root = workspaceDir(options);

  const ActorClass = await importActor(entry, exportName);
  const broker = new InMemoryBroker();
  const transport = new InMemoryTransport(broker, { name: `matrix-cli:${mount}`, root: 'sdk-local' });
  const runtime = new MatrixRuntime({ transport, logging: false });
  await runtime.create(ActorClass, mount);

  await serveRuntime({ runtime, transport, root: 'sdk-local', mount, entry, exportName, host, port, workspaceRoot: root });
}

function resolvePackageRunSpec(packageDir: string, options: CommandOptions): PackageRunSpec {
  const manifestPath = join(packageDir, 'matrix.json');
  if (!existsSync(manifestPath)) throw new Error(`matrix.json not found in ${packageDir}; run matrix init first`);
  const manifest = readJsonFile(manifestPath);
  if (!isRecord(manifest)) throw new Error(`matrix.json must be a JSON object: ${manifestPath}`);
  const runtime = isRecord(manifest.runtime) ? manifest.runtime : undefined;
  const factory = isRecord(runtime?.factory) ? runtime.factory : undefined;
  if (!factory) {
    throw new Error(`${manifestPath} must declare runtime.factory before matrix run/up can use package mode`);
  }
  const entry = stringOption(options, 'entry') ?? optionalString(factory.entry) ?? optionalString(runtime?.entry);
  if (!entry) throw new Error(`${manifestPath} runtime.factory requires entry or runtime.entry`);
  const exportName = stringOption(options, 'export') ?? optionalString(factory.export);
  if (!exportName) throw new Error(`${manifestPath} runtime.factory requires export`);
  const instance = isRecord(factory.instance) ? factory.instance : undefined;
  const mount = stringOption(options, 'mount')
    ?? optionalString(instance?.mount)
    ?? resolveComponentMount(manifest, instance);
  if (!mount) {
    throw new Error(`${manifestPath} runtime.factory must resolve a mount through --mount, instance.mount, or components[]`);
  }
  const entryPath = resolve(packageDir, entry);
  if (!existsSync(entryPath)) {
    throw new Error(`Package runtime entry does not exist: ${entryPath}. Run the package build first.`);
  }
  return { packageDir, entryPath, exportName, mount };
}

function resolveComponentMount(manifest: Record<string, unknown>, instance: Record<string, unknown> | undefined): string | undefined {
  const components = Array.isArray(manifest.components) ? manifest.components.filter(isRecord) : [];
  const componentType = optionalString(instance?.componentType);
  if (componentType) {
    const matching = components.find((component) => optionalString(component.type) === componentType);
    return optionalString(matching?.mount);
  }
  if (components.length === 1) return optionalString(components[0].mount);
  return undefined;
}

async function runPackageCommand(options: CommandOptions, packageDir: string): Promise<void> {
  const spec = resolvePackageRunSpec(packageDir, options);
  const host = stringOption(options, 'host') ?? '127.0.0.1';
  const port = numberOption(options, 'port', 3920);
  const root = workspaceDir(options);
  const rootName = stringOption(options, 'root') ?? 'sdk-local';
  const broker = new InMemoryBroker();
  const transport = new InMemoryTransport(broker, { name: `matrix-cli:${spec.mount}`, root: rootName });
  const runtime = new MatrixRuntime({ transport, logging: false });
  const bootstrap = await importPackageBootstrap(spec.entryPath, spec.exportName);
  await bootstrap({}, { runtime }, undefined, {
    root: rootName,
    mount: spec.mount,
    packageDir: spec.packageDir,
    entryPath: spec.entryPath,
    exportName: spec.exportName,
  });
  await serveRuntime({
    runtime,
    transport,
    root: rootName,
    mount: spec.mount,
    entry: spec.entryPath,
    exportName: spec.exportName,
    host,
    port,
    workspaceRoot: root,
  });
}

async function runCommand(options: CommandOptions, positional: string[]): Promise<void> {
  const root = workspaceDir(options);
  const target = positional[0] ? resolve(positional[0]) : root;
  if (existsSync(target) && statSync(target).isDirectory()) {
    await runPackageCommand(options, target);
    return;
  }
  if (!positional[0]) throw new Error('matrix run requires an entry file or package directory');
  await runActorEntryCommand(options, positional[0]);
}

async function invokeCommand(options: CommandOptions, positional: string[]): Promise<void> {
  const root = workspaceDir(options);
  const mount = positional[0];
  const op = positional[1];
  const payloadRaw = positional[2] ?? '{}';
  if (!mount) throw new Error('matrix invoke requires <mount>');
  if (!op) throw new Error('matrix invoke requires <op>');
  const statusPath = join(matrixDir(root), 'run.json');
  const status = existsSync(statusPath) ? readJsonFile(statusPath) as Partial<RunStatus> : {};
  const host = stringOption(options, 'host') ?? (typeof status.host === 'string' ? status.host : '127.0.0.1');
  const port = numberOption(options, 'port', typeof status.port === 'number' ? status.port : 3920);
  const payload = JSON.parse(payloadRaw);

  const response = await fetch(`http://${host}:${port}/invoke`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ mount, op, payload }),
  });
  const text = await response.text();
  if (!response.ok) throw new Error(`matrix invoke failed (${response.status}): ${text}`);
  console.log(JSON.stringify(JSON.parse(text), null, 2));
}

function printHelp(): void {
  console.log(`matrix SDK CLI

Commands:
  matrix init [dir] [--name name]
  matrix add actor <name> [--dir dir] [--mount mount]
  matrix actor <name> [--dir dir] [--mount mount]
  matrix configure --key key --cloud url --space space [--dir dir] [--runtime-host-bin path]
  matrix run [entry-or-dir] [--mount mount] [--export name] [--port port] [--dir dir]
  matrix invoke <mount> <op> [json] [--port port] [--dir dir]
  matrix up [dir] --runtime-host-bin path [--runtime-host-routing workspace-source|workspace-dist|release]
`);
}

async function upCommand(rawArgs: string[]): Promise<void> {
  const parsed = parseArgs(rawArgs);
  const root = workspaceDir(parsed.options);
  const target = parsed.positional[0] ? resolve(parsed.positional[0]) : root;
  if (!existsSync(target) || !statSync(target).isDirectory()) {
    throw new Error(`matrix up target must be a package directory: ${target}`);
  }
  resolvePackageRunSpec(target, parsed.options);
  const config = readPackageLocalConfig(root);
  const runtimeHostBin = stringOption(parsed.options, 'runtime-host-bin')
    ?? optionalString(config.runtimeHostBin)
    ?? process.env.MATRIX_RUNTIME_HOST_BIN;
  if (!runtimeHostBin) {
    throw new Error('MATRIX_RUNTIME_HOST_BIN_REQUIRED: matrix up requires --runtime-host-bin or MATRIX_RUNTIME_HOST_BIN pointing to a managed host matrix command');
  }
  const routing = stringOption(parsed.options, 'runtime-host-routing')
    ?? optionalString(config.runtimeHostRouting)
    ?? process.env.MATRIX_RUNTIME_HOST_ROUTING
    ?? 'release';
  if (!['release', 'workspace-dist', 'workspace-source'].includes(routing)) {
    throw new Error(`--runtime-host-routing must be release, workspace-dist, or workspace-source, got ${routing}`);
  }

  const runtimeHostPath = isPathLikeCommand(runtimeHostBin) ? resolve(runtimeHostBin) : runtimeHostBin;
  const command = runtimeHostPath.endsWith('.js') || runtimeHostPath.endsWith('.mjs')
    ? process.execPath
    : runtimeHostPath;
  const prefix = command === process.execPath ? [runtimeHostPath] : [];
  const routingArgs = routing === 'workspace-source'
    ? ['--workspace-source']
    : routing === 'workspace-dist'
      ? ['--workspace-dist']
      : [];
  const result = spawnSync(command, [
    ...prefix,
    ...routingArgs,
    'up',
    target,
    ...filterRuntimeHostArgs(rawArgs, parsed.positional.length > 0),
  ], {
    cwd: root,
    stdio: 'inherit',
    env: process.env,
  });
  if (result.error) throw result.error;
  if (result.status !== 0) {
    throw new Error(`matrix up managed host command failed with exit ${result.status ?? 'unknown'}`);
  }
}

function readPackageLocalConfig(root: string): Record<string, unknown> {
  const configPath = join(matrixDir(root), 'config.json');
  if (!existsSync(configPath)) return {};
  const value = readJsonFile(configPath);
  if (!isRecord(value)) throw new Error(`${configPath} must contain a JSON object`);
  return value;
}

function isPathLikeCommand(value: string): boolean {
  return value.startsWith('.')
    || value.startsWith('/')
    || value.includes('/');
}

function filterRuntimeHostArgs(rawArgs: readonly string[], hasExplicitTarget: boolean): string[] {
  const valueOptions = new Set(['--dir', '--runtime-host-bin', '--runtime-host-routing']);
  const filtered: string[] = [];
  let consumedTarget = false;
  for (let i = 0; i < rawArgs.length; i += 1) {
    const arg = rawArgs[i];
    if (arg === '--') {
      filtered.push(...rawArgs.slice(i));
      break;
    }
    const inlineOption = arg.includes('=') ? arg.slice(0, arg.indexOf('=')) : '';
    if (valueOptions.has(arg)) {
      i += 1;
      continue;
    }
    if (inlineOption && valueOptions.has(inlineOption)) {
      continue;
    }
    if (hasExplicitTarget && !arg.startsWith('-') && !consumedTarget) {
      consumedTarget = true;
      continue;
    }
    filtered.push(arg);
  }
  return filtered;
}

export async function main(args: string[]): Promise<void> {
  const [command, subcommand, ...rest] = args;
  if (!command || command === '--help' || command === '-h') {
    printHelp();
    return;
  }

  if (command === 'init') {
    const parsed = parseArgs([subcommand, ...rest].filter((value): value is string => Boolean(value)));
    await initCommand(parsed.options, parsed.positional);
    return;
  }

  if (command === 'add' && subcommand === 'actor') {
    const [name, ...tail] = rest;
    if (!name) throw new Error('matrix add actor requires <name>');
    const parsed = parseArgs(tail);
    await addActorCommand(parsed.options, name);
    return;
  }

  if (command === 'actor') {
    if (!subcommand) throw new Error('matrix actor requires <name>');
    const parsed = parseArgs(rest);
    await addActorCommand(parsed.options, subcommand);
    return;
  }

  if (command === 'configure') {
    const parsed = parseArgs([subcommand, ...rest].filter((value): value is string => Boolean(value)));
    await configureCommand(parsed.options);
    return;
  }

  if (command === 'run') {
    const parsed = parseArgs([subcommand, ...rest].filter((value): value is string => Boolean(value)));
    await runCommand(parsed.options, parsed.positional);
    return;
  }

  if (command === 'invoke') {
    const parsed = parseArgs([subcommand, ...rest].filter((value): value is string => Boolean(value)));
    await invokeCommand(parsed.options, parsed.positional);
    return;
  }

  if (command === 'up') {
    await upCommand([subcommand, ...rest].filter((value): value is string => Boolean(value)));
    return;
  }

  throw new Error(`Unknown command: ${command}`);
}
