"""Core types for paper2md pipeline."""
from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Callable, Optional


@dataclass
class BBox:
    x0: float
    y0: float
    x1: float
    y1: float

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

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

    @property
    def area(self) -> float:
        return self.width * self.height

    @property
    def midx(self) -> float:
        return (self.x0 + self.x1) / 2

    @property
    def midy(self) -> float:
        return (self.y0 + self.y1) / 2

    def overlaps(self, other: BBox, threshold: float = 0.5) -> bool:
        ix0 = max(self.x0, other.x0)
        iy0 = max(self.y0, other.y0)
        ix1 = min(self.x1, other.x1)
        iy1 = min(self.y1, other.y1)
        if ix1 <= ix0 or iy1 <= iy0:
            return False
        intersection = (ix1 - ix0) * (iy1 - iy0)
        return intersection / min(self.area, other.area) > threshold if min(self.area, other.area) > 0 else False

    def contains_y(self, y: float) -> bool:
        return self.y0 <= y <= self.y1

    @classmethod
    def from_tuple(cls, t: tuple) -> BBox:
        return cls(t[0], t[1], t[2], t[3])


@dataclass
class Span:
    text: str
    font: str
    size: float
    flags: int
    color: int
    bbox: BBox
    origin: tuple[float, float] = (0, 0)

    @property
    def is_bold(self) -> bool:
        return bool(self.flags & 2**4) or "Bold" in self.font or "Medi" in self.font or "bold" in self.font.lower()

    @property
    def is_italic(self) -> bool:
        return bool(self.flags & 2**1) or "Italic" in self.font or "Oblique" in self.font


@dataclass
class TextLine:
    spans: list[Span]
    bbox: BBox

    @property
    def text(self) -> str:
        return "".join(s.text for s in self.spans)

    @property
    def dominant_font(self) -> str:
        if not self.spans:
            return ""
        by_len = {}
        for s in self.spans:
            by_len[s.font] = by_len.get(s.font, 0) + len(s.text)
        return max(by_len, key=by_len.get)

    @property
    def dominant_size(self) -> float:
        if not self.spans:
            return 0.0
        by_len: dict[float, int] = {}
        for s in self.spans:
            by_len[s.size] = by_len.get(s.size, 0) + len(s.text)
        return max(by_len, key=by_len.get)


class BlockType(Enum):
    TITLE = auto()
    HEADING = auto()
    SUBHEADING = auto()
    BODY = auto()
    TABLE = auto()
    FIGURE = auto()
    CAPTION = auto()
    EQUATION = auto()
    FOOTNOTE = auto()
    HEADER = auto()
    FOOTER = auto()
    REFERENCE = auto()
    ABSTRACT = auto()
    LIST_ITEM = auto()
    METADATA = auto()


@dataclass
class DocumentBlock:
    block_type: BlockType
    text: str
    bbox: BBox
    page_num: int
    lines: list[TextLine] = field(default_factory=list)
    level: int = 0  # heading level
    metadata: dict = field(default_factory=dict)


@dataclass
class TableCell:
    text: str
    row: int
    col: int
    bbox: Optional[BBox] = None


@dataclass
class Table:
    cells: list[list[str]]  # rows x cols
    caption: str = ""
    bbox: Optional[BBox] = None
    page_num: int = 0

    @property
    def num_rows(self) -> int:
        return len(self.cells)

    @property
    def num_cols(self) -> int:
        return max((len(r) for r in self.cells), default=0)


@dataclass
class Figure:
    image_index: int
    bbox: Optional[BBox] = None
    caption: str = ""
    page_num: int = 0
    is_vector: bool = False
    image_data: Optional[bytes] = None  # PNG data for rendered vector figures
    xref: Optional[int] = None  # PDF image cross-reference for extraction


@dataclass
class Reference:
    raw: str
    number: Optional[int] = None
    authors: str = ""
    year: str = ""
    title: str = ""
    venue: str = ""


@dataclass
class PipelineConfig:
    extract_tables: bool = True
    extract_figures: bool = True
    extract_equations: bool = True
    extract_references: bool = True
    detect_vector_figures: bool = True
    ocr_enabled: bool = True
    ocr_dpi: int = 300
    ocr_lang: str = "eng"
    math_fallback_to_image: bool = True
    heading_size_threshold: float = 1.15  # ratio vs body size
    body_font_threshold: float = 0.3  # min fraction to be body
    column_gap_threshold: float = 20.0  # pts
    footnote_size_ratio: float = 0.85
    page_margin: float = 50.0  # pts from edge to ignore headers/footers
    progress_callback: Optional[Callable[[str, int, int], None]] = None
    # callback(stage: str, current: int, total: int)


@dataclass
class ConversionResult:
    markdown: str
    title: str = ""
    authors: list[str] = field(default_factory=list)
    abstract: str = ""
    tables: list[Table] = field(default_factory=list)
    figures: list[Figure] = field(default_factory=list)
    references: list[Reference] = field(default_factory=list)
    page_count: int = 0
    warnings: list[str] = field(default_factory=list)
