#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CHAR-HERO-DESIGN-PACKER
生成/整理苏白主角资产包，让他不再像路人甲。

功能:
1. 读取候选参考图
2. 用 Seedance/Kling 生成多视角变体
3. 输出完整资产包到 approved/ 目录
4. 更新 manifest.hdlp

用法:
  python char-hero-design-packer.py --character CHAR-003-SuBai --generate-all
  python char-hero-design-packer.py --character CHAR-003-SuBai --view front_half_body
"""

import os
import sys
import json
import argparse
import shutil
from pathlib import Path

# 添加项目根目录到路径
PROJECT_ROOT = Path(__file__).parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT / "engines"))

try:
    from image_api_adapter import generate_image
except Exception as exc:
    print(f"⚠️  图片生成适配器未接通: {exc}")
    print("💡 桥接需要 Node.js: image-api-bridge.js + image-api-adapter.js")
    generate_image = None

try:
    from hldp_prompt import expand_prompt
except Exception:
    expand_prompt = None


class CharHeroDesignPacker:
    """主角资产包生成器"""

    def __init__(self, character_id, assets_root=None):
        self.character_id = character_id
        self.assets_root = Path(assets_root or PROJECT_ROOT / "assets" / "characters" / character_id)
        self.manifest_path = self.assets_root / "manifest.hdlp"
        self.approved_dir = self.assets_root / "approved"
        self.candidates_dir = self.assets_root / "candidates"
        self.rejected_dir = self.assets_root / "rejected"
        self.turnarounds_dir = self.assets_root / "turnarounds"
        self.voice_dir = self.assets_root / "voice"

        # 确保目录存在
        for d in [self.approved_dir, self.candidates_dir, self.rejected_dir,
                   self.turnarounds_dir, self.voice_dir]:
            d.mkdir(parents=True, exist_ok=True)

        # 读取 manifest
        self.manifest = self._read_manifest()

        # 读取角色描述
        self.character_desc = self._load_character_description()

    def _read_manifest(self):
        """读取 manifest.hdlp"""
        if not self.manifest_path.exists():
            return {"asset_id": self.character_id, "approval_status": "DRAFT"}
        # 简单解析 HLDP 文件
        manifest = {}
        with open(self.manifest_path, "r", encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if line.startswith("asset_id:") or line.startswith("canonical_id:"):
                    manifest["asset_id"] = line.split(":", 1)[1].strip()
                elif line.startswith("approval_status:"):
                    manifest["approval_status"] = line.split(":", 1)[1].strip()
                elif line.startswith("candidate_front_half_body:"):
                    manifest["candidate_front_half_body"] = line.split(":", 1)[1].strip()
        return manifest

    def _load_character_description(self):
        """从 data/characters-v2.hdlp 加载角色描述"""
        char_file = PROJECT_ROOT / "data" / "characters-v2.hdlp"
        if not char_file.exists():
            return None

        # 简单解析
        description = {}
        with open(char_file, "r", encoding="utf-8") as f:
            content = f.read()
            # 找 CHAR-003 段落
            if "CHAR-003" in content:
                lines = content.split("\n")
                in_char = False
                for line in lines:
                    if "CHAR-003" in line:
                        in_char = True
                    elif in_char:
                        if line.strip().startswith("---"):
                            break
                        if ":" in line:
                            key, _, val = line.partition(":")
                            description[key.strip()] = val.strip()
        return description

    def generate_front_half_body(self, output_name="front_half_body.png"):
        """
        生成正面半身批准图
        使用候选图作为参考，用 Seedance/Kling 图生图生成稳定版本
        """
        print(f"[1/4] 生成正面半身图: {output_name}")

        candidate = self.manifest.get("candidate_front_half_body")
        if not candidate or not Path(candidate).exists():
            print(f"  ❌ 候选图不存在: {candidate}")
            print(f"  💡 请先准备候选图，放入 candidates/ 目录")
            return None

        # 构建提示词
        prompt = self._build_prompt("front_half_body")

        # 调用图像生成 API (图生图)
        print(f"  参考图: {candidate}")
        print(f"  提示词: {prompt[:100]}...")

        if generate_image is None:
            print("  ⚠️  当前仓库只有 JS 图片适配器 image-api-adapter.js，未提供 Python generate_image。")
            print("  ⚠️  暂不能生成新主角资产；仅将候选图复制到 candidates/ 供人工/后续工具处理。")
            fallback_path = self.candidates_dir / output_name
            shutil.copy2(candidate, fallback_path)
            print(f"  🟡 已复制候选图: {fallback_path}")
            return None

        try:
            result_path = generate_image(
                prompt=prompt,
                reference_image=candidate,
                output_dir=str(self.approved_dir),
                output_name=output_name
            )
            print(f"  ✅ 已生成: {result_path}")
            return result_path
        except Exception as e:
            print(f"  ❌ 生成失败: {e}")
            return None

    def generate_side_face(self, output_name="side_face.png"):
        """生成侧脸批准图"""
        print(f"[2/4] 生成侧脸图: {output_name}")
        prompt = self._build_prompt("side_face")
        candidate = self.approved_dir / "front_half_body.png"

        if not candidate.exists():
            print(f"  ❌ 请先生成正面半身图")
            return None

        try:
            result_path = generate_image(
                prompt=prompt,
                reference_image=str(candidate),
                output_dir=str(self.approved_dir),
                output_name=output_name
            )
            print(f"  ✅ 已生成: {result_path}")
            return result_path
        except Exception as e:
            print(f"  ❌ 生成失败: {e}")
            return None

    def generate_full_body_costume(self, output_name="full_body_costume.png"):
        """生成全身服装图"""
        print(f"[3/4] 生成全身服装图: {output_name}")
        prompt = self._build_prompt("full_body_costume")
        candidate = self.approved_dir / "front_half_body.png"

        if not candidate.exists():
            print(f"  ❌ 请先生成正面半身图")
            return None

        try:
            result_path = generate_image(
                prompt=prompt,
                reference_image=str(candidate),
                output_dir=str(self.approved_dir),
                output_name=output_name
            )
            print(f"  ✅ 已生成: {result_path}")
            return result_path
        except Exception as e:
            print(f"  ❌ 生成失败: {e}")
            return None

    def generate_expression_sheet(self, output_name="expression_sheet.png"):
        """生成表情表"""
        print(f"[4/4] 生成表情表: {output_name}")
        prompt = self._build_prompt("expression_sheet")
        candidate = self.approved_dir / "front_half_body.png"

        if not candidate.exists():
            print(f"  ❌ 请先生成正面半身图")
            return None

        try:
            result_path = generate_image(
                prompt=prompt,
                reference_image=str(candidate),
                output_dir=str(self.approved_dir),
                output_name=output_name
            )
            print(f"  ✅ 已生成: {result_path}")
            return result_path
        except Exception as e:
            print(f"  ❌ 生成失败: {e}")
            return None

    def _build_prompt(self, view_type):
        """构建特定视角的提示词"""
        base_desc = self.character_desc or {}

        prompts = {
            "front_half_body": f"""
            {base_desc.get('visual_description', '中国古代修仙少年，16岁，主角辨识度强')}
            正面半身像，胸部以上，面部清晰，眼神坚定，
            3D国风漫剧风格，统一渲染风格，
            高清，8K，最佳质量
            """.strip(),

            "side_face": f"""
            {base_desc.get('visual_description', '中国古代修仙少年')}
            侧脸45度，能看到面部轮廓和发型，
            3D国风漫剧风格，统一渲染风格，
            高清，8K，最佳质量
            """.strip(),

            "full_body_costume": f"""
            {base_desc.get('visual_description', '中国古代修仙少年')}
            全身像，站立姿势，完整展示服装细节，
            服装细节必须来自角色资产清单，不得自由改色，
            3D国风漫剧风格，统一渲染风格，
            高清，8K，最佳质量
            """.strip(),

            "expression_sheet": f"""
            {base_desc.get('visual_description', '中国古代修仙少年')}
            表情表，网格布局，包含:
            平静，微笑，大笑，生气，惊讶，悲伤，
            每个表情单独一格，统一光照和背景，
            3D国风漫剧风格，
            高清，8K，最佳质量
            """.strip(),
        }

        return prompts.get(view_type, prompts["front_half_body"])

    def generate_all(self):
        """生成所有资产"""
        print(f"\n🎬 开始生成 {self.character_id} 主角资产包")
        print(f"=" * 60)

        results = {}

        # 1. 正面半身
        path = self.generate_front_half_body()
        if path:
            results["front_half_body"] = path

        # 2. 侧脸
        path = self.generate_side_face()
        if path:
            results["side_face"] = path

        # 3. 全身服装
        path = self.generate_full_body_costume()
        if path:
            results["full_body_costume"] = path

        # 4. 表情表
        path = self.generate_expression_sheet()
        if path:
            results["expression_sheet"] = path

        # 更新 manifest。只有四项资产都真实生成时才可进入 APPROVED。
        self._update_manifest(results)

        print(f"\n✅ 资产包生成完成!")
        print(f"   已生成: {len(results)}/4 个资产")
        print(f"   位置: {self.approved_dir}")

        return results

    def _update_manifest(self, results):
        """更新 manifest.hdlp"""
        print(f"\n📝 更新 manifest.hdlp...")

        required_assets = {"front_half_body", "side_face", "full_body_costume", "expression_sheet"}
        is_complete = required_assets.issubset(results.keys())
        approval_status = "APPROVED" if is_complete else "DRAFT_GENERATED"
        type_label = "已批准" if is_complete else "待质检"
        lock_note = (
            "⊢ 资产已批准，可用于成片镜头。"
            if is_complete
            else "⊢ 资产包尚未完整生成/质检，禁止作为成片锁定资产。"
        )

        manifest_content = f"""# 资产清单 · {self.character_id}

