"""Writing-style checks for forecast text: the forecaster's own wording, phrase
suggestions, and Claude's suggested text all go through `check`.

Precedence (app/sources.py): USDA FS Forecast Guidance writing rules come first
and weigh more; Writing for Busy Readers (Rogers & Lasky-Fink 2023) is
advisory. Its wordy-phrase list is filtered so it never rewrites Forecast
Guidance vocabulary (see app/data/style-rules.json "excluded").
"""

from __future__ import annotations

import json
import re
from functools import lru_cache
from pathlib import Path

FG, BUSY = "fg", "busy_readers"
LONG_SENTENCE_WORDS = 25
BOTTOM_LINE_MAX_SENTENCES = 3

# (pattern, field or None for all fields, rule, note)
_FG_PATTERNS = [
    (re.compile(r"\bfacet(?:s|ed)?\b", re.I), "bottom_line", "FG §3.3", "Jargon: say “sugary snow” instead of “facets”."),
    (re.compile(r"\bwind[- ]?slabs?\b", re.I), "bottom_line", "FG §3.3", "Jargon: say “wind drifts” instead of “wind slabs”."),
    (re.compile(r"\b(?:surface|depth) hoar\b", re.I), "bottom_line", "FG §3.3", "Jargon: describe the weak layer in plain words."),
    (re.compile(r"\bpropagat\w*", re.I), "bottom_line", "FG §3.3", "Jargon: say how widely avalanches could break."),
    (re.compile(r"\bno (?:chance of avalanches|avalanche (?:danger|hazard))\b|\bavalanches? (?:are|is|will be) "
                r"(?:not possible|impossible)\b|\bsafe avalanche conditions\b", re.I),
     None, "FG §3.2", "Don't tell people there is no chance of avalanches."),
    (re.compile(r"\b(?:is|are|was|were|be|been|being)\s+(?:\w+ly\s+)?\w+(?:ed|en)\s+by\b", re.I),
     None, "FG §3.1", "Passive voice: say who or what did it first."),
]
_SENTENCE = re.compile(r"[^.!?]+[.!?]*")


@lru_cache
def rules() -> dict:
    return json.loads((Path(__file__).parent / "data" / "style-rules.json").read_text())


@lru_cache
def _wordy_regex() -> tuple[re.Pattern, dict]:
    by_phrase = {w["phrase"]: w for w in rules()["wordy"]}
    alts = sorted(by_phrase, key=len, reverse=True)  # longest first: "adjacent to" before "adjacent"
    rx = re.compile(r"(?<![\w-])(?:" + "|".join(re.escape(p) for p in alts) + r")(?![\w-])", re.I)
    return rx, by_phrase


def _finding(source, rule, text, start=None, end=None, match=None, replacements=None) -> dict:
    return {"source": source, "rule": rule, "text": text, "start": start, "end": end, "match": match,
            "replacements": replacements or []}


def sentences(text: str) -> list[tuple[int, int, str]]:
    return [(m.start(), m.end(), m.group()) for m in _SENTENCE.finditer(text) if m.group().strip()]


def check(text: str | None, field: str) -> list[dict]:
    """Findings for one piece of text, Forecast Guidance first."""
    text = text or ""
    fg, busy = [], []
    for rx, only, rule, note in _FG_PATTERNS:
        if only is None or only == field:
            m = rx.search(text)
            if m:
                fg.append(_finding(FG, rule, note, m.start(), m.end(), m.group()))
    sents = sentences(text)
    if field == "bottom_line" and len(sents) > BOTTOM_LINE_MAX_SENTENCES:
        fg.append(_finding(FG, "FG §3.3", f"Aim for three sentences or fewer (this has {len(sents)})."))
    for start, end, s in sents:
        n = len(s.split())
        if n > LONG_SENTENCE_WORDS:
            busy.append(_finding(BUSY, "Busy Readers 2", f"Long sentence ({n} words): write shorter sentences.",
                                 start, end, s.strip()[:60]))
    rx, by_phrase = _wordy_regex()
    for m in rx.finditer(text):
        w = by_phrase[m.group().lower()]
        opts = " / ".join(f"“{o}”" for o in w["options"])
        note = f" ({w['note']})" if w["note"] else ""
        busy.append(_finding(BUSY, "Busy Readers 1", f"Wordy: “{m.group()}” → {opts}{note}.", m.start(), m.end(),
                             m.group(), w["options"]))
    return fg + busy
