"""Typeset a reviewed English brief as three SVG cards; no AI or network calls.

Python 3.10+. Optional --png requires Pillow already installed and a local font.
Layout bounds are checked for the supplied example, not arbitrary text/languages.
"""
import argparse
import hashlib
import html
import json
from pathlib import Path
import textwrap
from urllib.parse import urlsplit

SIZES = {'wide': (1200, 675), 'square': (1080, 1080), 'portrait': (1080, 1350)}
LIMITS = {'brand': 30, 'kicker': 35, 'headline': 68, 'description': 115,
          'cta': 30, 'display_url': 45, 'disclosure': 70, 'destination': 220}

def unique(pairs):
    out = {}
    for key, value in pairs:
        if key in out:
            raise ValueError('Duplicate field: ' + key)
        out[key] = value
    return out

def load_brief(path):
    brief = json.loads(Path(path).read_text(encoding='utf-8'), object_pairs_hook=unique)
    if not isinstance(brief, dict) or set(brief) != set(LIMITS):
        raise ValueError('Use exactly the documented brief fields')
    for key, limit in LIMITS.items():
        text = brief[key]
        if not isinstance(text, str) or not text.strip() or len(text) > limit:
            raise ValueError('Empty or overlength field: ' + key)
        if any(ord(c) < 32 for c in text):
            raise ValueError('Control character in field: ' + key)
    target = urlsplit(brief['destination'])
    if target.scheme != 'https' or not target.hostname or target.username or target.password:
        raise ValueError('Destination must be an HTTPS URL without credentials')
    return brief

def elements(brief, key):
    w, h = SIZES[key]
    m = 78 if key == 'wide' else 76
    ink, muted, paper, lime = '#193b31', '#506956', '#f4f6ed', '#d6ed8b'
    result = [('rect', 0, 0, w, h, paper), ('rect', 0, 0, 18, h, ink),
              ('rect', w-178, 70, 100, 10, lime)]
    def text(x,y,size,value,color=ink):
        result.append(('text',x,y,size,value,color))
    text(m, 76, 23, brief['brand'])
    text(m, 148 if key == 'wide' else 208, 17, brief['kicker'].upper())
    headline_y = 210 if key == 'wide' else (300 if key == 'square' else 360)
    font = 66 if key == 'wide' else 76
    lines = textwrap.wrap(brief['headline'], width=29 if key == 'wide' else 23,
                          break_long_words=False, break_on_hyphens=False)
    for i, line in enumerate(lines):
        text(m, headline_y+i*(font+10),font,line)
    last = headline_y + (len(lines)-1)*(font+10)
    description_y = last + (70 if key == 'wide' else 95)
    for i,line in enumerate(textwrap.wrap(brief['description'],width=63 if key=='wide' else 46,
                                         break_long_words=False, break_on_hyphens=False)):
        text(m,description_y+i*36,25,line,muted)
    cta_y = 470 if key=='wide' else (790 if key=='square' else 1020)
    result.append(('rect',m,cta_y,440,68,ink))
    text(m+24,cta_y+44,24,brief['cta'],'#ffffff')
    text(m,cta_y+108,21,brief['display_url'],muted)
    result.append(('rect',m,h-68,w-2*m,1,'#bdcdb6'))
    text(m,h-30,16,brief['disclosure'],muted)
    return result

def svg(brief,key):
    w,h=SIZES[key]
    esc=html.escape
    body=[]
    for item in elements(brief,key):
        if item[0]=='rect':
            _,x,y,rw,rh,color=item
            body.append(f'<rect x="{x}" y="{y}" width="{rw}" height="{rh}" fill="{color}"/>')
        else:
            _,x,y,size,value,color=item
            body.append(f'<text x="{x}" y="{y}" font-size="{size}" fill="{color}" font-family="Segoe UI,Arial,sans-serif">{esc(value)}</text>')
    return 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">{esc(brief["headline"])}</title><desc id="desc">{esc(brief["description"]+" "+brief["disclosure"])}</desc>'+''.join(body)+'</svg>\n'

def png(brief,key,path,font_path):
    from PIL import Image, ImageDraw, ImageFont
    w,h=SIZES[key]
    image=Image.new('RGB',(w,h),'white');draw=ImageDraw.Draw(image)
    boxes=[]
    for item in elements(brief,key):
        if item[0]=='rect':
            _,x,y,rw,rh,color=item
            draw.rectangle((x,y,x+rw,y+rh),fill=color)
        else:
            _,x,y,size,value,color=item
            font=ImageFont.truetype(str(font_path),size)
            box=draw.textbbox((x,y),value,font=font,anchor='ls')
            if box[0]<36 or box[1]<0 or box[2]>w-36 or box[3]>h:
                raise ValueError('Text exceeds checked canvas bounds: '+key+' '+value)
            boxes.append({'text':value,'bbox':list(box)})
            draw.text((x,y),value,font=font,fill=color,anchor='ls')
    image.save(path)
    return boxes

def main():
    parser=argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--input',required=True,type=Path)
    parser.add_argument('--out',required=True,type=Path)
    parser.add_argument('--png',action='store_true')
    parser.add_argument('--font',type=Path)
    args=parser.parse_args()
    brief=load_brief(args.input)
    if args.out.exists() and any(args.out.iterdir()):
        parser.error('Choose a new or empty output directory; existing files are not overwritten')
    if args.png and (not args.font or not args.font.is_file()):
        parser.error('--png needs --font pointing to a local TTF font file')
    args.out.mkdir(parents=True,exist_ok=True)
    bounds={}
    for key in SIZES:
        (args.out/f'{key}.svg').write_text(svg(brief,key),encoding='utf-8')
        if args.png:
            bounds[key]=png(brief,key,args.out/f'{key}.png',args.font)
    cards=''.join('<section><h2>'+key+' · '+str(w)+' × '+str(h)+'</h2><img src="'+key+'.svg" alt="'+html.escape(brief['headline'],quote=True)+'"></section>' for key,(w,h) in SIZES.items())
    preview='<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Reviewed brief · three layout examples</title><style>body{margin:32px;background:#f4f6ed;color:#193b31;font:16px/1.6 Arial,sans-serif}main{max-width:1100px;margin:auto}section{margin:40px 0}img{display:block;max-width:100%;height:auto;border:1px solid #bdcdb6}h2{font-size:18px}a:focus-visible{outline:3px solid #648638;outline-offset:4px}</style><main><h1>One brief, three layout examples</h1><p>Code-generated typography. No image model, platform upload or campaign-result test.</p><p>Destination to verify: <a href="'+html.escape(brief['destination'],quote=True)+'">'+html.escape(brief['destination'])+'</a>. The drawn CTA is text, not a checkout or clickable image link.</p>'+cards+'</main></html>'
    (args.out/'preview.html').write_text(preview,encoding='utf-8')
    hashes={p.name:hashlib.sha256(p.read_bytes()).hexdigest() for p in args.out.iterdir() if p.is_file()}
    report={'input_sha256':hashlib.sha256(args.input.read_bytes()).hexdigest(),'sizes':{k:list(v) for k,v in SIZES.items()},'files_sha256':hashes,'png_text_bounds':bounds,'limits':'Code typesetting only. Bounds checked only for optional PNG with chosen font. SVG fonts may substitute. No AI-image generation, factual-claim checker, platform safe-zone acceptance or campaign evidence.'}
    (args.out/'layout-manifest.json').write_text(json.dumps(report,indent=2)+'\n',encoding='utf-8')
    print(json.dumps({'generated':len(hashes),'formats':list(SIZES),'png_text_bounds_checked':bool(bounds)}))

if __name__=='__main__':main()
