"""CharacterEval's published results, read from the LaTeX source of arXiv:2401.01275v2 (Table 4, tab:detail_result).
Writes the same TypeScript module into the server and the worker adapters (published.ts). Run from the project root:
  curl -sL https://arxiv.org/e-print/2401.01275v2 -o src.tar.gz && mkdir src && tar -xzf src.tar.gz -C src
  python3 -I tools/experiments/charactereval_paper.py src
Checks every row is complete and records, without changing any number, where a printed dimension average differs from the
mean of its printed metrics by more than rounding can explain: each printed value is within 0.0005 of its exact value, so
the mean of the printed metrics and the printed average can differ by up to 0.001 without any error."""
import hashlib, json, re, sys
from pathlib import Path

# Column order of the two tabulars of Table 4.
CC = ['ke', 'ka', 'kh', 'pb', 'pu', 'cc_avg', 'mbti']
CA_RA = ['flu', 'coh', 'cons', 'ca_avg', 'hl', 'cs', 'ed', 'emp', 'ra_avg']
AVGS = {'cc_avg': ['ke', 'ka', 'kh', 'pb', 'pu'], 'ca_avg': ['flu', 'coh', 'cons'], 'ra_avg': ['hl', 'cs', 'ed', 'emp']}


def clean(s: str) -> str:
    s = re.sub(r'\\multicolumn\{\d+\}\{[^}]*\}\{(.*)\}', r'\1', s.strip())
    return re.sub(r'\\(textbf|underline)\{([^}]*)\}', r'\2', s).strip()


def num(s: str):
    s = clean(s)
    return None if not re.search(r'\d', s) else float(s)


def tabulars(tex: str):
    # The label also appears in a commented-out draft of the table; use the live one (a line not starting with %).
    end = re.search(r'^\\label\{tab:detail_result\}', tex, re.M).start()
    start = tex.rindex('\\subsection{Overall Performance}', 0, end)  # the table* holds two tabulars
    body = tex[start:end]
    return re.findall(r'\\begin\{tabular\}(.*?)\\end\{tabular\}', body, re.S)


def rows(tab: str, cols: list[str]):
    out = {}
    for line in tab.split('\n'):
        line = re.sub(r'(\\\\)?\s*(\\bottomrule)?\s*$', '', line.strip()).strip()  # trailing row end and rule
        if not line or line.startswith('%') or line.startswith('&') or '\\textbf{Character' in line:
            continue
        parts = [p for p in line.split('&')]
        if len(parts) != len(cols) + 1:
            continue
        name = clean(parts[0])
        if not name:
            continue
        out[name] = dict(zip(cols, (num(p) for p in parts[1:])))
    return out


if __name__ == '__main__':
    src = Path(sys.argv[1])
    tex = (src / 'acl_latex.tex').read_text()
    t1, t2 = tabulars(tex)
    a, b = rows(t1, CC), rows(t2, CA_RA)
    assert list(a) == list(b) and len(a) == 15, (list(a), list(b))
    data = []
    for name in a:
        v = {**a[name], **b[name]}
        for k, x in v.items():
            assert x is not None or k == 'mbti', (name, k)
        mismatch = []
        for avg, parts in AVGS.items():
            mean = sum(v[p] for p in parts) / len(parts)
            if abs(mean - v[avg]) > 0.001:
                mismatch.append({'column': avg, 'printed': v[avg], 'mean': round(mean, 4)})
                print(f'  average mismatch: {name} {avg}: printed {v[avg]}, mean of metrics {mean:.4f}')
        data.append({'key': re.sub(r'[^a-z0-9.]+', '-', name.lower()).strip('-'), 'name': name, 'scores': v, 'avgMismatch': mismatch})
    digest = hashlib.sha256(tex.encode()).hexdigest()
    print(f'{len(data)} models; source sha256 {digest[:16]}')
    ts = ('/** Generated by tools/experiments/charactereval_paper.py from the LaTeX source of arXiv:2401.01275v2 (Table 4); do not edit by hand.\n'
          f' * Source file acl_latex.tex, sha256 {digest}. Metrics are CharacterRM scores on a 1–5 scale; mbti is the personality\n'
          ' * back-testing accuracy (0–1), null where the paper reports none (CharacterGLM). */\n'
          "import type { PaperRow } from './meta.ts';\n\n"
          f'export const SOURCE_SHA256 = {json.dumps(digest)};\n'
          f'export const PAPER_ROWS: PaperRow[] = {json.dumps(data, ensure_ascii=False, indent=1)};\n')
    for side in ('server', 'worker'):
        Path(f'{side}/src/benchmarks/charactereval').mkdir(exist_ok=True)
        Path(f'{side}/src/benchmarks/charactereval/published.ts').write_text(ts)
