import { validateRuntimeManifestSemantics } from "@covel/shared";
import type { EventBus } from "@covel/events";
import {
  createPluginRegistry,
  discoverPluginsMulti,
  loadPluginManifest,
  loadPluginSummary,
  type ParsedPluginMd,
  type PluginDiscoveryResult,
  type PluginRegistry,
} from "@covel/plugin-loader";

export interface DiscoverAndRegisterPluginsConfig {
  readonly pluginsDir: string;
  readonly pluginsDirs?: readonly string[];
  readonly eventBus: EventBus;
}

export interface DiscoverAndRegisterPluginsResult {
  readonly registry: PluginRegistry;
  readonly discoveryMap: Map<string, PluginDiscoveryResult>;
  readonly manifestCache: Map<string, readonly ParsedPluginMd[]>;
}

export async function discoverAndRegisterPlugins(
  config: DiscoverAndRegisterPluginsConfig,
): Promise<DiscoverAndRegisterPluginsResult> {
  const registry = createPluginRegistry({ eventBus: config.eventBus });
  const pluginsDirs =
    config.pluginsDirs && config.pluginsDirs.length > 0
      ? config.pluginsDirs
      : [config.pluginsDir];
  const discoveries = await discoverPluginsMulti(
    pluginsDirs,
    (id, kept, skipped) => {
      console.warn(
        `[bootstrap] Plugin id collision: "${id}" — keeping ${kept}, ignoring ${skipped}`,
      );
    },
  );

  const discoveryMap = new Map<string, PluginDiscoveryResult>();
  const manifestCache = new Map<string, readonly ParsedPluginMd[]>();

  for (const discovery of discoveries) {
    try {
      const summary = await loadPluginSummary(discovery);
      const manifests = await loadPluginManifest(discovery);

      discoveryMap.set(discovery.id, discovery);
      manifestCache.set(discovery.id, manifests);

      for (const parsed of manifests) {
        for (const diagnostic of validateRuntimeManifestSemantics(
          parsed.manifest,
        )) {
          console.warn(
            `[bootstrap] ${diagnostic.message} Remove one of the two to keep the scheduler intent clear.`,
          );
        }
      }

      // Register with all manifests (first is primary for getActiveRuntimes).
      // `source` comes from `discoverPluginsMulti`: bundled first-dir plugins
      // keep their prefix-derived trust, everything else is clamped to
      // `'community'` — so a third-party plugin can't self-assign a higher
      // trust level by forging `pluginType: core-plugin` in its frontmatter.
      registry.register({
        id: discovery.id,
        summary,
        rootPath: discovery.rootPath,
        manifest: manifests[0],
        manifests,
        loadedRuntimes: new Map(),
        status: "registered",
        ...(discovery.source ? { source: discovery.source } : {}),
      });
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      console.error(
        `[bootstrap] Failed to load plugin ${discovery.id}:`,
        message,
      );

      // Register as error so the frontend can display it without aborting boot.
      registry.register({
        id: discovery.id,
        summary: {
          id: discovery.id,
          name: discovery.id,
          description: "",
          pluginType: "plugin",
          runtimeCount: 0,
        },
        loadedRuntimes: new Map(),
        status: "error",
        error: message,
        ...(discovery.source ? { source: discovery.source } : {}),
      });
    }
  }

  return { registry, discoveryMap, manifestCache };
}
