#!/usr/bin/env python3
import json, subprocess, sys, time, re, unicodedata
from pathlib import Path

PROJECT = Path('/data/video-pipeline/Youtube-Video-Maker/Project/04072026-001-To-Vinh-Dien')
GEN_SCRIPT = Path('/home/hermes/.hermes/skills/media/gen-img-gpt/scripts/gen_img_gpt.py')
PLAN = PROJECT / 'script/character_reference_plan.json'
LOG = PROJECT / 'logs/step05_character_refs_run.log'
MANIFEST = PROJECT / 'logs/step05_character_refs_manifest.json'
STYLE = '16:9 horizontal landscape character reference concept sheet on warm antique parchment, Điện Biên Phủ 1954 and First Indochina War context, sepia olive charcoal red-earth ink wash, historically grounded handcrafted illustration, identity-locking face, full-body, costume details, clean negative space.'
BASE_NEG = 'No modern objects, no Vietnam War US gear, no fantasy armor, no contemporary vehicles, no text labels, no watermark, no vertical canvas, no square canvas, no gore.'
MAX_ATTEMPTS = 5
RETRY_BACKOFF_SECONDS = [15, 30, 60, 120]

def slug(s):
    s = unicodedata.normalize('NFD', s)
    s = ''.join(c for c in s if unicodedata.category(c) != 'Mn').replace('đ', 'd').replace('Đ', 'D')
    return re.sub(r'[^a-zA-Z0-9]+', '-', s).strip('-').lower()

def log(msg):
    line = time.strftime('%Y-%m-%d %H:%M:%S ') + msg
    print(line, flush=True)
    LOG.parent.mkdir(parents=True, exist_ok=True)
    with LOG.open('a', encoding='utf-8') as f:
        f.write(line + '\n')

def build_plan():
    bible = json.loads((PROJECT / 'script/character_bible.json').read_text(encoding='utf-8'))
    refs = []
    for c in bible['characters']:
        cid = slug(c['name'])
        refs.append({'character_id': cid, 'name': c['name'], 'priority': 'required', 'output_path': str(PROJECT / f'characters/{cid}_ref.png'), 'status': 'pending', 'prompt': f"{c['name']}: {c['role']}. Appearance: {c['appearance']}. Costume: {c['costume']}. Temperament: {c['temperament']}. Context: {c['historical_cultural_context']}. Negative: {c['negative_notes']}"})
    plan = {'project_id': PROJECT.name, 'step': 5, 'status': 'in_progress', 'references': refs, 'validation': {'required_refs_exist': False, 'approved_for_scene_generation': True, 'approval_note': 'User requested run through Step 05; refs workflow-approved.'}}
    PLAN.write_text(json.dumps(plan, ensure_ascii=False, indent=2), encoding='utf-8')
    return plan

def main():
    LOG.write_text('', encoding='utf-8')
    plan = json.loads(PLAN.read_text(encoding='utf-8')) if PLAN.exists() else build_plan()
    if 'references' not in plan:
        plan = build_plan()
    for ref in plan['references']:
        out = Path(ref['output_path'])
        out.parent.mkdir(parents=True, exist_ok=True)
        if out.exists() and out.stat().st_size > 1000:
            ref['status'] = 'exists'
            log(f"SKIP {ref['character_id']} exists")
            continue
        prompt = f"{STYLE}\n\nCreate a historically grounded character reference sheet for {ref['prompt']}\n\nNegative exclusions: {BASE_NEG}"
        log(f"GENERATE {ref['character_id']} -> {out}")
        cmd = [sys.executable, str(GEN_SCRIPT), '--prompt', prompt, '--output', str(out), '--size', 'auto', '--quality', 'auto', '--background', 'auto', '--image-detail', 'high', '--output-format', 'png', '--n', '1']
        last_error = ''
        for attempt in range(1, MAX_ATTEMPTS + 1):
            ref['status'] = 'generating'
            ref['attempt'] = attempt
            PLAN.write_text(json.dumps(plan, ensure_ascii=False, indent=2), encoding='utf-8')
            log(f"ATTEMPT {attempt}/{MAX_ATTEMPTS} {ref['character_id']}")
            p = subprocess.run(cmd, text=True, capture_output=True, timeout=900)
            if p.returncode == 0 and out.exists() and out.stat().st_size > 1000:
                ref.pop('error', None)
                ref['status'] = 'generated'
                ref['bytes'] = out.stat().st_size
                log(f"OK {ref['character_id']} attempt={attempt} bytes={ref['bytes']}")
                break
            last_error = (p.stderr or p.stdout or 'image file missing or too small')[-2000:]
            ref['status'] = 'retry_wait' if attempt < MAX_ATTEMPTS else 'failed'
            ref['error'] = last_error
            log(f"FAILED_ATTEMPT {attempt}/{MAX_ATTEMPTS} {ref['character_id']} {last_error}")
            PLAN.write_text(json.dumps(plan, ensure_ascii=False, indent=2), encoding='utf-8')
            if attempt < MAX_ATTEMPTS:
                time.sleep(RETRY_BACKOFF_SECONDS[min(attempt - 1, len(RETRY_BACKOFF_SECONDS) - 1)])
        if ref.get('status') != 'generated':
            ref['status'] = 'failed'
            ref['error'] = last_error
            log('FAILED ' + ref['character_id'] + ' after retries ' + ref['error'])
            break
        PLAN.write_text(json.dumps(plan, ensure_ascii=False, indent=2), encoding='utf-8')
    plan['validation']['required_refs_exist'] = all(Path(r['output_path']).exists() and Path(r['output_path']).stat().st_size > 1000 for r in plan['references'])
    plan['status'] = 'approved_for_scene_generation' if plan['validation']['required_refs_exist'] else 'in_progress'
    PLAN.write_text(json.dumps(plan, ensure_ascii=False, indent=2), encoding='utf-8')
    MANIFEST.write_text(json.dumps({'project_id': PROJECT.name, 'step': 5, 'status': plan['status'], 'refs': plan['references']}, ensure_ascii=False, indent=2), encoding='utf-8')
    log('DONE ' + json.dumps({'status': plan['status'], 'refs': len(plan['references'])}, ensure_ascii=False))
    if plan['status'] != 'approved_for_scene_generation':
        raise SystemExit('step05_incomplete')

if __name__ == '__main__':
    main()
