#!/usr/bin/env node
/**
 * generate-weapon-images.js
 *
 * Generates AI artwork for each Deathwatch weapon/armour card and saves
 * them to public/weapon-images/.
 *
 * Supports two providers:
 *   --provider openai   (DALL-E 3, ~$0.04/image)
 *   --provider gemini   (Imagen 3, free tier available)
 *
 * Usage:
 *   GOOGLE_API_KEY=... node scripts/generate-weapon-images.js --provider gemini
 *   GOOGLE_API_KEY=... node scripts/generate-weapon-images.js --provider gemini --limit 3
 *   OPENAI_API_KEY=... node scripts/generate-weapon-images.js --provider openai --limit 3
 *   Add --force to regenerate already-existing images
 */

const fetch = require('node-fetch');
const fs = require('fs');
const path = require('path');

// ── CLI args ──────────────────────────────────────────────────────────────
const args = process.argv.slice(2);

const PROVIDER = (() => {
  const i = args.indexOf('--provider');
  return i !== -1 ? args[i + 1] : 'huggingface';
})();
const LIMIT = (() => {
  const i = args.indexOf('--limit');
  return i !== -1 ? parseInt(args[i + 1], 10) : Infinity;
})();
const FORCE = args.includes('--force');

// ── Config ────────────────────────────────────────────────────────────────
const OUTPUT_DIR  = path.join(__dirname, '../public/weapon-images');
const MANIFEST    = path.join(OUTPUT_DIR, 'manifest.json');
const WEAPONS_API = 'http://localhost:5000/api/weapons';

// Rate limits
const DELAY = {
  openai: 13_000,  // DALL-E 3: 5 req/min on tier-1
  gemini: 4_000,   // Imagen 3: 15 req/min on free tier
};

// ── Helpers ───────────────────────────────────────────────────────────────
function slug(name) {
  return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
}
function loadManifest() {
  try { return JSON.parse(fs.readFileSync(MANIFEST, 'utf8')); }
  catch { return {}; }
}
function saveManifest(m) {
  fs.writeFileSync(MANIFEST, JSON.stringify(m, null, 2));
}
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

function buildPrompt(weapon) {
  const cat = weapon.category;
  const stats = weapon.stats || {};
  const cls = stats.class || '';

  const catDesc = {
    'Melee Weapon':  'melee weapon held in gauntleted hands',
    'Ranged Weapon': 'ranged firearm positioned for display',
    'Grenade':       'grenade or explosive device, palm-sized',
    'Armour':        'suit of Space Marine power armor',
    'Other':         'piece of military equipment',
  }[cat] || 'weapon';

  return [
    `Warhammer 40000 Deathwatch ${catDesc}: "${weapon.name}".`,
    cls ? `Type: ${cls}.` : '',
    'Dark gothic sci-fi aesthetic, ultra-detailed digital painting.',
    'Dramatic atmospheric lighting, glowing sigils, battle-worn metal.',
    'Centered isolated subject, black background, no text, no watermark.',
    'Style: official Games Workshop concept art.',
  ].filter(Boolean).join(' ');
}

// ── OpenAI DALL-E 3 ───────────────────────────────────────────────────────
async function generateOpenAI(prompt) {
  const key = process.env.OPENAI_API_KEY;
  if (!key) throw new Error('OPENAI_API_KEY is not set');

  const res = await fetch('https://api.openai.com/v1/images/generations', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${key}` },
    body: JSON.stringify({ model: 'dall-e-3', prompt, n: 1, size: '1024x1024',
      quality: 'standard', response_format: 'url' }),
  });
  if (!res.ok) throw new Error(`OpenAI error ${res.status}: ${await res.text()}`);
  const data = await res.json();
  const url = data.data[0].url;

  // download PNG
  const imgRes = await fetch(url);
  if (!imgRes.ok) throw new Error(`Download failed: ${imgRes.status}`);
  return imgRes.buffer();
}

// ── Hugging Face FLUX.1-schnell ───────────────────────────────────────────
async function generateHuggingFace(prompt) {
  const key = process.env.HF_TOKEN || process.env.HUGGINGFACE_TOKEN;
  if (!key) throw new Error('HF_TOKEN is not set');

  const res = await fetch(
    'https://router.huggingface.co/hf-inference/models/black-forest-labs/FLUX.1-schnell',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${key}` },
      body: JSON.stringify({ inputs: prompt }),
    }
  );
  if (!res.ok) throw new Error(`HuggingFace error ${res.status}: ${await res.text()}`);
  return res.buffer();
}

