import { MatrixActor } from '@open-matrix/core';
import { MatrixRuntime } from '@open-matrix/core';

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

export type BrowserRuntimeLifecycleState = 'live' | 'closing';

export interface IBrowserRuntimeControlService {
  readonly runtimeId: string;
  readonly runtimeMount: string;
  readonly startedAt: string;
  getState(): BrowserRuntimeLifecycleState;
  getRuntime(): MatrixRuntime;
  getInventory(): readonly IBrowserRuntimeControlInventoryEntry[];
}

export interface IMountBrowserRuntimeControlOptions {
  readonly runtime: MatrixRuntime;
  readonly runtimeId: string;
  readonly runtimeMount: string;
  readonly controlMount: string;
  readonly startedAt: string;
  getState(): BrowserRuntimeLifecycleState;
  getInventory?: () => readonly IBrowserRuntimeControlInventoryEntry[];
}

export interface IBrowserRuntimeControlInventoryEntry {
  readonly mount: string;
  readonly type?: string;
}

const BROWSER_RUNTIME_CONTROL_SERVICE = 'browser-runtime-control';

export class BrowserRuntimeControlActor extends MatrixActor {
  static readonly hasDynamicChildren = true;
  static description = 'Runtime control actor for a browser tab runtime.';

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

  protected override onIntrospect(payload?: { depth?: 'basic' | 'full' }): ReturnType<MatrixActor['onIntrospect']> {
    const base = super.onIntrospect(payload);
    const service = this._service();
    // The browser runtime actor extends the base introspect shape with
    // browser-specific runtime metadata. The supertype declares a strict
    // shape; cast through unknown so the extension stays type-safe at
    // the call site of `base` while satisfying the override signature.
    const extended: ReturnType<MatrixActor['onIntrospect']> = {
      ...base,
      children: [
        ...(base.children ?? []),
        ...this._directRuntimeChildren(service),
      ],
    };
    return Object.assign(extended, {
      type: 'runtime',
      runtimeId: service.runtimeId,
      runtimeType: 'browser',
      state: service.getState(),
      startedAt: service.startedAt,
      uptimeMs: Date.now() - Date.parse(service.startedAt),
    });
  }

  onRuntimeStatus(): Record<string, unknown> {
    const service = this._service();
    const runtime = service.getRuntime();
    const mounts = runtime.getAllComponents().map((component) => component.mount);
    return {
      ok: true,
      runtimeId: service.runtimeId,
      runtimeMount: service.runtimeMount,
      type: 'browser',
      state: service.getState(),
      startedAt: service.startedAt,
      uptimeMs: Date.now() - Date.parse(service.startedAt),
      actorCount: mounts.length,
      mounts,
      rootActors: this._directRuntimeChildren(service).map((child) => ({
        mount: child.mount,
        componentClass: child.componentClass,
      })),
    };
  }

  onRuntimeReady(): Record<string, unknown> {
    const state = this._service().getState();
    return {
      ok: true,
      ready: state === 'live',
      state,
    };
  }

  onRuntimeHealth(): Record<string, unknown> {
    const service = this._service();
    const state = service.getState();
    return {
      ok: true,
      healthy: state === 'live',
      runtimeId: service.runtimeId,
      state,
    };
  }

  private _directRuntimeChildren(service: IBrowserRuntimeControlService): MatrixIntrospectChild[] {
    const runtimeMount = service.runtimeMount.trim();
    const currentMount = this.mount.trim();
    const seen = new Set<string>();
    const children: MatrixIntrospectChild[] = [];

    for (const entry of service.getInventory()) {
      const actualMount = typeof entry.mount === 'string' ? entry.mount.trim() : '';
      const childMount = this._directChildMount(runtimeMount, currentMount, actualMount);
      if (!childMount || seen.has(childMount)) {
        continue;
      }
      seen.add(childMount);
      children.push({
        id: childMount,
        slot: 'runtime',
        mount: childMount,
        componentClass: entry.type || 'RuntimeInventoryActor',
        accepts: [],
        emits: [],
        tracks: [],
      });
    }

    for (const component of service.getRuntime().getAllComponents()) {
      const actualMount = component.mount.trim();
      const childMount = this._directChildMount(runtimeMount, currentMount, actualMount);
      if (!childMount || seen.has(childMount)) {
        continue;
      }
      seen.add(childMount);
      children.push({
        id: childMount,
        slot: 'runtime',
        mount: childMount,
        componentClass: component.constructor.name || 'MatrixActor',
        accepts: [],
        emits: [],
        tracks: [],
      });
    }

    return children.sort((left, right) => left.mount.localeCompare(right.mount));
  }

  private _directChildMount(runtimeMount: string, currentMount: string, actualMount: string): string | null {
    if (!actualMount || actualMount === currentMount) {
      return null;
    }
    if (runtimeMount && actualMount.startsWith(`${runtimeMount}.`)) {
      const localMount = actualMount.slice(runtimeMount.length + 1);
      return localMount && !localMount.includes('.') ? localMount : null;
    }
    if (!actualMount.includes('.')) {
      return actualMount;
    }
    return null;
  }

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

export async function mountBrowserRuntimeControl(
  options: IMountBrowserRuntimeControlOptions,
): Promise<string> {
  const service: IBrowserRuntimeControlService = {
    runtimeId: options.runtimeId,
    runtimeMount: options.runtimeMount,
    startedAt: options.startedAt,
    getRuntime: () => options.runtime,
    getState: options.getState,
    getInventory: () => options.getInventory?.() ?? [],
  };
  options.runtime.registerService(BROWSER_RUNTIME_CONTROL_SERVICE, service);
  if (!options.runtime.hasComponent(options.runtimeMount)) {
    await options.runtime.createSupervised(BrowserRuntimeControlActor, options.runtimeMount);
  }
  if (options.controlMount !== options.runtimeMount && !options.runtime.hasComponent(options.controlMount)) {
    await options.runtime.createSupervised(BrowserRuntimeControlActor, options.controlMount);
  }
  return options.controlMount;
}
