/**
 * Visual Regression Configuration
 *
 * Components are auto-discovered from the docs site sidebar.
 * Only special actions (click/hover to open overlays) need explicit config.
 */

export interface ComponentAction {
  type: "click" | "hover";
  selector: string;
  waitAfter?: number;
}

export interface DiscoveredComponent {
  id: string;
  name: string;
  url: string;
}

/**
 * Special actions for components that need interactions to show all states.
 * Key is the component slug (e.g., "dialog", "dropdown").
 * These components get TWO screenshots: default state + opened state.
 */
export const COMPONENT_ACTIONS: Record<string, ComponentAction> = {
  dialog: { type: "click", selector: "main button", waitAfter: 400 },
  dropdown: { type: "click", selector: "main button", waitAfter: 300 },
  popover: { type: "click", selector: "main button", waitAfter: 300 },
  tooltip: { type: "hover", selector: "main button", waitAfter: 500 },
  select: { type: "click", selector: "main button", waitAfter: 300 },
  combobox: { type: "click", selector: "main input", waitAfter: 300 },
  toast: { type: "click", selector: "main button", waitAfter: 500 },
  collapsible: { type: "click", selector: "main button", waitAfter: 300 },
  "command-palette": { type: "click", selector: "main button", waitAfter: 300 },
  "date-range-picker": {
    type: "click",
    selector: "main button",
    waitAfter: 300,
  },
};

/**
 * Discover all component pages from the docs site sidebar.
 */
export async function discoverComponents(
  baseUrl: string,
): Promise<DiscoveredComponent[]> {
  const response = await fetch(baseUrl);
  const html = await response.text();

  const componentLinks: DiscoveredComponent[] = [];
  const seen = new Set<string>();

  const linkRegex = /href="(\/components\/([^"]+))"/g;
  let match;

  while ((match = linkRegex.exec(html)) !== null) {
    const url = match[1];
    const slug = match[2];

    if (seen.has(slug)) continue;
    seen.add(slug);

    const name = slug
      .split("-")
      .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
      .join(" ");

    componentLinks.push({ id: slug, name, url });
  }

  return componentLinks;
}

/**
 * Representative components used as canaries for broad-impact changes.
 * Covers a simple input, a complex overlay, and a layout-heavy component
 * to catch regressions without screenshotting every page.
 */
export const CANARY_COMPONENTS = ["button", "dialog", "select"];

/**
 * Patterns that indicate broad visual impact across all components.
 * Changes to these files trigger a canary regression instead of granular checks.
 */
const FULL_REGRESSION_PATTERNS: RegExp[] = [
  // Shared styles and theming
  /packages\/kumo\/src\/styles\//,
  /packages\/kumo\/src\/utils\//,
  /packages\/kumo\/src\/hooks\//,
  /packages\/kumo\/src\/primitives\//,
  /theme-kumo\.css/,
  /tailwind\.config/,

  // Docs site infrastructure that wraps all component demos
  /packages\/kumo-docs-astro\/src\/components\/docs\//,
  /packages\/kumo-docs-astro\/src\/layouts\//,
  /packages\/kumo-docs-astro\/src\/styles\//,
];

/**
 * Patterns for files that have no visual impact and are safe to skip.
 */
const SKIP_PATTERNS: RegExp[] = [
  /\.md$/,
  /\.changeset\//,
  /^ci\/(?!visual-regression)/,
  /\.github\//,
  /lint\//,
  /\.test\.(ts|tsx)$/,
  /\.spec\.(ts|tsx)$/,
  /packages\/kumo\/ai\//,
  /packages\/kumo\/scripts\//,
  /packages\/kumo-figma\//,
  /packages\/kumo\/src\/command-line\//,
  /packages\/kumo\/src\/catalog\//,
  /packages\/kumo\/src\/blocks\//,
  /lefthook/,
  /tsconfig/,
  /\.env/,
];

export interface ChangeClassification {
  /** Component slugs that were directly changed */
  affectedComponents: Set<string>;
  /** Whether any broad-impact files were changed, requiring full regression */
  requiresFullRegression: boolean;
  /** Whether all changed files are safe to skip */
  allSkippable: boolean;
}

/**
 * Extract component slug from a changed file path.
 * Returns null if the file doesn't map to a specific component.
 *
 * Examples:
 *   packages/kumo/src/components/button/button.tsx -> "button"
 *   packages/kumo/src/components/button/use-button.ts -> "button"
 *   packages/kumo/src/components/button/index.ts -> "button"
 *   packages/kumo-docs-astro/.../ButtonDemo.tsx -> "button"
 */
export function getComponentFromFile(filePath: string): string | null {
  // Match any file under a component directory: packages/kumo/src/components/{name}/
  const componentMatch = filePath.match(
    /packages\/kumo\/src\/components\/([^/]+)\//,
  );
  if (componentMatch) {
    return componentMatch[1];
  }

  // Match demo files: *Demo.tsx -> extract component name
  const demoMatch = filePath.match(/([A-Z][a-zA-Z]+)Demo\.tsx$/);
  if (demoMatch) {
    // Convert PascalCase to kebab-case: DateRangePicker -> date-range-picker
    const pascalName = demoMatch[1];
    return pascalName.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
  }

  return null;
}

/**
 * Classify changed files into three tiers:
 * 1. Component-specific: test only those components (granular)
 * 2. Broad-impact: triggers full regression (shared styles, docs infra, etc.)
 * 3. Skippable: no visual impact (CI scripts, markdown, tests, etc.)
 *
 * Files that don't match any pattern are treated as broad-impact (safe default).
 */
export function classifyChangedFiles(
  changedFiles: string[],
): ChangeClassification {
  const affectedComponents = new Set<string>();
  let requiresFullRegression = false;
  let hasUnclassifiedFiles = false;

  for (const file of changedFiles) {
    // Check if it maps to a specific component
    const slug = getComponentFromFile(file);
    if (slug) {
      affectedComponents.add(slug);
      continue;
    }

    // Check if it's a known broad-impact file
    if (FULL_REGRESSION_PATTERNS.some((pattern) => pattern.test(file))) {
      requiresFullRegression = true;
      continue;
    }

    // Check if it's safe to skip
    if (SKIP_PATTERNS.some((pattern) => pattern.test(file))) {
      continue;
    }

    // Unknown file — treat as broad impact to be safe
    hasUnclassifiedFiles = true;
  }

  if (hasUnclassifiedFiles) {
    requiresFullRegression = true;
  }

  const allSkippable = !requiresFullRegression && affectedComponents.size === 0;

  return { affectedComponents, requiresFullRegression, allSkippable };
}

/** Strip hyphens for fuzzy slug comparison (e.g. "menubar" matches "menu-bar") */
function normalizeSlug(slug: string): string {
  return slug.replace(/-/g, "");
}

/**
 * Get the components that should be screenshotted based on changed files.
 */
export function getAffectedComponents(
  changedFiles: string[],
  allComponents: DiscoveredComponent[],
): DiscoveredComponent[] {
  const affectedSlugs = new Set<string>();

  for (const file of changedFiles) {
    const slug = getComponentFromFile(file);
    if (slug) {
      affectedSlugs.add(normalizeSlug(slug));
    }
  }

  return allComponents.filter((c) => affectedSlugs.has(normalizeSlug(c.id)));
}