// ── Google Gemini Imagen 3 ────────────────────────────────────────────────
async function generateGemini(prompt) {
  const key = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY;
  if (!key) throw new Error('GOOGLE_API_KEY (or GEMINI_API_KEY) is not set');

  const model = 'imagen-3.0-generate-002';
  const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:predict?key=${key}`;

  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      instances: [{ prompt }],
      parameters: {
        sampleCount: 1,
        aspectRatio: '1:1',
        safetyFilterLevel: 'block_only_high',
        personGeneration: 'allow_adult',
      },
    }),
  });

  if (!res.ok) throw new Error(`Gemini error ${res.status}: ${await res.text()}`);
  const data = await res.json();
  const b64 = data.predictions?.[0]?.bytesBase64Encoded;
  if (!b64) throw new Error('No image in Gemini response: ' + JSON.stringify(data));
  return Buffer.from(b64, 'base64');
}

// ── Main ──────────────────────────────────────────────────────────────────
async function main() {
  const generate = PROVIDER === 'openai' ? generateOpenAI
                 : PROVIDER === 'gemini' ? generateGemini
                 : generateHuggingFace;
  const delayMs  = DELAY[PROVIDER] ?? 2_000;

  const keyVar = PROVIDER === 'openai' ? 'OPENAI_API_KEY'
               : PROVIDER === 'gemini' ? 'GOOGLE_API_KEY'
               : 'HF_TOKEN';
  const keyVal = process.env[keyVar] || process.env.GEMINI_API_KEY || process.env.HUGGINGFACE_TOKEN;
  if (!keyVal) {
    console.error(`Error: ${keyVar} environment variable is not set.`);
    process.exit(1);
  }

  console.log(`Provider: ${PROVIDER.toUpperCase()} | Limit: ${LIMIT === Infinity ? 'all' : LIMIT} | Force: ${FORCE}`);

  console.log(`\nFetching weapons from ${WEAPONS_API} …`);
  let weapons;
  try {
    const res = await fetch(WEAPONS_API);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    weapons = await res.json();
  } catch (e) {
    console.error('Could not reach server:', e.message);
    console.error('Make sure the backend is running: npm run pm2:start');
    process.exit(1);
  }
  console.log(`Found ${weapons.length} weapons/items`);

  fs.mkdirSync(OUTPUT_DIR, { recursive: true });
  const manifest = loadManifest();

  let generated = 0, skipped = 0, errors = 0;

  for (const weapon of weapons) {
    if (generated >= LIMIT) break;

    const key  = slug(weapon.name);
    const file = `${key}.png`;
    const dest = path.join(OUTPUT_DIR, file);
    const webPath = `/weapon-images/${file}`;

    if (!FORCE && manifest[weapon.id] && fs.existsSync(dest)) {
      skipped++;
      continue;
    }

    const prompt = buildPrompt(weapon);
    console.log(`\n[${generated + 1}] ${weapon.name} (${weapon.category})`);
    console.log(`  prompt: ${prompt.substring(0, 90)}…`);

    try {
      const buf = await generate(prompt);
      fs.writeFileSync(dest, buf);
      manifest[weapon.id] = { name: weapon.name, path: webPath, provider: PROVIDER, generated: new Date().toISOString() };
      saveManifest(manifest);
      console.log(`  ✓ Saved → ${webPath}`);
      generated++;
    } catch (e) {
      console.error(`  ✗ Failed: ${e.message}`);
      errors++;
    }

    // Rate-limit throttle
    const isLast = weapons.indexOf(weapon) >= weapons.length - 1 || generated >= LIMIT;
    if (!isLast) {
      process.stdout.write(`  … waiting ${delayMs / 1000}s …`);
      await sleep(delayMs);
      process.stdout.write('\r\x1b[2K');
    }
  }

  console.log(`\nDone. Generated: ${generated}, Skipped: ${skipped}, Errors: ${errors}`);
  if (generated > 0) console.log(`Images in: public/weapon-images/`);
}

main().catch(e => { console.error(e); process.exit(1); });
