"""Extract kitchen cabinet collection names from Home Surplus 2025-26 catalog PDF."""
from __future__ import annotations

import csv
import json
import re
from pathlib import Path

from pypdf import PdfReader

PDF = Path(r"C:/Users/Mohsin.Abbas/Downloads/2025-26_General_Catalog_-_Home_Surplus.pdf")
TAX = Path(r"E:/2026/plytixmage_dbs_data/kitchen_cabinets_taxonomy.json")
BROCHURE = Path(r"E:/2026/plytixmage_dbs_data/Brochure_Data")
REG = Path(r"E:/2026/plytixmage_dbs_data/master_collection_registry.csv")
OUT = Path(r"E:/2026/plytixmage_dbs_data/audit_reports/general_catalog_collections_gap.json")
FULL_TXT = Path(r"E:/2026/plytixmage_dbs_data/audit_reports/_general_catalog_full.txt")


def norm(name: str) -> str:
    text = re.sub(r"\s+", " ", str(name or "").strip().lower())
    text = text.replace("&", "and")
    text = re.sub(r"[^a-z0-9 ]+", "", text)
    return text


def slugify(name: str) -> str:
    return norm(name).replace(" ", "-")


def clean_title(raw: str) -> str:
    text = re.sub(r"\s+", " ", raw.strip())
    # Fix split letters from PDF: "BA THROOM" etc already filtered; title-case ALL CAPS
    if text.isupper() and len(text) > 3:
        text = text.title()
    # Common PDF artifacts
    text = text.replace("Whit e", "White").replace("W ood", "Wood")
    return text.strip()


def extract_collections_from_pdf(text: str) -> list[dict]:
    """Collection pages typically have an ALL-CAPS name near CABINETRY / Specifications."""
    pages = re.split(r"\n--- PAGE \d+ ---\n", text)
    found: dict[str, dict] = {}

    junk = {
        "cabinetry",
        "specifications",
        "construction",
        "other features",
        "style",
        "matching styles",
        "limited",
        "warranty",
        "kitchen cabinets",
        "table of contents",
        "introduction",
        "about us",
        "grab go",
        "special order",
        "available",
        "green",
        "purple",
        "blue",
        "door",
        "drawer",
        "cabinet",
        "how does it work here",
        "what sets us apart",
        "our customers",
        "home surplus",
    }

    # Known brand/family first tokens for kitchen collections in this catalog
    family_tokens = {
        "anna",
        "belmont",
        "maya",
        "eleanor",
        "harriet",
        "hurston",
        "johnson",
        "merrill",
        "grace",
        "cooper",
        "chesapeake",
        "fabuwood",
        "quest",
        "riley",
        "whitney",
        "williamson",
        "sanibel",
        "coronet",
        "plymouth",
        "oxford",
        "somerset",
        "bristol",
        "hampton",
        "shaker",
        "metro",
        "galaxy",
        "hallmark",
        "andes",
        "aspyn",
        "barcelona",
        "bergen",
        "brooklyn",
        "cambridge",
        "charleston",
        "dallas",
        "denver",
        "essex",
        "fairfield",
        "geneva",
        "hartford",
        "lexington",
        "madison",
        "naples",
        "newport",
        "orlando",
        "portland",
        "richmond",
        "savannah",
        "seattle",
        "tampa",
        "toronto",
        "tucson",
        "versailles",
        "windsor",
    }

    for page_no, page in enumerate(pages, start=0):
        if not page.strip():
            continue
        # Only kitchen cabinetry section roughly: before bathroom vanities marker
        low = page.lower()
        if "bathroom vanit" in low and "kitchen" not in low[:200]:
            # still allow if cabinetry present
            pass
        if "exterior door" in low and "cabinetry" not in low:
            continue
        if "shower door" in low and "cabinetry" not in low:
            continue

        lines = [ln.strip() for ln in page.splitlines() if ln.strip()]
        # Matching Styles lists often include sibling collections
        for i, ln in enumerate(lines):
            if ln.lower() in {"matching styles", "matching style"}:
                for nxt in lines[i + 1 : i + 12]:
                    if nxt.lower() in {"cabinetry", "specifications", "construction", "style"}:
                        break
                    if re.match(r"^[A-Za-z].*[A-Za-z]$", nxt) and len(nxt.split()) >= 2:
                        name = clean_title(nxt)
                        key = norm(name)
                        if key in junk or len(key) < 6:
                            continue
                        if key not in found:
                            found[key] = {
                                "name": name,
                                "slug": slugify(name),
                                "source": "matching_styles",
                                "page": page_no,
                            }

        # Primary collection title: ALL CAPS line with 2+ words near CABINETRY
        for i, ln in enumerate(lines):
            if not re.match(r"^[A-Z][A-Z0-9'&\- ]{5,80}$", ln):
                continue
            if any(ch.isdigit() for ch in ln) and not re.search(r"[A-Z]{3,}", ln):
                continue
            name = clean_title(ln)
            key = norm(name)
            if key in junk or len(name.split()) < 2:
                continue
            # Must look like a product collection: first token family-like OR nearby CABINETRY
            nearby = " ".join(lines[max(0, i - 3) : i + 6]).lower()
            first = name.split()[0].lower()
            if "cabinetry" not in nearby and first not in family_tokens:
                continue
            if "warranty" in key or "limited" in key:
                continue
            prev = found.get(key)
            if prev and prev["source"] == "primary_heading":
                continue
            found[key] = {
                "name": name,
                "slug": slugify(name),
                "source": "primary_heading" if "cabinetry" in nearby else "caps_near_family",
                "page": page_no,
            }

    # Prefer primary_heading over matching_styles when both exist (already handled)
    return sorted(found.values(), key=lambda r: (r["name"], r["page"] or 0))


