"""The two concepts are spelled in full wherever a person reads them (rian, 18 Sep).

*"can we always use the full 'product line' and 'product variant'. I know it's a bit wordy but even
with my familiar[ity], just using the word 'fold straight into a line of the same name' confused me
at first. I thought we were talking about a new row somewhere until I realized we were talking about
product lines."*

`test_one_term_per_concept.py` holds the IDENTIFIERS; this holds the PROSE, which is a different
failure: nothing is misnamed, it is merely abbreviated, and the reader pays for it. He asked for it
as general guidance rather than a fix to one sentence, so it is a gate over the review's own words
and the guidelines a pass reads, not a list of the sentences that were wrong.

Where the word is ordinary English and not the concept, name it in ALLOWED with its reason.
"""
from __future__ import annotations

import pathlib
import re

MAIN = pathlib.Path(__file__).resolve().parents[1]

#: Where a person reads the review's own wording: the sentences the card and the page show, and
#: the guidelines a pass reads before it writes any of its own.
SOURCES = (
    MAIN / "app" / "services" / "review_detail.py",
    MAIN / "docs" / "AI-REVIEW-GUIDELINES.md",
    *sorted((MAIN / "web" / "src" / "components" / "review").glob("*.tsx")),
    MAIN / "web" / "src" / "pages" / "ReviewPage.tsx",
)

#: "line" and "variant" as the concept: the word on its own, with no "product" in front of it.
BARE = re.compile(r"(?<!product )(?<!Product )(?<!product-)\b(lines?|variants?)\b", re.IGNORECASE)

#: An f-string's `{...}` is an expression, not prose: `{loser['lines']}` renders a number.
BRACED = re.compile(r"\{[^{}]*\}")

#: A rule has to be able to NAME the word it bans, so a word in quotes is a mention, not a use.
MENTION = re.compile(r"""['"`\u201c\u2018](lines?|variants?)['"`\u201d\u2019]""", re.IGNORECASE)

#: A quoted sentence in code, which is the part a person sees. A bare identifier is the other
#: test's business, and `line` as a local variable is not prose.
STRING = re.compile(r'"((?:\\.|[^"\\])*)"|\'((?:\\.|[^\'\\])*)\'')

#: substring of the line -> why the short word is right there.
ALLOWED = (
    ("command line", "the shell, not a product line"),
    ("one line of reasoning", "a line of text"),
    ("No version line", "a line of a document"),
    ("version line", "a line of a document"),
    ("per line", "a line of a document"),
    ("line of the file", "a line of a document"),
    # The guideline that DEFINES this rule has to be able to show the wrong form to ban it.
    ("Never a bare", "the rule naming what it forbids"),
    ("The same goes for anything built out of them", "the rule showing the short form it replaces"),
)


def _prose(path: pathlib.Path, line: str) -> list[str]:
    """The reader-facing text on one source line."""
    if re.match(r"^\s*(#|//|\*|/\*)", line):
        return []  # a comment is for whoever edits the file, not for a reader of the page
    # A sentence has a space in it; `"lines"` on its own is a key, not prose.
    return [BRACED.sub(" ", g) for m in STRING.finditer(line) for g in m.groups() if g and " " in g.strip()]


def _paragraphs(text: str) -> list[tuple[int, str]]:
    """A markdown file as paragraphs with the line each starts on.

    Checking line by line called "a product\nvariant is what a barcode names" a bare variant, which
    is the wrap and not the wording. Prose has to be read the way it is read.
    """
    out, buf, start, skipping = [], [], 0, False
    for n, line in enumerate(text.splitlines(), 1):
        if line.lstrip().startswith("<!--"):
            skipping = True
        if skipping:
            if "-->" in line:
                skipping = False
            continue
        if line.strip():
            if not buf:
                start = n
            buf.append(line)
        elif buf:
            out.append((start, " ".join(buf)))
            buf = []
    if buf:
        out.append((start, " ".join(buf)))
    return out


def test_the_review_says_product_line_and_product_variant_in_full():
    hits: list[str] = []
    for path in SOURCES:
        if not path.exists():
            continue
        rel = path.relative_to(MAIN.parent)
        body = path.read_text()
        chunks = (_paragraphs(body) if path.suffix == ".md"
                  else [(n, t) for n, line in enumerate(body.splitlines(), 1) for t in _prose(path, line)])
        for n, text in chunks:
            if any(a in text for a, _ in ALLOWED):
                continue
            for found in BARE.finditer(MENTION.sub(" ", text)):
                hits.append(f"{rel}:{n}: ...{text.strip()[:96]}...  [{found.group(0)}]")
                break
    assert not hits, (
        "say \"product line\" and \"product variant\" in full where a person reads them "
        "(add an ALLOWED entry if the word is ordinary English there):\n" + "\n".join(hits[:40]))