> HLDP://video-ai-system/assets/characters/{self.character_id}/manifest
> 类型: 角色资产 · {type_label}
> 建立: D143 · 2026-06-23
> 更新: D144 · 2026-06-24
> 项目: 付费才能修仙 · EP01

---

## 状态

```
approval_status: {approval_status}
asset_type: character
canonical_id: {self.character_id}
canonical_name: 苏白
```

---

## 批准资产

```
front_half_body: {results.get("front_half_body", "NOT_GENERATED")}
side_face: {results.get("side_face", "NOT_GENERATED")}
full_body_costume: {results.get("full_body_costume", "NOT_GENERATED")}
expression_sheet: {results.get("expression_sheet", "NOT_GENERATED")}
```

---

## 视觉锁

```
face_shape: TODO_FROM_APPROVED_ASSET
hair_style: TODO_FROM_APPROVED_ASSET
costume: TODO_FROM_APPROVED_ASSET
age_band: 16岁
render_style: 3D国风漫剧
color_palette: TODO_FROM_APPROVED_ASSET
```

---

## 锁定

{lock_note}
⊢ 禁止使用 candidates/ 或 rejected/ 中的图片作为最终资产。
"""

        with open(self.manifest_path, "w", encoding="utf-8") as f:
            f.write(manifest_content)

        print(f"  ✅ 已更新: {self.manifest_path}")


def main():
    parser = argparse.ArgumentParser(description="CHAR-HERO-DESIGN-PACKER")
    parser.add_argument("--character", type=str, default="CHAR-003-SuBai",
                        help="角色ID (默认: CHAR-003-SuBai)")
    parser.add_argument("--generate-all", action="store_true",
                        help="生成所有资产")
    parser.add_argument("--view", type=str,
                        choices=["front_half_body", "side_face", "full_body_costume", "expression_sheet"],
                        help="生成特定视角的资产")

    args = parser.parse_args()

    packer = CharHeroDesignPacker(args.character)

    if args.generate_all:
        packer.generate_all()
    elif args.view:
        method_map = {
            "front_half_body": packer.generate_front_half_body,
            "side_face": packer.generate_side_face,
            "full_body_costume": packer.generate_full_body_costume,
            "expression_sheet": packer.generate_expression_sheet,
        }
        method_map[args.view]()
    else:
        print("请指定 --generate-all 或 --view <view_type>")
        parser.print_help()


if __name__ == "__main__":
    main()
