// src/flow/ShellRegistry.ts
//
// ShellRegistry - Maps componentClass -> ShellInfo for rendering.
//
// ═══════════════════════════════════════════════════════════════════════════
// EXPERT PANEL DECISION (2024-12): COLLECTOR PATTERN FOR DECORATOR TIMING
// ═══════════════════════════════════════════════════════════════════════════
//
// TypeScript decorators execute at MODULE LOAD TIME, before any DI exists.
// This is a language constraint that cannot be worked around.
//
// SOLUTION: The Collector Pattern (Joe Duffy, Robert C. Martin)
//
// Joe Duffy (Midori OS):
//   "Separate 'collection' from 'consumption'. Decorators collect to a
//    pending queue; runtime consumes into an instance-based registry."
//
// Anders Hejlsberg (TypeScript Creator):
//   "TypeScript decorators are a compile-time transformation. They MUST
//    execute during module evaluation. A pending queue is the right pattern."
//
// Robert C. Martin (Clean Code):
//   "The Collector pattern: decorators are collectors, the component is the
//    consumer. The static queue is write-only; the instance is the authority."
//
// ARCHITECTURE:
// - `_pendingRegistrations` (module-level): WRITE-ONLY queue for decorators
// - `ShellRegistryComponent` (instance): The REAL registry, instance-based
// - On bootstrap: Component drains pending queue into instance storage
// - After bootstrap: Component is the single source of truth
//
// Mark Miller (Capability Security):
//   "The pending queue is NOT a registry - it's a staging area. The real
//    authority lives in the component instance. No ambient lookup."
//
// This achieves:
// - Minimal module-level state (write-only pending queue)
// - Instance-based registry (test isolation, no ambient authority)
// - Clean separation of collection and consumption
// ═══════════════════════════════════════════════════════════════════════════

import { MatrixActor } from '../core/MatrixActor';
import type { IMatrixContext } from '../engine/core/IMatrixContext';

/**
 * Constructor type for shell classes (typically HTMLElement subclasses).
 */
type ShellConstructor = new (...args: unknown[]) => HTMLElement;

/**
 * Shell info for rendering a component type.
 */
export interface IShellInfo {
  /** Custom element tag name (e.g., "fractal-tree") */
  shellTag: string;
  /** Module URL for dynamic import */
  moduleUrl?: string;
}

/**
 * ShellRegistryComponent - MatrixActor service for shell metadata.
 *
 * This is the REAL registry. The module-level `_pendingRegistrations` is just
 * a staging area for decorator-time registrations.
 *
 * ═══════════════════════════════════════════════════════════════════════════
 * COLLECTOR PATTERN (Expert Panel 2024-12)
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * Joe Duffy (Midori OS):
 *   "The component is the consumer. On bootstrap, it drains the pending queue
 *    and becomes the single source of truth. No bidirectional sync needed."
 *
 * Robert C. Martin (Clean Code):
 *   "Instance owns the data. Static queue is consumed once, then ignored."
 *
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * Mount at: system.registry.shell
 *
 * Accepts:
 *   - register: { componentClass: string, info: ShellInfo }
 *   - lookup: { componentClass: string } -> ShellInfo | null
 *   - list: {} -> string[]
 *   - has: { componentClass: string } -> boolean
 *
 * Emits:
 *   - registered: { componentClass: string, info: ShellInfo }
 *   - unregistered: { componentClass: string }
 */
export class ShellRegistryComponent extends MatrixActor {
  static accepts = {
    register: { componentClass: 'string', info: 'ShellInfo' },
    lookup: { componentClass: 'string' },
    list: {},
    has: { componentClass: 'string' },
    unregister: { componentClass: 'string' },
  };

  static emits = {
    registered: { componentClass: 'string', info: 'ShellInfo' },
    unregistered: { componentClass: 'string' },
  };

  /**
   * Instance-based registry storage.
   * This is the REAL registry - the single source of truth after bootstrap.
   */
  private _registry = new Map<string, IShellInfo>();

  /**
   * Handle 'register' command.
   *
   * NOTE: After bootstrap, all registrations go through the component.
   * We do NOT write back to the static pending queue - that's write-only for decorators.
   */
  onRegister(params: { componentClass: string; info: IShellInfo }): void {
    const { componentClass, info } = params;
    this._registry.set(componentClass, info);
    this.emit('registered', { componentClass, info });
  }

  /**
   * Handle 'lookup' command.
   */
  onLookup(params: { componentClass: string }): IShellInfo | null {
    return this._registry.get(params.componentClass) ?? null;
  }

