# Architecture

## Overview

`axm-bib` follows a layered architecture with clear separation of concerns:

```mermaid
graph TD
    subgraph "User Interfaces"
        CLI["CLI (cyclopts)"]
        MCP["MCP Tools"]
    end

    subgraph "Orchestration"
        Pipeline["BibPipeline"]
    end

    subgraph "Processing"
        Extract["extract_pdf (PyMuPDF)"]
        Storage["FileSystemStorage"]
    end

    subgraph "API Clients"
        S2["SemanticScholarClient"]
        CR["CrossRefClient"]
        UW["UnpaywallClient"]
    end

    subgraph "External"
        S2API["Semantic Scholar API"]
        CRAPI["CrossRef API"]
        UWAPI["Unpaywall API"]
        ArXiv["arXiv.org"]
    end

    CLI --> Pipeline
    MCP --> Pipeline
    Pipeline --> S2
    Pipeline --> CR
    Pipeline --> UW
    Pipeline --> Extract
    Pipeline --> Storage
    S2 --> S2API
    CR --> CRAPI
    UW --> UWAPI
    Pipeline -.->|"arXiv fallback"| ArXiv
```

## Layers

### 1. User Interfaces

- **CLI** (`cli.py`) — `cyclopts`-based commands with input validation and formatted output
- **MCP Tools** (`tools/`) — `AXMTool` subclasses (from `axm.tools.base`) auto-discovered via `axm.tools` entry points

Both interfaces delegate to the pipeline layer — they handle input/output formatting only.

### 2. Pipeline

`BibPipeline` orchestrates calls across clients:

- **`search()`** — Tries Semantic Scholar, falls back to CrossRef
- **`doi_to_bibtex()`** — Calls CrossRef
- **`download_pdf()`** — Full pipeline: Unpaywall → download → extract → BibTeX

Validation (limit clamping, error handling) lives here.

### 3. Processing

- **`extract_pdf()`** — Converts PDF to Markdown + figures using PyMuPDF
- **`FileSystemStorage`** — Manages the paper folder layout (`{key}/{key}.pdf`, `paper.bib`)

### 4. API Clients

Each client wraps a single external API using `httpx`:

| Client | API | Transport |
|---|---|---|
| `SemanticScholarClient` | S2 Graph API | `httpx.get` |
| `CrossRefClient` | CrossRef REST | `habanero` |
| `UnpaywallClient` | Unpaywall REST | `httpx.get` |

### 5. Models

`Paper`, `BibEntry`, `DownloadResult`, `ExtractionResult`, `FigureInfo` — Pydantic dataclasses shared across all layers.

## Data Flow: PDF Pipeline

```mermaid
sequenceDiagram
    participant User
    participant CLI
    participant Pipeline
    participant Unpaywall
    participant CrossRef
    participant Storage
    participant Extract

    User->>CLI: axm-bib pdf DOI
    CLI->>Pipeline: download_pdf(DOI)
    Pipeline->>Unpaywall: get_pdf_urls(DOI)
    alt Unpaywall has URLs
        Unpaywall-->>Pipeline: [url1, url2]
    else No URLs + arXiv DOI
        Pipeline->>Pipeline: arxiv.org/pdf/{id} fallback
    end
    Pipeline->>CrossRef: doi_to_bibtex(DOI)
    CrossRef-->>Pipeline: BibEntry
    Pipeline->>Pipeline: httpx.get(url)
    Pipeline->>Storage: save(pdf_bytes, key)
    Storage-->>Pipeline: {key}/{key}.pdf
    Pipeline->>Storage: save_bibtex(key, raw)
    Storage-->>Pipeline: {key}/paper.bib
    Pipeline->>Extract: extract_pdf(pdf, dir)
    Extract-->>Pipeline: ExtractionResult
    Pipeline-->>CLI: DownloadResult
    CLI->>User: Formatted output
```

## Data Flow: Search

```mermaid
sequenceDiagram
    participant User
    participant CLI
    participant Pipeline
    participant S2
    participant CrossRef

    User->>CLI: axm-bib search "query" -n 5
    CLI->>CLI: Validate limit (1-100)
    CLI->>Pipeline: search("query", limit=5)
    Pipeline->>S2: GET /paper/search?query=...&fields=...
    alt S2 succeeds
        S2-->>Pipeline: list[Paper]
    else S2 fails
        Pipeline->>CrossRef: search_by_title("query", limit=5)
        CrossRef-->>Pipeline: list[Paper]
    end
    Pipeline-->>CLI: list[Paper]
    CLI->>User: Formatted output
```
