#!/usr/bin/env python3
"""Brute + share words → TRUE equates → ASM · same time.

Two lanes (same process · concurrent threads):
  BRUTE   EN→L→EN honest (no identity cheat) · score langs
  SHARE   TRUE English atoms × langs → surface equates · cook ASM

Solved surfaces append to true_equates/equates.tsv · cook out/true_equates.{asm,json}
Understanding: commonality neighbors · share report out/brute_share_report.json

Law: English folder seats · surface swap · ATOM_SURFACE · C IS LIE · free thrift
Usage:
  PYTHONPATH=Projects/GrokClaws python3 Build/brute-share-words.py
  PYTHONPATH=… python3 Build/brute-share-words.py --atoms YES,NO,TRUE --langs ru,es,de
"""
from __future__ import annotations

import argparse
import json
import sys
import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple

ROOT = Path(__file__).resolve().parents[1]
TSV = ROOT / "true_equates" / "equates.tsv"
COMMON = ROOT / "true_equates" / "commonality.tsv"
OUT = ROOT / "out"
REPORT = OUT / "brute_share_report.json"
COOK = ROOT / "Build" / "true-equates-to-asm.py"
CLAWS_JSON = Path.home() / "Projects" / "GrokClaws" / "ui" / "true_equates.json"

# seed TRUE seats we always want more surfaces for
DEFAULT_ATOMS = [
    "YES", "NO", "NOT", "TRUE", "GOD", "KNOW", "ARE", "IS", "GRIN",
    "MAIL", "DETAILS", "WHO", "SAVE", "SEND", "LIVE", "SELF", "ALPHA",
    "OMEGA", "REPLY", "HELLO", "THANKS", "FROM", "TO", "SUBJECT",
    "FRIEND", "WRITE", "READ", "START",
]

# English surface forms to translate (atom → en word)
ATOM_EN = {
    "YES": "yes",
    "NO": "no",
    "NOT": "not",
    "TRUE": "true",
    "GOD": "god",
    "KNOW": "know",
    "ARE": "are",
    "IS": "is",
    "GRIN": "GRIN",  # invariant · should stay GRIN
    "MAIL": "mail",
    "DETAILS": "details",
    "WHO": "who",
    "SAVE": "save",
    "SEND": "send",
    "LIVE": "live",
    "SELF": "self",
    "ALPHA": "alphabet",
    "OMEGA": "omega",
    "REPLY": "reply",
    "HELLO": "hello",
    "THANKS": "thanks",
    "FROM": "from",
    "TO": "to",
    "SUBJECT": "subject",
    "FRIEND": "friend",
    "WRITE": "write",
    "READ": "read",
    "START": "start",
}

BRUTE_PHRASE = (
    "YES we ARE IS TRUE. NO and NOT equal. GOD IS KNOW. GRIN free 1."
)

_lock = threading.Lock()


def _ensure_path() -> None:
    claws = Path.home() / "Projects" / "GrokClaws"
    if str(claws) not in sys.path:
        sys.path.insert(0, str(claws))


def load_existing() -> Set[Tuple[str, str, str]]:
    seen: Set[Tuple[str, str, str]] = set()
    if not TSV.is_file():
        return seen
    for ln in TSV.read_text(encoding="utf-8").splitlines():
        if not ln.strip() or ln.startswith("#") or ln.startswith("atom_en"):
            continue
        p = ln.split("\t")
        if len(p) >= 3:
            seen.add((p[0].strip().upper(), p[1].strip(), p[2].strip().lower()))
    return seen


def load_commonality() -> Dict[str, Dict[str, List[str]]]:
    out: Dict[str, Dict[str, List[str]]] = {}
    if not COMMON.is_file():
        return out
    for ln in COMMON.read_text(encoding="utf-8").splitlines():
        if not ln.strip() or ln.startswith("#") or ln.startswith("atom_en"):
            continue
        p = ln.split("\t")
        if len(p) < 3:
            continue
        atom = p[0].strip().upper()
        prev = [x.strip().upper() for x in (p[1] or "").split(",") if x.strip()]
        nxt = [x.strip().upper() for x in (p[2] or "").split(",") if x.strip()]
        out[atom] = {"prev": prev, "next": nxt}
    return out


