"""Camelot flow: pull cell grids straight from the PDF.

Runs both Camelot flavors over the whole document, drops only the
content-empty shells lattice leaves on whitespace-aligned pages, and tags
each surviving grid with an is-table verdict. The verdict is METADATA: it
is recorded for later cross-reference, never used to drop a candidate —
deciding what is a real table is the visual flow's job, not Camelot's.
"""

from __future__ import annotations

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

from quber.agents.classifier import TableClassifier
from quber.core.extractors.camelot.acquire import (
    CamelotCandidate,
    is_content_empty,
    run_camelot_flavors_parallel,
)
from quber.core.extractors.dual.models import CamelotTable


async def run_camelot_flow(
    source: Path,
    classifier: Optional[TableClassifier] = None,
    concurrency: int = 8,
) -> List[CamelotTable]:
    """Extract Camelot grids from `source`, tagging each with an is-table verdict.

    With no classifier, the grids are returned untagged (`classification`
    None). The content-empty shells are always dropped; nothing else is.
    """
    candidates = await asyncio.to_thread(run_camelot_flavors_parallel, source)
    kept = [c for c in candidates if not is_content_empty(c.markdown)]

    if classifier is None:
        return [CamelotTable(candidate=c, classification=None) for c in kept]

    semaphore = asyncio.Semaphore(concurrency)

    async def tag(candidate: CamelotCandidate) -> CamelotTable:
        async with semaphore:
            verdict = await classifier.classify(candidate.markdown)
        return CamelotTable(candidate=candidate, classification=verdict)

    return list(await asyncio.gather(*(tag(c) for c in kept)))
