from pathlib import Path
import json
from html import escape

BASE = Path(__file__).resolve().parent
WIDTH, HEIGHT = 1600, 1630
XS = (48, 435, 822, 1209)
BOX_W, BOX_H = 343, 146

LANES = [
    {
        'id': 'rows', 'title': '1  Source rows → Regular and Cumulative fields',
        'subtitle': 'Local changes generate complete ordered differences.', 'color': '#17615e',
        'nodes': [
            ['MT / LXX row families', 'Named ages and lifespans', 'Ordinary rows: b + r = L', 'Retain the native Cainan row'],
            ['Change the source rows', 'Begetting and life changes', 'act on their named supports', 'Cainan: one added vertex'],
            ['Choose a measurement', 'Regular: birth intervals', 'Cumulative: selected lives', 'Cainan contributes 130 / 460'],
            ['Reconstruct every boundary', 'Shared differences: 1250 / 430', 'Native differences: 1380 / 890', 'Both full fields retain their cuts'],
        ],
        'arrows': ['source rules', 'select weights', 'ordered sums'],
    },
    {
        'id': 'moses', 'title': '2  Rounded Moses path → the admitted inverse family',
        'subtitle': 'The new row reconstruction supplies the existing component words.', 'color': '#235a91',
        'nodes': [
            ['MT path to Moses', 'Resolve the selected intervals', 'Round each row; retain residuals', 'Hold Moses at 1526 BC'],
            ['Complete Rounded path', 'Five blocks: 130 | 490 | 1030', 'then 130 | 800; total 2580', 'Creation 4106; Noah 3056'],
            ['File52c original words', 'Add its named Flood 2456,', 'Conquest 1406 and Nativity 6', 'All three Regular words match'],
            ['Single reversal, then Key', 'Keep the original components', 'Inherited inverse total 14720', 'Whole-span E gives 16000'],
        ],
        'arrows': ['row sums', 'named-node match', 'admitted route'],
    },
    {
        'id': 'cap', 'title': '3  SP source mechanism → the calendar rectangle',
        'subtitle': 'Birth capacities explain life reductions before the source placement is compared.', 'color': '#8b5728',
        'nodes': [
            ['SP births + baseline lives', 'Nine resolved birth intervals', 'Selected baseline life ledger', 'Inclusive Flood-count convention'],
            ['Form capacities; cap lives', 'Select min(life, capacity)', 'Life losses: 115 | 249 | 124', 'Their total is 488'],
            ['Retain both complete fields', 'Regular row change: −350 + 650', 'Cumulative change: −488 − 120', 'Separate post-Flood inputs kept'],
            ['Place and compare by Keys', 'Add source frame and openings', 'Radii 2760 / 11960 gain 40 each', 'The retained difference is 9200'],
        ],
        'arrows': ['birth distances', 'propagate changes', 'state + node roles'],
    },
    {
        'id': 'covenant', 'title': '4  Covenant source relations → the clutch family',
        'subtitle': 'Shared biographies explain which joins are forced and which need a source relation.', 'color': '#76528c',
        'nodes': [
            ['Local source relationships', 'Birth ages, household intervals', 'Lifespans and held terminals', 'Minimum / full state retained'],
            ['Build the two paths', 'Regular: ages and events', 'Cumulative: full lifespan stack', 'Keep the stated comparison state'],
            ['Derive the clutch: 14', 'Cumulative Kohath to', 'regular Levi death is the', 'defining full-clutch join'],
            ['Generate the joined field', 'Next join: shared Levi lifespan', 'Third join: retained 147 relation', 'Same clutch preserves phase 3.5'],
        ],
        'arrows': ['ordered relations', 'compare paths', 'shared conditions'],
    },
    {
        'id': 'nt', 'title': '5  NT slots → comparison with native source births',
        'subtitle': 'One fixed list measure makes the differences between birth fields visible.', 'color': '#3d6860',
        'nodes': [
            ['NT slots + native births', 'Named NT positions: 70 per slot', 'MT, SP and LXX birth fields', 'Retain their own source states'],
            ['Align the named positions', 'Compare 22 common names', 'Keep two NT cells when the', 'shared list omits Cainan'],
            ['Measure the whole field', 'All 21 comparison edges agree', 'with the local interval rule', 'Native Cainan remains visible'],
            ['Explain a shared suffix', 'SP / LXX: ten identical births', 'Seven lifespans still differ', 'by 547 in their source measures'],
        ],
        'arrows': ['named alignment', 'complete recurrence', 'compare measures'],
    },
    {
        'id': 'counts', 'title': '6  Labelled counts → complete measured structures',
        'subtitle': 'The same ordered-measurement method also works on lists and literary occurrences.', 'color': '#596472',
        'nodes': [
            ['Two source count objects', 'Esau: nine labelled counts', 'Toledot: eleven occurrences', 'Keep categories and order'],
            ['Use their declared registers', 'Esau: ordered count prefixes', 'Toledot: group into ten sections', 'Carry occurrence multiplicities'],
            ['Retain the full measurement', 'Esau: recover all nine counts', 'Toledot: retain all eleven', 'occurrences within ten sections'],
            ['Explain the representation', 'Prefixes re-encode the list', 'Multiplicities preserve its count', 'Grouping has its own scope'],
        ],
        'arrows': ['source registers', 'retain information', 'interpret the map'],
    },
]

