"""Adună măsurătorile și scrie datele pe care le publică pagina.

Două numărători diferite, care nu dau același răspuns și de aici pornește
articolul:

  tipuri  — câte cuvinte distincte a dat fiecare donator
  ocurențe— cât din textul curgător ocupă cuvintele lui

Franceza a dat cele mai multe cuvinte. Latina ține cele mai multe ocurențe.
Aceeași limbă, două întrebări, două ierarhii inversate.

Ieșiri: content/data/strata.json, meta.json, index.json, words/*.json
"""
import json, math, os, sys
from collections import Counter, defaultdict

HERE = os.path.dirname(os.path.abspath(__file__))
RAW = os.path.join(HERE, "..", "raw")
DATA = os.path.join(HERE, "..", "content", "data")
sys.path.insert(0, HERE)
from strata import PRIORITY, STRATA, proximate_layers, load_overrides

MIN_HIST_TOKENS = 12      # sub atât, curba unui cuvânt e zgomot
MIN_HIST_BINS = 3


def load():
    j = lambda n: json.load(open(os.path.join(RAW, n), encoding="utf8"))
    e = lambda n: os.path.exists(os.path.join(RAW, n))
    return (j("categories.json"), j("now_counts.json"), j("hist_bins.json"),
            j("entries.json"), j("shortlist.json"),
            j("official.json") if e("official.json") else [],
            j("dex_ety.json") if e("dex_ety.json") else {})


def shares(lemmas, total, lay):
    """Ocurențe pe strat, plus cât din text a putut fi atribuit."""
    per = Counter()
    assigned = 0.0
    for w, n in lemmas.items():
        st = lay.get(w)
        if st:
            per[st] += n
            assigned += n
    return per, assigned, total


