#!/usr/bin/env python3
"""
Low-memory stat-row generator (bin empties / waste diverted / driving-emissions).
One endpoint, three row templates (icon+label baked), shared digit fragments.
Outputs:
    impact-stat.php            <- ?tpl=empties|waste|miles&value=N
    impact-stat-<row>-tpl.json <- one per row
    impact-stat-frag.json      <- digits + " lb"/" mi" suffixes (only thing PHP decodes)
Run here:  python build-stat-php.py
"""
import json, copy, importlib.util
from pathlib import Path
from fontTools.ttLib import TTFont

here = Path(__file__).parent
spec = importlib.util.spec_from_file_location('base', here / 'build-numbers.py')
b = importlib.util.module_from_spec(spec); spec.loader.exec_module(b)

INK, SOFT = '#151E29', '#51635F'
COLORS = {'blue': '#E2EDF1', 'green': '#CCDFCB'}
W, H, FR, OP = 680, 132, 60, 90
VX, VY, LY = 118, 72, 104          # value x/baseline, label baseline
FONT = TTFont(r'C:\Windows\Fonts\arialbd.ttf')
J = lambda o: json.dumps(o, separators=(',', ':'))

def contours(text, size, x, baseline):
    gs = FONT.getGlyphSet(); cmap = FONT.getBestCmap(); scale = size / FONT['head'].unitsPerEm
    items = []; cx = x
    for ch in text:
        gid = cmap.get(ord(ch))
        if gid is None: cx += size * 0.3; continue
        glyph = gs[gid]; pen = b.OutlinePen(gs, scale, cx, baseline); glyph.draw(pen); pen.finish()
        items += pen.contours; cx += glyph.width * scale
    return items, cx
def grp(text, size, color):
    c, w = contours(text, size, 0, 0); return b.group(c + [b.fill(color)]), w
def tok(group):
    return J(group).replace('"p":{"a":0,"k":[0,0,0]}', '"p":{"a":0,"k":[__X__,__Y__,0]}', 1)
def pos(group, x, y):
    g = copy.deepcopy(group); g['it'][-1]['p']['k'] = [x, y, 0]; return g
def tl(name, shapes):
    l = b.shapeLayer(name, shapes); l['op'] = OP; return l
def stroke(wdt, color):
    return {'ty': 'st', 'c': b.P(b.col(color)), 'o': b.P(100), 'w': b.P(wdt), 'lc': 2, 'lj': 2, 'ml': 4, 'r': 1}
def line(pts, closed, wdt, color):
    return b.group([{'ty': 'sh', 'ks': b.P({'c': closed, 'v': pts, 'i': [[0, 0]] * len(pts), 'o': [[0, 0]] * len(pts)})}, stroke(wdt, color)])
def bez(v, i, o, closed, wdt, color):
    return b.group([{'ty': 'sh', 'ks': b.P({'c': closed, 'v': v, 'i': i, 'o': o})}, stroke(wdt, color)])

# ---- icons (centred at cx,cy) ----
def refresh(cx, cy):
    ring = b.group([{'ty': 'el', 'p': b.P([cx, cy]), 's': b.P([30, 30]), 'd': 1}, stroke(2.6, INK)])
    head = line([[cx + 7, cy - 17], [cx + 16, cy - 15], [cx + 14, cy - 6]], False, 2.6, INK)  # arrowhead at top
    return tl('Icon', [ring, head])
def apple(cx, cy):
    body = b.group([{'ty': 'el', 'p': b.P([cx, cy + 4]), 's': b.P([30, 33]), 'd': 1}, stroke(2.6, INK)])
    stem = line([[cx, cy - 12], [cx + 5, cy - 20]], False, 2.6, INK)
    lf = b.group([{'ty': 'el', 'p': b.P([cx + 11, cy - 17]), 's': b.P([13, 8]), 'd': 1}, b.fill(INK)])
    return tl('Icon', [body, stem, lf])
def car(cx, cy):
    body = b.group([{'ty': 'rc', 'p': b.P([cx, cy + 3]), 's': b.P([46, 20]), 'r': b.P(6), 'd': 1}, stroke(2.6, INK)])
    roof = bez([[cx - 15, cy - 7], [cx, cy - 16], [cx + 15, cy - 7]],
               [[0, 0], [-8, 0], [-4, -6]], [[4, -6], [8, 0], [0, 0]], False, 2.6, INK)
    h1 = b.group([{'ty': 'el', 'p': b.P([cx - 12, cy + 5]), 's': b.P([7, 7]), 'd': 1}, b.fill(INK)])
    h2 = b.group([{'ty': 'el', 'p': b.P([cx + 12, cy + 5]), 's': b.P([7, 7]), 'd': 1}, b.fill(INK)])
    return tl('Icon', [roof, body, h1, h2])

ICONS = {'empties': refresh, 'waste': apple, 'miles': car}
ROWS = [('empties', 'bin empties', 'blue'), ('waste', 'waste diverted', 'green'),
        ('miles', 'driving emissions equivalent', 'blue')]

# ---- fragments (size 40 digits + suffixes) ----
big, bigW = {}, {}
for d in range(10):
    g, w = grp(str(d), 52, INK); big[str(d)] = tok(g); bigW[str(d)] = w
FRAG = {'big': big, 'bigW': bigW, 'lb': tok(grp(' lb', 52, INK)[0]), 'mi': tok(grp(' mi', 52, INK)[0])}
(here / 'impact-stat-frag.json').write_text(J(FRAG), encoding='utf-8')

def chevron():
    return tl('Chevron', [line([[W - 34, VY - 14], [W - 26, VY - 6], [W - 34, VY + 2]], False, 3, SOFT)])

