"""Indexează corpusul Wikisource: ce text, al cui, din ce an.

Datarea are două surse, în ordinea încrederii:
  1. un an de publicare scris în pagină („Convorbiri literare, 1875"),
  2. perioada activă a autorului, calculată din datele lui de la Wikidata.

Ieșiri:
  raw/ws_texts.json     un rând pe text: titlu, autor, an, sursa anului, lungime
  raw/ws_authors.json   autor → {naștere, deces, qid}
"""
import bz2, json, os, re, sys, time, unicodedata, urllib.parse, urllib.request

HERE = os.path.dirname(os.path.abspath(__file__))
RAW = os.path.join(HERE, "..", "raw")
UA = "straturi-unui-cuvant/1.0 (https://straturi.mariuscomper.uk/)"

# Autori pe care îi scoatem din corpusul istoric, cu motivul.
EXCLUDE_AUTHORS = {
    "Dorin Ștef": "autor contemporan; bibliografii și un dicționar dialectal",
    "Alexandru Gabriel Filotti": "autor contemporan",
    "Parlamentul Republicii Moldova": "text legislativ contemporan",
    "Parlamentul României": "text legislativ contemporan",
    "Adunarea Constituantă": "text legislativ",
}
# Titluri care conțin liste, bibliografii sau tabele, nu proză.
EXCLUDE_TITLE_RE = re.compile(
    r"bibliograf|indice|index|cuprins|list[ăa] de|tabel|dicționar|glosar", re.I
)


def norm_space(s):
    return re.sub(r"\s+", " ", s).strip()


def clean_wikitext(s):
    """Scoate marcajul, păstrează proza. Nu e un parser complet de wikitext:
    e destul cât să nu numărăm nume de șabloane și adrese ca vocabular."""
    s = s.replace("&lt;", "<").replace("&gt;", ">").replace("&amp;", "&")
    s = s.replace("&quot;", '"').replace("&#039;", "'").replace("&nbsp;", " ")
    s = re.sub(r"<ref[^>]*/>", " ", s)
    s = re.sub(r"<ref[^>]*>.*?</ref>", " ", s, flags=re.S)
    s = re.sub(r"<!--.*?-->", " ", s, flags=re.S)
    s = re.sub(r"<[^>]{1,80}>", " ", s)
    # șabloane, inclusiv imbricate
    for _ in range(6):
        new = re.sub(r"\{\{[^{}]*\}\}", " ", s)
        if new == s:
            break
        s = new
    s = re.sub(r"\{\|.*?\|\}", " ", s, flags=re.S)          # tabele
    s = re.sub(r"\[\[[^\]|]*\|", " ", s)                      # [[țintă|
    s = re.sub(r"\[\[|\]\]", " ", s)
    s = re.sub(r"\[https?://\S+\s([^\]]*)\]", r"\1", s)
    s = re.sub(r"https?://\S+", " ", s)
    s = re.sub(r"^[*#:;=|!].*$", " ", s, flags=re.M)          # liste, titluri, tabele
    s = re.sub(r"'{2,}", "", s)
    return norm_space(s)


YEAR_PATTERNS = [
    re.compile(r"\b(1[5-9]\d\d)\b"),
]


def year_hint(raw_text, title):
    """Un an de publicare, dacă pagina îl declară în antet sau în titlu."""
    m = re.search(r"\((1[5-9]\d\d)\)", title)
    if m:
        return int(m.group(1))
    head = raw_text[:1200]
    m = re.search(r"\|\s*(?:note|an|anul|ediți[ei]|data)\s*=\s*([^\|\}\n]{0,160})", head)
    if m:
        ys = YEAR_PATTERNS[0].findall(m.group(1))
        if ys:
            return int(ys[0])
    return None


# Entități Wikidata care leagă un autor de spațiul românesc: limba română,
# România și statele care au precedat-o, Moldova, Basarabia, Transilvania.
ROMANIAN_SPACE = {
    "Q7913",    # limba română
    "Q218",     # România
    "Q206414",  # Regatul României
    "Q217",     # Republica Moldova
    "Q131964",  # Imperiul Austriac
    "Q28513",   # Austro-Ungaria
    "Q203493",  # Țara Românească
    "Q149621",  # Moldova (principat)
    "Q170174",  # Principatele Unite
    "Q1019324", # RSS Moldovenească
    "Q672551",  # Republica Populară Română
    "Q15864",   # Basarabia
    "Q0",       # rezervat
}


def get_json(url, tries=5):
    for a in range(tries):
        try:
            req = urllib.request.Request(url, headers={"User-Agent": UA})
            with urllib.request.urlopen(req, timeout=90) as r:
                return json.load(r)
        except Exception as exc:  # noqa: BLE001
            if a == tries - 1:
                raise
            wait = 4 * (a + 1)
            print(f"    aștept {wait}s ({exc})", file=sys.stderr, flush=True)
            time.sleep(wait)
    raise RuntimeError("unreachable")


def chunks(xs, n):
    for i in range(0, len(xs), n):
        yield xs[i:i + n]


