"""Offline annual-rate extraction and original Meplar SVGs, no network.
Run python3 reproduce.py --out reproduced [--extract-pdf] [--render-png].
Default Python standard library parses the frozen mechanical pdftotext extract.
Optional PDF extraction requires an existing pdftotext; PNG needs existing librsvg.
"""
from pathlib import Path
from hashlib import sha256
from decimal import Decimal
import argparse,csv,html,json,re,subprocess,tempfile
PDF_SHA='338497ce3794ce740a54645a1e740519581713a0f7cc45d90e1b16cd93d39f6c'
TEXT_SHA='178a3260a5d97608a04653f15460cce0c2734b06b0909ae925a3b82c57cddc5e'
INK,MUTED,BLUE,RUST,PAPER,GRID,GRAY='#172128','#53616b','#2457c5','#b54718','#faf9f6','#d9dedf','#7d888e'
FONT='-apple-system, BlinkMacSystemFont, Helvetica Neue, Arial, sans-serif'
GROUPS=['25–29','30–34','35–39']
KEYS=['age_25_29','age_30_34','age_35_39']

def extract(root,extract_pdf=False):
    pdf=root/'raw/birth-statistics-2025-korean.pdf';assert sha256(pdf.read_bytes()).hexdigest()==PDF_SHA
    source=root/'raw/annual-table-extract.txt';assert sha256(source.read_bytes()).hexdigest()==TEXT_SHA
    text=source.read_text()
    if extract_pdf:
        with tempfile.TemporaryDirectory(prefix='meplar-asfr-extract-') as td:
            p=Path(td)/'extract.txt';subprocess.run(['/opt/homebrew/bin/pdftotext','-f','38','-l','38','-layout','-enc','UTF-8',str(pdf),str(p)],check=True)
            assert p.read_bytes()==source.read_bytes(),'PDF mechanical extract differs'
    pattern=r'^\s*((?:19|20)\d{2})\s+'+r'\s+'.join([r'(\d+\.\d+)']*8)+r'\s*$'
    full=[]
    for match in re.finditer(pattern,text,re.M):
        values=match.groups();full.append(dict(zip(['year','total_fertility_rate_children_per_woman','age_15_19','age_20_24','age_25_29','age_30_34','age_35_39','age_40_44','age_45_49'],[int(values[0])]+list(values[1:]))))
    assert [r['year'] for r in full]==list(range(1981,2026))
    selected=[{'year':r['year'],**{k:r[k] for k in KEYS}} for r in full if r['year']>=2015]
    assert len(selected)==11
    anchors={2015:['63.1','116.7','48.3'],2020:['30.6','78.9','42.3'],2025:['21.2','73.1','52.1']}
    for r in selected:
        if r['year'] in anchors:assert [r[k] for k in KEYS]==anchors[r['year']]
    higher=[r['year'] for r in selected if Decimal(r['age_35_39'])>Decimal(r['age_25_29'])]
    assert higher==list(range(2018,2026))
    return full,selected

def write_csv(path,rows):
    with path.open('w',newline='') as f:
        w=csv.DictWriter(f,fieldnames=list(rows[0]));w.writeheader();w.writerows(rows)

