/**
 * Module Registry — single source of truth for which modules are active at
 * runtime. Resolves the user's `modules.config.ts` against module manifests
 * and produces the effective active-set after applying dependency rules.
 *
 * Library code should NEVER read `modules.config.ts` directly — always go
 * through `isModuleEnabled(id)` or `getActiveModules()` so dependency rules
 * (and the "auto-disable on missing dep" behaviour) are honoured.
 */

import type { ModuleId, ModuleManifest, ModulesConfig } from './types';
import { modules as userConfig } from '@root/modules.config';

import { briefingModule } from '@/modules/briefing/module';
import { editorModule } from '@/modules/editor/module';
import { recipientsModule } from '@/modules/recipients/module';
import { sendingModule } from '@/modules/sending/module';
import { schedulerModule } from '@/modules/scheduler/module';
import { statsModule } from '@/modules/stats/module';
import { heroImageModule } from '@/modules/hero-image/module';
import { spamCheckModule } from '@/modules/spam-check/module';
import { sectionTemplatesModule } from '@/modules/section-templates/module';
import { triggerTemplatesModule } from '@/modules/trigger-templates/module';

const ALL_MANIFESTS: Record<ModuleId, ModuleManifest> = {
  briefing: briefingModule,
  editor: editorModule,
  recipients: recipientsModule,
  sending: sendingModule,
  scheduler: schedulerModule,
  stats: statsModule,
  heroImage: heroImageModule,
  spamCheck: spamCheckModule,
  sectionTemplates: sectionTemplatesModule,
  triggerTemplates: triggerTemplatesModule,
};

let cachedEffective: ModulesConfig | null = null;
let cachedWarnings: string[] = [];

/**
 * Compute the effective module config, applying dependency rules.
 * Memoized — config is static at runtime (changes require a redeploy).
 */
function computeEffective(): { config: ModulesConfig; warnings: string[] } {
  if (cachedEffective) return { config: cachedEffective, warnings: cachedWarnings };

  const effective: ModulesConfig = { ...userConfig };
  const warnings: string[] = [];

  // Iterate to fixpoint: disabling a module can cascade to its dependents.
  let changed = true;
  while (changed) {
    changed = false;
    for (const [id, manifest] of Object.entries(ALL_MANIFESTS) as Array<
      [ModuleId, ModuleManifest]
    >) {
      if (!effective[id]) continue;
      for (const dep of manifest.dependsOn ?? []) {
        if (!effective[dep]) {
          effective[id] = false;
          warnings.push(
            `Module "${id}" disabled — it depends on "${dep}" which is not enabled.`,
          );
          changed = true;
        }
      }
    }
  }

  cachedEffective = effective;
  cachedWarnings = warnings;
  return { config: effective, warnings };
}

/** Is the module active in the effective configuration? */
export function isModuleEnabled(id: ModuleId): boolean {
  return computeEffective().config[id] === true;
}

/** Return the manifest for a module, regardless of whether it's enabled. */
export function getModuleManifest(id: ModuleId): ModuleManifest {
  return ALL_MANIFESTS[id];
}

/** All modules that are currently enabled, sorted by tabPosition. */
export function getActiveModules(): ModuleManifest[] {
  const { config } = computeEffective();
  return Object.entries(ALL_MANIFESTS)
    .filter(([id]) => config[id as ModuleId])
    .map(([, manifest]) => manifest)
    .sort((a, b) => a.tabPosition - b.tabPosition);
}

/** All known modules, enabled or not — useful for admin UI listings. */
export function getAllManifests(): ModuleManifest[] {
  return Object.values(ALL_MANIFESTS).sort((a, b) => a.tabPosition - b.tabPosition);
}

/** Diagnostic warnings produced during dependency resolution. */
export function getModuleWarnings(): string[] {
  computeEffective();
  return [...cachedWarnings];
}
