# SPDX-FileCopyrightText: 2022 James R. Barlow
# SPDX-License-Identifier: MPL-2.0

"""Defines context objects that are passed to child processes/threads."""

from __future__ import annotations

from collections.abc import Iterator
from pathlib import Path
from typing import TYPE_CHECKING

from ocrmypdf._options import OcrOptions
from ocrmypdf.pdfinfo import PdfInfo
from ocrmypdf.pdfinfo.info import PageInfo

if TYPE_CHECKING:
    from ocrmypdf._plugin_manager import OcrmypdfPluginManager


class PdfContext:
    """Holds the context for a particular run of the pipeline."""

    options: OcrOptions  #: The specified options for processing this PDF.
    origin: Path  #: The filename of the original input file.
    pdfinfo: PdfInfo  #: Detailed data for this PDF.
    plugin_manager: (
        OcrmypdfPluginManager  #: PluginManager for processing the current PDF.
    )

    def __init__(
        self,
        options: OcrOptions,
        work_folder: Path,
        origin: Path,
        pdfinfo: PdfInfo,
        plugin_manager,
    ):
        self.options = options
        self.options.work_folder = work_folder
        self.work_folder = work_folder
        self.origin = origin
        self.pdfinfo = pdfinfo
        self.plugin_manager = plugin_manager

    def get_path(self, name: str) -> Path:
        """Generate a ``Path`` for an intermediate file involved in processing.

        The path will be in a temporary folder that is common for all processing
        of this particular PDF.
        """
        return self.work_folder / name

    def get_page_contexts(self) -> Iterator[PageContext]:
        """Get all ``PageContext`` for this PDF."""
        npages = len(self.pdfinfo)
        for n in range(npages):
            yield PageContext(self, n)

    def get_page_context_args(self) -> Iterator[tuple[PageContext]]:
        """Get all ``PageContext`` for this PDF packaged in tuple for args-splatting."""
        npages = len(self.pdfinfo)
        for n in range(npages):
            yield (PageContext(self, n),)


class PageContext:
    """Holds our context for a page.

    Must be pickle-able, so stores only intrinsic/simple data elements or those
    capable of their serializing themselves via ``__getstate__``.

    Note: Uses OcrOptions with JSON serialization for multiprocessing compatibility.
    """

    origin: Path  #: The filename of the original input file.
    pageno: int  #: This page number (zero-based).
    pageinfo: PageInfo  #: Information on this page.
    plugin_manager: (
        OcrmypdfPluginManager  #: PluginManager for processing the current PDF.
    )

    def __init__(self, pdf_context: PdfContext, pageno):
        self.work_folder = pdf_context.work_folder
        self.origin = pdf_context.origin
        # Store OcrOptions directly instead of Namespace
        self.options = pdf_context.options
        self.pageno = pageno
        self.pageinfo = pdf_context.pdfinfo[pageno]
        self.plugin_manager = pdf_context.plugin_manager
        # Ensure no reference to PdfContext which contains OcrOptions
        self._pdf_context = None

    def get_path(self, name: str) -> Path:
        """Generate a ``Path`` for a file that is part of processing this page.

        The path will be based in a common temporary folder and have a prefix based
        on the page number.
        """
        return self.work_folder / f"{(self.pageno + 1):06d}_{name}"

    def __getstate__(self):
        state = self.__dict__.copy()

        options_json = self.options.model_dump_json_safe()
        state['options_json'] = options_json
        # Remove the OcrOptions object to avoid pickle issues
        del state['options']

        # Remove any potential references to Pydantic objects
        state.pop('_pdf_context', None)
        return state

    def __setstate__(self, state):
        self.__dict__.update(state)

        # Reconstruct OcrOptions from JSON if available
        if 'options_json' in state:
            from ocrmypdf._options import OcrOptions

            self.options = OcrOptions.model_validate_json_safe(state['options_json'])
        # Otherwise, we have a fallback Namespace (shouldn't happen in normal operation)
        # Leave it as-is for compatibility
