"""
Document composition for MicroPDF (Platypus-style layout engine).

This module provides a high-level document composition API similar to
ReportLab's Platypus, allowing programmatic construction of PDF documents
with paragraphs, tables, images, lists, and table of contents.

Example usage:
    from micropdf.composition import DocTemplate, Paragraph, Table, Spacer

    template = DocTemplate("output.pdf")
    template.set_page_size(595.0, 842.0)  # A4
    template.set_margins(left=72, right=72, top=72, bottom=72)

    para = Paragraph("Hello, World!")
    para.set_font_size(14.0)

    spacer = Spacer(12.0)

    table = Table(3, 4)  # 3 rows, 4 columns
"""

from enum import IntEnum
from pathlib import Path
from typing import Optional, Union

from .ffi_native import lib, is_native_available
from .errors import MicroPDFError, ErrorCode


class Alignment(IntEnum):
    """Text alignment options."""
    LEFT = 0
    CENTER = 1
    RIGHT = 2
    JUSTIFY = 3


def _check_native() -> None:
    if not is_native_available():
        raise MicroPDFError(ErrorCode.SYSTEM, "Native library not available.")


def _check_result(result: int, operation: str) -> None:
    if result < 0:
        raise MicroPDFError(ErrorCode.GENERIC, f"{operation}: error code {result}")


class DocTemplate:
    """A document template that defines page layout and generates PDFs."""

    __slots__ = ('_handle',)

    def __init__(self, filename: Union[str, Path]) -> None:
        _check_native()
        self._handle = lib.mp_doc_template_create(str(filename).encode('utf-8'))
        if self._handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create document template")

    def set_page_size(self, width: float, height: float) -> 'DocTemplate':
        """Set the page size in points."""
        _check_result(
            lib.mp_doc_template_set_page_size(self._handle, width, height),
            "set_page_size"
        )
        return self

    def set_margins(
        self,
        left: float = 72.0,
        right: float = 72.0,
        top: float = 72.0,
        bottom: float = 72.0,
    ) -> 'DocTemplate':
        """Set page margins in points."""
        _check_result(
            lib.mp_doc_template_set_margins(self._handle, left, right, top, bottom),
            "set_margins"
        )
        return self

    def drop(self) -> None:
        if getattr(self, '_handle', 0) != 0:
            lib.mp_doc_template_free(self._handle)
            self._handle = 0

    def __del__(self) -> None:
        try:
            self.drop()
        except Exception:
            pass

    def __enter__(self) -> 'DocTemplate':
        return self

    def __exit__(self, *args: object) -> None:
        self.drop()


class Frame:
    """A rectangular area on a page where content can be placed."""

    __slots__ = ('_handle',)

    def __init__(
        self,
        frame_id: str,
        x: float,
        y: float,
        width: float,
        height: float,
    ) -> None:
        _check_native()
        self._handle = lib.mp_frame_create(
            frame_id.encode('utf-8'), x, y, width, height
        )
        if self._handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create frame")

    @property
    def available_width(self) -> float:
        """Get the available content width within the frame."""
        return lib.mp_frame_available_width(self._handle)

    @property
    def available_height(self) -> float:
        """Get the available content height within the frame."""
        return lib.mp_frame_available_height(self._handle)

    def drop(self) -> None:
        if getattr(self, '_handle', 0) != 0:
            lib.mp_frame_free(self._handle)
            self._handle = 0

    def __del__(self) -> None:
        try:
            self.drop()
        except Exception:
            pass

    def __enter__(self) -> 'Frame':
        return self

    def __exit__(self, *args: object) -> None:
        self.drop()


class Story:
    """A sequence of flowable elements that fill frames."""

    __slots__ = ('_handle',)

    def __init__(self) -> None:
        _check_native()
        self._handle = lib.mp_story_create()
        if self._handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create story")

    def __len__(self) -> int:
        return lib.mp_story_len(self._handle)

    def drop(self) -> None:
        if getattr(self, '_handle', 0) != 0:
            lib.mp_story_free(self._handle)
            self._handle = 0

    def __del__(self) -> None:
        try:
            self.drop()
        except Exception:
            pass

    def __enter__(self) -> 'Story':
        return self

    def __exit__(self, *args: object) -> None:
        self.drop()


class Paragraph:
    """A paragraph of text."""

    __slots__ = ('_handle',)

    def __init__(self, text: str) -> None:
        _check_native()
        self._handle = lib.mp_paragraph_create(text.encode('utf-8'))
        if self._handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create paragraph")

    def set_font_size(self, size: float) -> 'Paragraph':
        """Set the font size in points."""
        _check_result(
            lib.mp_paragraph_set_font_size(self._handle, size),
            "set_font_size"
        )
        return self

    def set_leading(self, leading: float) -> 'Paragraph':
        """Set the line spacing (leading) in points."""
        _check_result(
            lib.mp_paragraph_set_leading(self._handle, leading),
            "set_leading"
        )
        return self

    def drop(self) -> None:
        if getattr(self, '_handle', 0) != 0:
            lib.mp_paragraph_free(self._handle)
            self._handle = 0

    def __del__(self) -> None:
        try:
            self.drop()
        except Exception:
            pass

    def __enter__(self) -> 'Paragraph':
        return self

    def __exit__(self, *args: object) -> None:
        self.drop()


