import path from "node:path";

import type { StoryFacts } from "./types.js";
import type { StoryEvidence, UniversalComponentFact } from "../ucf/types.js";

export interface UnresolvedStoryEvidence {
  file: string;
  importSpecifier: string;
  importName: string;
  reason: "external-import" | "component-not-found";
}

export interface MergeStoryFactsIntoUcfsResult {
  ucfs: UniversalComponentFact[];
  unresolved: UnresolvedStoryEvidence[];
}

export function mergeStoryFactsIntoUcfs(args: {
  ucfs: readonly UniversalComponentFact[];
  stories: readonly StoryFacts[];
}): MergeStoryFactsIntoUcfsResult {
  const byResolvedImport = indexUcfsByResolvedImport(args.ucfs);
  const evidenceByUcfId = new Map<string, StoryEvidence[]>();
  const unresolved: UnresolvedStoryEvidence[] = [];

  for (const facts of args.stories) {
    if (facts.rung === "skipped" || !facts.componentImport) continue;

    const { specifier, name } = facts.componentImport;
    if (isExternalImport(specifier)) {
      unresolved.push({
        file: facts.file,
        importSpecifier: specifier,
        importName: name,
        reason: "external-import",
      });
      continue;
    }

    const resolved = resolveRelativeImportCandidates(facts.file, specifier)
      .map((candidate) => byResolvedImport.get(importKey(candidate, name)))
      .find((ucf): ucf is UniversalComponentFact => Boolean(ucf));

    if (!resolved) {
      unresolved.push({
        file: facts.file,
        importSpecifier: specifier,
        importName: name,
        reason: "component-not-found",
      });
      continue;
    }

    const evidence = evidenceForFacts(facts);
    evidenceByUcfId.set(resolved.id, [...(evidenceByUcfId.get(resolved.id) ?? []), evidence]);
  }

  return {
    ucfs: args.ucfs.map((ucf) => {
      const evidence = evidenceByUcfId.get(ucf.id);
      if (!evidence?.length) return { ...ucf };
      return {
        ...ucf,
        storyEvidence: [...(ucf.storyEvidence ?? []), ...evidence],
      };
    }),
    unresolved,
  };
}

function evidenceForFacts(facts: StoryFacts): StoryEvidence {
  return {
    source: "storybook",
    ...(facts.title ? { title: facts.title } : {}),
    componentImport: facts.componentImport!,
    storyCount: facts.stories.length,
    storyRefs: facts.stories.map((story) => story.ref),
    storyNames: facts.stories.map((story) => story.name),
    storyFilePaths: [facts.file],
    tags: facts.tags,
    metaArgs: facts.metaArgs,
    stories: facts.stories,
    ...(facts.autodocsDescription ? { autodocsDescription: facts.autodocsDescription } : {}),
  };
}

function indexUcfsByResolvedImport(
  ucfs: readonly UniversalComponentFact[]
): Map<string, UniversalComponentFact> {
  const byImport = new Map<string, UniversalComponentFact>();

  for (const ucf of ucfs) {
    for (const name of exportNamesForUcf(ucf)) {
      for (const candidate of sourcePathCandidates(ucf.filePath)) {
        byImport.set(importKey(candidate, name), ucf);
      }
    }
  }

  return byImport;
}

function exportNamesForUcf(ucf: UniversalComponentFact): string[] {
  const names = new Set<string>([ucf.componentName]);
  for (const entry of ucf.exports) {
    if (entry.localName) names.add(entry.localName);
    if (entry.exportedAs !== "default") names.add(entry.exportedAs);
  }
  return [...names];
}

function sourcePathCandidates(filePath: string): string[] {
  const normalized = normalize(filePath);
  const extless = normalized.replace(/\.(tsx?|jsx?|vue|svelte)$/, "");
  return [...new Set([normalized, extless])];
}

function resolveRelativeImportCandidates(storyFile: string, specifier: string): string[] {
  const base = path.posix.normalize(
    path.posix.join(path.posix.dirname(normalize(storyFile)), specifier)
  );
  return [
    base,
    `${base}.tsx`,
    `${base}.ts`,
    `${base}.jsx`,
    `${base}.js`,
    `${base}/index.tsx`,
    `${base}/index.ts`,
    `${base}/index.jsx`,
    `${base}/index.js`,
  ].map(normalize);
}

function isExternalImport(specifier: string): boolean {
  return !specifier.startsWith(".") && !specifier.startsWith("/");
}

function importKey(filePath: string, name: string): string {
  return `${normalize(filePath)}\0${name}`;
}

function normalize(filePath: string): string {
  return filePath.replace(/\\/g, "/").replace(/^\.\//, "");
}
