#!/usr/bin/env node
/**
 * char-hero-design-packer.js — 角色资产打包引擎 v2.0
 * 
 * 核心改进：
 *   1. 主视角先生成 → 记录 seed
 *   2. 其余视角用主视角作为图生图参考 → 锁定同一 seed
 *   3. 版本化输出目录
 *   4. manifest.json 记录所有种子和参考链
 * 
 * 用法:
 *   node char-hero-design-packer.js --character CHAR-001 --project deep-sea-voyage
 *   node char-hero-design-packer.js --character CHAR-001 --project deep-sea-voyage --dry-run
 */

const https = require('https');
const http = require('http');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

// ===== 配置 =====

const MODEL = 'doubao-seedream-4-0-250828';
const SIZE = '1440x2560';
const COST_PER_IMAGE = 0.21; // ¥

// 视角定义
const VIEW_CONFIGS = {
  front: {
    label: '正面',
    suffix: '_正面',
    promptAddon: 'full body shot, facing camera directly, looking at viewer',
    mode: 't2i'  // 第一张用文生图
  },
  side: {
    label: '侧面',
    suffix: '_侧面',
    promptAddon: 'side profile view, turned 90 degrees to the right, profile silhouette visible',
    mode: 'i2i'  // 用于主视角参考
  },
  hand: {
    label: '手部',
    suffix: '_手部',
    promptAddon: 'close-up shot focusing on hands, callused worker hands with engine oil stains, hands in foreground',
    mode: 'i2i'
  },
  threeQuarter: {
    label: '¾面',
    suffix: '¾面',
    promptAddon: 'three-quarter view, angled slightly to the right, facing partly away from camera, cinematic portrait',
    mode: 'i2i'
  }
};

// ===== API 调用 =====

function getArkKey() {
  const envPaths = [
    path.join(__dirname, '..', '..', '.env'),
    path.join(__dirname, '..', '..', '..', '.env'),
    'D:/WorkBuddy/cang-ying/.env'
  ];
  for (const p of envPaths) {
    if (fs.existsSync(p)) {
      const content = fs.readFileSync(p, 'utf8');
      const m = content.match(/ARK_API_KEY=(.+)/);
      if (m) return m[1].trim();
    }
  }
  throw new Error('ARK_API_KEY 未找到，请检查 .env 配置');
}

