import { MatrixActor } from '@open-matrix/core/core/MatrixActor.js';
import { MatrixRuntime } from '@open-matrix/core/runtime/MatrixRuntime.js';
import {
  LoadedPackageActor,
  RuntimeControlActor,
  RUNTIME_CONTROL_SERVICE,
  loadedPackageMount,
  loadedPackageServiceName,
  packageKeyFromPackageRef,
  runtimeControlMount,
  type ILoadedPackageService,
  type IRuntimeControlService,
  type LoadedPackageManifest,
  type LoadedPackageRecord,
  type RuntimeControlState,
} from '@open-matrix/system-runtimes';

type MatrixIntrospect = ReturnType<MatrixActor['on$introspect']>;
type MatrixIntrospectChild = NonNullable<MatrixIntrospect['children']>[number];

export type RunnerRuntimeLifecycleState =
  | 'starting'
  | 'ready'
  | 'live'
  | 'stopping'
  | 'stopped'
  | 'failed';

export interface IRunnerRuntimeControlService {
  readonly runtimeId: string;
  readonly startedAt: string;
  getState(): RunnerRuntimeLifecycleState;
  getRuntime(): MatrixRuntime;
  requestShutdown(reason: string): void;
}

const RUNNER_RUNTIME_CONTROL_SERVICE = 'runner-runtime-control';

export interface IMountedLoadedPackage {
  readonly packageRef: string;
  readonly packageKey: string;
  readonly mount: string;
}

export interface IMountRunnerRuntimeControlOptions {
  readonly runtime: MatrixRuntime;
  readonly runtimeId: string;
  readonly runtimeMount: string;
  readonly controlMount: string;
  readonly startedAt: string;
  getState(): RunnerRuntimeLifecycleState;
  requestShutdown(reason: string): void;
  /**
   * Optional loaded-package descriptor. When provided, a per-loaded-package
   * actor is mounted at `system.runtimes.<runtimeId>.packages.<packageKey>`
   * alongside the runtime control actor. Slice 1a wires this for runtimes
   * that pass a single manifest at startup; multi-package runtimes are
   * future work.
   */
  readonly loadedPackage?: {
    readonly packageRef: string;
    readonly version?: string;
    readonly manifest?: LoadedPackageManifest;
  };
}

export interface IMountRunnerRuntimeControlResult {
  readonly controlMount: string;
  readonly runtimeMount: string;
  readonly loadedPackages: readonly IMountedLoadedPackage[];
}

export class RunnerRuntimeControlActor extends MatrixActor {
  static readonly hasDynamicChildren = true;
  static description = 'Runtime control actor for Host-managed runner processes.';

  static override accepts: Record<string, unknown> = {
    'runtime.status': { description: 'Report runtime lifecycle and inventory status', options: 'object?' },
    'runtime.ready': { description: 'Report whether the runtime is ready for traffic', options: 'object?' },
    'runtime.health': { description: 'Report runtime health', options: 'object?' },
    'runtime.shutdown': { description: 'Request graceful runtime shutdown', reason: 'string?' },
  };

  protected override onIntrospect(payload?: { depth?: 'basic' | 'full' }): MatrixIntrospect {
    const base = super.onIntrospect(payload);
    const service = this._service();
    const currentMount = this.mount;
    const children = service.getRuntime().getAllComponents()
      .map((component): MatrixIntrospectChild | null => {
        const mount = component.mount.trim();
        if (!mount || mount === currentMount) {
          return null;
        }
        return {
          id: mount,
          slot: 'runtime',
          mount,
          componentClass: component.constructor.name || 'MatrixActor',
          accepts: [],
          emits: [],
          tracks: [],
        };
      })
      .filter((child): child is MatrixIntrospectChild => child !== null)
      .sort((left, right) => left.mount.localeCompare(right.mount));
    return {
      ...base,
      children: [...(base.children ?? []), ...children],
    };
  }

  onRuntimeStatus(): Record<string, unknown> {
    const service = this._service();
    const runtime = service.getRuntime();
    return {
      ok: true,
      runtimeId: service.runtimeId,
      pid: process.pid,
      ...(process.env.MATRIX_HOST_HOME ? { hostHome: process.env.MATRIX_HOST_HOME } : {}),
      state: service.getState(),
      startedAt: service.startedAt,
      uptimeMs: Date.now() - Date.parse(service.startedAt),
      actorCount: runtime.getAllComponents().length,
      mounts: runtime.getAllComponents().map((component) => component.mount),
    };
  }

  onRuntimeReady(): Record<string, unknown> {
    const service = this._service();
    const state = service.getState();
    return {
      ok: true,
      runtimeId: service.runtimeId,
      ready: state === 'ready' || state === 'live',
      state,
      pid: process.pid,
      ...(process.env.MATRIX_HOST_HOME ? { hostHome: process.env.MATRIX_HOST_HOME } : {}),
    };
  }

  onRuntimeHealth(): Record<string, unknown> {
    const service = this._service();
    const state = service.getState();
    return {
      ok: true,
      healthy: state === 'ready' || state === 'live',
      runtimeId: service.runtimeId,
      state,
      pid: process.pid,
      ...(process.env.MATRIX_HOST_HOME ? { hostHome: process.env.MATRIX_HOST_HOME } : {}),
    };
  }

