#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
=============================================================================
THẬP NGŨ NIÊN (THE FIFTEEN SPRINGS) - EPISODE PRODUCTION UNIT MANAGER
=============================================================================
Quản lý cấu trúc kịch bản và thư mục phân cấp theo Tập (Episode-Based Units):
- Mỗi tập phim (Ep01 -> Ep06) là một đơn vị sản xuất (Production Unit) độc lập.
- Thư mục gốc chỉ lưu những tài nguyên chung (Bibles, Core Pipeline, Master Assets).
- Tách biệt Screenplay, Prose, Prompts, Storyboards theo từng tập để triệt tiêu
  hoàn toàn tình trạng loãng context và chồng chéo dữ liệu giữa các AI Agent.
- Cung cấp cơ chế Two-Way Sync đồng bộ 2 chiều giữa các tập và Master Prompts.
=============================================================================
"""

import os
import sys
import re
import json
import shutil
import argparse
from pathlib import Path
from typing import Dict, List, Optional, Any

# Reconfigure console UTF-8 on Windows
if sys.platform == "win32":
    try:
        sys.stdout.reconfigure(encoding="utf-8")
        sys.stderr.reconfigure(encoding="utf-8")
    except Exception:
        pass

BASE_DIR = Path(__file__).resolve().parent.parent
EPISODES_DIR = BASE_DIR / "episodes"
SCRIPTS_DIR = BASE_DIR / "01_Scripts_And_Episodes"
FILMMAKER_DIR = BASE_DIR / "FilmMaker"
PROMPTS_DIR = BASE_DIR / "02_AI_Prompts"
STORYBOARDS_DIR = BASE_DIR / "03_Storyboards"
ASSETS_DIR = BASE_DIR / "04_Assets"
VIDEOS_DIR = ASSETS_DIR / "videos"
KEYFRAMES_DIR = ASSETS_DIR / "keyframes"
EXPORTS_DIR = BASE_DIR / "06_Exports"

MUSE_MASTER_PROMPTS = PROMPTS_DIR / "muse_ai_video_prompts.json"
BANANA_MASTER_PROMPTS = PROMPTS_DIR / "gemini_banana_prompts.json"
NOVEL_PROSE_FILE = SCRIPTS_DIR / "KIEU_STORY_TRUYEN_DAI_VAN_XUOI_KICH_BAN_GOC.md"

EPISODES_INFO = {
    1: {
        "id": "ep01",
        "num": 1,
        "title": "Phong Trần Nhập Cuộc (Xuân Sắc Thề Nguyện & Giông Bão Đoạn Trường)",
        "target_duration": "30 Phút (1800s) / Quy mô 188 shots",
        "screenplay_file": "TAP_01_XUAN_SAC_THE_NGUYEN_VA_GIONG_BAO_DOAN_TRUONG.md",
        "main_characters": ["Thúy Kiều", "Thúy Vân", "Vương Quan", "Kim Trọng", "Đạm Tiên", "Vương Ông", "Mã Giám Sinh"],
        "key_locations": ["Tư gia họ Vương", "Lễ hội Đạp Thanh", "Nấm mồ Đạm Tiên", "Thư phòng Kim Trọng", "Công đường Nha môn"],
        "summary": "Mở đầu cuộc đời hai chị em Thúy Kiều và Thúy Vân trong tiết Thanh Minh. Cuộc gặp gỡ định mệnh với Kim Trọng và nấm mồ đạm bạc của Đạm Tiên. Mối tình đầu chớm nở bằng lời thề nguyền dưới trăng, nhưng giông tố gia biến lập tức ập tới: cha và em bị vu oan giam cầm, Thúy Kiều buộc phải quyết định hy sinh thân mình bán cho Mã Giám Sinh chuộc cha."
    },
    2: {
        "id": "ep02",
        "num": 2,
        "title": "Thanh Lâu Nhập Mộng (Nỗi Đau Bán Mình & Cạm Bẫy Lầm Than)",
        "target_duration": "30 Phút (1800s)",
        "screenplay_file": "TAP_02_NOI_DAU_BAN_MINH_VA_CAM_BAY_LAM_THAN.md",
        "main_characters": ["Thúy Kiều", "Mã Giám Sinh", "Tú Bà", "Sở Khanh", "Mã Kiều Loan"],
        "key_locations": ["Thanh lâu Lâm Tri", "Lầu Ngưng Bích", "Rừng trúc đào thoát", "Quán trọ ven sông"],
        "summary": "Đêm trao duyên xé lòng cho Thúy Vân. Kiều dấn thân vào cõi phong trần, bị Mã Giám Sinh làm nhục và lừa bán vào thanh lâu của Tú Bà. Tại lầu Ngưng Bích ngắm cảnh bốn bề sầu muộn, Kiều lại rơi vào mưu độc 'kim thiền thoát xác' của Sở Khanh, buộc phải chấp nhận kiếp sống kỹ nữ tiếp khách làng chơi."
    },
    3: {
        "id": "ep03",
        "num": 3,
        "title": "Hoa Lạc Vô Đoan (Cơ Duyên Thúc Gia & Đòn Ghen Hoạn Thư)",
        "target_duration": "30 Phút (1800s)",
        "screenplay_file": "TAP_03_CO_DUYEN_THUC_GIA_VA_DON_GHEN_HOAN_THU.md",
        "main_characters": ["Thúy Kiều", "Thúc Sinh", "Hoạn Thư", "Hoạn Bà", "Khuyển Ưng"],
        "key_locations": ["Thúc phủ Vô Tích", "Mái ấm ven sông", "Hoạn phủ Vô Tích", "Quan Âm Các"],
        "summary": "Cuộc hội ngộ và kết duyên cùng chàng thư sinh buôn tơ Thúc Sinh. Tưởng chừng đã tìm được bến đỗ bình yên nhưng lại đối mặt với đòn ghen khôn lường của Hoạn Thư: sai người đốt nhà bắt cóc tráo xác, ép Kiều làm gia nô hầu rượu đánh đàn mua vui trong bữa tiệc đắng cay, rồi đày ra Quan Âm Các chép kinh."
    },
    4: {
        "id": "ep04",
        "num": 4,
        "title": "Hoành Đao Trảm Lãng (Bẻ Khóa Vượt Rào & Oai Danh Từ Hải)",
        "target_duration": "30 Phút (1800s)",
        "screenplay_file": "TAP_04_BE_KHOA_VUOT_RAO_VA_OAI_DANH_TU_HAI.md",
        "main_characters": ["Thúy Kiều", "Giác Duyên", "Bạc Bà", "Bạc Hạnh", "Từ Hải"],
        "key_locations": ["Chùa Chiêu Ẩn", "Châu Thai thanh lâu", "Trướng hùm Từ Hải", "Đại bản doanh quân doanh"],
        "summary": "Kiều bẻ khóa chuông vàng trốn khỏi Hoạn phủ, nương náu cửa Phật cùng sư Giác Duyên nhưng vì ngọc quý nên phải gửi gắm sang Bạc Bà. Bị Bạc Hạnh lừa bán vào lầu xanh lần thứ hai tại Châu Thai. Tại đây, định mệnh đưa nàng gặp người anh hùng cái thế Từ Hải. Khúc tỳ bà tri kỷ thức tỉnh hùng tâm, Từ Hải xuất quân làm nên nghiệp lớn rạng rỡ đón Kiều quy về."
    },
    5: {
        "id": "ep05",
        "num": 5,
        "title": "Đoạn Trường Thập Ngũ (Báo Ân Báo Oán & Cạm Bẫy Giang Sơn)",
        "target_duration": "30 Phút (1800s)",
        "screenplay_file": "TAP_05_BAO_AN_BAO_OAN_VA_CAM_BAY_GIANG_SON.md",
        "main_characters": ["Thúy Kiều", "Từ Hải", "Hồ Tôn Hiến", "Hoạn Thư", "Thúc Sinh", "Mã Giám Sinh", "Tú Bà", "Sở Khanh"],
        "key_locations": ["Đại sảnh công đường Từ Hải", "Doanh trại triều đình", "Sa trường Tiền Đường"],
        "summary": "Dưới cờ phướn trướng hùm, Thúy Kiều mở phiên tòa báo ân báo oán: đền ơn người tốt (Thúc Sinh, Giác Duyên), trừng trị đích đáng bọn gian ác (Mã Giám Sinh, Tú Bà, Sở Khanh) và tha bổng cho sự thông tuệ của Hoạn Thư. Thế nhưng, mưu mô nham hiểm của Tổng đốc Hồ Tôn Hiến với bức thư chiêu an giả mạo đã khiến Kiều khuyên Từ Hải buông gươm, dẫn tới thảm kịch phục kích sa trường."
    },
    6: {
        "id": "ep06",
        "num": 6,
        "title": "Hoàn Nguyên Tái Ngộ (Hồn Đoạn Tiền Đường & Đoàn Viên Thanh Tịnh)",
        "target_duration": "30 Phút (1800s)",
        "screenplay_file": "TAP_06_HON_DOAN_TIEN_DUONG_VA_DOAN_VIEN_THANH_TINH.md",
        "main_characters": ["Thúy Kiều", "Từ Hải", "Hồ Tôn Hiến", "Giác Duyên", "Kim Trọng", "Thúy Vân", "Vương Quan", "Vương Ông"],
        "key_locations": ["Bến sông Tiền Đường", "Chùa tranh Giác Duyên", "Tư gia họ Vương tân gia", "Đêm hoa chúc thanh tân"],
        "summary": "Từ Hải chết đứng giữa sa trường như tượng đồng tạc vào hoàng hôn. Kiều bị ép hầu đàn cho Hồ Tôn Hiến rồi gả ép cho thổ quan, nàng trầm mình xuống sông Tiền Đường để rửa sạch bụi trần. Được sư Giác Duyên cứu thoát, nàng nương bóng cửa thiền cho đến ngày Kim Trọng và cả gia đình tìm thấy sau 15 năm lưu lạc. Cuộc tái hợp đoàn viên thanh tịnh khép lại thiên trường ca Đoạn Trường."
    }
}


def extract_prose_by_episodes() -> Dict[int, str]:
    """Trích xuất từng phần văn xuôi tiểu thuyết tương ứng với 6 tập."""
    if not NOVEL_PROSE_FILE.exists():
        print(f"[!] Không tìm thấy file văn xuôi: {NOVEL_PROSE_FILE}")
        return {}

    text = NOVEL_PROSE_FILE.read_text(encoding="utf-8")
    ep_headers = list(re.finditer(r"^# TẬP (\d+):[^\n]+", text, re.MULTILINE))
    prose_map = {}

    for i, m in enumerate(ep_headers):
        ep_num = int(m.group(1))
        start = m.start()
        end = ep_headers[i + 1].start() if i + 1 < len(ep_headers) else len(text)
        prose_chunk = text[start:end].strip()
        prose_map[ep_num] = prose_chunk

    return prose_map


def extract_muse_prompts_by_episodes() -> Dict[str, Dict]:
    """Phân tách file muse_ai_video_prompts.json thành 6 tập độc lập."""
    if not MUSE_MASTER_PROMPTS.exists():
        print(f"[!] Không tìm thấy file prompt Muse: {MUSE_MASTER_PROMPTS}")
        return {}

    data = json.loads(MUSE_MASTER_PROMPTS.read_text(encoding="utf-8"))
    all_shots = data.get("motion_prompts", {})
    gen_settings = data.get("general_settings", {})

    ep_prompts: Dict[str, Dict[str, Any]] = {
        f"ep0{i}": {
            "episode": f"ep0{i}",
            "episode_num": i,
            "project_name": data.get("project_name", "THẬP NGŨ NIÊN (THE FIFTEEN SPRINGS)"),
            "ai_video_engine": data.get("ai_video_engine", "Muse.ai Headless Automation Engine"),
            "version": "2.0-modular",
            "general_settings": gen_settings,
            "motion_prompts": {}
        }
        for i in range(1, 7)
    }

    # Bổ sung category đặc biệt
    special_prompts = {
        "category": "thematic_and_shorts",
        "project_name": data.get("project_name", "THẬP NGŨ NIÊN (THE FIFTEEN SPRINGS)"),
        "motion_prompts": {}
    }

    for shot_id, shot_info in all_shots.items():
        prefix = shot_id.split("_")[0].lower()
        if prefix.startswith("ep0"):
            ep_key = prefix
            if ep_key in ep_prompts:
                ep_prompts[ep_key]["motion_prompts"][shot_id] = shot_info
        elif prefix.startswith("prologue"):
            # Prologue thuộc tập 1
            ep_prompts["ep01"]["motion_prompts"][shot_id] = shot_info
        else:
            special_prompts["motion_prompts"][shot_id] = shot_info

    return ep_prompts, special_prompts


def extract_banana_prompts_by_episodes() -> Dict[str, Dict]:
    """Phân tách gemini_banana_prompts.json cho từng tập."""
    if not BANANA_MASTER_PROMPTS.exists():
        return {}

    data = json.loads(BANANA_MASTER_PROMPTS.read_text(encoding="utf-8"))
    ep_banana: Dict[str, Dict[str, Any]] = {
        f"ep0{i}": {
            "episode": f"ep0{i}",
            "project_name": data.get("project_name", "THẬP NGŨ NIÊN"),
            "ai_image_engine": data.get("ai_image_engine", "Gemini Banana / Google Imagen 3"),
            "master_style_anchor": data.get("master_style_anchor", ""),
            "prompts": {}
        }
        for i in range(1, 7)
    }

    for k, v in data.items():
        if k in ["project_name", "ai_image_engine", "version", "master_style_anchor", "characters", "environments", "critical_keyframes"]:
            continue
        prefix = k.split("_")[0].lower()
        if prefix in ep_banana:
            ep_banana[prefix]["prompts"][k] = v

    return ep_banana


def generate_episode_readme(ep_meta: Dict, num_shots: int, rendered_shots: int, has_master: bool) -> str:
    """Tạo file README.md điều phối chi tiết cho từng tập phim."""
    ep_id = ep_meta["id"]
    title = ep_meta["title"]
    ep_num = ep_meta["num"]
    duration = ep_meta["target_duration"]
    chars = ", ".join(ep_meta["main_characters"])
    locs = ", ".join(ep_meta["key_locations"])
    summary = ep_meta["summary"]

    status_master = "✓ Đã có Master hoàn chỉnh" if has_master else "⏳ Đang trong quá trình sản xuất"
    pct = f"{rendered_shots}/{num_shots} ({rendered_shots/num_shots:.0%})" if num_shots > 0 else "0/0"

    content = f"""# THẬP NGŨ NIÊN (THE FIFTEEN SPRINGS)
