"""Compact Natural PDF API reference injected into the agent's system prompt.

This is the single most important file in the system — the quality of
generated code depends entirely on what we tell the agent about the API.
"""

API_REFERENCE = """\
## Selectors

Find elements with CSS-like syntax:
  page.find('text:contains("Invoice")')     # first match or None
  page.find_all('text:bold[size>=14]')      # all matches (ElementCollection, may be empty)

Element types: text, line, rect, image, region, form_cell
Pseudo-classes:
  :bold, :italic                            # style
  :contains("X"), :startswith("X"), :endswith("X")  # text content
  :ocr("X"), :closest("X"@0.8)             # fuzzy/OCR matching
  :regex("pattern")                         # regex match
  :not(:bold)                               # negation
  :checked, :unchecked                      # checkbox state
  :first, :last, :nth(2)                    # collection position

Attributes: [size>12], [size>=14], [fontname*=Arial], [confidence>=0.9]
Combine: text:bold[size>=14]:contains("Total")
OR combinator: page.find('text:contains("Total") | text:contains("Amount Due")')
Case-insensitive: page.find('text:contains("total")', case=False)

:ocr("X") — OCR-tolerant matching. Handles garbled chars (l/1, O/0, rn/m).
  page.find('text:ocr("Date received")')
  page.find('text:ocr("Identification"@0.6)')  # lower threshold for noisy docs

Rects and form cells support text selectors:
  page.find('rect:contains("Cost Information")')
  page.find('form_cell:contains("Name")')      # after detect_form_cells()

## Spatial Navigation

From an element, navigate to nearby content:
  label = page.find('text:contains("Name")')
  value = label.right().extract_text()             # region to the right
  value = label.below().extract_text()             # region below
  value = label.right(width=200).extract_text()    # bounded width

Bounded regions with until=:
  content = label.below(until='text:contains("Next Section")', include_endpoint=False)
  content.extract_text()

Key parameters:
  until='selector'       — stop at the matching element
  include_source=True    — include the anchor element's row in .below()/.above()
  include_endpoint=False — exclude the boundary element
  width="element"        — constrain to the anchor's column width
  multipage=True         — follow content across page boundaries

Two-column pattern:
  page.find('text:contains("Cost")').right(until='text:contains("Total")', include_endpoint=False)

Prefer until= over fixed heights. height=45 is fragile; until='text:contains("Next")' is structural.

## Text Extraction

  page.extract_text()                # all text on page
  page.extract_text(layout=True)     # layout-preserving (spaces for alignment)
  element.extract_text()             # text from one element
  region.extract_text()              # text from a region

## Table Extraction

  table = page.extract_table()       # returns TableResult
  df = table.to_df()                 # pandas DataFrame
  df = table.to_df(header="first")   # use first row as header
  tables = page.extract_tables()     # List[TableResult] — all tables on page

For borderless tables, use guides:
  g = region.guides()
  g.vertical.from_whitespace(min_gap=10)
  g.horizontal.from_whitespace(min_gap=5)
  table = g.extract_table()

Detect columns from headers:
  g = region.guides()
  g.vertical.from_content(['Name', 'Date', 'Amount'])
  g.horizontal.from_whitespace(min_gap=5)
  table = g.extract_table()

Other guide constructors:
  g.vertical.from_lines()            # use detected vertical ruling lines
  g.horizontal.from_lines()          # use detected horizontal ruling lines
  g.horizontal.from_stripes()        # use alternating color bands
  g.vertical.from_headers(headers, method='min_crossings')
  g.vertical.snap_to_whitespace(min_gap=10)
  g.vertical.divide(n=4)            # even divisions

## Form Cells (Grid Forms)

For government forms, permits, arrest reports with visible grid lines:
  cells = page.detect_form_cells()   # detects cell boundaries from ruling lines
  cell = page.find('form_cell:contains("First Name")')
  value = cell.extract_text()        # text inside the cell

Parameters:
  page.detect_form_cells(
      merge=True,              # merge cells where text spans boundaries
      cell_labels=[...],       # selectors for cells that should never merge
      adaptive_c=10,           # lower (e.g., 3) for faint lines
  )

## Checkboxes

  checkboxes = page.detect_checkboxes()   # returns ElementCollection of Region objects
  for cb in checkboxes:
      print(cb.is_checked, cb.bbox)       # .is_checked is True/False/None
      print(cb.checkbox_state)            # "checked", "unchecked", or "unknown"
      label = cb.right(until='text').extract_text()  # label next to checkbox

IMPORTANT: Use .is_checked (NOT .checked). Use .checkbox_state for string version.

detect_checkboxes() injects [CHECKED]/[UNCHECKED] markers into extract_text() at their
spatial positions. This means subsequent table extraction and text extraction will
include these markers automatically.

## Exclusions

Remove headers, footers, watermarks from extraction:
  pdf.add_exclusion(lambda p: p.region(top=0, bottom=50), label="header")
  page.add_exclusion('text:contains("DRAFT")', label="watermark")

## OCR

  page.apply_ocr(engine="rapidocr")  # replaces native text automatically, returns page

Engine recommendations:
  rapidocr          — default, fast (~3-8s/page), clean printed English
  glm_ocr           — better OCR quality, two-step (layout detect + per-region OCR)
  paddle            — CJK documents, one language at a time
  doctr             — finest element granularity

Decision tree:
  1. Native text clean (check .to_llm() TEXT LAYER)? → Use it, no OCR needed
  2. Native text garbled? → page.apply_ocr(engine="rapidocr")
  3. Still garbled? → page.apply_ocr(engine="glm_ocr", layout="rapidocr")
     Word-by-word: slower but accurate, good element granularity for selectors
  4. Dense forms? → page.apply_ocr(engine="glm_ocr", layout="cluster")
     Section-by-section: much faster but returns blocks, better for regex extraction

layout= controls granularity: "rapidocr" detects word boxes then OCRs each (slow,
fine-grained), "cluster" groups into sections then OCRs each section (fast, blocks).

Escalation triggers in .to_llm() output:
  - Labels garbled beyond recognition ("FERGHE" for "HEIGHT")
  - Mixed case artifacts ("SOUth SAnTA" instead of "SOUTH SANTA")
  - Fused words ("ARFESTNGAGENCY" instead of "ARRESTING AGENCY")

OCR-tolerant selectors — use :ocr() instead of :contains() after OCR:
  page.find('text:ocr("Date received")')      # handles l/1, O/0, rn/m confusion
  page.find('text:ocr("Identification"@0.6)') # lower threshold for severe garbling

OCR elements: element.source == "ocr", element.confidence (0-1)

Deskewing tilted scans (do before OCR):
  deskewed_pdf = pdf.deskew()
  for page in deskewed_pdf.pages:
      page.apply_ocr(engine="rapidocr")

## Layout Analysis

  page.analyze_layout()              # detect regions (YOLO model)
  page.analyze_layout(engine="tatr") # Table Transformer (tables only)
  page.find_all('region[type=table]')
  page.find_all('region[type=text]')
  page.find_all('region[confidence>=0.8]')

Region types: text, title, table, figure, caption, header, footer, list, form

## Lines and Rectangles as Structure

Lines and rects define document structure — section dividers, field boxes, table borders.
  label.below(until='line[width>100]')            # stop at next horizontal rule
  page.find('rect:contains("Cost Information")')  # find section box by content
  rect.find('text:contains("Total")')             # search within a specific rect

When .to_llm() shows many rects with text inside, the page is likely a form with rect
boundaries defining each field. Use the rects directly:
  phone_rect = page.find('rect:contains("Telephone")')
  phone_value = phone_rect.extract_text()         # gets all text inside that rect boundary
This is more reliable than spatial nav on dense forms because each rect defines exact boundaries.
rect:contains() returns the smallest matching rect, so nested rects work correctly.

## Region Operations

  region.find('text:bold')           # search within a region
  region.find_all('text[size>10]')
  region.extract_text()
  region.extract_table()
  region.clip(left=x, right=x)      # constrain region bounds

## Common Patterns

Regex on scoped text:
  section = label.below(until='text:contains("Next")', include_endpoint=False)
  text = section.extract_text()
  match = re.search(r'pattern', text)

## Forbidden in Generated Scripts

Do NOT use these — they call LLMs at runtime and are not deterministic:
  page.ask(...)                      # LLM question answering
  page.extract(Schema, client=...)   # LLM structured extraction
  page.classify(...)                 # LLM classification

## Common Mistakes

  page.get_text()           → Use: page.extract_text()
  PDF.open("file.pdf")      → Use: PDF("file.pdf")
  cb.checked                → Use: cb.is_checked
  page.find(selector)       → Returns Element or None. Calling .right() on None crashes.
  page.find_all(selector)   → Always returns ElementCollection (may be empty, never None).
"""