  onRuntimeShutdown(payload?: { reason?: string }): Record<string, unknown> {
    const service = this._service();
    const reason = typeof payload?.reason === 'string' && payload.reason.trim().length > 0
      ? payload.reason.trim()
      : 'runtime.shutdown';
    setTimeout(() => service.requestShutdown(reason), 0);
    return {
      ok: true,
      accepted: true,
      runtimeId: service.runtimeId,
      state: 'stopping',
      reason,
    };
  }

  private _service(): IRunnerRuntimeControlService {
    const service = this.context?.getService<IRunnerRuntimeControlService>(RUNNER_RUNTIME_CONTROL_SERVICE);
    if (!service) {
      throw new Error('Runner runtime control service is not registered');
    }
    return service;
  }
}

export async function mountRunnerRuntimeControl(
  options: IMountRunnerRuntimeControlOptions,
): Promise<IMountRunnerRuntimeControlResult> {
  const service: IRunnerRuntimeControlService = {
    runtimeId: options.runtimeId,
    startedAt: options.startedAt,
    getState: options.getState,
    getRuntime: () => options.runtime,
    requestShutdown: options.requestShutdown,
  };
  options.runtime.registerService(RUNNER_RUNTIME_CONTROL_SERVICE, service);

  const canonicalRuntimeMount = runtimeControlMount(options.runtimeId);

  // Build the loaded-package record set (Slice 1a: zero or one package).
  const loadedPackages: LoadedPackageRecord[] = [];
  if (options.loadedPackage && options.loadedPackage.packageRef.trim().length > 0) {
    const packageRef = options.loadedPackage.packageRef.trim();
    const packageKey = packageKeyFromPackageRef(packageRef);
    const record: LoadedPackageRecord = {
      packageRef,
      packageKey,
      mount: loadedPackageMount(options.runtimeId, packageRef),
      state: 'loaded',
      loadedAt: Date.parse(options.startedAt) || Date.now(),
      ...(options.loadedPackage.version ? { version: options.loadedPackage.version } : {}),
      ...(options.loadedPackage.manifest ? { manifest: options.loadedPackage.manifest } : {}),
    };
    loadedPackages.push(record);
  }

  // Register the runtime-control service consumed by RuntimeControlActor.
  const runtimeControlService: IRuntimeControlService = {
    getRuntimeState: (): RuntimeControlState => ({
      runtimeId: options.runtimeId,
      ...(process.env.MATRIX_RUNTIME_DISPLAY_NAME ? { displayName: process.env.MATRIX_RUNTIME_DISPLAY_NAME } : {}),
      ...(loadedPackages[0]?.packageRef ? { packageRef: loadedPackages[0].packageRef } : {}),
      controlMount: canonicalRuntimeMount,
      state: lifecycleStateToControlState(options.getState()),
      health: {
        status: 'ok',
        updatedAt: Date.now(),
      },
      registeredAt: Date.parse(options.startedAt) || Date.now(),
      lastHeartbeat: Date.now(),
    }),
    getLoadedPackages: () => loadedPackages,
    requestShutdown: options.requestShutdown,
    requestReload: async () => ({ accepted: false, reason: 'runtime.reload is not implemented in Slice 1a' }),
  };
  options.runtime.registerService(RUNTIME_CONTROL_SERVICE, runtimeControlService);

  // Mount the canonical Runtime control actor at system.runtimes.<runtimeId>.
  // Slice 1b (control-actor-nav): the legacy `.control` sibling mount has been
  // removed. The canonical runtime control mount is `system.runtimes.<runtimeId>`.
  if (!options.runtime.hasComponent(canonicalRuntimeMount)) {
    await options.runtime.createSupervised(RuntimeControlActor, canonicalRuntimeMount);
  }

  // Mount per-loaded-package actors and register their per-package services.
  const mountedLoadedPackages: IMountedLoadedPackage[] = [];
  for (const record of loadedPackages) {
    const loadedPackageService: ILoadedPackageService = {
      getRuntimeId: () => options.runtimeId,
      getLoadedPackage: () => record,
      requestReload: async () => ({ accepted: false, reason: 'package.reload is not implemented in Slice 1a' }),
    };
    options.runtime.registerService(loadedPackageServiceName(record.packageKey), loadedPackageService);
    if (!options.runtime.hasComponent(record.mount)) {
      await options.runtime.createSupervised(LoadedPackageActor, record.mount);
    }
    mountedLoadedPackages.push({
      packageRef: record.packageRef,
      packageKey: record.packageKey,
      mount: record.mount,
    });
  }

  return {
    controlMount: canonicalRuntimeMount,
    runtimeMount: canonicalRuntimeMount,
    loadedPackages: mountedLoadedPackages,
  };
}

function lifecycleStateToControlState(state: RunnerRuntimeLifecycleState): RuntimeControlState['state'] {
  switch (state) {
    case 'starting':
      return 'starting';
    case 'ready':
    case 'live':
      return 'running';
    case 'stopping':
      return 'stopping';
    case 'stopped':
    case 'failed':
      return 'stopped';
  }
}

export function defaultRunnerRuntimeMount(runtimeId: string): string {
  return `system.runtimes.${runtimeId}`;
}

export function defaultRunnerControlMount(runtimeId: string): string {
  // Slice 1b (control-actor-nav): the canonical runtime control mount is
  // `system.runtimes.<runtimeId>`. The legacy `.control` sibling is gone.
  return defaultRunnerRuntimeMount(runtimeId);
}