class Spacer:
    """A vertical space element."""

    __slots__ = ('_handle',)

    def __init__(self, height: float) -> None:
        _check_native()
        self._handle = lib.mp_spacer_create(height)
        if self._handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create spacer")

    def drop(self) -> None:
        if getattr(self, '_handle', 0) != 0:
            lib.mp_spacer_free(self._handle)
            self._handle = 0

    def __del__(self) -> None:
        try:
            self.drop()
        except Exception:
            pass

    def __enter__(self) -> 'Spacer':
        return self

    def __exit__(self, *args: object) -> None:
        self.drop()


class TableStyle:
    """Style configuration for a table."""

    __slots__ = ('_handle',)

    def __init__(self) -> None:
        _check_native()
        self._handle = lib.mp_table_style_create()
        if self._handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create table style")

    def add_grid(
        self,
        weight: float = 1.0,
        r: float = 0.0,
        g: float = 0.0,
        b: float = 0.0,
    ) -> 'TableStyle':
        """Add a grid with the specified line weight and color."""
        lib.mp_table_style_add_grid(self._handle, weight, r, g, b)
        return self

    def add_background(
        self,
        start_col: int,
        start_row: int,
        end_col: int,
        end_row: int,
        r: float = 0.9,
        g: float = 0.9,
        b: float = 0.9,
    ) -> 'TableStyle':
        """Add a background color to a cell range."""
        lib.mp_table_style_add_background(
            self._handle, start_col, start_row, end_col, end_row, r, g, b
        )
        return self

    def drop(self) -> None:
        if getattr(self, '_handle', 0) != 0:
            lib.mp_table_style_free(self._handle)
            self._handle = 0

    def __del__(self) -> None:
        try:
            self.drop()
        except Exception:
            pass

    def __enter__(self) -> 'TableStyle':
        return self

    def __exit__(self, *args: object) -> None:
        self.drop()


class Table:
    """A table layout element."""

    __slots__ = ('_handle',)

    def __init__(self, rows: int, cols: int) -> None:
        _check_native()
        self._handle = lib.mp_table_create(rows, cols)
        if self._handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create table")

    @property
    def num_rows(self) -> int:
        """Get the number of rows."""
        return lib.mp_table_num_rows(self._handle)

    @property
    def num_cols(self) -> int:
        """Get the number of columns."""
        return lib.mp_table_num_cols(self._handle)

    def drop(self) -> None:
        if getattr(self, '_handle', 0) != 0:
            lib.mp_table_free(self._handle)
            self._handle = 0

    def __del__(self) -> None:
        try:
            self.drop()
        except Exception:
            pass

    def __enter__(self) -> 'Table':
        return self

    def __exit__(self, *args: object) -> None:
        self.drop()


class TableOfContents:
    """A table of contents element."""

    __slots__ = ('_handle',)

    def __init__(self) -> None:
        _check_native()
        self._handle = lib.mp_toc_create()
        if self._handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create table of contents")

    def set_title(self, title: str) -> 'TableOfContents':
        """Set the TOC title."""
        lib.mp_toc_set_title(self._handle, title.encode('utf-8'))
        return self

    def add_entry(self, title: str, level: int, page: int) -> 'TableOfContents':
        """
        Add an entry to the table of contents.

        Args:
            title: Entry title text.
            level: Heading level (0-based).
            page: Page number.
        """
        lib.mp_toc_add_entry(self._handle, title.encode('utf-8'), level, page)
        return self

    def drop(self) -> None:
        if getattr(self, '_handle', 0) != 0:
            lib.mp_toc_free(self._handle)
            self._handle = 0

    def __del__(self) -> None:
        try:
            self.drop()
        except Exception:
            pass

    def __enter__(self) -> 'TableOfContents':
        return self

    def __exit__(self, *args: object) -> None:
        self.drop()


class TocBuilder:
    """Builder for constructing a table of contents incrementally."""

    __slots__ = ('_handle',)

    def __init__(self) -> None:
        _check_native()
        self._handle = lib.mp_toc_builder_create()
        if self._handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create TOC builder")

    def add_heading(self, title: str, level: int, page: int) -> 'TocBuilder':
        """Add a heading entry."""
        _check_result(
            lib.mp_toc_builder_add_heading(
                self._handle, title.encode('utf-8'), level, page
            ),
            "add_heading"
        )
        return self

    def drop(self) -> None:
        if getattr(self, '_handle', 0) != 0:
            lib.mp_toc_builder_free(self._handle)
            self._handle = 0

    def __del__(self) -> None:
        try:
            self.drop()
        except Exception:
            pass

    def __enter__(self) -> 'TocBuilder':
        return self

    def __exit__(self, *args: object) -> None:
        self.drop()