def resolve_qids(names):
    """Autor → QID, prin paginile Autor: de pe ro.wikisource (50 titluri/cerere)."""
    qids = {}
    for batch in chunks(names, 50):
        url = "https://ro.wikisource.org/w/api.php?" + urllib.parse.urlencode({
            "action": "query", "prop": "pageprops", "ppprop": "wikibase_item",
            "titles": "|".join("Autor:" + n for n in batch),
            "format": "json", "formatversion": "2", "redirects": "1",
        })
        d = get_json(url)
        byname = {}
        for p in d.get("query", {}).get("pages", []):
            t = p.get("title", "").replace("Autor:", "")
            q = p.get("pageprops", {}).get("wikibase_item")
            if q:
                byname[t] = q
        # urmărim redirecturile ca să legăm numele cerut la titlul găsit
        for r in d.get("query", {}).get("redirects", []):
            frm = r["from"].replace("Autor:", "")
            to = r["to"].replace("Autor:", "")
            if to in byname:
                byname[frm] = byname[to]
        for n in batch:
            if n in byname:
                qids[n] = byname[n]
        time.sleep(0.2)
    return qids


def entity_dates(qids):
    """QID → naștere/deces, prin wbgetentities (50 entități/cerere)."""
    out = {}
    for batch in chunks(sorted(set(qids)), 50):
        url = "https://www.wikidata.org/w/api.php?" + urllib.parse.urlencode({
            "action": "wbgetentities", "ids": "|".join(batch),
            "props": "claims", "format": "json",
        })
        d = get_json(url)
        for qid, ent in d.get("entities", {}).items():
            cl = ent.get("claims", {})

            def yr(prop):
                for s in cl.get(prop, []):
                    v = s.get("mainsnak", {}).get("datavalue", {}).get("value", {})
                    m = re.match(r"([+-])(\d{4})", v.get("time", ""))
                    if m:
                        return int(m.group(2)) * (1 if m.group(1) == "+" else -1)
                return None

            def ids(prop):
                return {s.get("mainsnak", {}).get("datavalue", {})
                         .get("value", {}).get("id") for s in cl.get(prop, [])}

            ties = ids("P27") | ids("P1412") | ids("P937")
            ties.discard(None)
            out[qid] = {
                "born": yr("P569"), "died": yr("P570"),
                # Autor din spațiul românesc? Dacă are declarate cetățenie sau
                # limbă și niciuna nu e românească, textele lui sunt traduceri și
                # nu pot fi datate după viața autorului.
                "romanian": (not ties) or bool(ties & ROMANIAN_SPACE),
            }
        time.sleep(0.2)
    return out


def wikidata_dates(names):
    """Naștere/deces pentru fiecare autor, de la Wikidata."""
    qids = resolve_qids(names)
    print(f"    QID găsit pentru {len(qids)}/{len(names)} autori")
    dates = entity_dates(qids.values())
    out = {}
    for n in names:
        q = qids.get(n)
        d = dates.get(q) if q else None
        out[n] = ({"qid": q, "born": d["born"], "died": d["died"],
                   "romanian": d["romanian"]}
                  if d and (d["born"] or d["died"]) else None)
    return out


def main():
    src = os.path.join(RAW, "rows.xml.bz2")
    print("citesc dumpul Wikisource...")
    blob = bz2.open(src, "rt", encoding="utf8").read()
    pages = re.findall(r"<page>.*?</page>", blob, re.S)

    texts, by_author = [], {}
    for p in pages:
        if "<ns>0</ns>" not in p:
            continue
        m = re.search(r"<text[^>]*>(.*?)</text>", p, re.S)
        if not m:
            continue
        body = m.group(1)
        if body[:20].lower().startswith("#redirect"):
            continue
        title = re.search(r"<title>(.*?)</title>", p).group(1)
        am = re.search(r"\|\s*autor\s*=\s*([^\|\}\n]*)", body)
        author = (am.group(1).strip().replace("[[", "").replace("]]", "")
                  .replace("Autor:", "").strip() if am else "")
        if not author or author in EXCLUDE_AUTHORS:
            continue
        if EXCLUDE_TITLE_RE.search(title):
            continue
        prose = clean_wikitext(body)
        if len(prose) < 400:
            continue
        texts.append({
            "title": title, "author": author, "chars": len(prose),
            "year_hint": year_hint(body, title),
        })
        by_author.setdefault(author, 0)
        by_author[author] += len(prose)

    print(f"texte utilizabile: {len(texts)}  autori: {len(by_author)}")
    order = sorted(by_author, key=lambda a: -by_author[a])
    # Datele de autor doar pentru cei care contează la volum: 300 acoperă >99%.
    need = order[:300]
    print(f"cer date de la Wikidata pentru {len(need)} autori...")
    auth = wikidata_dates(need)
    got = sum(1 for v in auth.values() if v and v.get("born"))
    print(f"date găsite pentru {got}/{len(need)}")

    with open(os.path.join(RAW, "ws_texts.json"), "w", encoding="utf8") as f:
        json.dump(texts, f, ensure_ascii=False)
    with open(os.path.join(RAW, "ws_authors.json"), "w", encoding="utf8") as f:
        json.dump(auth, f, ensure_ascii=False, indent=1)
    print("scris raw/ws_texts.json și raw/ws_authors.json")


if __name__ == "__main__":
    main()
