#!/usr/bin/env python3
# gen_sibling_pdfs.py - Custody + On Permanence and Footprint, in the trio's cloth.
# One generator, two outputs. Imports the blessed whitepaper engine (v6) for the
# palette, faces, styles, markdown parser, and flowables; adds per-document
# title pages, footers, and front-matter handling. Both close with the whisper leaf.
import re, os
from gen_whitepaper_pdf import (PAPER, INK, INKSOFT, LABEL, RULE, RULESOFT,
                                PW, PH, S, inline, esc, parse_blocks, ThinRule,
                                CodeBlock, WP, paint_paper, ML, MR, MT, MB,
                                caps_heading)
from reportlab.platypus import (PageTemplate, Frame, Paragraph, Spacer, Table,
                                TableStyle, PageBreak, NextPageTemplate, KeepTogether)
from reportlab.pdfgen import canvas as pdfcanvas
from reportlab.pdfbase.pdfmetrics import stringWidth
from reportlab.lib.utils import simpleSplit
from pypdf import PdfReader, PdfWriter

# siblings run a half-step tighter than the flagship (Geoff: Custody fits 3 pages)
for _k in ('body','li','oli','aside'):
    S[_k].leading = S[_k].leading - 0.5
S['body'].spaceAfter = 6.5
S['li'].spaceAfter = 5
S['oli'].spaceAfter = 5

OUTDIR='/home/claude/out'; os.makedirs(OUTDIR, exist_ok=True)
STAMP='07062026_HHMM'

# ---------- tracked chrome text via charSpace (handles long family names) ----------
def tracked_text(c, x, y, s, size=7.6, cs=1.6, font='TT2020', anchor='l'):
    w = stringWidth(s, font, size) + cs*max(0, len(s)-1)
    if anchor=='c': x -= w/2
    elif anchor=='r': x -= w
    t = c.beginText(x, y)
    t.setFont(font, size); t.setCharSpace(cs)
    t.textOut(s)
    c.drawText(t)

DOCS = {
 'custody': dict(
    md='/home/claude/custody_v24.md',
    out=f'{OUTDIR}/xbt-im-custody_{STAMP}.pdf',
    family='CUSTODY',
    title='CUSTODY',
    title_size=15,
    subtitle_tt=None,
    tagline='The origin of xbt.im, in the founder\u2019s voice',
    byline=None,
    meta_title='Custody',
    meta_subject='The origin of xbt.im, in the founder\u2019s voice',
    skip_italic_paras=1,   # subtitle + byline live on the title page
    outline_h2=False,
 ),
 'position': dict(
    md='/home/claude/position_v17.md',
    out=f'{OUTDIR}/xbt-im-on-permanence-and-footprint_{STAMP}.pdf',
    family='ON PERMANENCE AND FOOTPRINT',
    title='ON PERMANENCE AND FOOTPRINT',
    title_size=13,
    subtitle_tt='THE XBT.IM POSITION ON INSCRIPTION ETHICS',
    tagline='A public statement on why we write to Bitcoin, what it costs the network, and the restraint we place on ourselves',
    byline=None,
    meta_title='On Permanence and Footprint',
    meta_subject='The xbt.im position on inscription ethics',
    skip_italic_paras=1,   # tagline lives on the title page
    outline_h2=True,
 ),
}

def make_on_prose(cfg):
    def on_prose(c, doc):
        paint_paper(c)
        c.saveState(); c.setFillColor(LABEL)
        y=42
        tracked_text(c, ML, y, f'XBT.IM \u00b7 {cfg["family"]}', size=7.2, cs=1.2, anchor='l')
        tracked_text(c, PW-MR, y, 'V1 \u00b7 JULY 2026', size=7.2, cs=1.2, anchor='r')
        c.setFillColor(INKSOFT); c.setFont('TT2020', 7.6)
        c.drawCentredString(PW/2, y, str(doc.page))
        c.restoreState()
    return on_prose


# ---------- humble head block styles ----------
from reportlab.lib.styles import ParagraphStyle
S_title = ParagraphStyle('doctitle', fontName='TT2020', fontSize=16, leading=20,
                         textColor=INK, spaceAfter=6)
S_subtt = ParagraphStyle('docsub', fontName='TT2020', fontSize=9.8, leading=13,
                         textColor=LABEL, spaceAfter=8)
S_tagline = ParagraphStyle('doctag', fontName='Garamond-Italic', fontSize=10.5,
                           leading=14.5, textColor=INKSOFT, spaceAfter=3)
S_byline = ParagraphStyle('docby', fontName='Garamond', fontSize=10.5,
                          leading=14.5, textColor=INK, spaceAfter=3)
from reportlab.lib.enums import TA_RIGHT, TA_LEFT
S_signature = ParagraphStyle('docsig', fontName='Garamond', fontSize=11,
                             leading=15, textColor=INK, alignment=TA_LEFT,
                             spaceBefore=26)

