"""Auto-extract catalog metadata from a "כל יום" daily digest (X12).

A digest is a one-page secondary summary (Ofer Toister) of a single ruling.
This module reads its raw text and asks the local Claude CLI to extract the
fields the radar needs: yomon number, concept tag, headline holding, a short
summary, the UNDERLYING ruling's citation (the critical bridge field — INV-DIG3),
its court / date / judge, practice area and subject tags.

claude_session rule: this module imports ``claude_session`` (the local CLI),
so it is **MCP-tool-only** — never import it from the FastAPI container. It is
pulled in lazily inside ``digest_library.ingest_digest`` only.

Unlike ``precedent_metadata_extractor`` (which patches a DB row), this returns
a plain dict from raw text; ``digest_library`` decides how to merge/store it.
"""

from __future__ import annotations

import logging
from datetime import date as date_type

from legal_mcp import config
from legal_mcp.config import parse_llm_json
from legal_mcp.services import claude_session

logger = logging.getLogger(__name__)


_VALID_PRACTICE_AREAS = {"", "rishuy_uvniya", "betterment_levy", "compensation_197"}


# Concatenated with f-strings at call time, NOT .format() — the JSON example
# below contains '{' / '}' which str.format would treat as placeholders and
# crash (same trap documented in precedent_metadata_extractor).
DIGEST_EXTRACTION_PROMPT = """אתה מסייע משפטי בכיר. לפניך "יומון" — סיכום עמוד-אחד של משרד עפר טויסטר (עלון "כל יום")
על **החלטה/פסק דין** אחד, או על **עדכון/הודעה** (חקיקה, נוהל, הודעת-תכנון, ברכת-שנה) בתחום תכנון ובנייה / היטל השבחה / פיצויים (ס' 197). חלץ ממנו מטא-דאטה לקטלוג.

**אל תמציא** — שדה שלא מופיע בטקסט → השאר ריק (מחרוזת ריקה / מערך ריק).

## פלט נדרש
החזר JSON אחד (object — לא array), ללא markdown וללא הסברים:

{
  "digest_kind": "סווג את הגיליון: 'decision' = סיכום פסק דין/החלטה (יש מראה-מקום בתחתית) · 'announcement' = עדכון/הודעה ללא הכרעה (חקיקה, נוהל, הודעת-תכנון, ברכה) · 'other' = אחר. **חובה למלא תמיד.**",
  "yomon_number": "מספר היומון מהכותרת ('יומון מס' 5163' → '5163'). ספרות בלבד. אם אין — ריק.",
  "digest_date_iso": "YYYY-MM-DD — תאריך גיליון היומון (בכותרת, למשל '7 ביוני 2026' → '2026-06-07').",
  "concept_tag": "תג-המושג/הכותרת בראש העמוד (למשל 'שיקול הדעת המצומצם', 'Cherry-picking', או 'עדכונים לשנה החדשה' בעדכון). ביטוי קצר אחד. **חלץ תמיד — קיים לכל סוג גיליון.**",
  "headline_holding": "הכותרת המודגשת מתחת לתג — משפט אחד שמסכם את עיקר הגיליון (מה נקבע בהחלטה, או נושא העדכון). **חלץ תמיד.**",
  "summary": "תקציר ניטרלי 2-3 משפטים בגוף שלישי: בהחלטה — מה הייתה השאלה ומה הוכרע; בעדכון — מה תוכן/משמעות העדכון. בלי שיפוט. **חלץ תמיד.**",
  "underlying_citation": "**רק ל-decision** — מראה-המקום של פסק הדין/ההחלטה המקורי, כפי שמופיע בתחתית היומון, מילה במילה (למשל 'עת\\"מ 46111-12-22 יכין-אפק בע\\"מ נ' הוועדה המחוזית'). בעדכון/הודעה — ריק. זהו השדה הקריטי ל-decision — חלץ אותו במלואו ובדיוק.",
  "underlying_court": "הערכאה שנתנה את פסק הדין המקורי (למשל 'בית המשפט לעניינים מנהליים מרכז-לוד', 'ועדת הערר מחוז ירושלים').",
  "underlying_date_iso": "YYYY-MM-DD — תאריך מתן פסק הדין/ההחלטה המקורי (לרוב 'ניתן ביום DD.M.YY' בתחתית). שים לב: זה שונה מתאריך גיליון היומון!",
  "underlying_judge": "שם השופט/ת או יו\\"ר ההרכב שנתן את פסק הדין המקורי (למשל 'יעל טויסטר ישראלי'). בלי תארים ('עו\\"ד', 'כב' השופט').",
  "practice_area": "אחד מ-3: 'rishuy_uvniya' (רישוי ובנייה/הקלות/שימוש חורג) / 'betterment_levy' (היטל השבחה) / 'compensation_197' (פיצויים ס'197). אם לא ברור — ריק.",
  "appeal_subtype": "תת-סוג קצר אם בולט (למשל 'הקלה', 'שיקול דעת הוועדה', 'מימוש במכר'). אחרת ריק.",
  "subject_tags": ["3-7 תגיות בעברית snake_case (שיקול_דעת, הקלה, ועדה_מחוזית, היטל_השבחה, ...)"]
}

## כללי איכות
1. **digest_kind** — חובה. אם יש מראה-מקום של פסק דין/החלטה בתחתית → 'decision'. אם זה עדכון/הודעה/נוהל/ברכה ללא הכרעה → 'announcement'.
2. **concept_tag / headline_holding / summary** — חלץ **תמיד**, לכל סוג גיליון (גם עדכון). אלה לא ייחודיים להחלטות.
3. **underlying_citation** — רק ל-decision; הוא הגשר לפסק הדין. בעדכון — השאר ריק (זה תקין, לא חוסר).
4. **הבחן בין שני התאריכים**: digest_date_iso = תאריך גיליון היומון (בכותרת); underlying_date_iso = מועד מתן פסק הדין (בתחתית, 'ניתן ביום...'). אל תבלבל.
5. **subject_tags** — snake_case, תחום ועדת ערר תכנון ובנייה בלבד.
6. אם רכיב לא מופיע בבירור — השאר את אותו שדה ריק. אל תנחש.
"""