## TẬP {ep_num}: {title.upper()}
### PRODUCTION WORKSPACE & CONTEXT DIRECTIVES FOR AGENTS & WORKERS

> **QUY TẮC CÔ LẬP NGỮ CẢNH (ANTI-DILUTION DIRECTIVE)**:
> Khi làm việc với Tập {ep_num}, AI Agent và Worker **BẮT BUỘC CHỈ NẠP THƯ MỤC NÀY** (`episodes/{ep_id}/`).
> Tuyệt đối KHÔNG nạp kịch bản, prompt hay storyboard của các tập khác để tránh cồng kềnh context, triệt tiêu race condition và chống chồng chéo dữ liệu.
> Khi cần tra cứu chuẩn nhân vật 32 nhân vật hoặc chuẩn âm thanh chung, tham chiếu thư mục gốc [`00_Project_Bible/`](file:///c:/Projects/KieuStory/00_Project_Bible/).

---

## 1. THÔNG SỐ TỔNG QUAN (EPISODE PROFILE)
- **Mã tập**: `{ep_id.upper()}` (Tập {ep_num}/6)
- **Tên đầy đủ**: **{title}**
- **Thời lượng tiêu chuẩn**: **{duration}**
- **Nhân vật trọng tâm**: {chars}
- **Bối cảnh chính**: {locs}
- **Tình trạng Master**: **{status_master}**
- **Tiến độ Render Raw Shots**: **{pct}**

---

## 2. TÓM TẮT CỐT TRUYỆN (SYNOPSIS)
{summary}

---

## 3. CẤU TRÚC TÀI NGUYÊN TẬP PHIM (MODULAR STRUCTURE)

| Thành phần | Đường dẫn tệp | Mô tả nhiệm vụ |
| :--- | :--- | :--- |
| **Kịch bản Phân cảnh** | [`screenplay.md`](file:///c:/Projects/KieuStory/episodes/{ep_id}/screenplay.md) | Phân cảnh chi tiết từng shot, góc máy, chuyển động, ánh sáng và thoại. |
| **Văn xuôi Gốc** | [`prose.md`](file:///c:/Projects/KieuStory/episodes/{ep_id}/prose.md) | Đoạn trích tiểu thuyết văn xuôi giúp Agent nắm trọn xúc cảm văn học và tính cách nhân vật. |
| **Prompt Video Muse** | [`prompts/muse_prompts.json`](file:///c:/Projects/KieuStory/episodes/{ep_id}/prompts/muse_prompts.json) | Bộ motion prompt Muse.ai riêng cho tập này kèm Audio Guard tự nhiên. |
| **Prompt Ảnh Start Frame** | [`prompts/banana_prompts.json`](file:///c:/Projects/KieuStory/episodes/{ep_id}/prompts/banana_prompts.json) | Bộ prompt Imagen 3 / Banana sinh Start Frame 720p tỷ lệ 16:9. |
| **Bảng phân cảnh Storyboard** | [`storyboards/`](file:///c:/Projects/KieuStory/episodes/{ep_id}/storyboards/) | Bảng vẽ storyboard, bố cục trực quan cho các cảnh cao trào. |
| **Kiểm kê Tiến độ** | [`manifest.json`](file:///c:/Projects/KieuStory/episodes/{ep_id}/manifest.json) | Bản đồ trạng thái chi tiết của từng shot, tail frame và master scene. |

---

## 4. HƯỚNG DẪN SẢN XUẤT CHO WORKER & SUBAGENT (RUNBOOK)

```bash
# 1. Kiểm tra tiến độ riêng của Tập {ep_num}:
python 05_Production_Pipeline/episode_manager.py --episode {ep_id}

# 2. Render song song các cảnh của Tập {ep_num} bằng Worker chuyên trách:
python 05_Production_Pipeline/multi_worker_orchestrator.py --scenes $(python 05_Production_Pipeline/episode_manager.py --pending-scenes {ep_id})

# 3. Đồng bộ lại các prompt vừa chỉnh sửa vào Master File trung tâm:
python 05_Production_Pipeline/episode_manager.py --sync-to-master
```

---
*Bản quyền sản xuất © 2026 THẬP NGŨ NIÊN - Tác giả & Chủ sở hữu IP: Nguyễn Sĩ Sơn.*
"""
    return content


def get_episode_assets_manifest(ep_id: str, shots_dict: Dict) -> Dict:
    """Tạo manifest kiểm tra tình trạng assets của tập."""
    ep_prefix = ep_id.lower()
    manifest = {
        "episode_id": ep_id,
        "total_shots": len(shots_dict),
        "rendered_shots_count": 0,
        "missing_shots_count": 0,
        "master_scene_files": [],
        "shots_status": {}
    }

    for shot_id, shot_data in shots_dict.items():
        # Kiểm tra file video raw
        raw_candidates = list(VIDEOS_DIR.glob(f"{shot_id}*.mp4"))
        is_rendered = len(raw_candidates) > 0
        raw_file = str(raw_candidates[0].relative_to(BASE_DIR)) if is_rendered else None

        # Kiểm tra tail frame
        kf_dir = KEYFRAMES_DIR / shot_id
        tail_candidates = list(kf_dir.glob("clean_frame_*.jpg")) if kf_dir.exists() else []
        has_tail = len(tail_candidates) > 0
        tail_file = str(tail_candidates[0].relative_to(BASE_DIR)) if has_tail else None

        if is_rendered:
            manifest["rendered_shots_count"] += 1
        else:
            manifest["missing_shots_count"] += 1

        manifest["shots_status"][shot_id] = {
            "title": shot_data.get("scene_title", ""),
            "duration_sec": shot_data.get("duration_sec", 10),
            "rendered": is_rendered,
            "video_path": raw_file,
            "has_tail_frame": has_tail,
            "tail_frame_path": tail_file
        }

    # Kiểm tra scene masters
    scene_masters = list(VIDEOS_DIR.glob(f"{ep_prefix}_scene*_master*.mp4")) + list(EXPORTS_DIR.glob(f"{ep_prefix}_scene*_master*.mp4"))
    manifest["master_scene_files"] = [str(f.relative_to(BASE_DIR)) for f in scene_masters]

    return manifest


def split_all_episodes(overwrite: bool = False):
    """
    Phân tách toàn diện dự án sang mô hình thư mục từng tập:
    episodes/ep01 ... episodes/ep06.
    """
    print("\n" + "=" * 78)
    print("🎬 KHỞI TẠO CẤU TRÚC PHÂN CẤP TẬP PHIM (MODULAR EPISODE ARCHITECTURE)")
    print("=" * 78)

    EPISODES_DIR.mkdir(parents=True, exist_ok=True)
    prose_map = extract_prose_by_episodes()
    ep_muse_prompts, special_prompts = extract_muse_prompts_by_episodes()
    ep_banana_prompts = extract_banana_prompts_by_episodes()

    for ep_num, meta in EPISODES_INFO.items():
        ep_id = meta["id"]
        ep_dir = EPISODES_DIR / ep_id
        prompts_dir = ep_dir / "prompts"
        storyboard_dir = ep_dir / "storyboards"

        ep_dir.mkdir(parents=True, exist_ok=True)
        prompts_dir.mkdir(parents=True, exist_ok=True)
        storyboard_dir.mkdir(parents=True, exist_ok=True)

        print(f"\n📂 Đang đóng gói Tập {ep_num}: {meta['title'][:45]}... -> {ep_dir.relative_to(BASE_DIR)}")

        # 1. Screenplay (Kịch bản phân cảnh)
        screenplay_src = FILMMAKER_DIR / meta["screenplay_file"]
        screenplay_dst = ep_dir / "screenplay.md"
        if screenplay_src.exists():
            shutil.copy2(screenplay_src, screenplay_dst)
            print(f"   ✓ Kịch bản phân cảnh: screenplay.md ({screenplay_dst.stat().st_size // 1024} KB)")

        # 2. Novel Prose (Đoạn trích văn xuôi)
        prose_dst = ep_dir / "prose.md"
        prose_content = prose_map.get(ep_num, "")
        if prose_content:
            prose_dst.write_text(prose_content, encoding="utf-8")
            print(f"   ✓ Văn xuôi tiểu thuyết: prose.md ({len(prose_content)} ký tự)")

        # 3. Motion Prompts Muse riêng cho tập
        muse_data = ep_muse_prompts.get(ep_id, {"motion_prompts": {}})
        muse_prompts_dst = prompts_dir / "muse_prompts.json"
        muse_prompts_dst.write_text(json.dumps(muse_data, ensure_ascii=False, indent=2), encoding="utf-8")
        num_shots = len(muse_data.get("motion_prompts", {}))
        print(f"   ✓ Motion Prompts Muse: prompts/muse_prompts.json ({num_shots} shots)")

        # 4. Banana Prompts
        banana_data = ep_banana_prompts.get(ep_id, {"prompts": {}})
        banana_dst = prompts_dir / "banana_prompts.json"
        banana_dst.write_text(json.dumps(banana_data, ensure_ascii=False, indent=2), encoding="utf-8")
        print(f"   ✓ Image Prompts Banana: prompts/banana_prompts.json")

        # 5. Storyboard (Nếu là Ep01 thì chuyển Ep01_Gia_Bien_Storyboard.md)
        if ep_id == "ep01":
            sb_src = STORYBOARDS_DIR / "Ep01_Gia_Bien_Storyboard.md"
            if sb_src.exists():
                shutil.copy2(sb_src, storyboard_dir / "Ep01_Gia_Bien_Storyboard.md")
                print(f"   ✓ Storyboard: Ep01_Gia_Bien_Storyboard.md")

        # 6. Manifest
        manifest_data = get_episode_assets_manifest(ep_id, muse_data.get("motion_prompts", {}))
        manifest_dst = ep_dir / "manifest.json"
        manifest_dst.write_text(json.dumps(manifest_data, ensure_ascii=False, indent=2), encoding="utf-8")
        rendered_cnt = manifest_data["rendered_shots_count"]
        has_master = len(manifest_data["master_scene_files"]) > 0

        # 7. README.md riêng của tập
        readme_content = generate_episode_readme(meta, num_shots, rendered_cnt, has_master)
        (ep_dir / "README.md").write_text(readme_content, encoding="utf-8")
        print(f"   ✓ Sổ tay điều phối tập: README.md (Đã hoàn thiện)")

    # Lưu special / shorts prompts
    special_dst = EPISODES_DIR / "thematic_and_shorts_prompts.json"
    special_dst.write_text(json.dumps(special_prompts, ensure_ascii=False, indent=2), encoding="utf-8")

    # Tạo Master Index README cho thư mục episodes/
    generate_master_episodes_readme()

    print("\n" + "=" * 78)
    print("🎉 HOÀN TẤT THIẾT LẬP CẤU TRÚC 6 TẬP ĐỘC LẬP TẠI episodes/!")
    print("=" * 78 + "\n")


def generate_master_episodes_readme():
    """Tạo README.md tổng quan điều phối cho toàn bộ thư mục episodes/."""
    master_readme = EPISODES_DIR / "README.md"
    content = """# THẬP NGŨ NIÊN (THE FIFTEEN SPRINGS)
## HỆ THỐNG ĐIỀU PHỐI 6 TẬP ĐIỆN ẢNH (EPISODE PRODUCTION UNITS)

> **MỤC TIÊU KIẾN TRÚC**:
> 1. **Chống Loãng Ngữ Cảnh (Zero Dilution)**: Thư mục gốc chỉ lưu tài liệu tổng thể. Mỗi tập phim là một môi trường làm việc khép kín (self-contained). AI Agent chỉ nạp thư mục của tập tương ứng.
> 2. **Chống Chồng Chéo (Zero Overlap)**: Các Worker và Subagent có thể làm việc song song trên các tập khác nhau mà không bao giờ gây xung đột file hay git conflict.
> 3. **Mở Rộng Đa Worker**: Hỗ trợ 6 tài khoản Muse.ai chạy song song trên 6 tập phim.

---

## BẢNG ĐIỀU HƯỚNG 6 TẬP PHIM (EPISODE INDEX)

| Tập | Tên Phân Tập | Thư Mục | Số Shot | Kịch Bản & Văn Xuôi | Tình Trạng |
| :---: | :--- | :--- | :---: | :--- | :---: |
| **EP01** | Xuân Sắc Thề Nguyện & Giông Bão Đoạn Trường | [`ep01/`](file:///c:/Projects/KieuStory/episodes/ep01/) | 192 | [`screenplay.md`](file:///c:/Projects/KieuStory/episodes/ep01/screenplay.md) / [`prose.md`](file:///c:/Projects/KieuStory/episodes/ep01/prose.md) | Đang mở rộng |
| **EP02** | Nỗi Đau Bán Mình & Cạm Bẫy Lầm Than | [`ep02/`](file:///c:/Projects/KieuStory/episodes/ep02/) | 19 | [`screenplay.md`](file:///c:/Projects/KieuStory/episodes/ep02/screenplay.md) / [`prose.md`](file:///c:/Projects/KieuStory/episodes/ep02/prose.md) | Sẵn sàng |
| **EP03** | Cơ Duyên Thúc Gia & Đòn Ghen Hoạn Thư | [`ep03/`](file:///c:/Projects/KieuStory/episodes/ep03/) | 14 | [`screenplay.md`](file:///c:/Projects/KieuStory/episodes/ep03/screenplay.md) / [`prose.md`](file:///c:/Projects/KieuStory/episodes/ep03/prose.md) | Sẵn sàng |
| **EP04** | Bẻ Khóa Vượt Rào & Oai Danh Từ Hải | [`ep04/`](file:///c:/Projects/KieuStory/episodes/ep04/) | 12 | [`screenplay.md`](file:///c:/Projects/KieuStory/episodes/ep04/screenplay.md) / [`prose.md`](file:///c:/Projects/KieuStory/episodes/ep04/prose.md) | Sẵn sàng |
| **EP05** | Báo Ân Báo Oán & Cạm Bẫy Giang Sơn | [`ep05/`](file:///c:/Projects/KieuStory/episodes/ep05/) | 8 | [`screenplay.md`](file:///c:/Projects/KieuStory/episodes/ep05/screenplay.md) / [`prose.md`](file:///c:/Projects/KieuStory/episodes/ep05/prose.md) | Sẵn sàng |
| **EP06** | Hồn Đoạn Tiền Đường & Đoàn Viên Thanh Tịnh | [`ep06/`](file:///c:/Projects/KieuStory/episodes/ep06/) | 8 | [`screenplay.md`](file:///c:/Projects/KieuStory/episodes/ep06/screenplay.md) / [`prose.md`](file:///c:/Projects/KieuStory/episodes/ep06/prose.md) | Sẵn sàng |

---

## CÁC LỆNH ĐIỀU HÀNH THƯỜNG DÙNG

```bash
# Báo cáo tổng thể tiến độ cả 6 tập:
python 05_Production_Pipeline/episode_manager.py --status

# Xem chi tiết một tập cụ thể:
python 05_Production_Pipeline/episode_manager.py --episode ep01

# Đồng bộ ngược từ các tập về file master tổng:
python 05_Production_Pipeline/episode_manager.py --sync-to-master

# Kích hoạt 6 Worker render song song 6 Tập bằng 6 tài khoản Muse:
python 05_Production_Pipeline/multi_worker_orchestrator.py --dispatch-episodes
```
"""
    master_readme.write_text(content, encoding="utf-8")


def sync_to_master():
    """Gộp toàn bộ muse_prompts.json và banana_prompts.json từ các tập về file master."""
    print("\n🔄 Đang đồng bộ Prompts từ từng Tập về Master...")
    if not MUSE_MASTER_PROMPTS.exists():
        print(f"[!] File master không tồn tại: {MUSE_MASTER_PROMPTS}")
        return

    master_data = json.loads(MUSE_MASTER_PROMPTS.read_text(encoding="utf-8"))
    total_merged = 0

    for i in range(1, 7):
        ep_id = f"ep0{i}"
        ep_prompts_file = EPISODES_DIR / ep_id / "prompts" / "muse_prompts.json"
        if ep_prompts_file.exists():
            ep_data = json.loads(ep_prompts_file.read_text(encoding="utf-8"))
            shots = ep_data.get("motion_prompts", {})
            for sid, sdata in shots.items():
                master_data["motion_prompts"][sid] = sdata
                total_merged += 1

    MUSE_MASTER_PROMPTS.write_text(json.dumps(master_data, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"✓ Đã đồng bộ thành công {total_merged} shots từ 6 tập vào {MUSE_MASTER_PROMPTS.name}!")

    # Đồng bộ Banana prompts
    if BANANA_MASTER_PROMPTS.exists():
        banana_master = json.loads(BANANA_MASTER_PROMPTS.read_text(encoding="utf-8"))
        banana_merged = 0
        for i in range(1, 7):
            ep_id = f"ep0{i}"
            ep_banana_file = EPISODES_DIR / ep_id / "prompts" / "banana_prompts.json"
            if ep_banana_file.exists():
                ep_bdata = json.loads(ep_banana_file.read_text(encoding="utf-8"))
                bshots = ep_bdata.get("prompts", {})
                for sid, sdata in bshots.items():
                    banana_master[sid] = sdata
                    banana_merged += 1
        BANANA_MASTER_PROMPTS.write_text(json.dumps(banana_master, ensure_ascii=False, indent=2), encoding="utf-8")
        print(f"✓ Đã đồng bộ thành công {banana_merged} shots từ 6 tập vào {BANANA_MASTER_PROMPTS.name}!\n")


def show_episode_status(ep_id: Optional[str] = None):
    """Hiển thị báo cáo trạng thái các tập."""
    print("\n" + "=" * 80)
    print("📊 BÁO CÁO TIẾN ĐỘ SẢN XUẤT THEO TỪNG TẬP (EPISODE STATUS)")
    print("=" * 80)
    print(f"{'TẬP':<8} | {'TÊN TẬP':<35} | {'SỐ SHOT':<10} | {'RENDER':<12} | MASTER")
    print("-" * 80)

    target_eps = [ep_id.lower()] if ep_id else [f"ep0{i}" for i in range(1, 7)]

    for eid in target_eps:
        ep_dir = EPISODES_DIR / eid
        if not ep_dir.exists():
            print(f"{eid:<8} | Chưa khởi tạo thư mục")
            continue

        manifest_file = ep_dir / "manifest.json"
        if manifest_file.exists():
            mdata = json.loads(manifest_file.read_text(encoding="utf-8"))
            tot = mdata.get("total_shots", 0)
            done = mdata.get("rendered_shots_count", 0)
            masters = mdata.get("master_scene_files", [])
            has_master = "✓ Đã có" if masters else "⏳ Đang làm"
            pct = f"{done}/{tot} ({done/tot:.0%})" if tot > 0 else "0/0"
        else:
            tot, done, pct, has_master = 0, 0, "—", "—"

        # Lấy tên ngắn gọn
        ep_num = int(eid.replace("ep0", "").replace("ep", ""))
        info = EPISODES_INFO.get(ep_num, {})
        title = info.get("title", eid)[:34]

        print(f"{eid.upper():<8} | {title:<35} | {tot:<10} | {pct:<12} | {has_master}")

    print("=" * 80 + "\n")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Episode Manager for Thập Ngũ Niên")
    parser.add_argument("--split-all", action="store_true", help="Tách dữ liệu master thành 6 thư mục tập độc lập")
    parser.add_argument("--sync-to-master", action="store_true", help="Đồng bộ prompt từ các tập về file master")
    parser.add_argument("--status", action="store_true", help="Hiển thị báo cáo trạng thái toàn bộ 6 tập")
    parser.add_argument("--episode", type=str, help="Xem chi tiết hoặc thao tác với 1 tập cụ thể (vd: ep01)")

    args = parser.parse_args()

    if args.split_all:
        split_all_episodes()
    elif args.sync_to_master:
        sync_to_master()
    elif args.status or not any(vars(args).values()):
        show_episode_status(args.episode)
    elif args.episode:
        show_episode_status(args.episode)
