// src/runtime/ComponentRegistry.ts

import type { IRuntimeLogger } from './IRuntimeLogger';
import type { IHostCustomElementRegistry } from './IHostCustomElementRegistry';
import type { IRuntimeComponent } from './IRuntimeComponent';

/**
 * Interface for custom element prototype with lifecycle callbacks.
 */
interface CustomElementPrototype {
  connectedCallback?: () => void;
  disconnectedCallback?: () => void;
  attributeChangedCallback?: (name: string, oldValue: string | null, newValue: string | null) => void;
  adoptedCallback?: () => void;
}

export class ShellElementRegistry {
  private readonly components = new Map<string, CustomElementConstructor>();
  private readonly instances = new Map<string, Set<IRuntimeComponent>>();
  private totalInstanceCount = 0;

  constructor(
    private readonly registry: IHostCustomElementRegistry,
    private readonly logger: IRuntimeLogger
  ) {}

  register(tag: string, constructor: CustomElementConstructor): void {
    if (this.components.has(tag)) {
      this.logger.warn(`Component '${tag}' already registered, overriding`);
    }

    this.components.set(tag, constructor);

    try {
      this.registry.define(tag, constructor);
    } catch (error) {
      this.logger.warn(`Failed to register '${tag}' with platform registry: ${error}`);
    }

    this.logger.debug(`Component registered: ${tag}`);
  }

  isRegistered(tag: string): boolean {
    return this.components.has(tag) || this.registry.get(tag) !== undefined;
  }

  /**
   * Resolve the constructor for a registered component.
   * Prefer the explicit registry in this class, but fall back to the
   * host customElements registry if needed.
   */
  getConstructor(tag: string): CustomElementConstructor | undefined {
    return this.components.get(tag) ?? this.registry.get(tag);
  }

  trackInstance(
    tag: string,
    element: HTMLElement,
    scope: { context: { mount: string } },
    mountPath: string
  ): void {
    let instances = this.instances.get(tag);
    if (!instances) {
      instances = new Set();
      this.instances.set(tag, instances);
    }

    const component: IRuntimeComponent = {
      tag,
      element,
      context: { mount: mountPath }
    };

    instances.add(component);
    this.totalInstanceCount++;
    this.logger.debug(`Instance tracked: ${tag} at ${mountPath}`);
  }

  untrackInstance(element: HTMLElement): void {
    for (const [tag, instances] of this.instances.entries()) {
      for (const instance of instances) {
        if (instance.element === element) {
          instances.delete(instance);
          if (instances.size === 0) {
            this.instances.delete(tag);
          }
          this.logger.debug(`Instance untracked: ${tag} at ${instance.context.mount}`);
          return;
        }
      }
    }
  }

  clearInstances(): void {
    this.instances.clear();
    this.logger.debug('All instances cleared');
  }

  getInstances(tag: string): IRuntimeComponent[] {
    return Array.from(this.instances.get(tag) || []);
  }

  getAllInstances(): IRuntimeComponent[] {
    const allInstances: IRuntimeComponent[] = [];
    for (const instances of this.instances.values()) {
      allInstances.push(...instances);
    }
    return allInstances;
  }

  listRegisteredTags(): string[] {
    return Array.from(this.components.keys()).sort((left, right) => left.localeCompare(right));
  }

  validate(tag: string, constructor: CustomElementConstructor): string[] {
    const errors: string[] = [];

    // Check that tag name is valid
    if (!tag.includes('-')) {
      errors.push(`Custom element tag '${tag}' must contain a hyphen`);
    }

    // Check that basic requirements are met
    if (!constructor.prototype) {
      errors.push(`Component '${tag}' constructor has no prototype`);
    }

    // Check that key element lifecycle methods exist
    const proto = constructor.prototype as CustomElementPrototype;
    if (typeof proto.connectedCallback !== 'function') {
      errors.push(`Component '${tag}' missing connectedCallback method`);
    }

    if (typeof proto.disconnectedCallback !== 'function') {
      errors.push(`Component '${tag}' missing disconnectedCallback method`);
    }

    return errors;
  }

  getStats() {
    return {
      registeredComponents: this.components.size,
      activeInstances: this.getAllInstances().length,
      totalInstancesCreated: this.totalInstanceCount
    };
  }

  async whenDefined(tag: string): Promise<void> {
    if (this.components.has(tag)) return Promise.resolve();
    return this.registry.whenDefined(tag);
  }
}