function apiCall(payload) {
  return new Promise((resolve, reject) => {
    const body = JSON.stringify(payload);
    const req = https.request({
      hostname: 'ark.cn-beijing.volces.com',
      path: '/api/v3/images/generations',
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${getArkKey()}`,
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body)
      }
    }, (res) => {
      let data = '';
      res.on('data', c => data += c);
      res.on('end', () => {
        try {
          const result = JSON.parse(data);
          if (result.error) reject(new Error(JSON.stringify(result.error)));
          else resolve(result);
        } catch (e) { reject(new Error(data)); }
      });
    });
    req.on('error', reject);
    req.write(body);
    req.end();
  });
}

function t2i(prompt, seed) {
  const payload = { model: MODEL, prompt, size: SIZE, n: 1 };
  if (seed) payload.seed = seed;
  return apiCall(payload);
}

function i2i(prompt, imagePath, seed) {
  const imgBase64 = fs.readFileSync(imagePath).toString('base64');
  const ext = imagePath.endsWith('.png') ? 'png' : 'jpeg';
  const payload = {
    model: MODEL,
    prompt,
    image: `data:image/${ext};base64,${imgBase64}`,
    size: SIZE,
    n: 1
  };
  if (seed) payload.seed = seed;
  return apiCall(payload);
}

function downloadImage(url, outputPath) {
  return new Promise((resolve, reject) => {
    const proto = url.startsWith('https') ? https : http;
    const file = fs.createWriteStream(outputPath);
    proto.get(url, (res) => {
      res.pipe(file);
      file.on('finish', () => { file.close(); resolve(outputPath); });
    }).on('error', (e) => { fs.unlink(outputPath, () => {}); reject(e); });
  });
}

// ===== 剧本解析 =====

function findCharacterInBreakdown(charId, project, breakdownPath) {
  // 优先用指定路径
  if (breakdownPath && fs.existsSync(breakdownPath)) {
    const content = fs.readFileSync(breakdownPath, 'utf8');
    return parseCharFromHdlp(content, charId);
  }

  // 自动搜索项目目录
  const base = path.join(__dirname, '..', '..', 'projects', project);
  if (!fs.existsSync(base)) throw new Error(`项目目录不存在: ${base}`);

  const files = fs.readdirSync(base).filter(f => f.includes('BREAKDOWN') && f.endsWith('.hdlp'));
  // 优先 V3 > V2 > V1
  files.sort((a, b) => {
    const getV = f => { const m = f.match(/V(\d+)/); return m ? parseInt(m[1]) : 0; };
    return getV(b) - getV(a);
  });

  for (const f of files) {
    const content = fs.readFileSync(path.join(base, f), 'utf8');
    const result = parseCharFromHdlp(content, charId);
    if (result) return result;
  }

  throw new Error(`角色 ${charId} 未在项目的 BREAKDOWN 中找到`);
}

function parseCharFromHdlp(content, charId) {
  // 解析 CHAR 表格
  const lines = content.split('\n');
  let inCharTable = false;
  let headers = [];

  for (const line of lines) {
    if (line.includes('CHAR - 角色资产表') || line.includes('CHAR -')) {
      inCharTable = true;
      continue;
    }
    if (inCharTable && (line.startsWith('### ') || line.startsWith('## '))) {
      inCharTable = false;
      continue;
    }
    if (!inCharTable) continue;

    // 表头行
    if (line.includes('| ID |') || line.includes('| ID | 名称 |')) {
      headers = line.split('|').map(h => h.trim()).filter(Boolean);
      continue;
    }
    if (line.includes('| :---')) continue; // 分隔行

    // 数据行
    const cells = line.split('|').map(c => c.trim()).filter(Boolean);
    if (cells.length >= 4 && cells[0] === charId) {
      return {
        id: cells[0],
        name: cells[1] || '',
        description: cells[3] || cells[2] || '',
        englishPrompt: cells[4] || cells[3] || '',
        rawLine: line
      };
    }
  }
  return null;
}

// ===== 主流程 =====

async function packCharacter(charId, project, options = {}) {
  const {
    breakdownPath = null,
    views = ['front', 'side', 'hand'],
    dryRun = false
  } = options;

  // 1. 查找角色描述
  console.error(`📖 查找角色 ${charId}...`);
  const charInfo = findCharacterInBreakdown(charId, project, breakdownPath);
  console.error(`   名称: ${charInfo.name}`);
  console.error(`   描述: ${charInfo.description.substring(0, 80)}...`);

  // 2. 准备输出目录
  const outputDir = path.join(
    __dirname, '..', '..', 'outputs', 'pack', project, charId,
    `v${new Date().toISOString().slice(0,10).replace(/-/g,'')}`
  );
  if (!dryRun && !fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }

  console.error(`📁 输出目录: ${outputDir}`);
  console.error();

  const basePrompt = charInfo.englishPrompt;

  // 3. 干运行模式
  if (dryRun) {
    console.error('🔍 === 干运行预览 ===');
    const viewList = views.map(v => {
      const cfg = VIEW_CONFIGS[v];
      return {
        view: v,
        label: cfg.label,
        mode: cfg.mode,
        prompt: `${basePrompt} ${cfg.promptAddon}`,
        ref: cfg.mode === 'i2i' ? '<FRONT_MASTER>' : 'none',
        cost: COST_PER_IMAGE
      };
    });
    console.error(`   角色: ${charId} ${charInfo.name}`);
    console.error(`   视角度数: ${views.length} (${views.map(v => VIEW_CONFIGS[v].label).join(', ')})`);
    console.error(`   总成本: ¥${(views.length * COST_PER_IMAGE).toFixed(2)}`);
    console.error(`   成本明细:`);
    viewList.forEach(v => console.error(`     ${v.label} (${v.mode}): ¥${COST_PER_IMAGE.toFixed(2)}`));
    console.error();
    return { dryRun: true, charInfo, views: viewList, cost: views.length * COST_PER_IMAGE };
  }

  // 4. 首次视角用 T2I（主视角）
  let masterImage = null;
  let masterSeed = null;
  let masterViewConfig = null;
  const results = {};

  // 找第一个 t2i 视角
  for (const v of views) {
    const cfg = VIEW_CONFIGS[v];
    if (cfg && cfg.mode === 't2i') {
      masterViewConfig = { view: v, cfg };
      break;
    }
  }

  if (!masterViewConfig) {
    // 所有视角都是 i2i，先做一个 T2I 主视角
    masterViewConfig = { view: 'front', cfg: VIEW_CONFIGS.front };
  }

  console.error(`🎨 步骤1: 生成主视角 (${masterViewConfig.cfg.label})...`);
  const masterPrompt = `${basePrompt} ${masterViewConfig.cfg.promptAddon}`;
  console.error(`   提示词: ${masterPrompt.substring(0, 100)}...`);

  const masterResult = await t2i(masterPrompt);
  if (masterResult.error) throw new Error(JSON.stringify(masterResult.error));

  masterSeed = masterResult.data?.[0]?.seed;
  const masterUrl = masterResult.data?.[0]?.url;
  console.error(`   Seed: ${masterSeed || 'N/A'}`);

  // 下载主视角
  const masterFile = path.join(outputDir, `${charId}${VIEW_CONFIGS.front.suffix}.png`);
  await downloadImage(masterUrl, masterFile);
  masterImage = masterFile;
  console.error(`   ✅ ${masterFile}`);

  results.front = { seed: masterSeed, path: masterFile, mode: 't2i', cost: COST_PER_IMAGE };

  // 5. 其他视角用 I2I + 锁定 seed
  const remainingViews = views.filter(v => {
    const cfg = VIEW_CONFIGS[v];
    return cfg && cfg.mode !== 't2i';
  });

  for (const view of remainingViews) {
    const cfg = VIEW_CONFIGS[view];
    console.error(`🎨 步骤: 生成${cfg.label} (I2I，参考=正面)...`);

    const viewPrompt = `${basePrompt} ${cfg.promptAddon}`;
    console.error(`   提示词: ${viewPrompt.substring(0, 100)}...`);
    console.error(`   参考图: ${masterImage}`);
    if (masterSeed) console.error(`   锁定Seed: ${masterSeed}`);

    const result = await i2i(viewPrompt, masterImage, masterSeed);
    if (result.error) throw new Error(JSON.stringify(result.error));

    const viewSeed = result.data?.[0]?.seed;
    const viewUrl = result.data?.[0]?.url;
    console.error(`   Seed: ${viewSeed || 'N/A'}`);

    const viewFile = path.join(outputDir, `${charId}${cfg.suffix}.png`);
    await downloadImage(viewUrl, viewFile);
    console.error(`   ✅ ${viewFile}`);

    results[view] = { seed: viewSeed, path: viewFile, mode: 'i2i', refFrom: 'front', cost: COST_PER_IMAGE };
  }

  // 6. 写入 manifest
  const totalCost = Object.values(results).reduce((s, r) => s + r.cost, 0);
  const manifest = {
    characterId: charId,
    characterName: charInfo.name,
    project,
    model: MODEL,
    generatedAt: new Date().toISOString(),
    masterSeed,
    totalCost,
    views: results
  };

  const manifestPath = path.join(outputDir, 'manifest.json');
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
  console.error(`📋 Manifest: ${manifestPath}`);
  console.error(`💰 总成本: ¥${totalCost.toFixed(2)}`);

  return { dryRun: false, manifest, outputDir };
}

// ===== CLI =====

async function main() {
  const args = process.argv.slice(2);
  const getArg = (name) => {
    const idx = args.indexOf(name);
    return idx !== -1 ? args[idx + 1] : null;
  };

  const character = getArg('--character');
  const project = getArg('--project');
  const breakdown = getArg('--breakdown');
  const viewsStr = getArg('--views');
  const dryRun = args.includes('--dry-run');
  const generateAll = args.includes('--generate-all');

  if (!character) {
    console.log('用法: node char-hero-design-packer.js --character CHAR-001 --project <项目名> [--views front,side,hand] [--dry-run]');
    process.exit(1);
  }

  // 默认视图
  const views = viewsStr ? viewsStr.split(',').map(v => v.trim()) : ['front', 'side', 'hand'];
  const proj = project || 'default';

  try {
    const result = await packCharacter(character, proj, {
      breakdownPath: breakdown,
      views,
      dryRun
    });
    if (!dryRun) {
      console.log(JSON.stringify(result.manifest, null, 2));
    }
  } catch (e) {
    console.error(`❌ 错误: ${e.message}`);
    process.exit(1);
  }
}

if (require.main === module) {
  main();
}

module.exports = { packCharacter, findCharacterInBreakdown };
