import {
  Injectable,
  type Signal,
  computed,
  inject,
  signal
} from '@angular/core';
import {
  RegisteredPlugin,
  type PluginLoadOrderResult,
  type PluginRuntimeState
} from '../../domain/models/plugin-runtime.models';
import { resolvePluginLoadOrder } from '../../domain/logic/plugin-dependency-resolver.logic';
import { validateTojuPluginManifest } from '../../domain/logic/plugin-manifest-validation.logic';
import { PluginDesktopStateService } from './plugin-desktop-state.service';

const STORAGE_KEY_PLUGIN_REGISTRY_STATE = 'metoyou_plugin_registry_state';

@Injectable({ providedIn: 'root' })
export class PluginRegistryService {
  readonly entries: Signal<RegisteredPlugin[]>;
  readonly enabledEntries: Signal<RegisteredPlugin[]>;
  readonly loadOrder: Signal<PluginLoadOrderResult>;

  private readonly desktopState = inject(PluginDesktopStateService);
  private readonly entriesSignal = signal<RegisteredPlugin[]>([]);
  private disabledPluginIds = new Set<string>();

  constructor() {
    this.entries = this.entriesSignal.asReadonly();
    this.enabledEntries = computed(() => this.entries().filter((entry) => entry.enabled));
    this.loadOrder = computed<PluginLoadOrderResult>(() =>
      resolvePluginLoadOrder(this.entries().map((entry) => ({ enabled: entry.enabled, manifest: entry.manifest })))
    );

    void this.loadRegistryState();
  }

  clear(): void {
    this.entriesSignal.set([]);
  }

  registerManifest(manifestValue: unknown, sourcePath?: string): RegisteredPlugin {
    const validation = validateTojuPluginManifest(manifestValue);

    if (!validation.manifest) {
      throw new Error(validation.issues.map((issue) => `${issue.path}: ${issue.message}`).join('\n'));
    }

    const existingIndex = this.entries().findIndex((entry) => entry.manifest.id === validation.manifest?.id);
    const entry: RegisteredPlugin = {
      enabled: !this.disabledPluginIds.has(validation.manifest.id),
      manifest: validation.manifest,
      sourcePath,
      state: validation.valid ? 'validated' : 'blocked',
      validationIssues: validation.issues
    };

    if (existingIndex >= 0) {
      this.entriesSignal.update((entries) => entries.map((candidate, index) => index === existingIndex ? entry : candidate));
    } else {
      this.entriesSignal.update((entries) => [...entries, entry]);
    }

    this.syncLoadState();
    return entry;
  }

  setEnabled(pluginId: string, enabled: boolean): void {
    if (enabled) {
      this.disabledPluginIds.delete(pluginId);
    } else {
      this.disabledPluginIds.add(pluginId);
    }

    this.entriesSignal.update((entries) => entries.map((entry) => entry.manifest.id === pluginId
      ? {
        ...entry,
        enabled,
        state: enabled ? entry.state === 'disabled' ? 'validated' : entry.state : 'disabled'
      }
      : entry));

    this.syncLoadState();
    void this.saveRegistryState();
  }

  unregister(pluginId: string): void {
    this.entriesSignal.update((entries) => entries.filter((entry) => entry.manifest.id !== pluginId));
    this.syncLoadState();
  }

  setState(pluginId: string, state: PluginRuntimeState): void {
    this.entriesSignal.update((entries) => entries.map((entry) => entry.manifest.id === pluginId
      ? { ...entry, error: undefined, state }
      : entry));
  }

  setFailed(pluginId: string, error: string): void {
    this.entriesSignal.update((entries) => entries.map((entry) => entry.manifest.id === pluginId
      ? { ...entry, error, state: 'failed' }
      : entry));
  }

  find(pluginId: string): RegisteredPlugin | undefined {
    return this.entries().find((entry) => entry.manifest.id === pluginId);
  }

  private syncLoadState(): void {
    const loadOrder = this.loadOrder();
    const blockedIds = new Set(loadOrder.blocked.map((blocker) => blocker.pluginId));
    const loadIndexes = new Map(loadOrder.ordered.map((manifest, index) => [manifest.id, index]));

    this.entriesSignal.update((entries) => entries.map((entry) => {
      const loadIndex = loadIndexes.get(entry.manifest.id);

      if (!entry.enabled) {
        return { ...entry, loadIndex: undefined, state: 'disabled' };
      }

      if (blockedIds.has(entry.manifest.id)) {
        return { ...entry, loadIndex: undefined, state: 'blocked' };
      }

      return {
        ...entry,
        loadIndex,
        state: loadIndex === undefined ? entry.state : 'ready'
      };
    }));
  }

  private async loadRegistryState(): Promise<void> {
    const state = await this.desktopState.readJson<{ disabledPluginIds?: string[] }>(STORAGE_KEY_PLUGIN_REGISTRY_STATE, {});

    this.disabledPluginIds = new Set(
      Array.isArray(state.disabledPluginIds)
        ? state.disabledPluginIds.filter((pluginId): pluginId is string => typeof pluginId === 'string')
        : []
    );

    this.entriesSignal.update((entries) => entries.map((entry) => ({
      ...entry,
      enabled: !this.disabledPluginIds.has(entry.manifest.id)
    })));

    this.syncLoadState();
  }

  private async saveRegistryState(): Promise<void> {
    await this.desktopState.writeJson(STORAGE_KEY_PLUGIN_REGISTRY_STATE, {
      disabledPluginIds: Array.from(this.disabledPluginIds).sort()
    });
  }
}
