"""
CamelotLLMTableExtractor: the public face of the camelot.llm package.

DEPRECATED: superseded by `SetOfMarkExtractor` (vision-guided, in-region
Camelot, grounded correction). Do not build new work on it. It is a live
dependency of `quber validate`, which runs it with a mock classifier and
unifier and no structure correction for its raw Camelot table counts, and it
stays reachable through `quber table --engine camelot-llm`.

Wires run-scoped dependencies (LLM clients, semaphores, temp dir) into
`PipelineDeps` and runs the extraction graph built in `pipeline`.
"""

from __future__ import annotations

import asyncio
import tempfile
from pathlib import Path
from typing import List, Optional

from quber.agents.classifier import TableClassifier, get_classifier
from quber.agents.llm_client import LLMClient, get_llm_client
from quber.agents.unifier import TableUnifier, get_unifier
from quber.core.extractors.base import ExtractedTable
from quber.core.extractors.camelot.acquire import CAMELOT_FLAVOR_TIMEOUT_S
from quber.core.extractors.camelot.llm.pipeline import (
    PipelineDeps,
    PipelineState,
    build_pipeline_graph,
)


class CamelotLLMTableExtractor:
    llm: LLMClient
    classifier: TableClassifier
    unifier: TableUnifier
    dpi: int
    run_llm_correction: bool
    max_concurrent: int
    flavor_timeout_s: float

    def __init__(
        self,
        llm_client: Optional[LLMClient] = None,
        classifier: Optional[TableClassifier] = None,
        unifier: Optional[TableUnifier] = None,
        dpi: int = 200,
        run_llm_correction: bool = True,
        max_concurrent: int = 5,
        flavor_timeout_s: float = CAMELOT_FLAVOR_TIMEOUT_S,
    ) -> None:
        self.llm = llm_client or get_llm_client()
        self.classifier = classifier or get_classifier()
        self.unifier = unifier or get_unifier()
        self.dpi = dpi
        self.run_llm_correction = run_llm_correction
        self.max_concurrent = max_concurrent
        self.flavor_timeout_s = flavor_timeout_s

    async def extract_tables(self, source: Path) -> List[ExtractedTable]:
        source = Path(source)
        if not source.exists():
            raise FileNotFoundError(source)

        graph = build_pipeline_graph()
        with tempfile.TemporaryDirectory(prefix="quber-camelot-") as tmpdir:
            deps = PipelineDeps(
                llm=self.llm,
                classifier=self.classifier,
                unifier=self.unifier,
                source=source,
                tmp_dir=Path(tmpdir),
                dpi=self.dpi,
                run_llm_correction=self.run_llm_correction,
                flavor_timeout_s=self.flavor_timeout_s,
                classify_sem=asyncio.Semaphore(self.max_concurrent),
                unify_sem=asyncio.Semaphore(self.max_concurrent),
                correct_sem=asyncio.Semaphore(self.max_concurrent),
            )
            return await graph.run(state=PipelineState(), deps=deps, inputs=source)

    def extract_tables_sync(self, source: Path) -> List[ExtractedTable]:
        """Sync entry point for callers without an event loop (CLI,
        scripts). Wraps the async path with `asyncio.run`. Not callable
        from inside an existing event loop — async callers should use
        `await extract_tables(...)` directly.
        """
        return asyncio.run(self.extract_tables(source))