def main():
    cats, now, hist, entries, shortlist, official, dex_ety = load()
    lay = proximate_layers(cats)
    windows = {s["id"]: s["window"] for s in STRATA}

    hist_bins = sorted(hist["bins"].values(), key=lambda b: b["from"])

    # ---- ocurențe pe strat, în fiecare instrument -------------------------
    series = {}
    for b in hist_bins:
        per, assigned, total = shares(b["lemmas"], b["lemma_tokens"], lay)
        series[f"h{b['from']}"] = {
            "kind": "istoric", "from": b["from"], "to": b["to"],
            "tokens": b["lemma_tokens"], "assigned": round(assigned),
            "per": {k: round(v) for k, v in per.items()},
            "texts": b["texts"], "authors": b["authors"],
            "top_author": b["top_author"], "top_author_share": b["top_author_share"],
        }
    for reg in ("vorbit", "scris"):
        r = now[reg]
        per, assigned, total = shares(r["lemmas"], r["lemma_tokens"], lay)
        series[reg] = {
            "kind": "contemporan", "register": reg,
            "tokens": r["lemma_tokens"], "assigned": round(assigned),
            "per": {k: round(v) for k, v in per.items()},
        }
    for d in official:
        per, assigned, total = shares(d["lemmas"], d["lemma_tokens"], lay)
        series[f"o{d['year']}"] = {
            "kind": "oficial", "year": d["year"], "label": d["label"],
            "tokens": d["lemma_tokens"], "assigned": round(assigned),
            "per": {k: round(v) for k, v in per.items()},
        }

    # ---- tipuri pe strat --------------------------------------------------
    types_all = Counter(lay.values())
    types_shipped = Counter(lay[w] for w in shortlist if w in lay)

    # exemple: cele mai frecvente cuvinte din fiecare strat, în vorbit + scris
    V, S = now["vorbit"]["lemmas"], now["scris"]["lemmas"]
    vt, st_ = now["vorbit"]["lemma_tokens"], now["scris"]["lemma_tokens"]
    HL = defaultdict(float)
    for b in hist_bins:
        for k, v in b["lemmas"].items():
            HL[k] += v
    ht = sum(b["lemma_tokens"] for b in hist_bins)

    def ppm(w):
        return {
            "vorbit": round(V.get(w, 0) / vt * 1e6, 2) if vt else 0,
            "scris": round(S.get(w, 0) / st_ * 1e6, 2) if st_ else 0,
            "istoric": round(HL.get(w, 0) / ht * 1e6, 2) if ht else 0,
        }

    # Exemplele de pe pagină sunt cuvintele pe care ambele surse le pun în
    # același strat. Un cuvânt cu atribuire nesigură rămâne în statistică, dar
    # nu ajunge să fie chipul stratului.
    def confident(w, st):
        d = dex_ety.get(w)
        return bool(d) and d.get("layer") == st

    examples = {}
    for st in PRIORITY:
        ws = [w for w in shortlist if lay.get(w) == st]
        ws.sort(key=lambda w: -(V.get(w, 0) / max(vt, 1) + S.get(w, 0) / max(st_, 1)))
        solid = [w for w in ws if confident(w, st)]
        examples[st] = (solid or ws)[:24]

    strata_out = []
    for s in STRATA:
        st = s["id"]
        strata_out.append({
            "id": st, "window": s["window"],
            "types_all": types_all.get(st, 0),
            "types_shipped": types_shipped.get(st, 0),
            "examples": examples.get(st, []),
        })

    # ---- cuvintele, în felii după prima literă ----------------------------
    os.makedirs(os.path.join(DATA, "words"), exist_ok=True)
    shard = defaultdict(dict)
    index = []
    plotted = 0
    for w in shortlist:
        st = lay.get(w)
        if not st:
            continue
        e = entries.get(w, {})
        curve = [round(b["lemmas"].get(w, 0) / b["lemma_tokens"] * 1e6, 2)
                 if b["lemma_tokens"] else 0 for b in hist_bins]
        raw = [b["lemmas"].get(w, 0) for b in hist_bins]
        tot_raw = sum(raw)
        nonzero = sum(1 for r in raw if r >= 1)
        ok = tot_raw >= MIN_HIST_TOKENS and nonzero >= MIN_HIST_BINS
        if ok:
            plotted += 1
        first = next((hist_bins[i]["from"] for i, r in enumerate(raw) if r >= 1), None)
        rec = {
            "l": st,
            "p": e.get("pos"),
            "g": e.get("gloss"),
            "lb": e.get("labels") or None,
            "c": [[c["kind"], c["lang"], c.get("word"), c.get("tr")]
                  for c in (e.get("chain") or [])][:4],
            "af": e.get("affix"),
            "f": ppm(w),
            "cv": curve if ok else None,
            "cvn": tot_raw,
            "fa": first,
            # stratul pe care îl dă dexonline, notat numai când diferă de cel
            # publicat (deci după reconciliere: rămâne o părere minoritară)
            "dx": (dex_ety.get(w, {}).get("layer")
                   if dex_ety.get(w, {}).get("layer") not in (None, st) else None),
        }
        rec = {k: v for k, v in rec.items() if v not in (None, [], {})}
        shard[w[0].lower()][w] = rec
        index.append(w)

    # Formele flexionare ale cuvintelor publicate, ca cititorul să poată scrie
    # „caselor" și să primească „casă". Cheia se pune și în varianta fără
    # diacritice, fiindcă mulți scriu „branza".
    from lemma import Lemmatizer, normalize, flatten
    L = Lemmatizer()
    shipped = set(index)
    forms = defaultdict(dict)
    for form, lemmas in L.form2lemmas.items():
        hit = [w for w in lemmas if w in shipped]
        if not hit or form in shipped:
            continue
        target = min(hit, key=lambda w: (-len(w) if False else 0, w)) if len(hit) > 1 else hit[0]
        for key in {form, flatten(form)}:
            if key and key not in shipped:
                forms[key[0]].setdefault(key, target)
    for w in shipped:
        f = flatten(w)
        if f != w:
            forms[f[0]].setdefault(f, w)

    # Cuvintele și formele stau în fișiere separate: cine scrie o lemă nu are de
    # ce să aducă și cele douăzeci de mii de forme flexionare ale literei.
    os.makedirs(os.path.join(DATA, "forms"), exist_ok=True)
    for k in shard:
        with open(os.path.join(DATA, "words", f"{k}.json"), "w", encoding="utf8") as f:
            json.dump({"w": shard[k]}, f, ensure_ascii=False, separators=(",", ":"))
    for k in forms:
        with open(os.path.join(DATA, "forms", f"{k}.json"), "w", encoding="utf8") as f:
            json.dump({"f": forms[k]}, f, ensure_ascii=False, separators=(",", ":"))

    with open(os.path.join(DATA, "index.json"), "w", encoding="utf8") as f:
        json.dump(sorted(index), f, ensure_ascii=False, separators=(",", ":"))

    # ---- metadate despre instrumente -------------------------------------
    meta = {
        "hist": {
            "bins": [{"from": b["from"], "to": b["to"], "tokens": b["lemma_tokens"],
                      "texts": b["texts"], "authors": b["authors"],
                      "top_author": b["top_author"],
                      "top_author_share": b["top_author_share"],
                      "unmatched": round(b["unmatched_tokens"] / max(b["tokens"], 1), 4)}
                     for b in hist_bins],
            "cap_per_author": hist["cap_per_author"],
            "total_tokens": ht,
        },
        "now": {r: {"tokens": now[r]["lemma_tokens"],
                    "unmatched": round(now[r]["unmatched_tokens"] / max(now[r]["tokens"], 1), 4),
                    **{k: v for k, v in now[r].items()
                       if k in ("articles", "chars", "form_types")}}
                for r in ("vorbit", "scris")},
        "official": [{"year": d["year"], "label": d["label"], "page": d["page"],
                      "tokens": d["lemma_tokens"]} for d in official],
        "vocab": {
            "layered_lemmas": len(lay),
            "shipped": len(index),
            "plotted": plotted,
            "with_gloss": sum(1 for w in index if entries.get(w, {}).get("gloss")),
            "with_chain": sum(1 for w in index if entries.get(w, {}).get("chain")),
        },
        "overrides": load_overrides(),
        "dex_forms": sum(1 for _ in open(os.path.join(RAW, "dex_forms.tsv"),
                                        encoding="utf8")),
        "series": series,
    }
    with open(os.path.join(DATA, "strata.json"), "w", encoding="utf8") as f:
        json.dump(strata_out, f, ensure_ascii=False, indent=1)
    with open(os.path.join(DATA, "meta.json"), "w", encoding="utf8") as f:
        json.dump(meta, f, ensure_ascii=False, indent=1)

    # ---- raport pe ecran --------------------------------------------------
    print(f"cuvinte publicate: {len(index)}  cu curbă proprie: {plotted}  "
          f"cu sens în engleză: {meta['vocab']['with_gloss']}  "
          f"cu lanț: {meta['vocab']['with_chain']}")
    print("\ntipuri vs ocurențe (vorbit / scris), procent din ce s-a putut atribui:")
    tv = series["vorbit"]; ts = series["scris"]
    tt = sum(types_all.values())
    print(f"{'strat':<16}{'tipuri':>9}{'%tip':>7}{'%vorbit':>9}{'%scris':>8}")
    for st in PRIORITY:
        print(f"{st:<16}{types_all.get(st,0):>9}{types_all.get(st,0)/tt*100:>6.1f}%"
              f"{tv['per'].get(st,0)/tv['assigned']*100:>8.1f}%"
              f"{ts['per'].get(st,0)/ts['assigned']*100:>7.1f}%")
    print(f"\ntext atribuit unui strat: vorbit {tv['assigned']/tv['tokens']*100:.1f}%, "
          f"scris {ts['assigned']/ts['tokens']*100:.1f}%")
    print("\nturca și franceza în serie:")
    for k, s in series.items():
        if s["kind"] == "istoric":
            lbl = f"{s['from']}-{s['to']}"
        elif s["kind"] == "oficial":
            lbl = f"oficial {s['year']}"
        else:
            lbl = s["register"]
        print(f"  {lbl:<14} turcă {s['per'].get('turca',0)/s['assigned']*1000:>6.2f}‰"
              f"   franceză {s['per'].get('franceza',0)/s['assigned']*100:>5.1f}%"
              f"   slavonă {s['per'].get('slavona',0)/s['assigned']*100:>4.1f}%"
              f"   latină {s['per'].get('latina',0)/s['assigned']*100:>5.1f}%")


if __name__ == "__main__":
    main()
