"""Run the visual and Camelot flows side by side for one document.

Both flows run concurrently and each returns its own result; the extractor
hands back a `DualResult` carrying both, unmatched. There is no detector,
no box repair, no targeted recovery, and no grid escalation — the two
flows are independent first-class inputs, to be cross-referenced later.
"""

from __future__ import annotations

import asyncio
from pathlib import Path
from typing import Optional

from quber.agents.classifier import TableClassifier, get_classifier
from quber.agents.grid_locator import GridLocator, get_grid_locator
from quber.core.extractors.dual.camelot import run_camelot_flow
from quber.core.extractors.dual.models import DualResult
from quber.core.extractors.dual.vision import run_vision_flow


class DualFlowExtractor:
    """Produce a document's visual tables and Camelot grids as parallel flows.

    `locator` and `classifier` default to the configured API backends but
    can be injected (e.g. mocks in tests). `classify_camelot` controls only
    whether the Camelot grids get an is-table verdict attached; it never
    changes which grids are returned.
    """

    def __init__(
        self,
        locator: Optional[GridLocator] = None,
        classifier: Optional[TableClassifier] = None,
        dpi: int = 200,
        classify_camelot: bool = True,
    ) -> None:
        self.locator = locator or get_grid_locator()
        self.classifier = classifier or get_classifier()
        self.dpi = dpi
        self.classify_camelot = classify_camelot

    async def run(self, source: Path) -> DualResult:
        classifier = self.classifier if self.classify_camelot else None
        visual, camelot = await asyncio.gather(
            run_vision_flow(source, self.locator, dpi=self.dpi),
            run_camelot_flow(source, classifier),
        )
        return DualResult(source=str(source), visual=visual, camelot=camelot)

    def run_sync(self, source: Path) -> DualResult:
        return asyncio.run(self.run(source))
