openapi: 3.1.0
info:
  title: Nahj al-Balagha Sermons API
  version: 1.0.0
  description: |
    A queryable HTTP API over the **240 sermons** of Imam Ali ibn Abi Talib (a.s.),
    compiled by al-Sharif al-Radi. Backed by Cloudflare Workers + D1 (SQLite) with an
    FTS5 full-text index over titles, English, and Arabic text.

    All responses are JSON with permissive CORS (`Access-Control-Allow-Origin: *`)
    and a 5-minute edge cache (`Cache-Control: public, max-age=300`).
  license:
    name: CC0 1.0 (public-domain source text — Nahj al-Balagha)
    identifier: CC0-1.0
servers:
  - url: https://nahj-sermons-api.automatehub.workers.dev
    description: Production
# This API requires no authentication; an empty top-level security declares that explicitly.
security: []
tags:
  - name: meta
    description: API info and corpus statistics
  - name: sermons
    description: Browse and retrieve sermons
  - name: search
    description: Full-text search
paths:
  /:
    get:
      operationId: getApiInfo
      tags: [meta]
      summary: API info and endpoint list
      responses:
        "200":
          description: Service description and available endpoints.
          content:
            application/json:
              schema:
                type: object
                properties:
                  name: { type: string }
                  description: { type: string }
                  endpoints:
                    type: object
                    additionalProperties: { type: string }

  /stats:
    get:
      operationId: getStats
      tags: [meta]
      summary: Book metadata and corpus statistics
      responses:
        "200":
          description: Metadata key/values plus aggregate statistics.
          content:
            application/json:
              schema:
                type: object
                properties:
                  metadata:
                    type: object
                    description: Key/value pairs from the source's metadata table.
                    additionalProperties: { type: string }
                    example:
                      book_title: Nahj al-Balagha (Peak of Eloquence)
                      compiler: Al-Sharif al-Radi
                      attributed_to: Imam Ali ibn Abi Talib (a.s.)
                      total_sermons: "240"
                  statistics:
                    type: object
                    properties:
                      total_sermons: { type: integer, example: 240 }
                      total_words: { type: integer, example: 89819 }
                      avg_words: { type: integer, example: 374 }
                      min_words: { type: integer, example: 45 }
                      max_words: { type: integer, example: 5002 }

  /sermons:
    get:
      operationId: listSermons
      tags: [sermons]
      summary: List and paginate sermons
      description: |
        Returns a paginated list of sermons. By default only lightweight fields
        (`id`, `title`, `title_arabic`, `word_count`) are returned; pass
        `fields=full` to include the full text bodies.
      parameters:
        - name: q
          in: query
          description: Case-insensitive substring filter on title and English text.
          schema: { type: string }
        - name: min_words
          in: query
          description: Only sermons with at least this many words.
          schema: { type: integer, minimum: 0 }
        - name: max_words
          in: query
          description: Only sermons with at most this many words.
          schema: { type: integer, minimum: 0 }
        - name: sort
          in: query
          description: Sort order.
          schema:
            type: string
            enum: [id, "-id", word_count, "-word_count", title]
            default: id
        - name: fields
          in: query
          description: Set to `full` to include `english_text`, `arabic_text`, and `subsections`.
          schema:
            type: string
            enum: [full]
        - { $ref: "#/components/parameters/limit" }
        - { $ref: "#/components/parameters/offset" }
      responses:
        "200":
          description: A page of sermons.
          content:
            application/json:
              schema:
                type: object
                properties:
                  total:
                    type: integer
                    description: Total sermons matching the filter (ignores pagination).
                  limit: { type: integer }
                  offset: { type: integer }
                  count:
                    type: integer
                    description: Number of sermons in this page.
                  sermons:
                    type: array
                    items: { $ref: "#/components/schemas/SermonSummary" }

  /sermons/{id}:
    get:
      operationId: getSermonById
      tags: [sermons]
      summary: Get a single sermon by id
      parameters:
        - name: id
          in: path
          required: true
          description: Sermon id, 1–240.
          schema: { type: integer, minimum: 1, maximum: 240 }
      responses:
        "200":
          description: The full sermon.
          content:
            application/json:
              schema:
                type: object
                properties:
                  sermon: { $ref: "#/components/schemas/Sermon" }
        "404":
          $ref: "#/components/responses/NotFound"

  /random:
    get:
      operationId: getRandomSermon
      tags: [sermons]
      summary: Get a random sermon
      responses:
        "200":
          description: A randomly selected sermon (full text).
          content:
            application/json:
              schema:
                type: object
                properties:
                  sermon: { $ref: "#/components/schemas/Sermon" }

  /search:
    get:
      operationId: searchSermons
      tags: [search]
      summary: Full-text search with ranked, highlighted snippets
      description: |
        Searches the FTS5 index. The `q` value supports FTS5 query syntax, e.g.
        `piety OR taqwa`, `"angel of death"`, `believ*`. Results are ordered by
        relevance (`score`, lower is more relevant) and include a `snippet` with
        matches wrapped in `<mark>…</mark>`.
      parameters:
        - name: q
          in: query
          required: true
          description: Search term (FTS5 syntax supported).
          schema: { type: string }
          example: piety
        - name: lang
          in: query
          description: Restrict matching to a single column. Omit to search all.
          schema:
            type: string
            enum: [en, ar]
        - { $ref: "#/components/parameters/limit" }
        - { $ref: "#/components/parameters/offset" }
      responses:
        "200":
          description: Ranked search results.
          content:
            application/json:
              schema:
                type: object
                properties:
                  query: { type: string }
                  lang:
                    type: string
                    description: Echoes the `lang` filter; `all` when omitted.
                    example: all
                  total:
                    type: integer
                    description: Total matching sermons (ignores pagination).
                  limit: { type: integer }
                  offset: { type: integer }
                  results:
                    type: array
                    items: { $ref: "#/components/schemas/SearchResult" }
        "400":
          $ref: "#/components/responses/BadRequest"

