"""USPTO (ABD Patent ve Marka Ofisi) şekli inceleme kuralları.

37 CFR 1.52 (genel biçim), 1.72 (abstract) ve 1.75 (claims) ile MPEP
608/1826'ya dayanır. Tüm kurallar deterministiktir (LLM'siz). Ortak
kurallar (istem numaralandırma, tek cümle, özet uzunluğu) `rules.py`'de
çalıştırılır; bu modül yalnızca USPTO'ya ÖZGÜ kontrolleri içerir.

Kaynak: 37 CFR 1.72(b), 37 CFR 1.75, MPEP 608.01(b) — The Abstract.
"""

from __future__ import annotations

import re

from app.services.formality.models import Finding, Severity

_CLAIM_START = re.compile(r"^\s*(\d+)\s*[.)]\s+", re.MULTILINE)


def check_uspto_abstract_present(abstract: str) -> list[Finding]:
    """37 CFR 1.72(b): başvuruda abstract bulunması zorunludur."""
    if (abstract or "").strip():
        return []
    return [
        Finding(
            rule_id="uspto.abstract_missing",
            severity=Severity.ERROR,
            section="ozet",
            title="Abstract of the Disclosure is missing",
            detail=(
                "A patent application must include an abstract of the "
                "disclosure (37 CFR 1.72(b)). The abstract must commence "
                "on a separate sheet."
            ),
            suggestion=(
                "Add an abstract — a concise statement of the technical "
                "disclosure, on a separate page."
            ),
            legal_basis="37 CFR 1.72(b)",
        )
    ]


def check_uspto_claims_present(claims: str) -> list[Finding]:
    """37 CFR 1.75: en az bir istem bulunmalıdır."""
    if _CLAIM_START.search(claims or ""):
        return []
    return [
        Finding(
            rule_id="uspto.claims_missing",
            severity=Severity.ERROR,
            section="istemler",
            title="No claims found",
            detail=(
                "The specification must conclude with one or more claims "
                "(37 CFR 1.75). No numbered claim was detected."
            ),
            suggestion="Add at least one numbered claim (1., 2., ...).",
            legal_basis="37 CFR 1.75",
        )
    ]


def check_uspto_abstract_length(abstract: str) -> list[Finding]:
    """37 CFR 1.72(b): abstract 150 kelimeyi AŞAMAZ (kesin sınır)."""
    words = len(re.findall(r"\S+", abstract or ""))
    if words <= 150:
        return []
    return [
        Finding(
            rule_id="uspto.abstract_too_long",
            severity=Severity.ERROR,
            section="ozet",
            title=f"Abstract exceeds 150 words ({words} words)",
            detail=(
                "The abstract may not exceed 150 words (37 CFR 1.72(b)). "
                f"The current abstract has {words} words."
            ),
            suggestion=f"Shorten the abstract by at least {words - 150} words.",
            legal_basis="37 CFR 1.72(b)",
        )
    ]


def check_uspto_abstract_no_prior_art(abstract: str) -> list[Finding]:
    """MPEP 608.01(b): abstract önceki tekniğe atıf / karşılaştırma içermemeli."""
    prior_art_terms = [
        "prior art",
        "unlike conventional",
        "compared to",
        "in contrast to",
        "advantage over",
        "better than",
    ]
    low = (abstract or "").lower()
    hit = next((t for t in prior_art_terms if t in low), None)
    if not hit:
        return []
    return [
        Finding(
            rule_id="uspto.abstract_prior_art",
            severity=Severity.WARNING,
            section="ozet",
            title="Abstract may compare the invention with the prior art",
            detail=(
                "The abstract should not compare the invention with the "
                "prior art or refer to purported merits (MPEP 608.01(b)). "
                f"Found: \"{hit}\"."
            ),
            suggestion=(
                "Rephrase the abstract as a neutral technical statement "
                "of what is new, without prior-art comparison."
            ),
            excerpt=hit,
            legal_basis="MPEP 608.01(b)",
        )
    ]


def check_uspto_claim_dependency_language(claims: str) -> list[Finding]:
    """37 CFR 1.75(c): bağımlı istem önceki bir isteme atıf yapmalı.

    USPTO'da bağımlı istemler "The ... of claim N" biçimiyle başlar.
    Sezgisel kontrol: "claim" sözcüğünü hiç içermeyen ama 1'den büyük
    numaralı istemler bağımsız sayılır — bu geçerlidir ama çoğu başvuruda
    en az bir bağımlı istem beklenir; eksikse bilgilendirme verilir.
    """
    nums = [int(m.group(1)) for m in _CLAIM_START.finditer(claims or "")]
    if len(nums) < 2:
        return []
    low = (claims or "").lower()
    if "claim" in low or "claims" in low:
        return []
    return [
        Finding(
            rule_id="uspto.no_dependent_claims",
            severity=Severity.INFO,
            section="istemler",
            title="No dependent claims detected",
            detail=(
                "All claims appear to be independent (no reference to "
                "another claim). Dependent claims ('The ... of claim N') "
                "are common practice and broaden protection scope."
            ),
            suggestion=(
                "Consider adding dependent claims that reference the "
                "independent claim(s) to cover specific embodiments."
            ),
            legal_basis="37 CFR 1.75(c)",
        )
    ]


def check_uspto_rules(
    *,
    description: str = "",
    claims: str = "",
    abstract: str = "",
) -> list[Finding]:
    """Tüm USPTO-özgü deterministik kuralları çalıştırır."""
    findings: list[Finding] = []
    findings += check_uspto_abstract_present(abstract)
    findings += check_uspto_claims_present(claims)
    findings += check_uspto_abstract_length(abstract)
    findings += check_uspto_abstract_no_prior_art(abstract)
    findings += check_uspto_claim_dependency_language(claims)
    return findings
