/**
 * @file Storybook utility functions for Visual Regression Testing.
 * @description Helper functions to discover stories from Storybook index, build URLs, and interact with Storybook.
 */

import * as fs from "fs";
import * as path from "path";
import { Page } from "@playwright/test";

/**
 * Represents a discovered Storybook story.
 */
export interface StoryInfo {
  /** Unique identifier for the story (from Storybook index) */
  id: string;
  /** Human-readable name of the story */
  name: string;
  /** Component name this story belongs to */
  component: string;
  /** Full title path from Storybook */
  title: string;
  /** Story type (story or docs) */
  type: "story" | "docs";
}

/**
 * Storybook index.json structure.
 */
interface StorybookIndex {
  v: number;
  entries: Record<string, StorybookEntry>;
}

/**
 * Individual entry in Storybook index.
 */
interface StorybookEntry {
  id: string;
  title: string;
  name: string;
  type: "story" | "docs";
  importPath: string;
  tags?: string[];
}

/**
 * Available KERN-UX themes for testing.
 */
export type KernTheme = "light" | "dark";

/**
 * Configuration for taking a story screenshot.
 */
export interface ScreenshotConfig {
  /** Theme to apply before capturing screenshot */
  theme: KernTheme;
  /** Whether to wait for fonts to load */
  waitForFonts?: boolean;
  /** Additional wait time in milliseconds after page load */
  additionalWait?: number;
}

/**
 * Default screenshot configuration.
 */
const DEFAULT_SCREENSHOT_CONFIG: ScreenshotConfig = {
  theme: "light",
  waitForFonts: true,
  additionalWait: 500,
};

/**
 * Discovers all stories from the Storybook index.json file.
 * This reads the built Storybook's index to get accurate story IDs.
 *
 * @param storybookStaticDir - Path to the storybook-static directory
 * @returns Array of discovered story information
 */
export function discoverStoriesFromIndex(storybookStaticDir: string): StoryInfo[] {
  const indexPath = path.join(storybookStaticDir, "index.json");

  // Check if index.json exists
  if (!fs.existsSync(indexPath)) {
    console.warn(`Storybook index not found: ${indexPath}`);
    console.warn("Make sure to run 'npm run build-storybook' first.");
    return [];
  }

  try {
    const indexContent = fs.readFileSync(indexPath, "utf-8");
    const index: StorybookIndex = JSON.parse(indexContent);

    const stories: StoryInfo[] = [];

    for (const [, entry] of Object.entries(index.entries)) {
      // Skip docs entries - we'll handle them separately
      if (entry.type === "docs") {
        continue;
      }

      // Extract component name from title (e.g., "kern_ux_canvas/SDC/Button" -> "Button")
      const titleParts = entry.title.split("/");
      const componentName = titleParts[titleParts.length - 1];

      stories.push({
        id: entry.id,
        name: entry.name,
        component: componentName,
        title: entry.title,
        type: entry.type,
      });
    }

    return stories;
  } catch (error) {
    console.error(`Error reading Storybook index: ${error}`);
    return [];
  }
}

/**
 * Discovers all docs entries from the Storybook index.json file.
 *
 * @param storybookStaticDir - Path to the storybook-static directory
 * @returns Array of discovered docs information
 */
export function discoverDocsFromIndex(storybookStaticDir: string): StoryInfo[] {
  const indexPath = path.join(storybookStaticDir, "index.json");

  if (!fs.existsSync(indexPath)) {
    console.warn(`Storybook index not found: ${indexPath}`);
    return [];
  }

  try {
    const indexContent = fs.readFileSync(indexPath, "utf-8");
    const index: StorybookIndex = JSON.parse(indexContent);

    const docs: StoryInfo[] = [];

    for (const [, entry] of Object.entries(index.entries)) {
      if (entry.type !== "docs") {
        continue;
      }

      const titleParts = entry.title.split("/");
      const componentName = titleParts[titleParts.length - 1];

      docs.push({
        id: entry.id,
        name: entry.name,
        component: componentName,
        title: entry.title,
        type: entry.type,
      });
    }

    return docs;
  } catch (error) {
    console.error(`Error reading Storybook index: ${error}`);
    return [];
  }
}

/**
 * Builds the Storybook iframe URL for a specific story.
 *
 * @param storyId - The Storybook story ID
 * @param theme - The theme to apply
 * @returns URL string for the story iframe
 */
export function buildStoryUrl(storyId: string, theme: KernTheme): string {
  // Storybook iframe URL with theme global
  const params = new URLSearchParams({
    id: storyId,
    viewMode: "story",
    globals: `theme:${theme}`,
  });

  return `/iframe.html?${params.toString()}`;
}

/**
 * Builds the Storybook docs URL for a specific component.
 *
 * @param docsId - The Storybook docs ID
 * @param theme - The theme to apply
 * @returns URL string for the docs iframe
 */