def load_taxonomy() -> dict[str, dict]:
    data = json.loads(TAX.read_text(encoding="utf-8"))
    out = {}
    for code, row in (data.get("collections") or {}).items():
        name = row.get("display_name") or row.get("name") or code
        out[norm(name)] = {"code": code, "name": name, "source": "taxonomy_json"}
        out[norm(code.replace("-", " "))] = {"code": code, "name": name, "source": "taxonomy_json"}
    return out


def load_brochure() -> dict[str, dict]:
    out = {}
    for path in sorted(BROCHURE.glob("*.json")):
        payload = json.loads(path.read_text(encoding="utf-8"))
        coll = payload.get("collection") if isinstance(payload.get("collection"), dict) else {}
        name = coll.get("name") or coll.get("display_name") or path.stem.replace("_0226", "").replace("_0326", "").replace("_0426", "").replace("_site", "")
        # Humanize filename CamelCase
        if name == path.stem or "_" in str(name):
            stem = path.stem
            for suf in ("_0226", "_0326", "_0426", "_site"):
                stem = stem.replace(suf, "")
            name = re.sub(r"(?<=[a-z])(?=[A-Z])", " ", stem)
        code = coll.get("code") or coll.get("collection_code") or ""
        row = {"code": code, "name": name, "file": path.name, "source": "brochure_data"}
        out[norm(name)] = row
        if code:
            out[norm(code)] = row
    return out


def load_registry() -> dict[str, dict]:
    if not REG.exists():
        return {}
    out = {}
    with REG.open(encoding="utf-8", newline="") as fh:
        for row in csv.DictReader(fh):
            name = (row.get("name") or row.get("collection") or "").strip()
            code = (row.get("code") or "").strip()
            if not name and not code:
                continue
            payload = {"code": code, "name": name or code, "source": "registry_csv"}
            if name:
                out[norm(name)] = payload
            if code:
                out[norm(code)] = payload
                out[norm(code.replace("-", " "))] = payload
    return out


