/**
 * Pure parsing of a Storybook config file's *content* (.storybook/main.*).
 *
 * Extracts story glob patterns, framework, and builder without touching the
 * filesystem — the CLI reads the file and hands content here; the Cloud scan
 * fetches it via the GitHub API and does the same.
 */

import type { StorybookConfig } from "./types.js";
import path from "node:path";

/** Default story patterns when a config declares none. */
export const DEFAULT_STORY_PATTERNS = [
  "**/*.stories.@(ts|tsx|js|jsx|mdx)",
  "**/*.story.@(ts|tsx|js|jsx|mdx)",
];

/** Common Storybook config file names, in order of preference. */
export const STORYBOOK_CONFIG_FILENAMES = [
  ".storybook/main.ts",
  ".storybook/main.mts",
  ".storybook/main.js",
  ".storybook/main.mjs",
  ".storybook/main.cjs",
];

/**
 * Parse Storybook config content into structured config.
 *
 * @param content - The raw text of `.storybook/main.*`.
 * @param configPath - Where the content came from (informational).
 */
export function parseStorybookConfigContent(content: string, configPath: string): StorybookConfig {
  const errors: string[] = [];
  let storyPatterns: string[] = [];
  let framework: string | undefined;
  let builder: string | undefined;

  const storiesContent = extractStoriesArrayContent(content);
  if (storiesContent) {
    storyPatterns = parseStoriesEntries(storiesContent);
  }

  // Extract framework
  // Pattern: framework: '@storybook/react-vite' or framework: { name: '...' }
  const frameworkMatch = content.match(/framework:\s*['"`]([^'"`]+)['"`]/);
  if (frameworkMatch) {
    framework = frameworkMatch[1];
  } else {
    const frameworkNameMatch = content.match(/framework:\s*\{[^}]*name:\s*['"`]([^'"`]+)['"`]/);
    if (frameworkNameMatch) {
      framework = frameworkNameMatch[1];
    }
  }

  // Extract builder if specified
  // Pattern: builder: 'vite' or core: { builder: 'vite' }
  const builderMatch = content.match(/builder:\s*['"`]([^'"`]+)['"`]/);
  if (builderMatch) {
    builder = builderMatch[1];
  }

  // Use defaults if no patterns found
  if (storyPatterns.length === 0) {
    storyPatterns = DEFAULT_STORY_PATTERNS;
    errors.push("No story patterns found in config, using defaults");
  }

  return {
    configPath,
    storyPatterns,
    framework,
    builder,
    valid: errors.length === 0 || storyPatterns.length > 0,
    errors: errors.length > 0 ? errors : undefined,
  };
}

function extractStoriesArrayContent(content: string): string | null {
  const storiesMatch = /\bstories\s*:/.exec(content);
  if (!storiesMatch) return null;
  const arrayStart = content.indexOf("[", storiesMatch.index);
  if (arrayStart === -1) return null;
  const arrayEnd = findMatchingDelimiter(content, arrayStart, "[", "]");
  if (arrayEnd === -1) return null;
  return content.slice(arrayStart + 1, arrayEnd);
}

function parseStoriesEntries(content: string): string[] {
  return splitAtTopLevelCommas(content).flatMap((entry) => {
    const trimmed = entry.trim();
    if (!trimmed || trimmed.startsWith("//")) return [];
    const stringPattern = parseQuotedString(trimmed);
    if (stringPattern) return [stringPattern];
    if (!trimmed.startsWith("{")) return [];

    const directory = extractObjectStringProperty(trimmed, "directory");
    const files = extractObjectStringProperty(trimmed, "files");
    if (!directory && !files) return [];
    return [joinStoryPattern(directory ?? ".", files ?? "**/*.stories.@(js|jsx|ts|tsx|mdx)")];
  });
}

function joinStoryPattern(directory: string, files: string): string {
  return path.posix
    .join(directory.replace(/\\/g, "/"), files.replace(/\\/g, "/"))
    .replace(/^\.$/, "**/*");
}

function extractObjectStringProperty(content: string, property: string): string | null {
  const match = new RegExp(`\\b${property}\\s*:\\s*(['"\`])([^'"\`]+)\\1`).exec(content);
  return match?.[2] ?? null;
}

function parseQuotedString(content: string): string | null {
  const match = /^(['"`])([\s\S]*)\1$/.exec(content.trim());
  return match?.[2]?.trim() || null;
}

function splitAtTopLevelCommas(content: string): string[] {
  const entries: string[] = [];
  let start = 0;
  let depth = 0;
  let inString: string | null = null;

  for (let i = 0; i < content.length; i++) {
    const char = content[i];
    const prev = i > 0 ? content[i - 1] : "";
    if ((char === "'" || char === '"' || char === "`") && prev !== "\\") {
      inString = inString === char ? null : inString === null ? char : inString;
      continue;
    }
    if (inString) continue;
    if (char === "{" || char === "[" || char === "(") depth++;
    if (char === "}" || char === "]" || char === ")") depth--;
    if (char !== "," || depth !== 0) continue;
    entries.push(content.slice(start, i));
    start = i + 1;
  }
  entries.push(content.slice(start));
  return entries;
}

function findMatchingDelimiter(
  content: string,
  start: number,
  open: string,
  close: string
): number {
  let depth = 0;
  let inString: string | null = null;
  for (let i = start; i < content.length; i++) {
    const char = content[i];
    const prev = i > 0 ? content[i - 1] : "";
    if ((char === "'" || char === '"' || char === "`") && prev !== "\\") {
      inString = inString === char ? null : inString === null ? char : inString;
      continue;
    }
    if (inString) continue;
    if (char === open) depth++;
    if (char === close) {
      depth--;
      if (depth === 0) return i;
    }
  }
  return -1;
}
