"""PDF parsing and chapter detection for Condensr."""

from __future__ import annotations

import os
import re
import statistics
from dataclasses import dataclass

import pymupdf


@dataclass
class Chapter:
    """A detected chapter with title and page boundaries."""

    title: str
    start_page: int  # 0-indexed
    end_page: int  # 0-indexed, inclusive


def detect_chapters(pdf_path: str) -> list[Chapter]:
    """Detect chapter boundaries in a PDF.

    Detection strategy (in order):
    1. TOC extraction via doc.get_toc()
    2. Heading heuristics (bold text, large font, "Chapter N" patterns)
    3. No chapters found -> returns empty list

    No API calls are made. This is purely heuristic.

    Args:
        pdf_path: Path to the PDF file.

    Returns:
        List of Chapter dataclasses with title and page boundaries.

    Raises:
        ValueError: If the PDF is encrypted or contains no text.
    """
    doc = pymupdf.open(pdf_path)
    try:
        _validate_pdf(doc)
        last_page = len(doc) - 1

        chapters = _detect_from_toc(doc)
        if chapters:
            return chapters

        chapters = _detect_from_headings(doc)
        if chapters:
            return chapters

        return []
    finally:
        doc.close()


def extract_chapter_text(pdf_path: str, chapter: Chapter) -> str:
    """Extract plain text for a specific chapter by page range.

    Args:
        pdf_path: Path to the PDF file.
        chapter: Chapter with start_page and end_page boundaries.

    Returns:
        Concatenated text from all pages in the chapter's range.
    """
    doc = pymupdf.open(pdf_path)
    try:
        text_parts = []
        # end_page is inclusive, doc.pages stop is exclusive
        for page in doc.pages(chapter.start_page, chapter.end_page + 1):
            text_parts.append(page.get_text("text"))
        return "\n".join(text_parts)
    finally:
        doc.close()


def extract_full_text(pdf_path: str) -> str:
    """Extract all text from the PDF.

    Used when no chapters are detected and the entire book
    is summarized as one unit.

    Args:
        pdf_path: Path to the PDF file.

    Returns:
        Concatenated text from all pages.
    """
    doc = pymupdf.open(pdf_path)
    try:
        text_parts = []
        for page in doc:
            text_parts.append(page.get_text("text"))
        return "\n".join(text_parts)
    finally:
        doc.close()


def get_book_title(pdf_path: str) -> str:
    """Return book title from PDF metadata, or cleaned filename as fallback.

    Args:
        pdf_path: Path to the PDF file.

    Returns:
        The book title string.
    """
    doc = pymupdf.open(pdf_path)
    try:
        title = doc.metadata.get("title", "").strip()
        if title:
            return title
        # Fallback: filename without extension, capitalized
        basename = os.path.basename(pdf_path)
        name_without_ext = basename.rsplit(".", 1)[0]
        return name_without_ext.replace("-", " ").replace("_", " ").title()
    finally:
        doc.close()


def _validate_pdf(doc: pymupdf.Document) -> None:
    """Validate that the PDF is readable and contains text.

    Raises:
        ValueError: If the PDF is encrypted or has no extractable text.
    """
    if doc.is_encrypted:
        raise ValueError(
            "PDF is password-protected. Condensr does not support encrypted PDFs."
        )

    # Sample first few pages for text content
    sample_text = ""
    pages_to_check = min(len(doc), 10)
    for i in range(pages_to_check):
        sample_text += doc[i].get_text("text")
        if len(sample_text) >= 100:
            break

    if len(sample_text) < 100:
        raise ValueError(
            "No text content found. The PDF may be scanned/image-based."
        )


def _detect_from_toc(doc: pymupdf.Document) -> list[Chapter]:
    """Use doc.get_toc() to build chapter list with page boundaries.

    Filters to level 1 and level 2 entries. Converts 1-based page numbers
    to 0-based. Each chapter runs from its start page to the next chapter's
    start page minus one.
    """
    toc = doc.get_toc(simple=True)
    if not toc:
        return []

    # Filter to level 1 and level 2 entries only
    entries = [(title, page - 1) for level, title, page in toc if level <= 2]
    if not entries:
        return []

    last_page = len(doc) - 1
    chapters = []
    for i, (title, start_page) in enumerate(entries):
        if i + 1 < len(entries):
            end_page = entries[i + 1][1] - 1
        else:
            end_page = last_page
        # Clamp pages to valid range
        start_page = max(0, min(start_page, last_page))
        end_page = max(start_page, min(end_page, last_page))
        chapters.append(Chapter(title=title, start_page=start_page, end_page=end_page))

    return chapters


_CHAPTER_PATTERN = re.compile(
    r"^(chapter|part|section)\s+(\d+|[IVXLCDM]+)",
    re.IGNORECASE,
)


def _detect_from_headings(doc: pymupdf.Document) -> list[Chapter]:
    """Scan pages for heading-like text using font metadata.

    Looks for text spans that are significantly larger than the median font size
    or are bold and match common chapter title patterns.
    """
    # First pass: collect all font sizes to compute median
    all_sizes = []
    for page in doc:
        blocks = page.get_text("dict")["blocks"]
        for block in blocks:
            if block.get("type") != 0:  # text blocks only
                continue
            for line in block["lines"]:
                for span in line["spans"]:
                    text = span["text"].strip()
                    if text:
                        all_sizes.append(span["size"])

    if not all_sizes:
        return []

    median_size = statistics.median(all_sizes)
    size_threshold = median_size * 1.3

    # Second pass: find heading candidates
    candidates = []  # (page_index, title)
    for page_idx in range(len(doc)):
        page = doc[page_idx]
        blocks = page.get_text("dict")["blocks"]
        for block in blocks:
            if block.get("type") != 0:
                continue
            for line in block["lines"]:
                line_text_parts = []
                is_heading = False
                for span in line["spans"]:
                    text = span["text"].strip()
                    if not text:
                        continue
                    line_text_parts.append(text)
                    size = span["size"]
                    is_bold = bool(span["flags"] & 16)
                    # Heading if: large font, or bold + matches chapter pattern
                    if size >= size_threshold:
                        is_heading = True
                    elif is_bold and _CHAPTER_PATTERN.match(text):
                        is_heading = True

                if is_heading and line_text_parts:
                    title = " ".join(line_text_parts)
                    # Avoid duplicates on the same page
                    if not candidates or candidates[-1] != (page_idx, title):
                        candidates.append((page_idx, title))

    if not candidates:
        return []

    # Build chapters from candidates
    last_page = len(doc) - 1
    chapters = []
    for i, (start_page, title) in enumerate(candidates):
        if i + 1 < len(candidates):
            end_page = candidates[i + 1][0] - 1
        else:
            end_page = last_page
        end_page = max(start_page, end_page)
        chapters.append(Chapter(title=title, start_page=start_page, end_page=end_page))

    return chapters