  /**
   * Handle 'list' command.
   */
  onList(): string[] {
    return Array.from(this._registry.keys());
  }

  /**
   * Handle 'has' command.
   */
  onHas(params: { componentClass: string }): boolean {
    return this._registry.has(params.componentClass);
  }

  /**
   * Handle 'unregister' command.
   *
   * NOTE: We do NOT modify the static pending queue - it's write-only for decorators.
   */
  onUnregister(params: { componentClass: string }): boolean {
    const existed = this._registry.delete(params.componentClass);
    if (existed) {
      this.emit('unregistered', { componentClass: params.componentClass });
    }
    return existed;
  }

  /**
   * Bootstrap: Drain the pending registrations queue.
   *
   * Joe Duffy (Midori OS):
   *   "Consume once, then the instance is the authority. The pending queue
   *    is just a staging area for decorator-time registrations."
   *
   * After this runs, the component's `_registry` is the single source of truth.
   */
  protected async onBootstrap(): Promise<void> {
    // Drain pending registrations into instance storage
    for (const [key, value] of ShellRegistry._pendingRegistrations) {
      this._registry.set(key, value);
    }
    // NOTE: We do NOT clear the pending queue - other components may need it
    // if multiple ShellRegistryComponents are created (e.g., in tests)
  }
}

/**
 * ShellRegistry - Static utility class for decorator-time registration.
 *
 * ═══════════════════════════════════════════════════════════════════════════
 * COLLECTOR PATTERN (Expert Panel 2024-12)
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * This class provides a WRITE-ONLY staging area for decorator registrations.
 * The REAL registry is `ShellRegistryComponent` (instance-based).
 *
 * Joe Duffy (Midori OS):
 *   "The pending queue is write-only for decorators. The component drains it
 *    on bootstrap and becomes the authority. Clean separation."
 *
 * Mark Miller (Capability Security):
 *   "The static lookup methods are for pre-bootstrap scenarios only. After
 *    bootstrap, use the component. The pending queue is NOT the registry."
 *
 * Robert C. Martin (Clean Code):
 *   "Naming matters: _pendingRegistrations makes clear this is a queue,
 *    not a registry. The component owns the real data."
 *
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * Usage:
 * ```typescript
 * // At module load (via decorator) - writes to pending queue
 * @RegisterShell("fractal-tree")
 * class FractalTree extends MatrixActor { ... }
 *
 * // After bootstrap - query via component (the real registry)
 * const info = await RequestReply.execute(ctx, shellRegistryMount, 'lookup', { componentClass: 'FractalTree' });
 * ```
 *
 * DEPRECATED PATTERN (pre-bootstrap lookup):
 * ```typescript
 * // This works but is deprecated - use the component after bootstrap
 * const info = ShellRegistry.lookup('FractalTree');
 * ```
 */
export class ShellRegistry {
  /**
   * Pending registrations queue.
   *
   * WRITE-ONLY for decorators. ShellRegistryComponent drains this on bootstrap.
   * After bootstrap, the component's instance `_registry` is the authority.
   *
   * @internal - Use ShellRegistryComponent for lookups after bootstrap
   */
  static _pendingRegistrations = new Map<string, IShellInfo>();

  // NOTE: _instance singleton REMOVED per expert panel review (2024-12)
  // Callers should hold their own reference to mounted components
  // Mark Miller: "Explicit capability passing, not ambient authority"

  /**
   * Register shell info for a component type.
   *
   * Called by decorators at module load time. Writes to the pending queue.
   * The ShellRegistryComponent will drain this queue on bootstrap.
   */
  static register(componentClass: string, info: IShellInfo): void {
    ShellRegistry._pendingRegistrations.set(componentClass, info);
  }

  /**
   * Look up shell info from the pending queue.
   *
   * @deprecated After bootstrap, use ShellRegistryComponent via Matrix protocol.
   * This only checks the pending queue, not the component's registry.
   */
  static lookup(componentClass: string): IShellInfo | undefined {
    return ShellRegistry._pendingRegistrations.get(componentClass);
  }

  /**
   * Check if a component type has pending shell info.
   *
   * @deprecated After bootstrap, use ShellRegistryComponent via Matrix protocol.
   */
  static has(componentClass: string): boolean {
    return ShellRegistry._pendingRegistrations.has(componentClass);
  }

  /**
   * Get all pending component types.
   *
   * @deprecated After bootstrap, use ShellRegistryComponent via Matrix protocol.
   */
  static getRegisteredTypes(): string[] {
    return Array.from(ShellRegistry._pendingRegistrations.keys());
  }