class ParagraphStyle:
    """A named paragraph style with font and layout properties."""

    __slots__ = ('_handle',)

    def __init__(self, name: str) -> None:
        _check_native()
        self._handle = lib.mp_paragraph_style_create(name.encode('utf-8'))
        if self._handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create paragraph style")

    def set_font_size(self, size: float) -> 'ParagraphStyle':
        """Set the font size in points."""
        lib.mp_paragraph_style_set_font_size(self._handle, size)
        return self

    def set_leading(self, leading: float) -> 'ParagraphStyle':
        """Set the line spacing in points."""
        lib.mp_paragraph_style_set_leading(self._handle, leading)
        return self

    def set_alignment(self, alignment: Alignment) -> 'ParagraphStyle':
        """Set the text alignment."""
        lib.mp_paragraph_style_set_alignment(self._handle, int(alignment))
        return self

    def drop(self) -> None:
        if getattr(self, '_handle', 0) != 0:
            lib.mp_paragraph_style_free(self._handle)
            self._handle = 0

    def __del__(self) -> None:
        try:
            self.drop()
        except Exception:
            pass

    def __enter__(self) -> 'ParagraphStyle':
        return self

    def __exit__(self, *args: object) -> None:
        self.drop()


class StyleSheet:
    """A collection of named paragraph styles."""

    __slots__ = ('_handle',)

    def __init__(self) -> None:
        _check_native()
        self._handle = lib.mp_stylesheet_create()
        if self._handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create stylesheet")

    def add_style(self, style: ParagraphStyle) -> 'StyleSheet':
        """Add a paragraph style to the stylesheet."""
        _check_result(
            lib.mp_stylesheet_add_style(self._handle, style._handle),
            "add_style"
        )
        return self

    def drop(self) -> None:
        if getattr(self, '_handle', 0) != 0:
            lib.mp_stylesheet_free(self._handle)
            self._handle = 0

    def __del__(self) -> None:
        try:
            self.drop()
        except Exception:
            pass

    def __enter__(self) -> 'StyleSheet':
        return self

    def __exit__(self, *args: object) -> None:
        self.drop()


class CompositionImage:
    """An image element for document composition."""

    __slots__ = ('_handle',)

    def __init__(self, path: Union[str, Path]) -> None:
        _check_native()
        self._handle = lib.mp_image_create(str(path).encode('utf-8'))
        if self._handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create image")

    def set_width(self, width: float) -> 'CompositionImage':
        """Set the image display width in points."""
        lib.mp_image_set_width(self._handle, width)
        return self

    def set_height(self, height: float) -> 'CompositionImage':
        """Set the image display height in points."""
        lib.mp_image_set_height(self._handle, height)
        return self

    def drop(self) -> None:
        if getattr(self, '_handle', 0) != 0:
            lib.mp_image_free(self._handle)
            self._handle = 0

    def __del__(self) -> None:
        try:
            self.drop()
        except Exception:
            pass

    def __enter__(self) -> 'CompositionImage':
        return self

    def __exit__(self, *args: object) -> None:
        self.drop()


class HorizontalRule:
    """A horizontal rule/separator element."""

    __slots__ = ('_handle',)

    def __init__(self) -> None:
        _check_native()
        self._handle = lib.mp_hr_create()
        if self._handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create horizontal rule")

    def set_thickness(self, thickness: float) -> 'HorizontalRule':
        """Set the rule thickness in points."""
        lib.mp_hr_set_thickness(self._handle, thickness)
        return self

    def drop(self) -> None:
        if getattr(self, '_handle', 0) != 0:
            lib.mp_hr_free(self._handle)
            self._handle = 0

    def __del__(self) -> None:
        try:
            self.drop()
        except Exception:
            pass

    def __enter__(self) -> 'HorizontalRule':
        return self

    def __exit__(self, *args: object) -> None:
        self.drop()


class ListItem:
    """A list item element (bullet or numbered)."""

    __slots__ = ('_handle',)

    def __init__(self, handle: int) -> None:
        self._handle = handle

    @classmethod
    def bullet(cls, text: str) -> 'ListItem':
        """Create a bullet list item."""
        _check_native()
        handle = lib.mp_list_item_bullet(text.encode('utf-8'))
        if handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create bullet list item")
        return cls(handle)

    @classmethod
    def numbered(cls, number: int, text: str) -> 'ListItem':
        """Create a numbered list item."""
        _check_native()
        handle = lib.mp_list_item_numbered(number, text.encode('utf-8'))
        if handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create numbered list item")
        return cls(handle)

    def drop(self) -> None:
        if getattr(self, '_handle', 0) != 0:
            lib.mp_list_item_free(self._handle)
            self._handle = 0

    def __del__(self) -> None:
        try:
            self.drop()
        except Exception:
            pass

    def __enter__(self) -> 'ListItem':
        return self

    def __exit__(self, *args: object) -> None:
        self.drop()


__all__ = [
    "Alignment",
    "DocTemplate",
    "Frame",
    "Story",
    "Paragraph",
    "Spacer",
    "TableStyle",
    "Table",
    "TableOfContents",
    "TocBuilder",
    "ParagraphStyle",
    "StyleSheet",
    "CompositionImage",
    "HorizontalRule",
    "ListItem",
]
