import { ApplicationError } from "../errors";
import { IPresenterRegistry, PresenterKey } from "./presenter-registry.interface";
import { IPresenter } from "./presenter.interface";

export class InMemoryPresenterRegistry implements IPresenterRegistry {
  private registry: Map<string, IPresenter<any, any>> = new Map();

  private _normalizeKey(key: PresenterKey): PresenterKey {
    return {
      ...key,
      format: key.format ?? "JSON", // 👈 valor por defecto
    };
  }

  /**
   * 🔹 Construye la clave única para el registro.
   */
  private _buildKey(key: PresenterKey): string {
    const { resource, projection, format, version, locale } = key;
    return [
      resource.toLowerCase(),
      projection.toLowerCase(),
      format!.toLowerCase(),
      version ?? "latest",
      locale ?? "default",
    ].join("::");
  }

  private _registerPresenter<TSource, TOutput>(
    key: PresenterKey,
    presenter: IPresenter<TSource, TOutput>
  ): void {
    const exactKey = this._buildKey(this._normalizeKey(key));
    this.registry.set(exactKey, presenter);
  }

  getPresenter<TSource, TOutput>(key: PresenterKey): IPresenter<TSource, TOutput> {
    const exactKey = this._buildKey(this._normalizeKey(key));

    // 1) Intentar clave exacta
    if (this.registry.has(exactKey)) {
      return this.registry.get(exactKey)!;
    }

    // 2) Fallback por versión: si no se indicó, buscar la última registrada
    if (key.version === undefined) {
      const candidates = [...this.registry.keys()].filter((k) =>
        k.startsWith(this._buildKey({ ...key, version: undefined, locale: undefined }))
      );

      if (candidates.length > 0) {
        const latest = candidates.sort().pop()!; // simplificación: versión más alta lexicográficamente
        return this.registry.get(latest)!;
      }
    }

    // 3) Fallback por locale: intentar sin locale si no se encuentra exacto
    if (key.locale) {
      const withoutLocale = this._buildKey({ ...key, locale: undefined });
      if (this.registry.has(withoutLocale)) {
        return this.registry.get(withoutLocale)!;
      }
    }

    if (!this.registry.has(exactKey)) {
      throw new ApplicationError(
        `[InMemoryPresenterRegistry] Presenter not registered: ${key.resource}::${key.projection}`
      );
    }

    throw new ApplicationError(
      `[InMemoryPresenterRegistry] Presenter not registered: ${key.resource}::${key.projection}`
    );
  }

  registerPresenter<TSource, TOutput>(
    key: PresenterKey,
    presenter: IPresenter<TSource, TOutput>
  ): this {
    this._registerPresenter(key, presenter);
    return this;
  }
  /**
   * ✅ Registro en lote de presentadores.
   */
  registerPresenters(
    presenters: Array<{ key: PresenterKey; presenter: IPresenter<any, any> }>
  ): this {
    presenters.forEach(({ key, presenter }) => this._registerPresenter(key, presenter));
    return this;
  }
}