def svg(rows,mobile=False):
    w,h=(400,1110) if mobile else (1200,1040)
    a=[f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" viewBox="0 0 {w} {h}" role="img" aria-labelledby="title desc">',
       '<title id="title">South Korea age-specific birth rates, 2015–2025</title>',
       '<desc id="desc">Annual live births per 1,000 women in each age group. Ages 25–29 fall from 63.1 to 21.2; ages 35–39 move from 48.3 to 52.1 and first exceed ages 25–29 in 2018 within this window. Ages 30–34 are shown for context, from 116.7 to 73.1. All 11 annual observations in each series are plotted. Registered-birth population rates, not eventual children per woman or individual outcomes.</desc>',
       f'<rect width="{w}" height="{h}" fill="{PAPER}"/>',f'<g font-family="{FONT}" fill="{INK}">']
    def t(x,y,s,size=20,color=INK,weight=400,anchor='start'):
        a.append(f'<text x="{x}" y="{y}" font-size="{size}" fill="{color}" font-weight="{weight}" text-anchor="{anchor}">{html.escape(s)}</text>')
    def line(x1,y1,x2,y2,color=GRID,width=1,extra=''):
        a.append(f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" stroke="{color}" stroke-width="{width}" {extra}/>')
    margin=24 if mobile else 58
    t(margin,38 if mobile else 52,'MEPLAR / POPULATION',12 if mobile else 16,MUTED,650)
    if mobile:
        for y,s in [(86,'Birth rates'),(124,'switched places.'),(165,'Ages 35–39 moved ahead'),(195,'of ages 25–29.')]:t(margin,y,s,33 if y<150 else 24,weight=700)
        t(margin,232,'South Korea · 2015–2025',17,MUTED)
        t(margin,268,'Live births per 1,000 women',17,weight=600)
        t(margin,292,'in each age group',17,weight=600)
        t(margin,322,'Annual observations · No values interpolated',12,MUTED)
        x0,x1,top,bottom=48,290,362,785
    else:
        t(margin,116,'Birth rates switched places.',50,weight=750)
        t(margin,174,'Ages 35–39 moved ahead of ages 25–29.',40,weight=700)
        t(margin,218,'South Korea · 2015–2025',24,MUTED)
        t(margin,270,'Live births per 1,000 women in each age group',23,weight=600)
        t(margin,306,'Annual observations · No values interpolated',18,MUTED)
        x0,x1,top,bottom=116,900,350,805
    x=lambda year:round(x0+(x1-x0)*(year-2015)/10,2)
    y=lambda value:round(bottom-(bottom-top)*float(value)/130,2)
    for tick in range(0,121,20):
        line(x0,y(tick),x1+4,y(tick),GRID,0.9)
        t(x0-12,y(tick)+4,str(tick),12 if mobile else 17,MUTED,anchor='end')
    for year in [2015,2020,2025]:
        t(x(year),bottom+27,str(year),14 if mobile else 20,MUTED,anchor='middle')
    for key,group,color in [('age_30_34','30–34',GRAY),('age_35_39','35–39',BLUE),('age_25_29','25–29',RUST)]:
        points=' '.join(str(x(r['year']))+','+str(y(r[key])) for r in rows)
        a.append(f'<polyline points="{points}" fill="none" stroke="{color}" stroke-width="{2.5 if mobile else 4}" stroke-linejoin="round" stroke-linecap="round"/>')
        for r in rows:
            radius=(3 if mobile else 4.5) if r['year'] not in [2015,2020,2025] else (4.8 if mobile else 7)
            a.append(f'<circle cx="{x(r["year"])}" cy="{y(r[key])}" r="{radius}" fill="{color}" stroke="{PAPER}" stroke-width="1.5"/>')
            if r['year'] in [2015,2020]:
                dy=23 if key=='age_25_29' and r['year']==2020 else (-14 if key!='age_35_39' else 23)
                t(x(r['year'])+(5 if r['year']==2015 else 0),y(r[key])+dy,r[key],14 if mobile else 21,color,650,'start' if r['year']==2015 else 'middle')
        last=rows[-1]
        t(x1+16,y(last[key])-5,'Ages '+group,12 if mobile else 21,color,650)
        t(x1+16,y(last[key])+20,last[key],19 if mobile else 29,color,750)
    if mobile:
        for yy,s in [(861,'By 2018, ages 35–39 had moved above 25–29.'),(883,'That ordering remains through 2025.'),(916,'These are annual population rates, not eventual'),(937,'children per woman or individual outcomes.'),(970,'Registered live births, by year of occurrence.'),(991,'Source: Ministry of Data and Statistics,'),(1012,'Birth Statistics in 2025, Table 2; 26 Aug 2026.'),(1043,'KOGL Type 1 · Full source and notes in download.'),(1074,'meplar.com · Source captured 22 Oct 2026')]:t(margin,yy,s,13,MUTED)
    else:
        for yy,s in [(883,'By 2018, ages 35–39 had moved above 25–29. That ordering remains through 2025.'),(916,'Registered live births by year of occurrence. Annual population rates, not eventual children per woman or individual outcomes.'),(957,'Source: Ministry of Data and Statistics, Birth Statistics in 2025, Table 2 (26 August 2026). KOGL Type 1.')]:t(margin,yy,s,18,MUTED)
        t(margin,1003,'meplar.com',19,INK,650);t(1142,1003,'Source captured 22 Oct 2026',16,MUTED,anchor='end')
    a.append('</g></svg>\n');return '\n'.join(a)

def run(root,out,extract_pdf=False,render=False):
    full,rows=extract(root,extract_pdf);out.mkdir(parents=True,exist_ok=True)
    write_csv(out/'chart-data.csv',rows);write_csv(out/'full-source-rates-1981-2025.csv',full)
    tidy=[{'year':r['year'],'age_group':g,'live_births_per_1000_women':r[k],'geography':'South Korea','source_pdf_page':38,'source_printed_page':33} for r in rows for k,g in zip(KEYS,GROUPS)]
    write_csv(out/'annual-age-specific-rates.csv',tidy)
    for name,mobile in [('chart',False),('chart_mobile',True)]:
        (out/(name+'.svg')).write_text(svg(rows,mobile))
        if render:subprocess.run(['/opt/homebrew/bin/rsvg-convert','--width','800' if mobile else '1800','--output',str(out/(name+'.png')),str(out/(name+'.svg'))],check=True)
    return {'status':'PASS','sourceRows':45,'chartedYears':11,'chartedValues':33,'labelledEnglishAnchorsMatched':9,'firstYearOlderExceedsYoungerInWindow':2018,'allYearsAfterCrossingRemainHigher':True,'pdfMechanicallyReextractedAndByteMatched':extract_pdf,'noMissingInterpolationOrReweighting':True}
if __name__=='__main__':
    ap=argparse.ArgumentParser();ap.add_argument('--out',type=Path,default=Path('reproduced'));ap.add_argument('--extract-pdf',action='store_true');ap.add_argument('--render-png',action='store_true');args=ap.parse_args()
    print(json.dumps(run(Path(__file__).resolve().parent,args.out,args.extract_pdf,args.render_png),indent=2))
