from __future__ import annotations

import re
from dataclasses import dataclass, field
from enum import StrEnum
from hashlib import sha1
from pathlib import PurePath, PureWindowsPath


_SLUG_PARTS = re.compile(r"[a-z0-9]+")


def _slugify(value: str) -> str:
    parts = _SLUG_PARTS.findall(value.lower())
    if parts:
        return "-".join(parts)
    digest = sha1(value.encode("utf-8")).hexdigest()[:10]
    return f"document-{digest}"


def _path_name(path: str) -> str:
    windows_name = PureWindowsPath(path).name
    return PurePath(windows_name).name


def _path_stem(path: str) -> str:
    windows_stem = PureWindowsPath(path).stem
    return PurePath(windows_stem).stem


@dataclass(frozen=True)
class DocumentIdentity:
    """Stable document identity and output naming seed."""

    source_path: str
    filename: str
    stem: str
    slug: str
    title: str | None = None
    asset_dir: str = "images"

    @classmethod
    def from_path(cls, source_path: str, title: str | None = None) -> "DocumentIdentity":
        stem = _path_stem(source_path)
        return cls(
            source_path=source_path,
            filename=_path_name(source_path),
            stem=stem,
            slug=_slugify(title or stem),
            title=title,
        )

    def chunk_filename(self, index: int) -> str:
        if index < 1:
            raise ValueError("chunk index must be >= 1")
        return f"{self.slug}_{index:03d}.md"

    def figure_asset_filename(self, figure_number: str) -> str:
        normalized = _slugify(figure_number)
        return f"{self.slug}_fig-{normalized}.png"


@dataclass(frozen=True)
class PageRange:
    """Inclusive one-based page range."""

    start: int
    end: int

    def __post_init__(self) -> None:
        if self.start < 1:
            raise ValueError("page range start must be >= 1")
        if self.end < self.start:
            raise ValueError("page range end must be >= start")

    @property
    def count(self) -> int:
        return self.end - self.start + 1

    @property
    def label(self) -> str:
        return str(self.start) if self.start == self.end else f"{self.start}-{self.end}"

    def contains(self, page: int) -> bool:
        return self.start <= page <= self.end


@dataclass(frozen=True)
class BoundingBox:
    """Page-local bounding box in parser coordinate units."""

    page: int
    x0: float
    y0: float
    x1: float
    y1: float

    def __post_init__(self) -> None:
        if self.page < 1:
            raise ValueError("bounding box page must be >= 1")
        if self.x1 <= self.x0:
            raise ValueError("bounding box x1 must be greater than x0")
        if self.y1 <= self.y0:
            raise ValueError("bounding box y1 must be greater than y0")

    @property
    def width(self) -> float:
        return self.x1 - self.x0

    @property
    def height(self) -> float:
        return self.y1 - self.y0


class BlockRole(StrEnum):
    HEADING = "heading"
    PARAGRAPH = "paragraph"
    LIST_ITEM = "list_item"
    TABLE = "table"
    FIGURE = "figure"
    CAPTION = "caption"
    FORMULA = "formula"
    FOOTNOTE = "footnote"
    HEADER = "header"
    FOOTER = "footer"
    UNKNOWN = "unknown"


class AssetKind(StrEnum):
    FIGURE = "figure"
    TABLE_IMAGE = "table_image"
    PAGE_IMAGE = "page_image"
    OTHER = "other"


@dataclass(frozen=True)
class Formula:
    id: str
    latex: str
    display: bool
    source_text: str
    number: str | None = None
    confidence: float | None = None


@dataclass(frozen=True)
class Table:
    id: str
    rows: tuple[tuple[str, ...], ...] = field(default_factory=tuple)
    caption: str | None = None
    number: str | None = None
    html: str | None = None
    fallback_asset_id: str | None = None


@dataclass(frozen=True)
class Figure:
    id: str
    caption: str | None = None
    number: str | None = None
    asset_id: str | None = None


@dataclass(frozen=True)
class Asset:
    id: str
    kind: AssetKind
    relative_path: str
    page: int
    bbox: BoundingBox | None = None
    content_hash: str | None = None

    def __post_init__(self) -> None:
        if self.page < 1:
            raise ValueError("asset page must be >= 1")


@dataclass(frozen=True)
class DocumentBlock:
    id: str
    role: BlockRole
    page_range: PageRange
    text: str = ""
    bbox: BoundingBox | None = None
    children: tuple[str, ...] = field(default_factory=tuple)
    formula: Formula | None = None
    table: Table | None = None
    figure: Figure | None = None
    anchor: str | None = None


@dataclass(frozen=True)
class PageAnalysis:
    page: int
    text_length: int
    image_count: int
    has_text_layer: bool
    needs_ocr: bool

    def __post_init__(self) -> None:
        if self.page < 1:
            raise ValueError("page must be >= 1")
        if self.text_length < 0:
            raise ValueError("text_length must be >= 0")
        if self.image_count < 0:
            raise ValueError("image_count must be >= 0")

    @property
    def text_layer_quality(self) -> str:
        if not self.has_text_layer or self.needs_ocr:
            return "ocr"
        if self.text_length == 0:
            return "empty"
        return "text"


@dataclass(frozen=True)
class ChunkMetadata:
    index: int
    document: DocumentIdentity
    page_range: PageRange
    block_ids: tuple[str, ...] = field(default_factory=tuple)
    asset_ids: tuple[str, ...] = field(default_factory=tuple)

    def __post_init__(self) -> None:
        if self.index < 1:
            raise ValueError("chunk index must be >= 1")

    @property
    def slug(self) -> str:
        return self.document.slug

    @property
    def filename(self) -> str:
        return self.document.chunk_filename(self.index)
