/**
 * RuntimeDirectory — the canonical client for runtime directory data.
 *
 * `system.runtimes` is the runtime directory authority. Consumers must call
 * `runtimes.list` on it through this adapter; they MUST NOT read runtime
 * directory data from `system.registry` (which carries actor/app/service
 * claims, not runtime presence).
 *
 * The single legitimate `kind: "runtime"` registry claim from RootActor is NOT
 * a runtime directory entry — see `core/src/core/RootActor.ts` for the
 * annotated reason.
 *
 * The adapter returns a thin `RuntimeDirectoryEntry` per runtime. Consumers
 * that need detailed actor/app/service inventory must call:
 *
 *   - `system.runtimes.<runtimeId>` `runtime.inventory`
 *   - `system.runtimes.<runtimeId>.packages.<packageKey>` `package.actors.list`
 *
 * Slice 1b (control-actor-nav workstream).
 */

import { RequestReply, type IRrRequestOptions } from '../engine/remoting/RequestReply';
import type { IMatrixContext } from '../engine/core/IMatrixContext';
import type { HealthSnapshotV1 } from '@open-matrix/contracts/service-registry';
import type {
  RuntimeClaimKind,
  RuntimeInventoryEntryV1,
  RuntimeInventoryMode,
  RuntimeType,
} from '@open-matrix/contracts/runtime-presence';

export const RUNTIME_DIRECTORY_MOUNT = 'system.runtimes';

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

export interface RuntimeDirectoryWebapp {
  readonly appName: string;
  readonly route?: string;
  readonly assetMount?: string;
}

export interface RuntimeDirectoryEntry {
  readonly runtimeId: string;
  readonly runtimeName: string;
  readonly displayName: string;
  readonly runtimeMount: string;
  readonly controlMount: string;
  readonly packageRef: string;
  readonly runtimeRoot: string;
  readonly runtimeWireRoot: string;
  readonly status: string;
  readonly health: HealthSnapshotV1;
  readonly loadedPackages: ReadonlyArray<RuntimeDirectoryLoadedPackage>;
  readonly webapp?: RuntimeDirectoryWebapp;
  readonly metadata?: Record<string, unknown>;

  /**
   * Runtime type from the presence record (daemon | browser | cell | container
   * | leaf | worker | folder). Consumers use this to distinguish runtimes
   * that live on the authority-root NATS (server runtimes — daemon/cell/
   * container/worker/folder/leaf) from runtimes that live on a per-tab
   * realm (browser runtimes). The mount `system.runtimes.<runtimeId>` is
   * NOT a callable subject on the authority root for browser runtimes —
   * the actor lives at `<runtimeRoot>.system.runtimes.<runtimeId>` where
   * runtimeRoot is the tab realm. Consumers that introspect runtimes
   * MUST check this field before issuing RPCs to the runtime mount.
   */
  readonly type: RuntimeType;

  /**
   * Convenience alias for `packageRef`. Some consumer contracts pre-date
   * the rename and read `app`. Same value as `packageRef`.
   */
  readonly app: string;

  /**
   * Convenience alias for `runtimeRoot`. Some consumer contracts pre-date
   * the rename and read `sessionMount`. Same value as `runtimeRoot`.
   *
   * For server runtimes this equals the authority root (the runtime listens
   * directly on that NATS subject prefix). For browser runtimes this is the
   * per-tab realm root (e.g. `SPACE-...DIRECTOR-ROOT.SESSION-...`) — the
   * runtime listens on `<sessionMount>.system.runtimes.<runtimeId>`, NOT
   * `<authorityRoot>.system.runtimes.<runtimeId>`.
   */
  readonly sessionMount: string;

  /** Number of inventory entries advertised by the runtime. */
  readonly actorCount: number;

  /**
   * Inventory entries advertised by the runtime in its presence record.
   * Same shape as `RuntimePresenceRecordV1.inventory`.
   */
  readonly inventory: ReadonlyArray<RuntimeInventoryEntryV1>;

  /**
   * How the runtime exposes its inventory (`embedded` carries it in the
   * presence record; `pull` requires a catalog actor call).
   */
  readonly inventoryMode: RuntimeInventoryMode;

  /** Namespace prefixes the runtime claims to serve. */
  readonly claims: ReadonlyArray<{ readonly prefix: string; readonly kind: RuntimeClaimKind }>;

  /** Optional catalog mount when `inventoryMode === 'pull'`. */
  readonly catalogMount?: string;

