"""Parse IRI/CPC ENSO plume SVGs (ensoforecast.iri.columbia.edu/figure4_plot/Y/M0 -> raw/f4/YYYY-MM.svg) into
data/plume.csv: issue, model, k (0 = first forecast season), season, nino34 (degC).
Plot lines are matched to legend entries by line style (stroke colour, width, dash) plus marker id; values from the
y-tick positions (linear fit), seasons from the x-tick positions (labels without '-OBS')."""
import re, glob, os, csv
import numpy as np
R = os.environ.get('ROOT', '/work/enso-objective')
def style_sig(block):
    p = re.search(r'<path d="[^"]*"[^>]*style="([^"]*)"', block)
    st = p.group(1) if p else ''
    col = re.search(r'stroke: (#[0-9a-f]{6})', st); w = re.search(r'stroke-width: ([\d.]+)', st); da = re.search(r'stroke-dasharray: ([\d.,]+)', st)
    mk = re.search(r'<use xlink:href="#(m[0-9a-f]+)"', block)
    return (col.group(1) if col else None, w.group(1) if w else '1.5', da.group(1) if da else '', mk.group(1) if mk else '')
rows, log = [], []
for f in sorted(glob.glob(f'{R}/raw/f4/*.svg')):
    s = open(f, errors='replace').read(); tag = os.path.basename(f)[:-4]
    if not s.startswith('<?xml'): log.append((tag, 'not svg')); continue
    mt = re.search(r'<!-- CCSR/IRI Model Predictions of ENSO from (\w+ \d{4}) -->', s)
    if not mt: log.append((tag, 'no title')); continue
    xt = [(float(m.group(1)), m.group(2)) for m in re.finditer(r'<g id="xtick_\d+">.*?<use [^>]*x="([\d.]+)".*?<!-- ([^>]+?) -->', s, re.S)]
    yt = [(float(m.group(1)), float(m.group(2).replace('−', '-'))) for m in re.finditer(r'<g id="ytick_\d+">.*?<use [^>]*y="([\d.]+)".*?<!-- ([^>]+?) -->', s, re.S)]
    A = np.polyfit([a for a, _ in yt], [b for _, b in yt], 1)
    fc_ticks = [(x, l) for x, l in xt if 'OBS' not in l]
    li = s.find('<g id="legend_1">'); body, leg = s[:li], s[li:]
    L = [(m.group(2).strip(), style_sig(m.group(1))) for m in re.finditer(r'<g id="line2d_\d+">(.*?)</g>\s*(?:</g>\s*)?<g id="text_\d+">\s*<!-- ([^>]*?) -->', leg, re.S)]
    L = [(n, sg) for n, sg in L if n and not n.endswith(':') and sg[0]]
    sigs = {sg for _, sg in L}
    B = [(style_sig(m.group(0)), m.group(1)) for m in re.finditer(r'<g id="line2d_\d+">\s*<path d="([^"]*)"[^>]*clip-path[^>]*/>(.*?)(?=<g id="line2d_|<g id="legend_1">)', body, re.S)]
    B = [(sg, d_) for sg, d_ in B if sg in sigs]
    # body lines are drawn in legend order within each signature: assign greedily in order
    queue = {}
    for n, sg in L: queue.setdefault(sg, []).append(n)
    dup = {sg: v for sg, v in queue.items() if len(v) > 1}
    cnt = {}
    for sg, _ in B: cnt[sg] = cnt.get(sg, 0) + 1
    if any(cnt.get(sg, 0) != len(v) for sg, v in queue.items()):
        log.append((tag, 'legend/line count mismatch', [(v, cnt.get(sg, 0)) for sg, v in queue.items() if cnt.get(sg, 0) != len(v)]))
        if any(cnt.get(sg, 0) != len(v) for sg, v in dup.items()):
            B = [(sg, d_) for sg, d_ in B if sg not in dup]  # cannot assign shared styles safely
    used = {}
    for sg, dpath in B:
        i = used.get(sg, 0); used[sg] = i + 1; name = queue[sg][i]
        pts = [tuple(map(float, p)) for p in re.findall(r'([\d.]+) ([\d.]+)', dpath)]
        for x, y in pts:
            j = int(np.argmin([abs(x - t[0]) for t in fc_ticks])); xs, lab = fc_ticks[j]
            if abs(x - xs) > 1: continue
            rows.append(dict(issue=tag, title=mt.group(1), model=name, k=j, season=lab, nino34=round(float(np.polyval(A, y)), 3)))
with open(f'{R}/data/plume.csv', 'w', newline='') as fh:
    w = csv.DictWriter(fh, fieldnames=['issue', 'title', 'model', 'k', 'season', 'nino34']); w.writeheader(); w.writerows(rows)
print(len(rows), 'values', len({r['issue'] for r in rows}), 'issues', len({r['model'] for r in rows}), 'model names')
for l in log[:30]: print(l)
print(len(log), 'log entries')
