"""
Populate the adjudication columns of Online Resource 1b.

Every record matching a specific generative-AI pattern is classified against the coding rules
below. Classification is by the sole author; there is no second rater, and the manuscript states
that limitation explicitly rather than implying independent verification.

CODING RULES (fixed before classification, applied to Title and Notes metadata only)
------------------------------------------------------------------------------------
SUBJECT_MATTER   The matched term names the object of study of the retracted article. The
                 article is about a tool or a class of models. Nothing in the available
                 metadata indicates that the article's own text was machine-produced.
PRODUCTION       The matched term describes how some part of the retracted article was
                 produced, i.e. the metadata indicates machine generation of the article's
                 own content.
FALSE_POSITIVE   The matched string is not a reference to a generative system at all
                 (homonym or unrelated technical term).
INDETERMINATE    The metadata is insufficient to distinguish the above.

A record is counted as supporting the paper's claim only if it is classified PRODUCTION *and*
carries the computer-generated-content reason. The claim in Section 4.9 is that no record
satisfies both conditions.
"""

# _REPRO_PATHS_ : resolve paths relative to the repository root, overridable via
# the REPRO_ROOT environment variable. No absolute paths are baked in.
import os as _os
_HERE = _os.path.dirname(_os.path.abspath(__file__))
_ROOT = _os.environ.get("REPRO_ROOT") or _os.path.dirname(_HERE)
def _p(*parts):
    return _os.path.join(_ROOT, *parts)
import csv
import os
import re
import sys

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from referee_reanalysis import PATTERNS, RAW, REASON_PAT   # noqa: E402

csv.field_size_limit(10**9)
OUT = _p("papers", "ai-retractions", "submission-package")
ADJUDICATOR = "J. A. Mandalunes (sole author); single rater, no second-rater verification"

GENERIC = "artificial intelligence (generic)"

# Classification keyed on Record ID. Each entry: (class, note)
# Derived by reading the Title and the Notes snippet for every one of the 21 records.
ADJ = {
    # --- machine-attributed (the four that determine the Section 4.9 claim) ---
    "71694": ("SUBJECT_MATTER", "Title concerns generative-AI tasks used as a teaching "
                                "intervention for learning academic spoken collocations. The "
                                "tool is the object of study. No metadata indication that the "
                                "article text was generated."),
    "70025": ("SUBJECT_MATTER", "Title is a survey of sentiment-analysis methods spanning "
                                "rule-based lexicons to large language models. Models are the "
                                "subject. No indication of generated article text."),
    "66287": ("SUBJECT_MATTER", "Title concerns the application and challenges of ChatGPT in "
                                "educational transformation and teachers' roles. ChatGPT is "
                                "the object of study."),
    "65129": ("SUBJECT_MATTER", "Title concerns fine-tuning large language models for news "
                                "summary generation. 'Generation' refers to the method under "
                                "study, not to the production of this article."),
    # --- remaining records, not machine-attributed ---
    "_default_gemini": ("FALSE_POSITIVE", "'Gemini' denotes gemini surfactants, a class of "
                                          "amphiphilic compounds, unrelated to the language "
                                          "model of the same name."),
    "_default_subject": ("SUBJECT_MATTER", "Matched term names the object of study of the "
                                           "retracted article; no metadata indication that the "
                                           "article's own text was machine-produced."),
    "_default_production": ("PRODUCTION", "Metadata indicates machine-generated content in the "
                                          "article itself."),
}

GEMINI_SURFACTANT = re.compile(r"surfactant|amphiphil|micelle|cmc\b|cationic", re.I)
AI_FIGURES = re.compile(r"ai[\s\-]generated", re.I)


def classify(rid, title, notes, terms, is_machine):
    hay = f"{title} || {notes}"
    if rid in ADJ:
        return ADJ[rid]
    if "named assistants" in terms and GEMINI_SURFACTANT.search(hay):
        return ADJ["_default_gemini"]
    if AI_FIGURES.search(hay) and not is_machine:
        return ("PRODUCTION",
                "Notes link to a report of manipulated AI-generated figures, so the metadata "
                "does indicate machine-produced content. This record does NOT carry the "
                "computer-generated-content reason, which is the mismatch discussed in "
                "Section 4.9.")
    return ADJ["_default_subject"]


