import {
  Injectable,
  Signal,
  computed,
  signal
} from '@angular/core';
import type {
  PluginApiActionContribution,
  PluginApiChannelSectionContribution,
  PluginApiDomMountRequest,
  PluginApiEmbedRendererContribution,
  PluginApiPageContribution,
  PluginApiPanelContribution,
  PluginApiSettingsPageContribution,
  PluginApiUiContributionMap,
  TojuPluginDisposable
} from '../../domain/models/plugin-api.models';

type ContributionKind = keyof PluginApiUiContributionMap;

export interface PluginUiContributionRecord<TContribution> {
  contribution: TContribution;
  contributionKey: string;
  id: string;
  pluginId: string;
}

export interface PluginUiConflictDiagnostic {
  contributionId: string;
  kind: ContributionKind;
  pluginIds: string[];
}

interface PluginDomMountRecord {
  element: HTMLElement;
  id: string;
  pluginId: string;
}

@Injectable({ providedIn: 'root' })
export class PluginUiRegistryService {
  readonly appPages = this.createContributionSignal('appPages');
  readonly appPageRecords = this.createContributionRecordSignal('appPages');
  readonly channelSections = this.createContributionSignal('channelSections');
  readonly channelSectionRecords = this.createContributionRecordSignal('channelSections');
  readonly composerActions = this.createContributionSignal('composerActions');
  readonly composerActionRecords = this.createContributionRecordSignal('composerActions');
  readonly embeds = this.createContributionSignal('embeds');
  readonly embedRecords = this.createContributionRecordSignal('embeds');
  readonly profileActions = this.createContributionSignal('profileActions');
  readonly profileActionRecords = this.createContributionRecordSignal('profileActions');
  readonly settingsPages = this.createContributionSignal('settingsPages');
  readonly settingsPageRecords = this.createContributionRecordSignal('settingsPages');
  readonly sidePanels = this.createContributionSignal('sidePanels');
  readonly sidePanelRecords = this.createContributionRecordSignal('sidePanels');
  readonly toolbarActions = this.createContributionSignal('toolbarActions');
  readonly toolbarActionRecords = this.createContributionRecordSignal('toolbarActions');
  readonly conflicts = computed(() => this.collectConflicts());
  private readonly domMounts = new Map<string, PluginDomMountRecord>();

  private readonly contributionsSignal = signal<{
    appPages: PluginUiContributionRecord<PluginApiPageContribution>[];
    channelSections: PluginUiContributionRecord<PluginApiChannelSectionContribution>[];
    composerActions: PluginUiContributionRecord<PluginApiActionContribution>[];
    embeds: PluginUiContributionRecord<PluginApiEmbedRendererContribution>[];
    profileActions: PluginUiContributionRecord<PluginApiActionContribution>[];
    settingsPages: PluginUiContributionRecord<PluginApiSettingsPageContribution>[];
    sidePanels: PluginUiContributionRecord<PluginApiPanelContribution>[];
    toolbarActions: PluginUiContributionRecord<PluginApiActionContribution>[];
  }>({
    appPages: [],
    channelSections: [],
    composerActions: [],
    embeds: [],
    profileActions: [],
    settingsPages: [],
    sidePanels: [],
    toolbarActions: []
  });

  registerAppPage(pluginId: string, id: string, contribution: PluginApiPageContribution): TojuPluginDisposable {
    return this.register('appPages', pluginId, id, contribution);
  }

  registerChannelSection(pluginId: string, id: string, contribution: PluginApiChannelSectionContribution): TojuPluginDisposable {
    return this.register('channelSections', pluginId, id, contribution);
  }

  registerComposerAction(pluginId: string, id: string, contribution: PluginApiActionContribution): TojuPluginDisposable {
    return this.register('composerActions', pluginId, id, contribution);
  }

  registerEmbedRenderer(pluginId: string, id: string, contribution: PluginApiEmbedRendererContribution): TojuPluginDisposable {
    return this.register('embeds', pluginId, id, contribution);
  }

  mountElement(pluginId: string, id: string, request: PluginApiDomMountRequest): TojuPluginDisposable {
    const mountId = `${pluginId}:${id}`;
    const target = this.resolveMountTarget(request.target);

    if (!target) {
      throw new Error(`Plugin mount target not found: ${typeof request.target === 'string' ? request.target : request.target.tagName}`);
    }

    this.unmountElement(mountId);
    request.element.dataset['pluginOwner'] = pluginId;
    request.element.dataset['pluginMountId'] = mountId;
    target.insertAdjacentElement(request.position ?? 'beforeend', request.element);
    this.domMounts.set(mountId, { element: request.element, id: mountId, pluginId });

    return {
      dispose: () => this.unmountElement(mountId)
    };
  }

