#!/usr/bin/env python3
"""Compile reviewed storyboard into production inputs; never generate or submit media."""
from pathlib import Path
import csv
import hashlib
import json
import re

ROOT = Path(__file__).resolve().parents[1]
SOURCE = ROOT / 'cinematic-storyboard-v2.md'
OUT = ROOT / 'assembly'
OUT.mkdir(exist_ok=True)
text = SOURCE.read_text()
source_hash = hashlib.sha256(SOURCE.read_bytes()).hexdigest()
rows = {'user': [], 'channel': []}
section = None
for line in text.splitlines():
    if line.startswith('## 3. 用户片'):
        section = 'user'
    elif line.startswith('## 4. 渠道片'):
        section = 'channel'
    elif line.startswith('## 5.'):
        section = None
    if section and re.match(r'^\| \d{2}:\d{2}–\d{2}:\d{2} \|', line):
        cells = [x.strip() for x in line.split('|')[1:-1]]
        assert len(cells) == 5, cells
        start, end = cells[0].split('–')
        sec = lambda v: sum(int(n) * m for n, m in zip(v.split(':'), [60, 1]))
        idx = len(rows[section]) + 1
        rows[section].append({
            'id': ('U' if section == 'user' else 'C') + f'{idx:02}',
            'slot_start': sec(start), 'slot_end': sec(end),
            'slot_seconds': int(cells[1]), 'visual_direction': cells[2],
            'voiceover': cells[3], 'evidence_boundary': cells[4],
            'voice_status': 'not_recorded', 'picture_status': 'not_ready',
            'source_sha256': source_hash,
        })

