#!/usr/bin/env node
/**
 * Placeholder asset generator.
 *
 * Produces minimal-but-valid binary files for every entry in
 * `assets/resources/ASSETS.md`, so the Cocos Creator project can be opened
 * and navigated end-to-end before the real art/audio pass.
 *
 *   • PNG : 1×1 solid-color (distinct per category for visual debugging)
 *   • WAV : 0.1 s of silence, mono, 8 kHz, 8-bit PCM (~ 1 KB)
 *   • MP3 : minimal silent MP3 header frame
 *
 * Safe to re-run: files are only (re)written when their content differs or
 * the path does not exist yet.
 *
 * Usage:
 *     node scripts/gen_placeholder_assets.js
 */

const fs = require('fs');
const path = require('path');
const zlib = require('zlib');

// --------------------------------------------------------------------- //
// PNG builder (1×1 RGBA solid color, no external deps)                  //
// --------------------------------------------------------------------- //
function makeSolidPng(r, g, b, a = 255) {
    const crcTable = (() => {
        const t = new Uint32Array(256);
        for (let n = 0; n < 256; n++) {
            let c = n;
            for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
            t[n] = c >>> 0;
        }
        return t;
    })();
    function crc32(buf) {
        let c = 0xffffffff;
        for (let i = 0; i < buf.length; i++) c = crcTable[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
        return (c ^ 0xffffffff) >>> 0;
    }
    function chunk(type, data) {
        const len = Buffer.alloc(4); len.writeUInt32BE(data.length, 0);
        const typeBuf = Buffer.from(type, 'ascii');
        const crcInput = Buffer.concat([typeBuf, data]);
        const crc = Buffer.alloc(4); crc.writeUInt32BE(crc32(crcInput), 0);
        return Buffer.concat([len, typeBuf, data, crc]);
    }

    const signature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
    const ihdr = Buffer.alloc(13);
    ihdr.writeUInt32BE(1, 0);    // width
    ihdr.writeUInt32BE(1, 4);    // height
    ihdr[8] = 8;                  // bit depth
    ihdr[9] = 6;                  // color type RGBA
    ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0;
    const raw = Buffer.from([0x00, r & 0xff, g & 0xff, b & 0xff, a & 0xff]); // filter byte + pixel
    const idatData = zlib.deflateSync(raw);
    return Buffer.concat([
        signature,
        chunk('IHDR', ihdr),
        chunk('IDAT', idatData),
        chunk('IEND', Buffer.alloc(0)),
    ]);
}

// --------------------------------------------------------------------- //
// WAV builder (0.1s silence, mono 8 kHz, 8-bit PCM)                     //
// --------------------------------------------------------------------- //
function makeSilentWav(durationSec = 0.1) {
    const sampleRate = 8000;
    const numSamples = Math.floor(sampleRate * durationSec);
    const dataSize = numSamples; // 1 byte per sample
    const fileSize = 44 + dataSize - 8;
    const buf = Buffer.alloc(44 + dataSize);
    buf.write('RIFF', 0);
    buf.writeUInt32LE(fileSize, 4);
    buf.write('WAVE', 8);
    buf.write('fmt ', 12);
    buf.writeUInt32LE(16, 16);           // subchunk1 size
    buf.writeUInt16LE(1, 20);            // PCM
    buf.writeUInt16LE(1, 22);            // mono
    buf.writeUInt32LE(sampleRate, 24);
    buf.writeUInt32LE(sampleRate, 28);   // byteRate = sampleRate * 1 * 8/8
    buf.writeUInt16LE(1, 32);            // blockAlign
    buf.writeUInt16LE(8, 34);            // bits per sample
    buf.write('data', 36);
    buf.writeUInt32LE(dataSize, 40);
    buf.fill(128, 44);                    // unsigned silence = 128
    return buf;
}

// --------------------------------------------------------------------- //
// MP3 builder (single silent MPEG-1 layer 3 frame, 8 kHz mono 8 kbps)   //
// --------------------------------------------------------------------- //
// 13-byte ID3v2 empty tag + one 24-byte frame. Any real MP3 decoder will
// render it as ~0.07s of silence.
function makeSilentMp3() {
    const id3 = Buffer.from([
        0x49, 0x44, 0x33,       // "ID3"
        0x03, 0x00,              // v2.3
        0x00,                    // flags
        0x00, 0x00, 0x00, 0x00,  // size (0)
    ]);
    // MPEG-1 Layer III, 32 kbps, 32 kHz, mono, CRC off, no padding
    // Frame header bytes chosen to be universally parseable.
    const frame = Buffer.from([
        0xff, 0xfb, 0x10, 0xc4,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
    ]);
    return Buffer.concat([id3, frame]);
}

// --------------------------------------------------------------------- //
// Spec table                                                            //
// --------------------------------------------------------------------- //
const ROOT = path.resolve(__dirname, '..', 'assets', 'resources');

// [relativePath, kind, colorOrNote]
const SPEC = [
    // 1. Protagonist — 3 color states
    ['textures/characters/kage_red.png',    'png', [220,  60,  60]],
    ['textures/characters/kage_green.png',  'png', [ 60, 200, 100]],
    ['textures/characters/kage_yellow.png', 'png', [240, 210,  60]],

    // 2. Enemies + BOSS
    ['textures/enemies/qing_ren.png',          'png', [ 80, 160, 220]],
    ['textures/enemies/chi_ren.png',           'png', [200,  70,  70]],
    ['textures/enemies/hei_ren.png',           'png', [ 40,  40,  50]],
    ['textures/enemies/yao_fang.png',          'png', [180, 100, 200]],
    ['textures/bosses/shuang_huan_fang.png',   'png', [120,  40, 140]],
    ['textures/bosses/butterfly.png',          'png', [240, 240, 100]],

    // 3. Scenes — 3 themes × 4 layers
    ['textures/scenes/forest/far.png',        'png', [ 30,  80,  50]],
    ['textures/scenes/forest/mid.png',        'png', [ 50, 110,  70]],
    ['textures/scenes/forest/near.png',       'png', [ 70, 140,  80]],
    ['textures/scenes/forest/fx.png',         'png', [180, 220, 160]],

    ['textures/scenes/castle_wall/far.png',   'png', [ 60,  60,  80]],
    ['textures/scenes/castle_wall/mid.png',   'png', [100, 100, 120]],
    ['textures/scenes/castle_wall/near.png',  'png', [140, 140, 150]],
    ['textures/scenes/castle_wall/fx.png',    'png', [220, 220, 200]],

    ['textures/scenes/demon_castle/far.png',  'png', [ 40,  20,  50]],
    ['textures/scenes/demon_castle/mid.png',  'png', [ 70,  30,  80]],
    ['textures/scenes/demon_castle/near.png', 'png', [110,  50, 120]],
    ['textures/scenes/demon_castle/fx.png',   'png', [200, 120, 220]],

    // 4. Story illustrations
    ['textures/story/ch1_page1_ninja.png',    'png', [ 50,  80, 140]],
    ['textures/story/ch1_page2_princess.png', 'png', [220, 160, 180]],
    ['textures/story/ch1_page3_depart.png',   'png', [180, 180, 110]],

    // 5. FX textures
    ['textures/fx/leaf_particle.png',         'png', [110, 180,  70]],
    ['textures/fx/jump_dust.png',             'png', [210, 210, 200]],
    ['textures/fx/parry_spark.png',           'png', [255, 240, 130]],

    // 6. SFX WAVs
    ['audio/sfx/attack.wav', 'wav'],
    ['audio/sfx/jump.wav',   'wav'],
    ['audio/sfx/hurt.wav',   'wav'],
    ['audio/sfx/pickup.wav', 'wav'],
    ['audio/sfx/parry.wav',  'wav'],

    // 7. BGM MP3s
    ['audio/bgm/bgm_forest.mp3', 'mp3'],
    ['audio/bgm/bgm_castle.mp3', 'mp3'],
    ['audio/bgm/bgm_final.mp3',  'mp3'],
    ['audio/bgm/bgm_boss.mp3',   'mp3'],
    ['audio/bgm/bgm_story.mp3',  'mp3'],
];

// --------------------------------------------------------------------- //
// Writer                                                                //
// --------------------------------------------------------------------- //
function ensureDir(dir) {
    if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
}
function writeIfDifferent(filePath, buf) {
    if (fs.existsSync(filePath)) {
        const existing = fs.readFileSync(filePath);
        if (existing.length === buf.length && existing.equals(buf)) {
            return 'unchanged';
        }
    }
    ensureDir(path.dirname(filePath));
    fs.writeFileSync(filePath, buf);
    return 'wrote';
}

let wrote = 0;
let skipped = 0;
for (const entry of SPEC) {
    const rel = entry[0];
    const kind = entry[1];
    const abs = path.join(ROOT, rel);
    let buf;
    switch (kind) {
        case 'png': {
            const [r, g, b] = entry[2];
            buf = makeSolidPng(r, g, b, 255);
            break;
        }
        case 'wav':
            buf = makeSilentWav();
            break;
        case 'mp3':
            buf = makeSilentMp3();
            break;
        default:
            throw new Error(`unknown kind: ${kind}`);
    }
    const action = writeIfDifferent(abs, buf);
    if (action === 'wrote') { wrote++; console.log(`  + ${rel}`); }
    else { skipped++; }
}
console.log(`\nPlaceholder asset generation done: ${wrote} written, ${skipped} unchanged.`);