svg = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{WIDTH}" height="{HEIGHT}" viewBox="0 0 {WIDTH} {HEIGHT}" role="img" aria-labelledby="title desc">',
       '<title id="title">How the chronological families connect</title>',
       '<desc id="desc">Six source-controlled constructive routes connect genealogical rows, Regular and Cumulative measurement, the SP cap, the Rounded Moses path and File52c inverse family, Covenant joins, NT slots, and labelled counts. Arrows represent the stated operations, not equal numbers.</desc>',
       '<defs><marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M 0 1 L 9 5 L 0 9 z" fill="#73808b"/></marker></defs>',
       '<rect width="1600" height="1630" fill="#f8fafb"/>']

def text(x, y, value, size=19, weight='400', fill='#263440', anchor='start'):
    svg.append(f'<text x="{x}" y="{y}" font-family="DejaVu Sans, sans-serif" font-size="{size}" font-weight="{weight}" fill="{fill}" text-anchor="{anchor}">{escape(value)}</text>')

text(48, 57, 'How the chronological families connect', 36, '700', '#172733')
text(48, 96, 'Source-labelled structures  →  declared operations  →  complete measured families', 23, '400', '#425765')
text(48, 131, 'Each lane follows a specified source route. The same measurement methods recur across distinct objects.', 19, '400', '#5c6d78')

for k, lane in enumerate(LANES):
    y = 170 + 220 * k
    svg.append(f'<g id="{lane["id"]}">')
    text(48, y + 20, lane['title'], 23, '700', lane['color'])
    text(48, y + 48, lane['subtitle'], 18, '400', '#5b6872')
    box_y = y + 65
    for i, lines in enumerate(lane['nodes']):
        x = XS[i]
        svg.append(f'<rect x="{x}" y="{box_y}" width="{BOX_W}" height="{BOX_H}" rx="10" fill="#ffffff" stroke="#d7e0e5" stroke-width="1.4"/>')
        svg.append(f'<path d="M {x+10} {box_y} H {x+BOX_W-10}" stroke="{lane["color"]}" stroke-width="4" fill="none"/>')
        text(x+15, box_y+31, lines[0], 20, '700', '#243540')
        for j, line in enumerate(lines[1:]):
            text(x+15, box_y+64+27*j, line, 17.5, '400', '#354752')
        if i < 3:
            ax1, ax2 = x+BOX_W+6, XS[i+1]-7
            ay = box_y + 91
            svg.append(f'<path d="M {ax1} {ay} H {ax2}" stroke="#73808b" stroke-width="1.8" marker-end="url(#arrow)" fill="none"/>')
            # Arrow labels are stored in the companion relation table; the
            # node verbs supply readable in-figure descriptions at this scale.
    svg.append('</g>')

svg.append('<path d="M 48 1520 H 1552" stroke="#d2dce2" stroke-width="1.5"/>')
text(48, 1556, 'Shared source structure, distinct measures', 20, '700', '#314c5b')
text(580, 1556, 'Located changes, complete fields', 20, '700', '#314c5b')
text(1080, 1556, 'Source roles travel with coordinates', 20, '700', '#314c5b')
text(48, 1593, 'Regular and Cumulative use different weights. Equal totals can retain different interiors. Source and proof locators accompany this map.', 17.5, '400', '#586a75')
svg.append('</svg>')

with (BASE/'Family_Map_Proposal.svg').open('x') as f:
    f.write('\n'.join(svg)+'\n')
with (BASE/'Family_Map_Design.json').open('x') as f:
    json.dump({'width':WIDTH,'height':HEIGHT,'maximum_nodes_across':4,'lanes':LANES},f,ensure_ascii=False,indent=2)
    f.write('\n')
print('Wrote Family_Map_Proposal.svg and Family_Map_Design.json')