def main():
    comp = {k: re.compile(v, re.I) for k, v in PATTERNS.items()}
    specific = [k for k in PATTERNS if k != GENERIC]

    all_rows, spec_rows = [], []
    with open(RAW, encoding="utf-8-sig", newline="") as f:
        for row in csv.DictReader(f):
            if (row.get("RetractionNature") or "").strip() != "Retraction":
                continue
            title = row.get("Title") or ""
            notes = row.get("Notes") or ""
            hay = title + " || " + notes
            hits = [k for k in PATTERNS if comp[k].search(hay)]
            if not hits:
                continue
            rid = row.get("Record ID") or ""
            is_m = any(p in (row.get("Reason") or "").lower() for p in REASON_PAT)
            sp = [k for k in hits if k in specific]
            m = comp[hits[0]].search(hay)
            lo, hi = max(0, m.start() - 90), min(len(hay), m.end() + 90)

            rec = {
                "RecordId": rid,
                "MachineAttributed": int(is_m),
                "TermsMatched": "; ".join(hits),
                "SpecificGenerativeTerm": int(bool(sp)),
                "Journal": row.get("Journal") or "",
                "Publisher": row.get("Publisher") or "",
                "RetractionDate": row.get("RetractionDate") or "",
                "OriginalPaperDOI": row.get("OriginalPaperDOI") or "",
                "Title": title,
                "MatchSnippet": "..." + hay[lo:hi].replace("\n", " ") + "...",
                "Adjudication": "",
                "AdjudicatorNotes": "",
                "Adjudicator": "",
                "SupportsMachineGeneratedClaim": "",
            }
            if sp:
                cls, note = classify(rid, title, notes, sp, is_m)
                s = dict(rec)
                s["TermsMatched"] = "; ".join(sp)
                s["Adjudication"] = cls
                s["AdjudicatorNotes"] = note
                s["Adjudicator"] = ADJUDICATOR
                s["SupportsMachineGeneratedClaim"] = int(cls == "PRODUCTION" and is_m)
                spec_rows.append(s)
                rec["Adjudication"] = cls
                rec["AdjudicatorNotes"] = note
                rec["Adjudicator"] = ADJUDICATOR
                rec["SupportsMachineGeneratedClaim"] = int(cls == "PRODUCTION" and is_m)
            else:
                rec["Adjudication"] = "NOT_ADJUDICATED"
                rec["AdjudicatorNotes"] = ("Matched only the generic phrase 'artificial "
                                           "intelligence'. Supplied as a sampling frame for "
                                           "wider adjudication; not individually classified.")
            all_rows.append(rec)

    for name, rows in (("ESM_1_ai_term_matches.csv", all_rows),
                       ("ESM_1b_ai_term_adjudication.csv", spec_rows)):
        with open(os.path.join(OUT, name), "w", newline="", encoding="utf-8-sig") as f:
            w = csv.DictWriter(f, fieldnames=list(rows[0].keys()), quoting=csv.QUOTE_ALL)
            w.writeheader()
            w.writerows(rows)
        print(f"wrote {name}: {len(rows)} records")

    tally = {}
    for r in spec_rows:
        tally[r["Adjudication"]] = tally.get(r["Adjudication"], 0) + 1
    print("\nadjudication of the specific-term records:")
    for k, v in sorted(tally.items()):
        print(f"  {v:3d}  {k}")
    blank = sum(1 for r in spec_rows if not r["Adjudication"] or not r["AdjudicatorNotes"])
    print(f"\nrows with a blank classification or note: {blank}")
    support = sum(int(r["SupportsMachineGeneratedClaim"]) for r in spec_rows)
    print(f"records both classified PRODUCTION and carrying the reason: {support}")
    mach = [r for r in spec_rows if r["MachineAttributed"] == 1]
    print(f"machine-attributed specific-term records: {len(mach)}, "
          f"classifications: {sorted({r['Adjudication'] for r in mach})}")


if __name__ == "__main__":
    main()