  /** Presence TTL in milliseconds. */
  readonly heartbeatTtlMs: number;

  /** Authority root the runtime is published under, if known. */
  readonly authorityRoot?: string;
}

export interface RuntimeDirectoryListResult {
  readonly ok: boolean;
  readonly count: number;
  readonly runtimes: ReadonlyArray<RuntimeDirectoryEntry>;
}

export interface IRuntimeDirectoryReadOptions {
  readonly mount?: string;
  readonly timeoutMs?: number;
  readonly includeStale?: boolean;
}

/**
 * List runtimes from the runtime directory (`system.runtimes`). No fallback.
 */
export async function listRuntimeDirectory(
  context: IMatrixContext,
  options: IRuntimeDirectoryReadOptions = {},
): Promise<RuntimeDirectoryListResult> {
  const mount = (options.mount ?? RUNTIME_DIRECTORY_MOUNT).trim();
  if (!mount) {
    throw new Error('listRuntimeDirectory requires a non-empty mount (default: system.runtimes)');
  }
  const requestOptions: IRrRequestOptions = {
    timeoutMs: options.timeoutMs ?? 5_000,
  };
  const raw = await RequestReply.execute<
    Record<string, unknown>,
    { ok?: boolean; count?: number; runtimes?: unknown[] }
  >(
    context,
    mount,
    'runtimes.list',
    { includeStale: options.includeStale === true },
    requestOptions,
  );
  if (raw?.ok !== true || !Array.isArray(raw.runtimes)) {
    return { ok: false, count: 0, runtimes: [] };
  }
  const runtimes = raw.runtimes
    .map(toRuntimeDirectoryEntry)
    .filter((entry): entry is RuntimeDirectoryEntry => entry !== null);
  return {
    ok: true,
    count: runtimes.length,
    runtimes,
  };
}

/**
 * Count runtimes currently in the directory. Convenience wrapper.
 */
export async function countRuntimeDirectory(
  context: IMatrixContext,
  options: IRuntimeDirectoryReadOptions = {},
): Promise<number> {
  const result = await listRuntimeDirectory(context, options);
  return result.count;
}

function toRuntimeDirectoryEntry(value: unknown): RuntimeDirectoryEntry | null {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return null;
  }
  const record = value as Record<string, unknown>;
  const runtimeId = readString(record.runtimeId);
  if (!runtimeId) {
    return null;
  }
  const runtimeMount = readString(record.runtimeMount) || `system.runtimes.${runtimeId}`;
  const controlMount = readString(record.controlMount) || runtimeMount;
  const packageRef = readString(record.packageRef) || readString(record.app) || '';
  const runtimeName = readString(record.runtimeName) || readString(record.displayName) || runtimeId;
  const displayName = readString(record.displayName) || runtimeName;
  const runtimeRoot = readString(record.runtimeRoot) || readString(record.sessionMount) || '';
  const runtimeWireRoot = readString(record.runtimeWireRoot) || runtimeRoot;
  const status = readString(record.status) || readString(record.state) || 'unknown';
  const health = readHealth(record.health);
  const loadedPackages = readLoadedPackages(record.loadedPackages);
  const webapp = readWebapp(record.webapp);
  const metadata = readRecord(record.metadata);
  const type = readRuntimeType(record.type) ?? readRuntimeType(record.runtimeType) ?? 'daemon';
  const inventory = readInventory(record.inventory);
  const inventoryMode = readInventoryMode(record.inventoryMode) ?? 'embedded';
  const claims = readClaims(record.claims);
  const catalogMount = readString(record.catalogMount);
  const heartbeatTtlMs = readNumber(record.heartbeatTtlMs) ?? 0;
  const authorityRoot = readString(record.authorityRoot);
  const actorCount = readNumber(record.actorCount) ?? inventory.length;
  return {
    runtimeId,
    runtimeName,
    displayName,
    runtimeMount,
    controlMount,
    packageRef,
    runtimeRoot,
    runtimeWireRoot,
    status,
    health,
    loadedPackages,
    ...(webapp ? { webapp } : {}),
    ...(metadata && Object.keys(metadata).length > 0 ? { metadata } : {}),
    type,
    app: packageRef,
    sessionMount: runtimeRoot,
    actorCount,
    inventory,
    inventoryMode,
    claims,
    ...(catalogMount ? { catalogMount } : {}),
    heartbeatTtlMs,
    ...(authorityRoot ? { authorityRoot } : {}),
  };
}

