import type { Heading, List, RootContent } from "mdast";
import { fromMarkdown } from "mdast-util-from-markdown";
import { toString } from "mdast-util-to-string";

function isHeading(node: RootContent): node is Heading {
  return node.type === "heading";
}

function isList(node: RootContent): node is List {
  return node.type === "list";
}

/**
 * Parse test case descriptions from the `## Test Cases` section of a Markdown doc.
 * Uses mdast to traverse the AST instead of string matching.
 */
export function parseTestCasesFromDoc(markdown: string): string[] {
  const tree = fromMarkdown(markdown);
  const cases: string[] = [];
  let collecting = false;

  for (const node of tree.children) {
    if (isHeading(node)) {
      if (collecting) break;
      if (node.depth === 2 && toString(node) === "Test Cases") {
        collecting = true;
        continue;
      }
    }
    if (collecting && isList(node)) {
      for (const item of node.children) {
        const text = toString(item).trim();
        if (text) {
          cases.push(text);
        }
      }
    }
  }

  return cases;
}

/**
 * Parse scenario pattern descriptions from the `## Scenario Patterns` section of a Markdown doc.
 */
export function parseScenarioPatternsFromDoc(markdown: string): string[] {
  const tree = fromMarkdown(markdown);
  const patterns: string[] = [];
  let collecting = false;

  for (const node of tree.children) {
    if (isHeading(node)) {
      if (collecting) break;
      if (node.depth === 2 && toString(node) === "Scenario Patterns") {
        collecting = true;
        continue;
      }
    }
    if (collecting && isList(node)) {
      for (const item of node.children) {
        const text = toString(item).trim();
        if (text) {
          patterns.push(text);
        }
      }
    }
  }

  return patterns;
}

/**
 * Parse describe blocks and their nested test/it descriptions from a spec file.
 * Returns a map of describe name → test descriptions.
 */
export function parseDescribeBlocksFromSpec(content: string): Map<string, string[]> {
  const result = new Map<string, string[]>();
  const describeRegex =
    /\b(?:test\.)?describe(?:\.skip)?\(\s*["'`]([^"'`]+)["'`]\s*,\s*(?:\(\)|[^)]*\))\s*=>\s*\{/g;
  let describeMatch: RegExpExecArray | null;

  while ((describeMatch = describeRegex.exec(content)) !== null) {
    const describeName = describeMatch[1];
    const startIndex = describeMatch.index + describeMatch[0].length;

    // Find matching closing brace by counting braces
    let depth = 1;
    let i = startIndex;
    while (i < content.length && depth > 0) {
      if (content[i] === "{") depth++;
      if (content[i] === "}") depth--;
      i++;
    }
    const blockContent = content.substring(startIndex, i - 1);

    const tests: string[] = [];
    for (const testRegex of [
      /\b(?:test|it)(?:\.skip)?\(\s*"([^"]+)"/g,
      /\b(?:test|it)(?:\.skip)?\(\s*'([^']+)'/g,
      /\b(?:test|it)(?:\.skip)?\(\s*`([^`]+)`/g,
    ]) {
      let testMatch: RegExpExecArray | null;
      while ((testMatch = testRegex.exec(blockContent)) !== null) {
        tests.push(testMatch[1].replace(/\s+/g, " ").trim());
      }
    }

    result.set(describeName, tests);
  }

  return result;
}

/**
 * Parse `it("...")` descriptions from a test file using regex.
 * Test files are not Markdown, so regex is appropriate here.
 * Uses separate patterns per quote type so that e.g. an apostrophe
 * inside a double-quoted string does not end the match early.
 */
export function parseItDescriptionsFromTest(content: string): string[] {
  const descriptions: string[] = [];
  const regex = /\bit\(\s*"([^"]+)"/gs;
  let match: RegExpExecArray | null;
  while ((match = regex.exec(content)) !== null) {
    descriptions.push(match[1].replace(/\s+/g, " ").trim());
  }
  // Also match single-quoted and backtick-quoted descriptions
  const singleQuoteRegex = /\bit\(\s*'([^']+)'/gs;
  while ((match = singleQuoteRegex.exec(content)) !== null) {
    descriptions.push(match[1].replace(/\s+/g, " ").trim());
  }
  const backtickRegex = /\bit\(\s*`([^`]+)`/gs;
  while ((match = backtickRegex.exec(content)) !== null) {
    descriptions.push(match[1].replace(/\s+/g, " ").trim());
  }
  return descriptions;
}