def build_doc(cfg):
    body_fn = cfg['out'].replace('.pdf', '_body.pdf')
    mb = cfg.get('mb', MB)
    doc = WP(body_fn, leftMargin=ML, rightMargin=MR, topMargin=MT, bottomMargin=mb)
    fr = Frame(ML, mb, PW-ML-MR, PH-MT-mb, id='f')
    doc.addPageTemplates([PageTemplate(id='Prose', frames=[fr], onPage=make_on_prose(cfg))])
    lines = open(cfg['md']).read().split('\n')
    blocks = parse_blocks(lines)
    story=[]
    # quiet head block: title, subtitle line(s), byline, a rule, then the prose
    story.append(Paragraph(esc(cfg['title']), S_title))
    if cfg['subtitle_tt']:
        story.append(Paragraph(esc(cfg['subtitle_tt']), S_subtt))
    story.append(Paragraph(esc(cfg['tagline']), S_tagline))
    if cfg['byline'] and not cfg.get('signature'):
        story.append(Paragraph(esc(cfg['byline']), S_byline))
    story.append(ThinRule(width=PW-ML-MR, space=11))
    story.append(Spacer(1,2))
    italics_to_skip = cfg['skip_italic_paras']
    skipped_sub_h3=False; skipped_first_hr=False
    for kind, val in blocks:
        if kind=='h1':
            continue
        if kind=='h3' and not skipped_sub_h3 and cfg['subtitle_tt']:
            skipped_sub_h3=True; continue        # subtitle already in the head block
        if kind=='p' and italics_to_skip and re.fullmatch(r'\*[^*].*\*', val) and '**' not in val:
            italics_to_skip-=1; continue          # front-matter italics already in the head block
        if kind=='hr' and not skipped_first_hr:
            skipped_first_hr=True; continue       # front-matter rule already drawn
        if kind=='h2':
            p=caps_heading(val, S['h2'])
            if cfg['outline_h2']: p._outline=(0,val)
            story.append(p)
        elif kind=='h3':
            p=caps_heading(val, S['h3']); p._outline=(1,val); story.append(p)
        elif kind=='hr':
            story.append(ThinRule())
        elif kind=='p':
            if re.fullmatch(r'\*[^*].*\*', val) and '**' not in val:
                story.append(KeepTogether(Paragraph('<i>'+inline(val[1:-1])+'</i>', S['aside'])))
            else:
                story.append(Paragraph(inline(val), S['body']))
        elif kind=='ol':
            for num,it in val:
                story.append(Paragraph(f'{num}.&nbsp;&nbsp;'+inline(it), S['oli']))
        elif kind=='ul':
            for it in val:
                story.append(Paragraph(inline(it), S['li'], bulletText='\u00b7'))
        elif kind=='code':
            story.append(Spacer(1,4)); story.append(CodeBlock(val)); story.append(Spacer(1,6))
        elif kind=='table':
            head=[Paragraph(esc(c0.upper()), S['tabhead']) for c0 in val[0]]
            body=[[Paragraph(inline(c0), S['tabcell']) for c0 in r] for r in val[1:]]
            ncols=len(val[0]); tw=PW-ML-MR
            if ncols==5: widths=[tw*0.24, tw*0.15, tw*0.17, tw*0.16, tw*0.28]
            else: widths=[tw/ncols]*ncols
            t=Table([head]+body, colWidths=widths, hAlign='LEFT')
            t.setStyle(TableStyle([
                ('LINEABOVE',(0,0),(-1,0),1.0,RULE),
                ('LINEBELOW',(0,0),(-1,0),0.7,RULE),
                ('LINEBELOW',(0,-1),(-1,-1),1.0,RULE),
                ('LINEBELOW',(0,1),(-1,-2),0.4,RULESOFT),
                ('TOPPADDING',(0,0),(-1,-1),4.5),
                ('BOTTOMPADDING',(0,0),(-1,-1),4.5),
                ('LEFTPADDING',(0,0),(-1,-1),2),
                ('RIGHTPADDING',(0,0),(-1,-1),8),
                ('VALIGN',(0,0),(-1,-1),'TOP'),
            ]))
            story.append(Spacer(1,4)); story.append(t); story.append(Spacer(1,8))
    if cfg['byline'] and cfg.get('signature'):
        story.append(KeepTogether(Paragraph(esc(cfg['byline']), S_signature)))
    # headings never dangle
    bundled=[]
    for fl in story:
        if (isinstance(fl, KeepTogether) and bundled
                and isinstance(bundled[-1], Paragraph)
                and getattr(bundled[-1], 'style', None) is not None
                and bundled[-1].style.name in ('h2','h3')):
            h=bundled.pop()
            kt=KeepTogether([h]+list(fl._content))
            if hasattr(h,'_outline'): kt._outline=h._outline
            bundled.append(kt)
        else:
            bundled.append(fl)
    doc.build(bundled)

    w=PdfWriter()
    r1=PdfReader(body_fn)
    for pg in r1.pages: w.add_page(pg)
    for lvl, title, page in doc.outline:
        w.add_outline_item(title, page-1)
    w.add_metadata({'/Title': cfg['meta_title'], '/Author': 'xbt.im',
                    '/Subject': cfg['meta_subject'],
                    '/Creator': 'xbt.im \u00b7 V1 \u00b7 July 2026'})
    with open(cfg['out'],'wb') as f: w.write(f)
    os.remove(body_fn)
    print(cfg['out'], '->', len(r1.pages), 'pages')

if __name__=='__main__':
    for k in ('custody','position'):
        build_doc(DOCS[k])
    print('trio siblings done')
