"""
Office document support for MicroPDF (DOCX, XLSX, PPTX).

This module provides creation and manipulation of Office documents
(Word, Excel, PowerPoint) through the MicroPDF native library.

Example usage:
    from micropdf.office import OfficeDocument, DocumentType

    # Create a Word document
    doc = OfficeDocument.new_docx(ctx)
    doc.set_title("My Document")
    doc.add_heading("Introduction", level=1)
    doc.add_paragraph("Hello, World!")

    # Create a spreadsheet
    xlsx = OfficeDocument.new_xlsx(ctx)
    xlsx.add_sheet("Sheet1")
    xlsx.set_cell_string(0, 0, 0, "Name")
    xlsx.set_cell_number(0, 0, 1, 42.0)

    # Create a presentation
    pptx = OfficeDocument.new_pptx(ctx)
    pptx.add_slide()
    pptx.set_slide_title(0, "Title Slide")
"""

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

from .ffi_native import ffi, lib, is_native_available
from .context import Context
from .errors import MicroPDFError, ErrorCode


class DocumentType(IntEnum):
    """Office document types."""
    DOCX = 0
    XLSX = 1
    PPTX = 2


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 OfficeDocument:
    """An Office document (DOCX, XLSX, or PPTX)."""

    __slots__ = ('_handle', '_ctx')

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

    @classmethod
    def new_docx(cls, ctx: Context) -> 'OfficeDocument':
        """Create a new Word document."""
        _check_native()
        handle = lib.office_new_docx(ctx._handle)
        if handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create DOCX document")
        return cls(handle, ctx)

    @classmethod
    def new_xlsx(cls, ctx: Context) -> 'OfficeDocument':
        """Create a new Excel spreadsheet."""
        _check_native()
        handle = lib.office_new_xlsx(ctx._handle)
        if handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create XLSX document")
        return cls(handle, ctx)

    @classmethod
    def new_pptx(cls, ctx: Context) -> 'OfficeDocument':
        """Create a new PowerPoint presentation."""
        _check_native()
        handle = lib.office_new_pptx(ctx._handle)
        if handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, "Failed to create PPTX document")
        return cls(handle, ctx)

    @classmethod
    def new(cls, ctx: Context, doc_type: DocumentType) -> 'OfficeDocument':
        """Create a new Office document of the specified type."""
        _check_native()
        handle = lib.office_new_document(ctx._handle, int(doc_type))
        if handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, f"Failed to create {doc_type.name} document")
        return cls(handle, ctx)

    @classmethod
    def open(cls, ctx: Context, filename: Union[str, Path]) -> 'OfficeDocument':
        """Open an existing Office document."""
        _check_native()
        handle = lib.office_open_document(ctx._handle, str(filename).encode('utf-8'))
        if handle == 0:
            raise MicroPDFError(ErrorCode.GENERIC, f"Failed to open document: {filename}")
        return cls(handle, ctx)

    @property
    def doc_type(self) -> DocumentType:
        """Get the document type."""
        return DocumentType(lib.office_get_type(self._ctx._handle, self._handle))

    @property
    def page_count(self) -> int:
        """Get the number of pages/slides/sheets."""
        result = lib.office_page_count(self._ctx._handle, self._handle)
        if result < 0:
            _check_result(result, "page_count")
        return result

    def get_page_size(self) -> tuple[float, float]:
        """Get the page size as (width, height) in points."""
        width_ptr = ffi.new("float*")
        height_ptr = ffi.new("float*")
        _check_result(
            lib.office_get_page_size(self._ctx._handle, self._handle, width_ptr, height_ptr),
            "get_page_size"
        )
        return (width_ptr[0], height_ptr[0])

    def set_page_size(self, width: float, height: float) -> None:
        """Set the page size in points."""
        _check_result(
            lib.office_set_page_size(self._ctx._handle, self._handle, width, height),
            "set_page_size"
        )

    @property
    def title(self) -> Optional[str]:
        """Get the document title."""
        ptr = lib.office_get_title(self._ctx._handle, self._handle)
        if ptr == ffi.NULL:
            return None
        try:
            return ffi.string(ptr).decode('utf-8')
        finally:
            lib.office_free_string(ptr)

    def set_title(self, title: str) -> None:
        """Set the document title."""
        _check_result(
            lib.office_set_title(self._ctx._handle, self._handle, title.encode('utf-8')),
            "set_title"
        )

    @property
    def creator(self) -> Optional[str]:
        """Get the document creator/author."""
        ptr = lib.office_get_creator(self._ctx._handle, self._handle)
        if ptr == ffi.NULL:
            return None
        try:
            return ffi.string(ptr).decode('utf-8')
        finally:
            lib.office_free_string(ptr)

    def set_creator(self, creator: str) -> None:
        """Set the document creator/author."""
        _check_result(
            lib.office_set_creator(self._ctx._handle, self._handle, creator.encode('utf-8')),
            "set_creator"
        )

    # === DOCX-specific methods ===

    def add_paragraph(self, text: str) -> None:
        """Add a paragraph to a DOCX document."""
        _check_result(
            lib.office_add_paragraph(self._ctx._handle, self._handle, text.encode('utf-8')),
            "add_paragraph"
        )

    def add_heading(self, text: str, level: int = 1) -> None:
        """Add a heading to a DOCX document."""
        _check_result(
            lib.office_add_heading(
                self._ctx._handle, self._handle, text.encode('utf-8'), level
            ),
            "add_heading"
        )

    @property
    def content_count(self) -> int:
        """Get the number of content elements in a DOCX document."""
        result = lib.office_content_count(self._ctx._handle, self._handle)
        if result < 0:
            _check_result(result, "content_count")
        return result

    # === XLSX-specific methods ===

    def add_sheet(self, name: str) -> None:
        """Add a sheet to an XLSX document."""
        _check_result(
            lib.office_add_sheet(self._ctx._handle, self._handle, name.encode('utf-8')),
            "add_sheet"
        )

    @property
    def sheet_count(self) -> int:
        """Get the number of sheets in an XLSX document."""
        result = lib.office_sheet_count(self._ctx._handle, self._handle)
        if result < 0:
            _check_result(result, "sheet_count")
        return result

    def get_sheet_name(self, sheet_idx: int) -> Optional[str]:
        """Get the name of a sheet by index."""
        ptr = lib.office_get_sheet_name(self._ctx._handle, self._handle, sheet_idx)
        if ptr == ffi.NULL:
            return None
        try:
            return ffi.string(ptr).decode('utf-8')
        finally:
            lib.office_free_string(ptr)

    def set_cell_string(self, sheet_idx: int, row: int, col: int, value: str) -> None:
        """Set a cell value as a string."""
        _check_result(
            lib.office_set_cell_string(
                self._ctx._handle, self._handle, sheet_idx, row, col, value.encode('utf-8')
            ),
            "set_cell_string"
        )

    def set_cell_number(self, sheet_idx: int, row: int, col: int, value: float) -> None:
        """Set a cell value as a number."""
        _check_result(
            lib.office_set_cell_number(
                self._ctx._handle, self._handle, sheet_idx, row, col, value
            ),
            "set_cell_number"
        )

    def get_cell_string(self, sheet_idx: int, row: int, col: int) -> Optional[str]:
        """Get a cell value as a string."""
        ptr = lib.office_get_cell_string(
            self._ctx._handle, self._handle, sheet_idx, row, col
        )
        if ptr == ffi.NULL:
            return None
        try:
            return ffi.string(ptr).decode('utf-8')
        finally:
            lib.office_free_string(ptr)

    # === PPTX-specific methods ===

    def add_slide(self) -> None:
        """Add a slide to a PPTX document."""
        _check_result(
            lib.office_add_slide(self._ctx._handle, self._handle),
            "add_slide"
        )

    @property
    def slide_count(self) -> int:
        """Get the number of slides in a PPTX document."""
        result = lib.office_slide_count(self._ctx._handle, self._handle)
        if result < 0:
            _check_result(result, "slide_count")
        return result

    def set_slide_title(self, slide_num: int, title: str) -> None:
        """Set the title of a slide."""
        _check_result(
            lib.office_set_slide_title(
                self._ctx._handle, self._handle, slide_num, title.encode('utf-8')
            ),
            "set_slide_title"
        )

    def get_slide_title(self, slide_num: int) -> Optional[str]:
        """Get the title of a slide."""
        ptr = lib.office_get_slide_title(self._ctx._handle, self._handle, slide_num)
        if ptr == ffi.NULL:
            return None
        try:
            return ffi.string(ptr).decode('utf-8')
        finally:
            lib.office_free_string(ptr)

    # === Utility ===

    def drop(self) -> None:
        """Release the document resources."""
        if getattr(self, '_handle', 0) != 0:
            lib.office_drop_document(self._ctx._handle, self._handle)
            self._handle = 0

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

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

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


def type_name(ctx: Context, doc_type: DocumentType) -> Optional[str]:
    """Get the human-readable name of a document type."""
    _check_native()
    ptr = lib.office_type_name(ctx._handle, int(doc_type))
    if ptr == ffi.NULL:
        return None
    try:
        return ffi.string(ptr).decode('utf-8')
    finally:
        lib.office_free_string(ptr)


def type_extension(ctx: Context, doc_type: DocumentType) -> Optional[str]:
    """Get the file extension for a document type."""
    _check_native()
    ptr = lib.office_type_extension(ctx._handle, int(doc_type))
    if ptr == ffi.NULL:
        return None
    try:
        return ffi.string(ptr).decode('utf-8')
    finally:
        lib.office_free_string(ptr)


__all__ = [
    "DocumentType",
    "OfficeDocument",
    "type_name",
    "type_extension",
]
