{"version":3,"sources":["../src/xd.ts"],"names":[],"mappings":"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","file":"chunk-TVGBJSWO.mjs","sourcesContent":["import { XdParseError } from './errors';\nimport { ErrorCode } from './types';\nimport type { Puzzle, Grid, Cell as UnifiedCell, Clues, ParseOptions } 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 (section && section.every((line) => /^[A-Za-z0-9#._]+$/.test(line) && line.length > 0)) {\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('Invalid XD file: no grid section found', ErrorCode.XD_FORMAT_ERROR);\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) additionalProps.notepad = puzzle.metadata.notepad;\n\n  if (Object.keys(additionalProps).length > 0) {\n    result.additionalProperties = additionalProps;\n  }\n\n  return result;\n}\n"]}