  registerProfileAction(pluginId: string, id: string, contribution: PluginApiActionContribution): TojuPluginDisposable {
    return this.register('profileActions', pluginId, id, contribution);
  }

  registerSettingsPage(pluginId: string, id: string, contribution: PluginApiSettingsPageContribution): TojuPluginDisposable {
    return this.register('settingsPages', pluginId, id, contribution);
  }

  registerSidePanel(pluginId: string, id: string, contribution: PluginApiPanelContribution): TojuPluginDisposable {
    return this.register('sidePanels', pluginId, id, contribution);
  }

  registerToolbarAction(pluginId: string, id: string, contribution: PluginApiActionContribution): TojuPluginDisposable {
    return this.register('toolbarActions', pluginId, id, contribution);
  }

  unregisterPlugin(pluginId: string): void {
    for (const mount of this.domMounts.values()) {
      if (mount.pluginId === pluginId) {
        this.unmountElement(mount.id);
      }
    }

    this.contributionsSignal.update((current) => ({
      appPages: current.appPages.filter((entry) => entry.pluginId !== pluginId),
      channelSections: current.channelSections.filter((entry) => entry.pluginId !== pluginId),
      composerActions: current.composerActions.filter((entry) => entry.pluginId !== pluginId),
      embeds: current.embeds.filter((entry) => entry.pluginId !== pluginId),
      profileActions: current.profileActions.filter((entry) => entry.pluginId !== pluginId),
      settingsPages: current.settingsPages.filter((entry) => entry.pluginId !== pluginId),
      sidePanels: current.sidePanels.filter((entry) => entry.pluginId !== pluginId),
      toolbarActions: current.toolbarActions.filter((entry) => entry.pluginId !== pluginId)
    }));
  }

  private register<TKind extends ContributionKind>(
    kind: TKind,
    pluginId: string,
    id: string,
    contribution: PluginApiUiContributionMap[TKind][number]
  ): TojuPluginDisposable {
    const contributionId = `${pluginId}:${id}`;

    this.contributionsSignal.update((current) => ({
      ...current,
      [kind]: [
        ...current[kind].filter((entry) => entry.id !== contributionId),
        {
          contribution,
          contributionKey: id,
          id: contributionId,
          pluginId
        }
      ]
    }));

    return {
      dispose: () => this.unregister(kind, contributionId)
    };
  }

  private unregister(kind: ContributionKind, contributionId: string): void {
    this.contributionsSignal.update((current) => ({
      ...current,
      [kind]: current[kind].filter((entry) => entry.id !== contributionId)
    }));
  }

  private resolveMountTarget(target: Element | string): Element | null {
    return typeof target === 'string'
      ? document.querySelector(target)
      : target;
  }

  private unmountElement(mountId: string): void {
    const mount = this.domMounts.get(mountId);

    if (!mount) {
      return;
    }

    mount.element.remove();
    this.domMounts.delete(mountId);
  }

  private createContributionSignal<TKind extends ContributionKind>(kind: TKind): Signal<PluginApiUiContributionMap[TKind]> {
    return computed(() => this.contributionsSignal()[kind].map((entry) => entry.contribution) as PluginApiUiContributionMap[TKind]);
  }

  private createContributionRecordSignal<TKind extends ContributionKind>(
    kind: TKind
  ): Signal<PluginUiContributionRecord<PluginApiUiContributionMap[TKind][number]>[]> {
    return computed(() => this.contributionsSignal()[kind] as PluginUiContributionRecord<PluginApiUiContributionMap[TKind][number]>[]);
  }

  private collectConflicts(): PluginUiConflictDiagnostic[] {
    const conflicts: PluginUiConflictDiagnostic[] = [];

    for (const kind of Object.keys(this.contributionsSignal()) as ContributionKind[]) {
      const byKey = new Map<string, Set<string>>();

      for (const entry of this.contributionsSignal()[kind]) {
        const pluginIds = byKey.get(entry.contributionKey) ?? new Set<string>();

        pluginIds.add(entry.pluginId);
        byKey.set(entry.contributionKey, pluginIds);
      }

      for (const [contributionId, pluginIds] of byKey.entries()) {
        if (pluginIds.size > 1) {
          conflicts.push({
            contributionId,
            kind,
            pluginIds: Array.from(pluginIds).sort()
          });
        }
      }
    }

    return conflicts;
  }
}