  /**
   * Clear pending registrations (mainly for testing).
   *
   * NOTE: This only clears the pending queue. Any mounted
   * ShellRegistryComponent instances have their own copy of the data.
   */
  static clear(): void {
    ShellRegistry._pendingRegistrations.clear();
  }

  /**
   * Mount a ShellRegistryComponent as a Matrix service.
   *
   * ═══════════════════════════════════════════════════════════════════════════
   * EXPERT PANEL REVIEW (2024-12): NO SINGLETON CACHING
   * ═══════════════════════════════════════════════════════════════════════════
   *
   * Robert C. Martin (Clean Code):
   *   "Each call creates a fresh component. The caller is responsible for
   *    storing the reference. No hidden getInstance() magic."
   *
   * Mark Miller (Capability Security):
   *   "The returned component IS the capability. Pass it explicitly to
   *    consumers who need it. No ambient authority."
   * ═══════════════════════════════════════════════════════════════════════════
   *
   * Call this during application bootstrap:
   * ```typescript
   * const registryContext = rootContext.derive('system').derive('registry').derive('shell');
   * const shellRegistry = await ShellRegistry.mount(registryContext);
   * // Store shellRegistry reference - no getInstance() available
   * ```
   *
   * After mounting, the registry is queryable via Matrix protocol:
   * ```typescript
   * const info = await RequestReply.execute(ctx, 'system.registry.shell', 'lookup', { componentClass: 'FractalTree' });
   * ```
   */
  static async mount(context: IMatrixContext): Promise<ShellRegistryComponent> {
    // NOTE: No singleton caching - each call creates a new component
    // Callers should store their own reference to the returned component
    const component = new ShellRegistryComponent();
    await component.initialize(context, 'shell');
    // eslint-disable-next-line no-console -- flow infrastructure, no actor context
    console.log(`[ShellRegistry] Mounted at ${context.mount}`);
    return component;
  }

  // NOTE: getInstance() REMOVED per expert panel review (2024-12)
  // Mark Miller: "Ambient authority via getInstance() is a capability leak.
  // Callers should hold explicit references to mounted components."

  /**
   * Register shell info from a Shell class.
   * Convenience method that extracts info from static members.
   */
  static registerShell(ShellClass: {
    getActorClass?: () => { name: string };
    shellTag?: string;
    moduleUrl?: string;
    name: string;
  }): void {
    const componentClass =
      ShellClass.getActorClass?.()?.name ?? ShellClass.name.replace('Shell', '');
    const shellTag = ShellClass.shellTag ?? toKebabCase(ShellClass.name.replace('Shell', ''));

    ShellRegistry.register(componentClass, {
      shellTag,
      moduleUrl: ShellClass.moduleUrl,
    });
  }
}

/**
 * Convert PascalCase to kebab-case.
 * FractalTree -> fractal-tree
 */
function toKebabCase(str: string): string {
  return str
    .replace(/([A-Z])/g, '-$1')
    .toLowerCase()
    .replace(/^-/, '');
}

/**
 * Decorator for auto-registering Shell classes.
 *
 * Two usage patterns:
 *
 * 1. Parameter-less (auto-derives from class):
 * ```typescript
 * @RegisterShell
 * class FractalTreeShell extends MatrixActorHtmlElement {
 *   static getActorClass() { return FractalTree; }
 *   static moduleUrl = import.meta.url;
 * }
 * ```
 *
 * 2. With explicit parameters (preferred for components):
 * ```typescript
 * @RegisterShell("fractal-tree", import.meta.url)
 * class FractalTree extends MatrixActor { ... }
 * ```
 */
export function RegisterShell<T extends ShellConstructor>(target: T): T;
export function RegisterShell(
  shellTag: string,
  moduleUrl?: string
): <T extends ShellConstructor>(target: T) => T;
export function RegisterShell<T extends ShellConstructor>(
  targetOrShellTag: T | string,
  moduleUrl?: string
): T | ((target: T) => T) {
  // Check if called with parameters: @RegisterShell("tag", "url")
  if (typeof targetOrShellTag === 'string') {
    const shellTag = targetOrShellTag;
    return function (target: T): T {
      ShellRegistry.register(target.name, { shellTag, moduleUrl });
      return target;
    };
  }

  // Called without parameters: @RegisterShell
  const target = targetOrShellTag;
  ShellRegistry.registerShell(target);
  return target;
}