export function buildDocsUrl(docsId: string, theme: KernTheme): string {
  const params = new URLSearchParams({
    id: docsId,
    viewMode: "docs",
    globals: `theme:${theme}`,
  });

  return `/iframe.html?${params.toString()}`;
}

/**
 * Navigates to a Storybook story and prepares it for screenshot capture.
 *
 * @param page - Playwright Page object
 * @param storyId - The Storybook story ID
 * @param config - Screenshot configuration
 */
export async function navigateToStory(
  page: Page,
  storyId: string,
  config: Partial<ScreenshotConfig> = {}
): Promise<void> {
  const mergedConfig = { ...DEFAULT_SCREENSHOT_CONFIG, ...config };
  const url = buildStoryUrl(storyId, mergedConfig.theme);

  // Navigate to the story
  await page.goto(url, { waitUntil: "domcontentloaded" });

  // Wait for Storybook root to be present
  await page.waitForSelector("#storybook-root", { state: "attached", timeout: 30000 });

  // Wait for network to be idle
  await page.waitForLoadState("networkidle");

  // Wait for fonts to load if configured
  if (mergedConfig.waitForFonts) {
    await waitForFontsToLoad(page);
  }

  // Apply theme via data attribute
  await applyTheme(page, mergedConfig.theme);

  // Additional wait for rendering to complete
  if (mergedConfig.additionalWait && mergedConfig.additionalWait > 0) {
    await page.waitForTimeout(mergedConfig.additionalWait);
  }

  // Wait for any animations to complete
  await disableAnimations(page);
}

/**
 * Navigates to a Storybook docs page.
 *
 * @param page - Playwright Page object
 * @param docsId - The Storybook docs ID
 * @param config - Screenshot configuration
 */
export async function navigateToDocs(
  page: Page,
  docsId: string,
  config: Partial<ScreenshotConfig> = {}
): Promise<void> {
  const mergedConfig = { ...DEFAULT_SCREENSHOT_CONFIG, ...config };
  const url = buildDocsUrl(docsId, mergedConfig.theme);

  await page.goto(url, { waitUntil: "networkidle" });

  if (mergedConfig.waitForFonts) {
    await waitForFontsToLoad(page);
  }

  await applyTheme(page, mergedConfig.theme);

  if (mergedConfig.additionalWait && mergedConfig.additionalWait > 0) {
    await page.waitForTimeout(mergedConfig.additionalWait);
  }

  await disableAnimations(page);
}

/**
 * Waits for all fonts to be loaded.
 *
 * @param page - Playwright Page object
 */
async function waitForFontsToLoad(page: Page): Promise<void> {
  await page.evaluate(async () => {
    // Wait for document fonts to be ready
    await document.fonts.ready;
  });
}

/**
 * Applies the specified theme to the page.
 *
 * @param page - Playwright Page object
 * @param theme - Theme to apply
 */
async function applyTheme(page: Page, theme: KernTheme): Promise<void> {
  await page.evaluate((themeValue: string) => {
    // Apply theme attribute to html element (KERN-UX uses data-kern-theme)
    document.documentElement.setAttribute("data-kern-theme", themeValue);
    // Also try body for compatibility
    document.body.setAttribute("data-kern-theme", themeValue);
  }, theme);
}

/**
 * Disables all CSS animations and transitions for consistent screenshots.
 *
 * @param page - Playwright Page object
 */
async function disableAnimations(page: Page): Promise<void> {
  await page.addStyleTag({
    content: `
      *, *::before, *::after {
        animation-duration: 0s !important;
        animation-delay: 0s !important;
        transition-duration: 0s !important;
        transition-delay: 0s !important;
      }
    `,
  });
}

/**
 * Gets the Storybook root element for screenshot capture.
 *
 * @param page - Playwright Page object
 * @returns Locator for the Storybook root element
 */
export function getStorybookRoot(page: Page) {
  // Storybook renders stories in #storybook-root
  return page.locator("#storybook-root");
}

/**
 * Generates a snapshot name for a story.
 *
 * @param story - Story information
 * @param theme - Current theme
 * @param browser - Browser name
 * @returns Formatted snapshot name
 */
export function generateSnapshotName(
  story: StoryInfo,
  theme: KernTheme,
  browser: string
): string {
  // Sanitize names for file system compatibility
  const componentName = story.component.toLowerCase().replace(/[^a-z0-9]/g, "-");
  const storyName = story.name.toLowerCase().replace(/[^a-z0-9]/g, "-");
  return `${componentName}--${storyName}--${theme}--${browser}.png`;
}

/**
 * Groups stories by component for organized test output.
 *
 * @param stories - Array of story information
 * @returns Map of component name to stories
 */
export function groupStoriesByComponent(
  stories: StoryInfo[]
): Map<string, StoryInfo[]> {
  const grouped = new Map<string, StoryInfo[]>();

  for (const story of stories) {
    const existing = grouped.get(story.component);
    if (existing) {
      existing.push(story);
    } else {
      grouped.set(story.component, [story]);
    }
  }

  return grouped;
}
