// src/core/ComponentRegistry.ts

import type {
  IComponentRegistry,
  ComponentConstructor,
  ComponentLoader,
} from './IComponentRegistry';

/**
 * Interface for dynamically imported module result.
 */
interface IDynamicModule {
  default?: ComponentConstructor;
  [key: string]: unknown;
}

/**
 * ComponentRegistry - Hierarchical component registry implementation.
 *
 * NO SINGLETONS - this is instantiated per-context/per-composite.
 *
 * Features:
 * - Parent chain inheritance (child shadows parent)
 * - Lazy loading via dynamic imports
 * - Module path resolution
 * - Caching of resolved constructors
 */
export class HeadlessComponentRegistry implements IComponentRegistry {
  private readonly _registrations = new Map<string, ComponentLoader>();
  private readonly _resolved = new Map<string, ComponentConstructor>();
  readonly parent?: IComponentRegistry;

  constructor(parent?: IComponentRegistry) {
    this.parent = parent;
  }

  /**
   * Resolve a tag to a component constructor.
   */
  async resolve(tag: string): Promise<ComponentConstructor | undefined> {
    const normalizedTag = tag.toLowerCase();

    // Check local cache first
    if (this._resolved.has(normalizedTag)) {
      return this._resolved.get(normalizedTag);
    }

    // Check local registrations
    if (this._registrations.has(normalizedTag)) {
      const loader = this._registrations.get(normalizedTag)!;
      const ctor = await this._resolveLoader(loader);
      if (ctor) {
        this._resolved.set(normalizedTag, ctor);
        return ctor;
      }
    }

    // Check parent chain
    if (this.parent) {
      return this.parent.resolve(normalizedTag);
    }

    return undefined;
  }

  /**
   * Register a component loader.
   */
  register(tag: string, loader: ComponentLoader): void {
    const normalizedTag = tag.toLowerCase();
    this._registrations.set(normalizedTag, loader);
    // Clear cache in case it was resolved before
    this._resolved.delete(normalizedTag);
  }

  /**
   * Check if tag is registered anywhere in the chain.
   */
  has(tag: string): boolean {
    const normalizedTag = tag.toLowerCase();
    if (this._registrations.has(normalizedTag)) {
      return true;
    }
    if (this.parent) {
      return this.parent.has(normalizedTag);
    }
    return false;
  }

  /**
   * List tags registered locally.
   */
  localTags(): string[] {
    return Array.from(this._registrations.keys());
  }

  /**
   * List all available tags (local + parent chain).
   */
  allTags(): string[] {
    const tags = new Set<string>(this._registrations.keys());
    if (this.parent) {
      for (const tag of this.parent.allTags()) {
        tags.add(tag);
      }
    }
    return Array.from(tags);
  }

  /**
   * Create a child registry inheriting from this one.
   */
  createChild(): IComponentRegistry {
    return new HeadlessComponentRegistry(this);
  }

  /**
   * Resolve a loader to a constructor.
   */
  private async _resolveLoader(loader: ComponentLoader): Promise<ComponentConstructor | undefined> {
    // Direct constructor
    if (typeof loader === 'function' && 'prototype' in loader && loader.prototype) {
      // Check if it's a class (has prototype with constructor)
      try {
        // If it looks like a class constructor (not an arrow function)
        const loaderProto = loader.prototype as { constructor?: unknown };
        if (loader.toString().startsWith('class') || loaderProto.constructor === loader) {
          return loader as ComponentConstructor;
        }
      } catch {
        // Fall through to try as dynamic import
      }
    }

    // Dynamic import function
    if (typeof loader === 'function') {
      try {
        const result = await (loader as () => Promise<unknown>)();
        // Handle { default: Constructor } or direct Constructor
        if (result && typeof result === 'object' && 'default' in result) {
          const moduleResult = result as { default: unknown };
          if (typeof moduleResult.default === 'function') {
            return moduleResult.default as ComponentConstructor;
          }
        }
        if (typeof result === 'function') {
          return result as ComponentConstructor;
        }
      } catch (err) {
        // eslint-disable-next-line no-console -- kernel component loading failures need direct diagnostics
        console.error(`[ComponentRegistry] Failed to load component:`, err);
        return undefined;
      }
    }

    // Module path string
    if (typeof loader === 'string') {
      try {
        const module = await import(loader) as IDynamicModule;
        if (module.default) {
          return module.default;
        }
        // Try to find first exported class
        for (const key of Object.keys(module)) {
          const exported = module[key];
          if (typeof exported === 'function' && 'prototype' in exported && exported.prototype) {
            return exported as ComponentConstructor;
          }
        }
      } catch (err) {
        // eslint-disable-next-line no-console -- kernel module import failures need direct diagnostics
        console.error(`[ComponentRegistry] Failed to import '${loader}':`, err);
        return undefined;
      }
    }

    return undefined;
  }

  /**
   * Create a root registry with no parent.
   */
  static createRoot(): IComponentRegistry {
    return new HeadlessComponentRegistry();
  }
}