dependencies = {
    'U01':['G01','P01'], 'U02':['P01','P02','P03'], 'U03':['P04'],
    'U04':['P05'], 'U05':['G02','G03','G04','P06'], 'U06':['G05','P07'],
    'U07':['G02','P08'], 'U08':['P09','P10'], 'U09':['G04','B01'],
    'C01':['G06'], 'C02':['P01','P02','B02'], 'C03':['P04','B03'],
    'C04':['P02','P05','P06','P08'], 'C05':['G06'], 'C06':['M01'],
    'C07':['M02'], 'C08':['P11','M03'], 'C09':['G06','M04'], 'C10':['B01','P01'],
}
asset_specs = {
    'G01':('清晨生活镜头','generative_broll','cloud_access_or_quota','生活演绎；不能替代设备/报告证据'),
    'G02':('普通一餐','generative_broll','cloud_access_or_quota','无营养数值、品牌或疗效前后对比'),
    'G03':('外食场景','generative_broll','cloud_access_or_quota','无可识别菜单/手机UI/个人信息'),
    'G04':('普通步行','generative_broll','cloud_access_or_quota','非训练指导或消耗测量'),
    'G05':('加班后调整安排','generative_broll','cloud_access_or_quota','匿名虚构成年人，无屏幕工作内容'),
    'G06':('服务交流情境','generative_broll','cloud_access_or_quota','明确演绎，不是客户证言或签约证据'),
    'P01':('设备外观和操作','real_capture','capture_and_rights_review','产品外观不能生成变形，人物获许可'),
    'P02':('同会话领取和查看','screen_capture','current_build_verification','同一演示用户/设备/会话，不能拼成假联通'),
    'P03':('无设备手机开始','screen_or_concept','capability_verification','未开放时标体验示意且同步限定旁白'),
    'P04':('来源/时间/报告解释','screen_or_concept','capability_verification','不展示真实健康数值，示例标识明确'),
    'P05':('目标确认与一致性','screen_or_concept','capability_verification','当前目标与后续吃动建议相符'),
    'P06':('今日行动与选择','screen_or_concept','capability_verification','可执行资源真实，未实现则标体验示意'),
    'P07':('反馈困难后调整','screen_or_concept','capability_verification','用户确认后才改变计划；不得假称已上线'),
    'P08':('实际执行确认','screen_or_concept','capability_verification','订单/取餐/实吃分开，未知不补零'),
    'P09':('周复盘','screen_or_concept','capability_verification','行为进展不是健康疗效'),
    'P10':('复测与可比记录','real_capture','capture_and_professional_review','同条件可比，不编造改善'),
    'P11':('安装培训与验收','real_capture','capture_and_delivery_scope','不用生成视频当真实交付证明'),
    'B01':('品牌与最终CTA','approved_brand','brand_and_entry_review','无假二维码；只用批准Logo/入口'),
    'B02':('可选原装SKU','approved_product','sku_and_policy_review','未审核的包装和价格不入片'),
    'B03':('专业知识与规则依据','approved_evidence','professional_and_brand_review','不将知识库称专有基础模型训练'),
    'M01':('收益与成本关系图','motion_graphic','channel_policy_review','无未核金额/保证收益'),
    'M02':('渠道门店个人分工','motion_graphic','channel_policy_review','同笔订单不重复计益'),
    'M03':('交付责任分工','motion_graphic','delivery_scope_review','不承诺免费/无限服务'),
    'M04':('一店验证步骤','motion_graphic','reviewed_design','无虚假试点数据'),
}
manifest=[]
for film, expected, length in [('user', 9, 150), ('channel', 10, 180)]:
    shots = rows[film]
    assert len(shots) == expected
    cursor=0
    film_dir=OUT/film
    film_dir.mkdir(exist_ok=True)
    narration=[]
    for shot in shots:
        assert shot['slot_start'] == cursor
        assert shot['slot_end'] - shot['slot_start'] == shot['slot_seconds']
        cursor=shot['slot_end']
        shot['requires'] = dependencies[shot['id']]
        shot['voice_file'] = f"voice/{shot['id']}.wav"
        shot['voice_file_exists'] = False
        shot['timing_basis']='reviewed_scene_slot_not_recorded_audio'
        (film_dir/f"{shot['id']}-voice.txt").write_text(shot['voiceover']+'\n')
        narration.append(shot['voiceover'])
        manifest.append({'film':film, 'shot_id':shot['id'], 'start_seconds':shot['slot_start'],
            'end_seconds':shot['slot_end'], 'duration_seconds':shot['slot_seconds'],
            'required_assets':';'.join(shot['requires']), 'picture_status':'not_ready',
            'voice_status':'not_recorded','public_release':'blocked_pending_evidence'})
    assert cursor==length
    (film_dir/'narration.txt').write_text('\n\n'.join(narration)+'\n')
    (film_dir/'timeline.json').write_text(json.dumps({'film':film,'duration_seconds':length,
       'width':1080 if film=='user' else 1920,'height':1920 if film=='user' else 1080,
       'fps':30,'state':'assembly_inputs_only_not_a_film','source':'../../cinematic-storyboard-v2.md',
       'source_sha256':source_hash,'shots':shots},ensure_ascii=False,indent=2)+'\n')
with (OUT/'shot-readiness.csv').open('w',newline='') as f:
    w=csv.DictWriter(f,fieldnames=manifest[0]);w.writeheader();w.writerows(manifest)
with (OUT/'asset-requirements.csv').open('w',newline='') as f:
    w=csv.writer(f);w.writerow(['asset_id','name','type','blocking_condition','claim_boundary','used_by'])
    for key,(name,typ,block,boundary) in asset_specs.items():
        used=';'.join(s for s,ids in dependencies.items() if key in ids)
        assert used,key
        w.writerow([key,name,typ,block,boundary,used])
for deps in dependencies.values():
    assert all(x in asset_specs for x in deps)
report={'films':2,'shots':len(manifest),'asset_requirements':len(asset_specs),
    'durations':{'user':150,'channel':180},'recorded_voice_tracks':0,
    'generated_broll_assets':0,'new_generation_submissions':0,'ready_final_films':0,
    'timeline_continuity':'passed','asset_references':'passed','source_sha256':source_hash}
(OUT/'validation.json').write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n')
print(json.dumps(report,ensure_ascii=False))
