/**
 * Процедурные генераторы воксельных моделей — основной путь генерации контента
 * (docs/plan.md, «Направление v2»). Чистые функции: (вид, сид) → VoxelModel,
 * детерминированно; никакой зависимости от мира и рендера.
 *
 * Слоты палитры — из арт-библии v1 (демка передаёт свою карту слот→hex).
 */
import { Rng } from '../math/rng';
import { VoxelGrid } from '../voxel/grid';
import { encodeModel } from './format';
import type { VoxelModel } from './format';

// Слоты арт-библии, используемые генераторами
const TRUNK = 4;      // R1 — ствол
const CROWN_DARK = 3; // G2 — крона, тень
const CROWN_LIGHT = 5; // G2H — крона, свет
const ROCK = 6;       // P4 — камень
const ROCK_LIGHT = 7; // P6 — камень, свет

export type Variant = 0 | 1 | 2;

/**
 * Дерево: вариант 0 — круглая крона, 1 — колонна, 2 — ель.
 * Ствол входит в крону (соседство 6-связности), крона пятнистая —
 * свет только сверху/снаружи, как в арт-библии.
 */
export function treeModel(variant: Variant, seed: number): VoxelModel {
    const rng = new Rng(seed ^ 0x7ee1);
    const trunkH = rng.range(3, 5);
    let sx = 7, sz = 7, cx = 3, ch: number;
    if (variant === 1) { sx = 5; sz = 5; cx = 2; ch = rng.range(3, 4); }
    else if (variant === 2) { sx = 5; sz = 5; cx = 2; ch = rng.range(4, 5); }
    else ch = rng.range(2, 3);
    const g = new VoxelGrid(sx, trunkH + ch + 1, sz);
    const mid = Math.floor(sx / 2);

    for (let y = 0; y < trunkH; y++) g.set(mid, y, mid, TRUNK);

    // Крона: предикат формы на слой dy от основания кроны (y = trunkH-1+dy)
    const crownBase = trunkH - 1;
    const r = 2 + rng.next() * 0.6; // джиттер радиуса фиксируем заранее
    for (let dy = 0; dy < ch; dy++) {
        const y = crownBase + dy;
        for (let dz = -mid; dz <= mid; dz++)
            for (let dx = -mid; dx <= mid; dx++) {
                if (!inCrown(variant, dx, dy, dz, ch, r)) continue;
                const light = dy === ch - 1 || rng.chance(0.3);
                g.set(cx + dx, y, cx + dz, light ? CROWN_LIGHT : CROWN_DARK);
            }
    }
    return encodeModel(g, {
        [TRUNK]: '#5c3b2e', [CROWN_DARK]: '#626c4c', [CROWN_LIGHT]: '#7d8662',
    });
}

/** Предикат формы кроны по варианту (dx/dz от центра, dy от основания). */
function inCrown(variant: Variant, dx: number, dy: number, dz: number, ch: number, r: number): boolean {
    const d2 = dx * dx + dz * dz;
    if (variant === 0) return d2 + dy * dy <= r * r;
    if (variant === 1) return d2 <= 1 && dy < ch; // колонна: радиус 1
    // ель: радиус слоя сужается кверху
    const layerR = Math.max(0, 2 - Math.floor(dy / 2));
    return d2 <= layerR * layerR;
}

/**
 * Валун: union 2–4 сфер-глыб (вариант = размер и число глыб), свет
 * на верхней поверхности каждого столбца. Всегда непустой.
 */
export function boulderModel(variant: Variant, seed: number): VoxelModel {
    const rng = new Rng(seed ^ 0x3b9a);
    const s = 5 + variant;
    const g = new VoxelGrid(s, s - 1, s);
    const blobs = 2 + variant;
    const spheres: Array<{ cx: number; cy: number; cz: number; r: number }> = [];
    for (let i = 0; i < blobs; i++) {
        spheres.push({
            cx: rng.range(1, s - 2), cy: rng.range(0, 1),
            cz: rng.range(1, s - 2), r: rng.range(1, 2),
        });
    }
    for (let z = 0; z < g.sz; z++)
        for (let y = 0; y < g.sy; y++)
            for (let x = 0; x < g.sx; x++) {
                const hit = spheres.some((b) =>
                    (x - b.cx) ** 2 + (y - b.cy) ** 2 + (z - b.cz) ** 2 <= b.r * b.r);
                if (hit) g.set(x, y, z, ROCK);
            }
    if (g.count() === 0) g.set(Math.floor(s / 2), 0, Math.floor(s / 2), ROCK); // страховка
    // свет — верхняя поверхность каждого столбца
    for (let z = 0; z < g.sz; z++)
        for (let x = 0; x < g.sx; x++)
            for (let y = g.sy - 1; y >= 0; y--)
                if (g.get(x, y, z) !== 0) { g.set(x, y, z, ROCK_LIGHT); break; }
    return encodeModel(g, { [ROCK]: '#5b5b66', [ROCK_LIGHT]: '#94949e' });
}
// ---- Снаряжение для креплений к персонажам (трек 32) ----

/** Палитра снаряжения: слоты называются, незаданные — дефолт (как у манекена). */
export interface GearPalette {
    /** Кожа изделия (рюкзак, обмотка). */
    leather?: string;
    /** Ремни/лямки/обугленное дерево. */
    strap?: string;
    /** Пряжка. */
    metal?: string;
    /** Древко факела. */
    wood?: string;
    /** Пламя (внешний слой). */
    flame?: string;
    /** Пламя (ядро). */
    flameCore?: string;
}

const GEAR_DEFAULTS: Required<GearPalette> = {
    leather: '#6f4a2a', strap: '#3a2c1e', metal: '#c9a04a',
    wood: '#5e4526', flame: '#e0962e', flameCore: '#f2c14e',
};

/**
 * Рюкзак: кожаный меш 6×7×2 (клапан темным ремнём, пряжка спереди),
 * угол-origine — нижний левый задний угол; на манекене ставится за торс
 * (attach pos ≈ [2, 5, -1] в связочном пространстве). Без сида — дизайн
 * фиксирован, детерминирован.
 */
export function backpackModel(p: GearPalette = {}): VoxelModel {
    const c = { ...GEAR_DEFAULTS, ...p };
    const g = new VoxelGrid(6, 7, 2);
    for (let y = 0; y <= 5; y++) for (let z = 0; z <= 1; z++) for (let x = 0; x <= 5; x++) g.set(x, y, z, 1);
    for (let x = 0; x <= 5; x++) for (let z = 0; z <= 1; z++) g.set(x, 6, z, 2); // клапан
    g.set(2, 4, 1, 3); // пряжка клапана спереди
    g.set(3, 4, 1, 3);
    return encodeModel(g, { 1: c.leather!, 2: c.strap!, 3: c.metal! });
}

/** Факел: рукоять 1×6×1 (обмотка у головки), пламя с ярким ядром; угол — низ рукояти. */
export function torchModel(p: GearPalette = {}): VoxelModel {
    const c = { ...GEAR_DEFAULTS, ...p };
    const g = new VoxelGrid(1, 6, 1);
    for (let y = 0; y <= 2; y++) g.set(0, y, 0, 4); // древко
    g.set(0, 3, 0, 2); // обмотка
    g.set(0, 4, 0, 5); // пламя
    g.set(0, 5, 0, 6); // ядро пламени
    return encodeModel(g, { 2: c.strap!, 4: c.wood!, 5: c.flame!, 6: c.flameCore! });
}