for row, label, color in ROWS:
    lbl, _ = grp(label, 21, SOFT)
    card = b.shapeLayer('Card', [b.rect(W / 2, H / 2, W, H, 22, COLORS[color])]); card['op'] = OP
    card['ks']['o'] = b.anim([(0, 0), (18, 100)])
    layers = [
        tl('Value', ['__VALUE__']),
        tl('Label', [pos(lbl, VX, LY)]),
        ICONS[row](58, 66),
        chevron(),
        card,
    ]
    for i2, l in enumerate(layers): l['ind'] = i2 + 1
    doc = {'v': '5.12.2', 'fr': FR, 'ip': 0, 'op': OP, 'w': W, 'h': H,
           'nm': 'Root impact stat ' + row, 'ddd': 0, 'assets': [], 'layers': layers}
    (here / ('impact-stat-%s-tpl.json' % row)).write_text(J(doc), encoding='utf-8')
    # python mirror -> test json + validate
    def numshapes(num, startX, Y):
        out = ''; cx = startX
        for ch in num:
            f = FRAG['big'][ch].replace('__X__', repr(cx)).replace('__Y__', repr(Y))
            out += ('' if out == '' else ',') + f; cx += FRAG['bigW'][ch]
        return out, cx
    sample = {'empties': ('18', ''), 'waste': ('38', 'lb'), 'miles': ('67', 'mi')}[row]
    ns, ex = numshapes(sample[0], VX, VY); val = ns
    if sample[1]:
        val += ',' + FRAG[sample[1]].replace('__X__', repr(ex)).replace('__Y__', repr(VY))
    test = J(doc).replace('"__VALUE__"', val); json.loads(test)
    (here / ('impact-stat-%s-test.json' % row)).write_text(test, encoding='utf-8')

php = r'''<?php
@ini_set('memory_limit','256M');
register_shutdown_function(function(){ $e=error_get_last(); if($e){ @file_put_contents(__DIR__.'/last_error.txt',$e['message'].' in '.$e['file'].':'.$e['line']); } });
$dir=__DIR__;
$tpl=isset($_GET['tpl'])?preg_replace('/[^a-z]/','',$_GET['tpl']):'';
$allow=array('empties'=>'','waste'=>'lb','miles'=>'mi');
if(!isset($allow[$tpl])){ header('HTTP/1.1 400 Bad Request'); echo '{"error":"bad tpl"}'; exit; }
$value=isset($_GET['value'])?(int)$_GET['value']:0; if($value<0)$value=0; if($value>9999999)$value=9999999;
$T=@file_get_contents($dir.'/impact-stat-'.$tpl.'-tpl.json'); $fj=@file_get_contents($dir.'/impact-stat-frag.json');
if($T===false||$fj===false){ header('HTTP/1.1 500 Internal Server Error'); echo '{"error":"missing stat data files"}'; exit; }
$F=json_decode($fj,true); if(!is_array($F)){ header('HTTP/1.1 500 Internal Server Error'); echo '{"error":"frag decode failed"}'; exit; }
$VX=118; $VY=72; $num=(string)$value; $out=''; $cx=$VX; $n=strlen($num);
for($i=0;$i<$n;$i++){ $d=$num[$i]; $f=str_replace(array('__X__','__Y__'),array((string)$cx,(string)$VY),$F['big'][$d]);
  $out.=($out===''?'':',').$f; $cx+=$F['bigW'][$d]; }
$unit=$allow[$tpl];
if($unit!==''){ $out.=','.str_replace(array('__X__','__Y__'),array((string)$cx,(string)$VY),$F[$unit]); }
echo str_replace('"__VALUE__"',$out,$T);
header('Content-Type: application/json; charset=utf-8');
'''
# Note: header() after echo would warn; put headers first instead.
php = r'''<?php
@ini_set('memory_limit','256M');
register_shutdown_function(function(){ $e=error_get_last(); if($e){ @file_put_contents(__DIR__.'/last_error.txt',$e['message'].' in '.$e['file'].':'.$e['line']); } });
header('Content-Type: application/json; charset=utf-8'); header('Access-Control-Allow-Origin: *');
$dir=__DIR__;
$tpl=isset($_GET['tpl'])?preg_replace('/[^a-z]/','',$_GET['tpl']):'';
$allow=array('empties'=>'','waste'=>'lb','miles'=>'mi');
if(!isset($allow[$tpl])){ echo '{"error":"bad tpl"}'; exit; }
$value=isset($_GET['value'])?(int)$_GET['value']:0; if($value<0)$value=0; if($value>9999999)$value=9999999;
$T=@file_get_contents($dir.'/impact-stat-'.$tpl.'-tpl.json'); $fj=@file_get_contents($dir.'/impact-stat-frag.json');
if($T===false||$fj===false){ echo '{"error":"missing stat data files"}'; exit; }
$F=json_decode($fj,true); if(!is_array($F)){ echo '{"error":"frag decode failed"}'; exit; }
$VX=118; $VY=72; $num=(string)$value; $out=''; $cx=$VX; $n=strlen($num);
for($i=0;$i<$n;$i++){ $d=$num[$i]; $f=str_replace(array('__X__','__Y__'),array((string)$cx,(string)$VY),$F['big'][$d]);
  $out.=($out===''?'':',').$f; $cx+=$F['bigW'][$d]; }
$unit=$allow[$tpl];
if($unit!==''){ $out.=','.str_replace(array('__X__','__Y__'),array((string)$cx,(string)$VY),$F[$unit]); }
echo str_replace('"__VALUE__"',$out,$T);
'''
(here / 'impact-stat.php').write_text(php, encoding='utf-8')
print('Wrote impact-stat.php + 3 row templates + impact-stat-frag.json')