def lookup(maps: dict[str, dict], name: str, slug: str):
    key = norm(name)
    if key in maps:
        return maps[key]
    skey = norm(slug.replace("-", " "))
    if skey in maps:
        return maps[skey]
    # partial: all tokens present
    tokens = set(key.split())
    for k, v in maps.items():
        kt = set(k.split())
        if tokens and tokens <= kt:
            return v
        if kt and kt <= tokens and len(kt) >= 2:
            return v
    return None


def main() -> None:
    reader = PdfReader(str(PDF))
    parts = []
    for i, page in enumerate(reader.pages):
        parts.append(f"--- PAGE {i+1} ---\n{page.extract_text() or ''}")
    text = "\n".join(parts)
    FULL_TXT.write_text(text, encoding="utf-8")

    pdf_cols = extract_collections_from_pdf(text)
    tax = load_taxonomy()
    brochure = load_brochure()
    registry = load_registry()

    # Unique by name
    unique = {}
    for row in pdf_cols:
        unique[norm(row["name"])] = row
    pdf_cols = sorted(unique.values(), key=lambda r: r["name"])

    rows = []
    missing_tax = []
    missing_brochure = []
    for item in pdf_cols:
        hit_tax = lookup(tax, item["name"], item["slug"])
        hit_bro = lookup(brochure, item["name"], item["slug"])
        hit_reg = lookup(registry, item["name"], item["slug"])
        row = {
            **item,
            "in_taxonomy": bool(hit_tax),
            "in_brochure": bool(hit_bro),
            "in_registry": bool(hit_reg),
            "taxonomy": hit_tax,
            "brochure": hit_bro,
            "registry": hit_reg,
        }
        rows.append(row)
        if not hit_tax:
            missing_tax.append(row)
        if not hit_bro:
            missing_brochure.append(row)

    # Taxonomy/brochure not mentioned in PDF
    pdf_keys = {norm(r["name"]) for r in pdf_cols}
    tax_only = []
    for k, v in tax.items():
        if v.get("source") != "taxonomy_json":
            continue
        # skip code-alias keys
        if "-" in v.get("code", "") and norm(v["code"].replace("-", " ")) == k and norm(v["name"]) != k:
            continue
        if norm(v["name"]) not in pdf_keys and k == norm(v["name"]):
            tax_only.append(v)

    brochure_only = []
    seen_files = set()
    for k, v in brochure.items():
        if v.get("file") in seen_files:
            continue
        if k != norm(v["name"]):
            continue
        seen_files.add(v.get("file"))
        if norm(v["name"]) not in pdf_keys:
            brochure_only.append(v)

    report = {
        "pdf": str(PDF),
        "pages": len(reader.pages),
        "pdf_collections": rows,
        "missing_in_taxonomy_json": missing_tax,
        "missing_in_brochure_data": missing_brochure,
        "in_taxonomy_but_not_seen_in_pdf": tax_only,
        "in_brochure_but_not_seen_in_pdf": brochure_only,
        "counts": {
            "pdf": len(rows),
            "missing_taxonomy": len(missing_tax),
            "missing_brochure": len(missing_brochure),
            "taxonomy_not_in_pdf": len(tax_only),
            "brochure_not_in_pdf": len(brochure_only),
        },
    }
    OUT.write_text(json.dumps(report, indent=2), encoding="utf-8")

    print(f"PDF collections found: {len(rows)}")
    for r in rows:
        flags = []
        flags.append("TAX" if r["in_taxonomy"] else "no-tax")
        flags.append("BRO" if r["in_brochure"] else "no-bro")
        flags.append("REG" if r["in_registry"] else "no-reg")
        print(f"  {r['name']:40} {' '.join(flags):20} src={r['source']} p={r['page']}")
    print("\nMissing in taxonomy_json:")
    for r in missing_tax:
        print(f"  - {r['name']}")
    print("\nMissing in Brochure_Data:")
    for r in missing_brochure:
        print(f"  - {r['name']}")
    print("\nIn taxonomy but not seen in PDF parse:")
    for r in tax_only:
        print(f"  - {r['name']} ({r['code']})")
    print("wrote", OUT)


if __name__ == "__main__":
    main()