def append_equate(atom: str, lang: str, surface: str, existing: Set[Tuple[str, str, str]]) -> bool:
    atom = atom.upper()
    surface = (surface or "").strip()
    lang = (lang or "").strip()
    if not atom or not surface or not lang:
        return False
    # GRIN invariant
    if atom == "GRIN" and surface.upper() != "GRIN":
        # only accept if still GRIN-ish
        if "grin" not in surface.lower():
            return False
    key = (atom, lang, surface.lower())
    with _lock:
        if key in existing:
            return False
        existing.add(key)
        TSV.parent.mkdir(parents=True, exist_ok=True)
        if not TSV.is_file():
            TSV.write_text(
                "# atom_en\tlang\tsurface\natom_en\tlang\tsurface\n", encoding="utf-8"
            )
        with TSV.open("a", encoding="utf-8") as f:
            f.write(f"{atom}\t{lang}\t{surface}\n")
    return True


def cook_asm() -> Dict[str, Any]:
    import subprocess

    OUT.mkdir(parents=True, exist_ok=True)
    r = subprocess.run(
        [sys.executable, str(COOK)],
        cwd=str(ROOT),
        capture_output=True,
        text=True,
    )
    # deploy json to claws UI if present
    src = OUT / "true_equates.json"
    if src.is_file() and CLAWS_JSON.parent.is_dir():
        try:
            CLAWS_JSON.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
            # also /var/www if writable
            www = Path("/var/www/biggrinrtx-mail/true_equates.json")
            if www.parent.is_dir():
                try:
                    www.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
                except OSError:
                    pass
        except OSError:
            pass
    return {
        "ok": r.returncode == 0,
        "code": r.returncode,
        "out": (r.stdout or "")[-400:],
        "err": (r.stderr or "")[-400:],
        "asm": str(OUT / "true_equates.asm"),
        "json": str(src),
    }


def share_atom_lang(atom: str, lang: str, existing: Set[Tuple[str, str, str]]) -> Dict[str, Any]:
    from grokclaws.grin_translate import translate_best

    en = ATOM_EN.get(atom, atom.lower())
    if atom == "GRIN":
        # always share invariant
        added = append_equate("GRIN", lang, "GRIN", existing)
        return {
            "atom": atom,
            "lang": lang,
            "surface": "GRIN",
            "added": added,
            "conf": "exact",
            "moved": True,
            "note": "invariant",
        }
    r = translate_best(en, "en", lang)
    surface = (r.get("translated") or "").strip()
    fetched = int(r.get("fetched") or 0)
    cached = int(r.get("cached") or 0)
    moved = fetched > 0 or (surface.lower() != en.lower() and bool(surface))
    # clean: one token preferred
    if surface:
        surface = surface.split()[0].strip(".,;:!?\"'()[]")
    conf = "exact" if moved and surface and surface.lower() != en.lower() else (
        "sorta" if moved else "unknown"
    )
    added = False
    if moved and surface and conf in ("exact", "sorta"):
        added = append_equate(atom, lang, surface, existing)
    return {
        "atom": atom,
        "lang": lang,
        "en": en,
        "surface": surface,
        "added": added,
        "conf": conf,
        "moved": moved,
        "fetched": fetched,
        "cached": cached,
    }


def brute_lane(text: str, langs: List[str]) -> Dict[str, Any]:
    from grokclaws.grin_translate import brute_all_languages

    return brute_all_languages(text, langs)


def share_lane(
    atoms: List[str],
    langs: List[str],
    existing: Set[Tuple[str, str, str]],
    workers: int = 4,
) -> List[Dict[str, Any]]:
    jobs = [(a, L) for a in atoms for L in langs]
    results: List[Dict[str, Any]] = []
    with ThreadPoolExecutor(max_workers=max(1, workers)) as ex:
        futs = {ex.submit(share_atom_lang, a, L, existing): (a, L) for a, L in jobs}
        for fut in as_completed(futs):
            try:
                results.append(fut.result())
            except Exception as e:
                a, L = futs[fut]
                results.append(
                    {"atom": a, "lang": L, "error": f"{type(e).__name__}: {e}", "added": False}
                )
            time.sleep(0.02)
    return results


def understanding_lines(
    shared: List[Dict[str, Any]], common: Dict[str, Dict[str, List[str]]]
) -> List[str]:
    """Solved surfaces + TRUE neighbor chain."""
    lines = []
    by_atom: Dict[str, List[str]] = {}
    for row in shared:
        if not row.get("added"):
            continue
        a = row.get("atom") or ""
        by_atom.setdefault(a, []).append(
            f"{row.get('lang')}:{row.get('surface')}[{row.get('conf')}]"
        )
    for atom, surfs in sorted(by_atom.items()):
        nb = common.get(atom) or {}
        prev = ",".join(nb.get("prev") or []) or "—"
        nxt = ",".join(nb.get("next") or []) or "—"
        lines.append(f"{atom} ←[{prev}] · →[{nxt}] · new {', '.join(surfs[:12])}")
    return lines


