import { z } from "zod";
import type { Detector, DetectorContext } from "./detector.js";

export type DetectorModuleRef = {
  module: string;
  export: string;
};

const detectorExportSchema = z.object({
  id: z.string().min(1),
  version: z.string().min(1),
  defaultLane: z.enum(["blocking", "blocking-eligible", "advisory"]),
  evaluate: z.custom<Detector<unknown, unknown>["evaluate"]>(
    (value) => typeof value === "function",
    "evaluate must be a function",
  ),
});

export class DetectorLoadError extends Error {
  readonly module: string;
  readonly exportName: string;
  readonly reason: string;
  readonly cause?: unknown;

  constructor(ref: DetectorModuleRef, reason: string, cause?: unknown) {
    super(`Failed to load detector ${ref.export} from ${ref.module}: ${reason}`);
    this.name = "DetectorLoadError";
    this.module = ref.module;
    this.exportName = ref.export;
    this.reason = reason;
    if (cause !== undefined) {
      this.cause = cause;
    }
  }
}

function formatZodIssues(error: z.ZodError): string {
  return error.issues
    .map((issue) => {
      const path = issue.path.length > 0 ? issue.path.join(".") : "export";
      return `${path}: ${issue.message}`;
    })
    .join("; ");
}

function validateDetectorExport(
  exported: unknown,
  ref: DetectorModuleRef,
): Detector<unknown, unknown> {
  const parsed = detectorExportSchema.safeParse(exported);
  if (!parsed.success) {
    throw new DetectorLoadError(ref, formatZodIssues(parsed.error), parsed.error);
  }

  return exported as Detector<unknown, unknown>;
}

export async function loadDetectors(
  refs: readonly DetectorModuleRef[],
): Promise<Detector<unknown, unknown>[]> {
  const detectors: Detector<unknown, unknown>[] = [];

  for (const ref of refs) {
    let importedModule: Record<string, unknown>;
    try {
      importedModule = (await import(ref.module)) as Record<string, unknown>;
    } catch (error) {
      throw new DetectorLoadError(ref, "module import failed", error);
    }

    const exported = importedModule[ref.export];
    if (exported === undefined) {
      throw new DetectorLoadError(ref, `export "${ref.export}" is missing from module`);
    }

    detectors.push(validateDetectorExport(exported, ref));
  }

  return detectors;
}

export type { DetectorContext };