def _norm_str(result: dict, key: str) -> str:
    v = result.get(key)
    return v.strip() if isinstance(v, str) else ""


def _norm_date(result: dict, key: str) -> date_type | None:
    v = result.get(key)
    if not isinstance(v, str) or not v.strip():
        return None
    try:
        return date_type.fromisoformat(v.strip()[:10])
    except ValueError:
        logger.debug("digest_metadata_extractor: ignoring invalid %s=%r", key, v)
        return None


async def extract(raw_text: str, model: str | None = None) -> dict:
    """Extract digest metadata from raw text. Returns a dict (never raises).

    Keys: yomon_number, digest_date (date|None), concept_tag, headline_holding,
    summary, underlying_citation, underlying_court, underlying_date (date|None),
    underlying_judge, practice_area, appeal_subtype, subject_tags (list[str]).
    Missing/invalid fields are omitted so the caller's merge keeps user values.

    Model: defaults to ``config.DIGEST_EXTRACT_MODEL`` (Sonnet — this is a
    high-volume, simple extraction; no need for Opus). Override per-call via
    ``model``.
    """
    text = (raw_text or "").strip()
    if not text:
        return {}

    user_msg = f"--- תחילת היומון ---\n{text}\n--- סוף היומון ---"
    try:
        result = await claude_session.query_json(
            user_msg, system=DIGEST_EXTRACTION_PROMPT,
            model=(model or config.DIGEST_EXTRACT_MODEL or None),
            tools="",  # pure text→JSON: disable tools so the model never emits
                       # stop_reason=tool_use and trips --max-turns (error_max_turns).
        )
    except Exception as e:  # surfaced as warning, not swallowed silently (§6)
        logger.warning("digest_metadata_extractor: query failed: %s", e)
        return {}

    if not isinstance(result, dict):
        logger.warning(
            "digest_metadata_extractor: expected dict, got %s",
            type(result).__name__,
        )
        return {}

    out: dict = {}
    for key in (
        "yomon_number", "concept_tag", "headline_holding", "summary",
        "underlying_citation", "underlying_court", "underlying_judge",
        "appeal_subtype",
    ):
        s = _norm_str(result, key)
        if s:
            out[key] = s

    kind = _norm_str(result, "digest_kind").lower()
    if kind in ("decision", "announcement", "other"):
        out["digest_kind"] = kind

    dd = _norm_date(result, "digest_date_iso")
    if dd is not None:
        out["digest_date"] = dd
    ud = _norm_date(result, "underlying_date_iso")
    if ud is not None:
        out["underlying_date"] = ud

    pa = _norm_str(result, "practice_area")
    if pa in _VALID_PRACTICE_AREAS and pa:
        out["practice_area"] = pa

    tags = result.get("subject_tags")
    if isinstance(tags, list):
        clean = [str(t).strip() for t in tags if str(t).strip()]
        if clean:
            out["subject_tags"] = clean

    return out
