{"version":3,"file":"xd-gZJiCv-c.mjs","names":[],"sources":["../src/xd.ts"],"sourcesContent":["import { XdParseError } from \"./errors\";\nimport { ErrorCode } from \"./types\";\nimport type {\n  Puzzle,\n  Grid,\n  Cell as UnifiedCell,\n  Clues,\n  ParseOptions,\n} from \"./types\";\nimport { MAX_GRID_WIDTH, MAX_GRID_HEIGHT } from \"./constants\";\n\nexport interface XdMetadata {\n  title?: string;\n  author?: string;\n  editor?: string;\n  copyright?: string;\n  date?: string;\n  rebus?: string;\n  [key: string]: string | undefined;\n}\n\nexport interface XdClue {\n  number: string;\n  clue: string;\n  answer: string;\n  metadata?: Record<string, string>;\n}\n\nexport interface XdPuzzle {\n  metadata: XdMetadata;\n  grid: string[][];\n  across: XdClue[];\n  down: XdClue[];\n  notes?: string;\n}\n\nfunction parseMetadata(lines: string[]): XdMetadata {\n  const metadata: XdMetadata = {};\n\n  for (const line of lines) {\n    const colonIndex = line.indexOf(\":\");\n    if (colonIndex === -1) continue;\n\n    const key = line.substring(0, colonIndex).trim();\n    const value = line.substring(colonIndex + 1).trim();\n\n    if (key && value) {\n      // Convert first character to lowercase to match TypeScript conventions\n      const normalizedKey = (key[0] ?? \"\").toLowerCase() + key.slice(1);\n      metadata[normalizedKey] = value;\n    }\n  }\n\n  return metadata;\n}\n\nfunction parseGrid(lines: string[]): string[][] {\n  return lines.map((line) => {\n    const cells: string[] = [];\n    for (let i = 0; i < line.length; i++) {\n      const char = line[i];\n      if (char !== undefined) {\n        cells.push(char);\n      }\n    }\n    return cells;\n  });\n}\n\nfunction parseClues(lines: string[]): { across: XdClue[]; down: XdClue[] } {\n  const across: XdClue[] = [];\n  const down: XdClue[] = [];\n\n  for (const line of lines) {\n    if (line.length === 0) continue;\n\n    const match = line.match(/^([AD])(\\d+)\\.\\s+(.+?)(?:\\s+~\\s+(.+))?$/);\n    if (match) {\n      const [, direction, number, clue, answer] = match;\n\n      const clueObj: XdClue = {\n        number: number ?? \"\",\n        clue: clue?.trim() ?? \"\",\n        answer: answer?.trim() ?? \"\",\n      };\n\n      if (direction === \"A\") {\n        across.push(clueObj);\n      } else {\n        down.push(clueObj);\n      }\n    }\n  }\n\n  return { across, down };\n}\n\nfunction splitIntoSections(content: string): {\n  metadata: string[];\n  grid: string[];\n  clues: string[];\n  notes: string[];\n} {\n  const lines = content.split(\"\\n\");\n  const sections: string[][] = [];\n  let currentSection: string[] = [];\n  let blankLineCount = 0;\n\n  for (const line of lines) {\n    if (line.trim() === \"\") {\n      blankLineCount++;\n      if (blankLineCount >= 2 && currentSection.length > 0) {\n        sections.push(currentSection);\n        currentSection = [];\n        blankLineCount = 0;\n      }\n    } else {\n      if (blankLineCount === 1 && currentSection.length > 0) {\n        sections.push(currentSection);\n        currentSection = [];\n      }\n      blankLineCount = 0;\n      currentSection.push(line);\n    }\n  }\n\n  if (currentSection.length > 0) {\n    sections.push(currentSection);\n  }\n\n  const result = {\n    metadata: [] as string[],\n    grid: [] as string[],\n    clues: [] as string[],\n    notes: [] as string[],\n  };\n\n  let sectionIndex = 0;\n\n  if (sections.length > sectionIndex) {\n    const section = sections[sectionIndex];\n    if (section && section.some((line) => line.includes(\":\"))) {\n      result.metadata = section;\n      sectionIndex++;\n    }\n  }\n\n  if (sections.length > sectionIndex) {\n    const section = sections[sectionIndex];\n    if (\n      section &&\n      section.every((line) => /^[A-Za-z0-9#._]+$/.test(line) && line.length > 0)\n    ) {\n      result.grid = section;\n      sectionIndex++;\n    }\n  }\n\n  while (sections.length > sectionIndex) {\n    const section = sections[sectionIndex];\n    if (section && section.some((line) => /^[AD]\\d+\\./.test(line))) {\n      result.clues.push(...section);\n      sectionIndex++;\n    } else {\n      break;\n    }\n  }\n\n  if (sections.length > sectionIndex) {\n    for (let i = sectionIndex; i < sections.length; i++) {\n      const section = sections[i];\n      if (section) {\n        result.notes.push(...section);\n      }\n    }\n  }\n\n  return result;\n}\n\n/**\n * Parse an XD format crossword puzzle.\n *\n * @param content - The XD text data as string\n * @param options - Optional parsing options\n * @param options.maxGridSize - Maximum allowed grid dimensions\n * @returns An XdPuzzle object containing all puzzle data\n * @throws {XdParseError} When the content is not valid XD format\n *\n * @example\n * ```typescript\n * import { parseXd } from 'xword-parser';\n * const xdData = 'Title: Example\\\\nAuthor: John Doe\\\\n\\\\n## Grid\\\\n...';\n * const puzzle = parseXd(xdData);\n * ```\n */\nexport function parseXd(content: string, options?: ParseOptions): XdPuzzle {\n  const sections = splitIntoSections(content);\n\n  if (sections.grid.length === 0) {\n    throw new XdParseError(\n      \"Invalid XD file: no grid section found\",\n      ErrorCode.XD_FORMAT_ERROR,\n    );\n  }\n\n  const metadata = parseMetadata(sections.metadata);\n  const grid = parseGrid(sections.grid);\n  const { across, down } = parseClues(sections.clues);\n\n  // Validate grid is not empty\n  if (grid.length === 0) {\n    throw new XdParseError(\"Grid is empty\", ErrorCode.XD_INVALID_GRID);\n  }\n\n  // Validate grid has consistent row lengths\n  const width = grid[0]?.length || 0;\n  if (width === 0) {\n    throw new XdParseError(\"Grid has no columns\", ErrorCode.XD_INVALID_GRID);\n  }\n\n  const height = grid.length;\n\n  // Validate grid dimensions are within limits\n  const maxWidth = options?.maxGridSize?.width ?? MAX_GRID_WIDTH;\n  const maxHeight = options?.maxGridSize?.height ?? MAX_GRID_HEIGHT;\n\n  if (width > maxWidth || height > maxHeight) {\n    throw new XdParseError(\n      `Grid dimensions too large: ${width}x${height}. Maximum supported size is ${maxWidth}x${maxHeight}`,\n      ErrorCode.XD_INVALID_GRID,\n    );\n  }\n\n  for (let i = 0; i < grid.length; i++) {\n    if (!grid[i] || grid[i]?.length !== width) {\n      throw new XdParseError(\n        `Grid row ${i} has inconsistent width (expected ${width}, got ${grid[i]?.length || 0})`,\n        ErrorCode.XD_INVALID_GRID,\n      );\n    }\n  }\n\n  const puzzle: XdPuzzle = {\n    metadata,\n    grid,\n    across,\n    down,\n  };\n\n  if (sections.notes.length > 0) {\n    puzzle.notes = sections.notes.join(\"\\n\").trim();\n  }\n\n  return puzzle;\n}\n\n/**\n * Convert an XD puzzle to the unified Puzzle format.\n *\n * @param puzzle - The XdPuzzle object to convert\n * @returns A unified Puzzle object\n *\n * @example\n * ```typescript\n * import { parseXd, convertXdToUnified } from 'xword-parser';\n * const xdPuzzle = parseXd(xdData);\n * const unifiedPuzzle = convertXdToUnified(xdPuzzle);\n * ```\n */\nexport function convertXdToUnified(puzzle: XdPuzzle): Puzzle {\n  // Parser guarantees grid is not empty and has consistent width\n  const firstRow = puzzle.grid[0];\n  if (!firstRow) {\n    // This should never happen as parser validates grid is not empty\n    throw new Error(\"Invalid state: grid is empty\");\n  }\n\n  const grid: Grid = {\n    width: firstRow.length,\n    height: puzzle.grid.length,\n    cells: [],\n  };\n\n  // Convert grid - XD grid is string[][], we need to determine cell properties\n  let cellNumber = 1;\n  for (let y = 0; y < puzzle.grid.length; y++) {\n    const row = puzzle.grid[y]!; // Parser guarantees all rows exist\n\n    const cellRow: UnifiedCell[] = [];\n    for (let x = 0; x < row.length; x++) {\n      const cellValue = row[x]!; // Parser guarantees consistent width\n      const isBlack = cellValue === \"#\";\n\n      // Determine if this cell should have a number\n      let number: number | undefined;\n      if (!isBlack) {\n        const needsNumber =\n          // Start of across word\n          ((x === 0 || puzzle.grid[y]![x - 1] === \"#\") &&\n            x < row.length - 1 &&\n            puzzle.grid[y]![x + 1] !== \"#\") ||\n          // Start of down word\n          ((y === 0 || puzzle.grid[y - 1]?.[x] === \"#\") &&\n            y < puzzle.grid.length - 1 &&\n            puzzle.grid[y + 1]?.[x] !== \"#\");\n\n        if (needsNumber) {\n          number = cellNumber++;\n        }\n      }\n\n      cellRow.push({\n        solution: isBlack ? undefined : cellValue,\n        number,\n        isBlack,\n      });\n    }\n    grid.cells.push(cellRow);\n  }\n\n  // Convert clues\n  const clues: Clues = {\n    across: puzzle.across.map((c) => ({\n      number: parseInt(c.number),\n      text: c.clue,\n    })),\n    down: puzzle.down.map((c) => ({\n      number: parseInt(c.number),\n      text: c.clue,\n    })),\n  };\n\n  const result: Puzzle = {\n    title: puzzle.metadata.title,\n    author: puzzle.metadata.author,\n    copyright: puzzle.metadata.copyright,\n    notes: puzzle.notes,\n    date: puzzle.metadata.date,\n    grid,\n    clues,\n  };\n\n  // Add puzzle-level metadata if present\n  const additionalProps: Record<string, unknown> = {};\n\n  if (puzzle.metadata.editor) additionalProps.editor = puzzle.metadata.editor;\n\n  // XD format also supports rebus metadata (different format than PUZ)\n  if (puzzle.metadata.rebus) additionalProps.rebus = puzzle.metadata.rebus;\n  if (puzzle.metadata.notepad)\n    additionalProps.notepad = puzzle.metadata.notepad;\n\n  if (Object.keys(additionalProps).length > 0) {\n    result.additionalProperties = additionalProps;\n  }\n\n  return result;\n}\n"],"mappings":"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"}