"""
sheet_index.py - Stage 1: sheet index and drawing classification (LLM).

Classifies the set's sheets (discipline, drawing type, level, QAQC relevance)
and flags missing expected sheets, reusing the title-block data the extractor
already produced -- no extra image calls. Also exposes a cheap, no-LLM helper
that scrapes best-effort project metadata off the cover sheet so Stage 0
(jurisdiction) has something to work with when the intake form is blank.
"""

import json
import re
from typing import Dict, List, Optional

from backend import config
from backend.pipeline._stage import call_stage
from backend.prompts import SHEET_INDEX_SYSTEM_PROMPT, SHEET_INDEX_USER_INSTRUCTION


def _index_input(sheets: List[Dict]) -> List[Dict]:
    """Compact per-sheet records for the classification call."""
    return [
        {
            "sheet_id": s.get("sheet_number") or f"page-{s.get('page_number')}",
            "sheet_number": s.get("sheet_number"),
            "sheet_title": s.get("sheet_title"),
            "discipline": s.get("discipline"),
            "level": s.get("level"),
            "page_number": s.get("page_number"),
        }
        for s in sheets
    ]


def classify_sheets(sheets: List[Dict]) -> Dict:
    """Return the classified sheet index, or {} on failure."""
    parsed = call_stage(
        SHEET_INDEX_SYSTEM_PROMPT,
        SHEET_INDEX_USER_INSTRUCTION,
        subs={"sheet_index_input": json.dumps(_index_input(sheets), ensure_ascii=True)},
        max_tokens=config.SHEET_INDEX_MAX_TOKENS,
    )
    if isinstance(parsed, dict):
        return parsed
    if isinstance(parsed, list):
        # Model returned a bare array of sheet entries -> wrap it.
        return {"sheet_index": parsed, "missing_expected_sheets": []}
    return {}


# --- cover-sheet metadata (no LLM) -----------------------------------------

_ADDR_RE = re.compile(r"\d{1,6}\s+\w[\w .]*\b(?:st|street|ave|avenue|rd|road|blvd|"
                      r"boulevard|dr|drive|ln|lane|way|hwy|highway|ct|court)\b",
                      re.IGNORECASE)


def _is_cover(sheet: Dict) -> bool:
    title = (sheet.get("sheet_title") or "").lower()
    num = (sheet.get("sheet_number") or "").upper()
    return ("cover" in title or "title sheet" in title
            or num in ("G000", "G-000", "G0.0", "T0.0", "G001", "G-001")
            or sheet.get("page_number") == 1)


# Words that, alone, never make up a real project name (so a title built only
# from these is "generic" and should not be used as the project name).
_GENERIC_WORDS = {"cover", "sheet", "title", "index", "general", "notes",
                  "drawing", "drawings", "of", "the", "and"}
_FILE_NOISE_RE = re.compile(
    r"(?i)(?:\d{2,3}%|\breview set\b|\bpermit set\b|\bbid set\b|"
    r"\bconstruction set\b|\bdrawings?\b|\bset\b|\bissued for \w+\b|"
    r"\bifc\b|\bifp\b)")
_TMP_PREFIX_RE = re.compile(r"^tmp\w+?_")
_DATE_RE = re.compile(r"\b\d{6,8}\b")


def _is_generic_title(title: str) -> bool:
    t = re.sub(r"[^a-z ]", "", (title or "").lower()).strip()
    return (not t) or all(w in _GENERIC_WORDS for w in t.split())


def _name_from_filename(source_name: Optional[str]) -> Optional[str]:
    """'tmpXXXX_20260618_Verizon Elyson_100% Review Set.pdf' -> 'Verizon Elyson'."""
    if not source_name:
        return None
    stem = re.sub(r"\.pdf$", "", source_name, flags=re.IGNORECASE)
    stem = _TMP_PREFIX_RE.sub("", stem)
    stem = stem.replace("_", " ")        # spaces first so dates get boundaries
    stem = _DATE_RE.sub(" ", stem)
    stem = _FILE_NOISE_RE.sub(" ", stem)
    stem = re.sub(r"\s+", " ", stem).strip(" -")
    return stem or None


def derive_project_meta_from_cover(sheets: List[Dict],
                                   source_name: Optional[str] = None) -> Dict:
    """Best-effort {project_name, address} from cover-sheet assertions + filename."""
    meta: Dict[str, str] = {}
    covers = [s for s in sheets if _is_cover(s)] or sheets[:1]
    for s in covers:
        for a in s.get("assertions", []):
            text = (a.get("source_text") or a.get("value") or "").strip()
            if text and "address" not in meta:
                m = _ADDR_RE.search(text)
                if m:
                    meta["address"] = m.group(0).strip()
        # Use the cover title as the name only if it isn't a generic label.
        title = (s.get("sheet_title") or "").strip()
        if "project_name" not in meta and title and not _is_generic_title(title):
            meta["project_name"] = title
    # Filename is the most reliable name for retail/prototype sets.
    if "project_name" not in meta:
        fn = _name_from_filename(source_name)
        if fn:
            meta["project_name"] = fn
    return meta