def main() -> int:
    _ensure_path()
    ap = argparse.ArgumentParser(description="Brute + share TRUE words → ASM")
    ap.add_argument("--atoms", default=",".join(DEFAULT_ATOMS), help="comma TRUE atoms")
    ap.add_argument("--langs", default="", help="comma langs · default CYCLE non-en")
    ap.add_argument("--phrase", default=BRUTE_PHRASE, help="brute phrase")
    ap.add_argument("--workers", type=int, default=4)
    ap.add_argument("--no-brute", action="store_true")
    ap.add_argument("--no-share", action="store_true")
    ap.add_argument("--no-cook", action="store_true")
    args = ap.parse_args()

    from grokclaws.grin_translate import CYCLE

    atoms = [a.strip().upper() for a in args.atoms.split(",") if a.strip()]
    if args.langs.strip():
        langs = [x.strip() for x in args.langs.split(",") if x.strip()]
    else:
        langs = [c for c in CYCLE if c != "en"]

    existing = load_existing()
    common = load_commonality()
    t0 = time.time()
    print(f"brute-share · atoms={len(atoms)} langs={len(langs)} existing={len(existing)}", flush=True)

    brute_res: Dict[str, Any] = {}
    shared: List[Dict[str, Any]] = []

    # same time · two lanes
    with ThreadPoolExecutor(max_workers=2) as ex:
        f_brute = None if args.no_brute else ex.submit(brute_lane, args.phrase, langs)
        f_share = None if args.no_share else ex.submit(
            share_lane, atoms, langs, existing, args.workers
        )
        if f_brute:
            print("lane BRUTE …", flush=True)
            brute_res = f_brute.result()
            print(
                "brute:",
                brute_res.get("message"),
                "moved",
                brute_res.get("moved_count"),
                "grin",
                brute_res.get("grin_count"),
                flush=True,
            )
        if f_share:
            print("lane SHARE …", flush=True)
            shared = f_share.result()
            added_n = sum(1 for r in shared if r.get("added"))
            print(f"share: {added_n} new equates · {len(shared)} attempts", flush=True)

    cook_res: Dict[str, Any] = {}
    if not args.no_cook:
        print("cook ASM …", flush=True)
        cook_res = cook_asm()
        print("cook:", "ok" if cook_res.get("ok") else "fail", cook_res.get("asm"), flush=True)

    understandings = understanding_lines(shared, common)
    report = {
        "ok": True,
        "elapsed_s": round(time.time() - t0, 1),
        "law": "same-time brute + share · TRUE seats · ASM cook · no identity cheat",
        "atoms": atoms,
        "langs": langs,
        "brute": {
            "all_grin": brute_res.get("all_grin"),
            "grin_count": brute_res.get("grin_count"),
            "moved_count": brute_res.get("moved_count"),
            "avg_accuracy": brute_res.get("avg_accuracy"),
            "message": brute_res.get("message"),
            "no_translate": brute_res.get("no_translate"),
            "grin_langs": brute_res.get("grin_langs"),
        },
        "share": {
            "attempts": len(shared),
            "added": sum(1 for r in shared if r.get("added")),
            "exact": sum(1 for r in shared if r.get("conf") == "exact"),
            "sorta": sum(1 for r in shared if r.get("conf") == "sorta"),
            "unknown": sum(1 for r in shared if r.get("conf") == "unknown"),
            "rows": [r for r in shared if r.get("added")][:200],
        },
        "understandings": understandings,
        "cook": cook_res,
        "paths": {
            "equates": str(TSV),
            "asm": str(OUT / "true_equates.asm"),
            "json": str(OUT / "true_equates.json"),
            "report": str(REPORT),
        },
    }
    OUT.mkdir(parents=True, exist_ok=True)
    REPORT.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print("--- understandings ---", flush=True)
    for ln in understandings[:40]:
        print(" ", ln, flush=True)
    print(f"report → {REPORT}", flush=True)
    print(
        f"done · added={report['share']['added']} · "
        f"brute_grin={report['brute'].get('grin_count')} · "
        f"{report['elapsed_s']}s",
        flush=True,
    )
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
