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

export interface StorybookSeededVariant {
  name: string;
  provenance: "storybook";
}

export interface StorybookUsageExample {
  code: string;
  provenance: "storybook";
}

export interface StorybookEnrichmentSeed {
  description?: string;
  variants: StorybookSeededVariant[];
  usageExamples: StorybookUsageExample[];
}

export interface StorybookSeedProvenance {
  description?: "storybook" | "human" | "ai";
  variants?: "storybook" | "human" | "ai";
  usageExamples?: "storybook" | "human" | "ai";
}

export interface StorybookSeedableEnrichment {
  description?: string;
  variants?: StorybookSeededVariant[];
  usageExamples?: StorybookUsageExample[];
  dos?: readonly string[];
  provenance?: StorybookSeedProvenance;
}

export interface ApplyStorybookEnrichmentSeedResult {
  enrichment: StorybookSeedableEnrichment;
  changed: boolean;
}

export function projectStoryEvidenceToEnrichmentSeed(args: {
  componentName: string;
  storyEvidence: readonly StoryEvidence[];
}): StorybookEnrichmentSeed {
  const variants = new Map<string, StorybookSeededVariant>();
  const usageExamples: StorybookUsageExample[] = [];
  let description: string | undefined;

  for (const evidence of args.storyEvidence) {
    if (!description && evidence.autodocsDescription) {
      description = evidence.autodocsDescription;
    }
    for (const name of evidence.storyNames) {
      variants.set(name, { name, provenance: "storybook" });
    }
    for (const story of evidence.stories) {
      if (!story.args) continue;
      usageExamples.push({
        code: synthesizeUsageExample(args.componentName, evidence.metaArgs, story.args),
        provenance: "storybook",
      });
    }
  }

  return {
    ...(description ? { description } : {}),
    variants: [...variants.values()],
    usageExamples: dedupeUsageExamples(usageExamples),
  };
}

export function applyStorybookEnrichmentSeed(args: {
  existing: StorybookSeedableEnrichment;
  seed: StorybookEnrichmentSeed;
}): ApplyStorybookEnrichmentSeedResult {
  const before = stableJson(args.existing);
  const enrichment: StorybookSeedableEnrichment = {
    ...args.existing,
    ...(args.existing.dos ? { dos: [...args.existing.dos] } : {}),
    provenance: { ...(args.existing.provenance ?? {}) },
  };

  if (args.seed.description && shouldSeedText(enrichment)) {
    enrichment.description = args.seed.description;
    enrichment.provenance = {
      ...(enrichment.provenance ?? {}),
      description: "storybook",
    };
  }

  if (
    args.seed.variants.length > 0 &&
    shouldSeedList(enrichment, "variants", enrichment.variants)
  ) {
    enrichment.variants = args.seed.variants.map((variant) => ({ ...variant }));
    enrichment.provenance = {
      ...(enrichment.provenance ?? {}),
      variants: "storybook",
    };
  }

  if (
    args.seed.usageExamples.length > 0 &&
    shouldSeedList(enrichment, "usageExamples", enrichment.usageExamples)
  ) {
    enrichment.usageExamples = args.seed.usageExamples.map((example) => ({
      ...example,
    }));
    enrichment.provenance = {
      ...(enrichment.provenance ?? {}),
      usageExamples: "storybook",
    };
  }

  if (Object.keys(enrichment.provenance ?? {}).length === 0) {
    delete enrichment.provenance;
  }

  return {
    enrichment,
    changed: before !== stableJson(enrichment),
  };
}

function shouldSeedText(enrichment: StorybookSeedableEnrichment): boolean {
  if (!enrichment.description) return true;
  return enrichment.provenance?.description === "storybook";
}

function shouldSeedList<T>(
  enrichment: StorybookSeedableEnrichment,
  field: "variants" | "usageExamples",
  value: readonly T[] | undefined
): boolean {
  if (!value || value.length === 0) return true;
  return enrichment.provenance?.[field] === "storybook";
}

function synthesizeUsageExample(
  componentName: string,
  metaArgs: Record<string, unknown>,
  storyArgs: Record<string, unknown>
): string {
  const storyEntries = Object.entries(storyArgs).sort(([a], [b]) => a.localeCompare(b));
  const storyKeys = new Set(storyEntries.map(([key]) => key));
  const metaEntries = Object.entries(metaArgs)
    .filter(([key]) => !storyKeys.has(key))
    .sort(([a], [b]) => a.localeCompare(b));
  const props = [...storyEntries, ...metaEntries]
    .filter(([, value]) => isRenderablePropValue(value))
    .map(([key, value]) => renderProp(key, value))
    .join(" ");
  return props ? `<${componentName} ${props} />` : `<${componentName} />`;
}

function renderProp(key: string, value: unknown): string {
  if (value === true) return key;
  if (typeof value === "string") return `${key}="${escapeAttribute(value)}"`;
  return `${key}={${JSON.stringify(value)}}`;
}

function isRenderablePropValue(value: unknown): boolean {
  return (
    value !== undefined &&
    value !== false &&
    (typeof value === "string" || typeof value === "number" || typeof value === "boolean")
  );
}

function escapeAttribute(value: string): string {
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
}

function dedupeUsageExamples(examples: StorybookUsageExample[]): StorybookUsageExample[] {
  const seen = new Set<string>();
  return examples.filter((example) => {
    if (seen.has(example.code)) return false;
    seen.add(example.code);
    return true;
  });
}

function stableJson(value: unknown): string {
  if (Array.isArray(value)) {
    return `[${value.map(stableJson).join(",")}]`;
  }
  if (value && typeof value === "object") {
    return `{${Object.entries(value)
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`)
      .join(",")}}`;
  }
  return JSON.stringify(value);
}
