"""Per-sheet Drawing Integrity QA agent.

Reads ONE sheet's own extracted objects + sheet image + deterministic text
layer and flags defects internal to that single sheet: dangling detail/
callout/keynote references, schedule-vs-plan/legend disagreements on the same
sheet, dimension strings that do not sum, missing title-block/scale/north
essentials, and duplicate/inconsistent tags. This is the drawing-focused pass
that complements the cross-sheet conflict critic; it never does code/ADA or
cross-sheet coordination.
"""

from typing import Dict, List

from backend import config
from backend.agents.base import AgentResult, AgentScope, AgentUsage, failure
from backend.agents.prompts import (
    DRAWING_INTEGRITY_SYSTEM_PROMPT,
    DRAWING_INTEGRITY_USER_PROMPT,
)
from backend.llm import call_json
from backend.pipeline._serialize import dumps
from backend.pipeline._stage import collect_list, validate_issue


def _sheet_meta(sheet: Dict) -> Dict:
    """Compact title-block-ish descriptor of the sheet (no raw assertions)."""
    return {
        "sheet_number": sheet.get("sheet_number"),
        "sheet_title": sheet.get("sheet_title"),
        "discipline": sheet.get("discipline"),
        "drawing_type": sheet.get("drawing_type"),
        "level": sheet.get("level"),
        "scale": sheet.get("scale"),
    }


def build_integrity_scopes(
    sheets: List[Dict], page_to_b64: Dict, page_to_text: Dict
) -> List[AgentScope]:
    """One scope per sheet that carries enough objects to judge internal
    consistency. Sheets below INTEGRITY_MIN_ASSERTIONS are skipped as too
    sparse for a meaningful single-sheet back-check."""
    scopes: List[AgentScope] = []
    for sheet in sheets:
        assertions = sheet.get("assertions") or []
        if len(assertions) < config.INTEGRITY_MIN_ASSERTIONS:
            continue
        page_number = sheet.get("page_number")
        scopes.append(AgentScope(
            scope_id=f"integrity:{page_number}",
            payload={
                "sheet": sheet,
                "page_number": page_number,
                "image_b64": page_to_b64.get(page_number),
                "text_layer": page_to_text.get(page_number) or "",
            },
        ))
    return scopes


class DrawingIntegrityAgent:
    name = "drawing_integrity"

    def __init__(self, usage: AgentUsage) -> None:
        self.usage = usage

    def run(self, scope: AgentScope) -> AgentResult:
        try:
            sheet = dict(scope.payload["sheet"])
            assertions = (
                sheet.get("assertions") or []
            )[:config.AGENT_INTEGRITY_MAX_ASSERTIONS]
            text_layer = (scope.payload.get("text_layer") or "")[
                :config.TEXT_LAYER_MAX_CHARS
            ]
            image_b64 = scope.payload.get("image_b64")
            images = [image_b64] if image_b64 else []
            images = images[:config.AGENT_INTEGRITY_MAX_IMAGES]

            instruction = DRAWING_INTEGRITY_USER_PROMPT
            for key, value in {
                "sheet_meta": dumps(_sheet_meta(sheet)),
                "assertions": dumps(assertions),
                "text_layer": text_layer,
            }.items():
                instruction = instruction.replace("{" + key + "}", value)

            parsed = call_json(
                system_prompt=DRAWING_INTEGRITY_SYSTEM_PROMPT,
                user_text=instruction,
                images_b64=images,
                max_tokens=config.INTEGRITY_MAX_TOKENS,
                model=config.AGENT_INTEGRITY_MODEL,
                usage_tracker=self.usage,
                usage_stage="agent.drawing_integrity",
                reasoning_effort=config.EXTRACT_REASONING_EFFORT or None,
                reasoning_max_tokens=config.EXTRACT_REASONING_MAX_TOKENS or None,
            )
            findings = collect_list(
                parsed, "issues",
                lambda item: validate_issue(item, "drawing_integrity"),
            )
            sheet_number = sheet.get("sheet_number")
            for finding in findings:
                finding.update(agent=self.name, scope_id=scope.scope_id)
                # Anchor the finding to this sheet if the model left it blank.
                if not finding.get("sheets") and sheet_number:
                    finding["sheets"] = [sheet_number]
            return AgentResult(scope_id=scope.scope_id, artifacts=findings)
        except Exception as exc:
            return failure(scope, exc)