components:
  parameters:
    limit:
      name: limit
      in: query
      description: Max items to return (1–100).
      schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
    offset:
      name: offset
      in: query
      description: Number of items to skip.
      schema: { type: integer, minimum: 0, default: 0 }
  responses:
    NotFound:
      description: Resource not found.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { error: "Sermon 999 not found (valid range 1–240)" }
    BadRequest:
      description: Invalid request.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { error: "Missing required query parameter: q" }
  schemas:
    Error:
      type: object
      properties:
        error: { type: string }
      required: [error]
    SermonSummary:
      type: object
      description: Lightweight sermon record returned by list endpoints (no text bodies unless `fields=full`).
      properties:
        id: { type: integer, example: 1 }
        title:
          type: string
          example: Praise is due to Allah whose worth cannot be described
        title_arabic:
          type: [string, "null"]
        word_count: { type: integer, example: 2037 }
        english_text:
          type: string
          description: Present only when `fields=full`.
        arabic_text:
          type: [string, "null"]
          description: Present only when `fields=full`.
        subsections:
          type: [string, "null"]
          description: Present only when `fields=full`.
        source_url:
          type: [string, "null"]
          description: Present only when `fields=full`.
      required: [id, title, word_count]
    Sermon:
      type: object
      description: Full sermon record.
      properties:
        id: { type: integer, example: 1 }
        title: { type: string }
        title_arabic: { type: [string, "null"] }
        english_text: { type: string }
        arabic_text: { type: [string, "null"] }
        subsections:
          type: [string, "null"]
          description: JSON-encoded array of sub-parts, when present in the source.
        source_url: { type: [string, "null"] }
        word_count: { type: integer }
        created_at: { type: string }
      required: [id, title, english_text, word_count]
    SearchResult:
      type: object
      properties:
        id: { type: integer, example: 16 }
        title: { type: string }
        title_arabic: { type: [string, "null"] }
        word_count: { type: integer }
        snippet:
          type: string
          description: Excerpt with matched terms wrapped in <mark>…</mark>.
          example: "…Beware that <mark>piety</mark> is like trained horses…"
        score:
          type: number
          description: FTS5 relevance rank; lower (more negative) is more relevant.
      required: [id, title, snippet, score]