function readRuntimeType(value: unknown): RuntimeType | null {
  const raw = readString(value);
  if (raw === 'daemon' || raw === 'browser' || raw === 'cell' || raw === 'container'
    || raw === 'leaf' || raw === 'worker' || raw === 'folder') {
    return raw;
  }
  return null;
}

function readInventory(value: unknown): RuntimeInventoryEntryV1[] {
  if (!Array.isArray(value)) {
    return [];
  }
  const out: RuntimeInventoryEntryV1[] = [];
  for (const item of value) {
    if (!item || typeof item !== 'object' || Array.isArray(item)) continue;
    const record = item as Record<string, unknown>;
    const mount = readString(record.mount);
    const runtimeId = readString(record.runtimeId);
    const claimKind = readClaimKind(record.claimKind);
    if (!mount || !runtimeId || !claimKind) continue;
    out.push({
      mount,
      runtimeId,
      claimKind,
      ...(readString(record.type) ? { type: readString(record.type) } : {}),
      ...(readSurface(record.surface) ? { surface: readSurface(record.surface)! } : {}),
      ...(readString(record.description) ? { description: readString(record.description) } : {}),
    });
  }
  return out;
}

function readInventoryMode(value: unknown): RuntimeInventoryMode | null {
  const raw = readString(value);
  if (raw === 'embedded' || raw === 'pull') return raw;
  return null;
}

function readClaims(value: unknown): Array<{ prefix: string; kind: RuntimeClaimKind }> {
  if (!Array.isArray(value)) return [];
  const out: Array<{ prefix: string; kind: RuntimeClaimKind }> = [];
  for (const item of value) {
    if (!item || typeof item !== 'object' || Array.isArray(item)) continue;
    const record = item as Record<string, unknown>;
    const prefix = readString(record.prefix);
    const kind = readClaimKind(record.kind);
    if (prefix && kind) out.push({ prefix, kind });
  }
  return out;
}

function readClaimKind(value: unknown): RuntimeClaimKind | null {
  const raw = readString(value);
  if (raw === 'authoritative' || raw === 'proxy' || raw === 'mirror'
    || raw === 'local-only' || raw === 'pool-member') return raw;
  return null;
}

function readSurface(value: unknown): RuntimeInventoryEntryV1['surface'] | null {
  const raw = readString(value);
  if (raw === 'headless' || raw === 'dom' || raw === 'webapp') return raw;
  return null;
}

function readNumber(value: unknown): number | undefined {
  return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
}

function readLoadedPackages(value: unknown): RuntimeDirectoryLoadedPackage[] {
  if (!Array.isArray(value)) {
    return [];
  }
  const out: RuntimeDirectoryLoadedPackage[] = [];
  for (const item of value) {
    if (!item || typeof item !== 'object' || Array.isArray(item)) continue;
    const record = item as Record<string, unknown>;
    const packageRef = readString(record.packageRef);
    const packageKey = readString(record.packageKey);
    const mount = readString(record.mount);
    if (!packageRef || !packageKey || !mount) continue;
    out.push({ packageRef, packageKey, mount });
  }
  return out;
}

function readWebapp(value: unknown): RuntimeDirectoryWebapp | undefined {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return undefined;
  }
  const record = value as Record<string, unknown>;
  const appName = readString(record.appName);
  if (!appName) {
    return undefined;
  }
  const route = readString(record.route);
  const assetMount = readString(record.assetMount);
  return {
    appName,
    ...(route ? { route } : {}),
    ...(assetMount ? { assetMount } : {}),
  };
}

function readHealth(value: unknown): HealthSnapshotV1 {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return { status: 'unknown', updatedAt: 0 };
  }
  const record = value as Record<string, unknown>;
  const status = readString(record.status) || 'unknown';
  const updatedAt = typeof record.updatedAt === 'number' && Number.isFinite(record.updatedAt) ? record.updatedAt : 0;
  const reason = readString(record.reason);
  return {
    status: status as HealthSnapshotV1['status'],
    updatedAt,
    ...(reason ? { reason } : {}),
  };
}

function readRecord(value: unknown): Record<string, unknown> | undefined {
  return value && typeof value === 'object' && !Array.isArray(value)
    ? (value as Record<string, unknown>)
    : undefined;
}

function readString(value: unknown): string {
  return typeof value === 'string' ? value.trim() : '';
}
