/**
 * Чистая математика освещения — без Pixi, тестируется в Vitest.
 * Lighting использует эти функции для кадров источников и лерпа ambient.
 */

import { lerpColor } from '../anim/flash';

/** Базовый радиус текстуры свечения в px — на него нормируется scale спрайта. */
export const GLOW_BASE_PX = 32;

/**
 * Источник света в экранных пикселях (позицию считает сцена через Camera.toScreen —
 * движок ничего не знает об изометрии).
 */
export interface LightDef {
    id: string;
    x: number;
    y: number;
    /** Цвет свечения 0xRRGGBB. */
    color: number;
    /** Яркость: 0..~1.5 (по умолчанию 1). */
    intensity?: number;
    /** Радиус гало в px (по умолчанию 2 * GLOW_BASE_PX). */
    radius?: number;
    /** Амплитуда мерцания 0..1 (0 — ровный свет). */
    flicker?: number;
    /** Период мерцания, сек (по умолчанию 0.9). */
    flickerPeriod?: number;
    /** Фаза мерцания — детерминизм между запусками. */
    seed?: number;
    /** Выключенный источник не рисуется (кадр с alpha 0). */
    enabled?: boolean;
}

/** Видимое состояние источника на шаге. */
export interface LightFrame {
    id: string;
    x: number;
    y: number;
    tint: number;
    alpha: number;
    scale: number;
}

/**
 * Детерминированное мерцание: сумма двух синусов с фазой от seed.
 * Одинаковые (time, seed) — одинаковый результат; диапазон [1-amount, 1].
 */
export function flickerFactor(time: number, seed: number, amount: number, period: number): number {
    if (amount <= 0) return 1;
    const p = period > 0 ? period : 0.9;
    const f = time / p + seed * Math.PI * 2;
    const wave = (Math.sin(f) + Math.sin(f * 2.37 + seed * 5)) * 0.5; // -1..1
    return 1 - amount * (wave * 0.5 + 0.5);
}

/** Кадр источника: чистая функция, без Pixi и без мутаций. */
export function lightFrame(def: LightDef, time: number, out?: LightFrame): LightFrame {
    const intensity = def.intensity ?? 1;
    const radius = def.radius ?? 2 * GLOW_BASE_PX;
    const enabled = def.enabled ?? true;
    const alpha = enabled
        ? intensity * flickerFactor(time, def.seed ?? 0, def.flicker ?? 0, def.flickerPeriod ?? 0.9)
        : 0;
    const f: LightFrame = out ?? { id: '', x: 0, y: 0, tint: 0xffffff, alpha: 0, scale: 1 };
    f.id = def.id;
    f.x = def.x;
    f.y = def.y;
    f.tint = def.color;
    f.alpha = alpha;
    f.scale = radius / GLOW_BASE_PX;
    return f;
}

/**
 * Огибающая импульса (вспышки): трапеция «рост → плато → спад».
 * attack/decay — сек; hold — плато на пике (по умолчанию 0).
 */
export interface PulseSpec {
    /** Рост 0→1, сек (0 — мгновенный пик). */
    attack: number;
    /** Плато на пике, сек (по умолчанию 0). */
    hold?: number;
    /** Спад 1→0, сек. */
    decay: number;
}

/** Полная длительность импульса, сек. */
export function pulseTotal(spec: PulseSpec): number {
    return Math.max(0, spec.attack) + Math.max(0, spec.hold ?? 0) + Math.max(0, spec.decay);
}

/** Огибающая импульса в момент t от старта: 0..1, вне [0, total] — 0. */
export function pulseEnvelope(t: number, spec: PulseSpec): number {
    const attack = Math.max(0, spec.attack);
    const hold = Math.max(0, spec.hold ?? 0);
    const decay = Math.max(0, spec.decay);
    if (t <= 0 || t >= attack + hold + decay) return 0;
    if (t < attack) return t / attack;
    if (t < attack + hold) return 1;
    return 1 - (t - attack - hold) / decay;
}

// --- тьма с прорезями: ступенчатые профили текстур света ---

/** Ступень круговой текстуры: внешнее кольцо и его краска. */
export interface RingStep {
    /** Радиус кольца в px (текстура радиусом GLOW_BASE_PX). */
    radius: number;
    /** Альфа краски кольца (кольца рисуются в порядке массива, снаружи внутрь). */
    paintAlpha: number;
}

/**
 * Ступени из накопленного профиля A (изнутри наружу, A[0] — центр):
 * радиус кольца k — GLOW_BASE_PX·k/n, краска p_k = 1 − (1−A_k)/(1−A_{k+1}) —
 * чтобы перекрытие колец (рисование снаружи внутрь, source-over) дало в
 * кольце k ровно накопленную A_k. Возвращает в порядке рисования.
 */
export function stepAlphas(profile: readonly number[]): RingStep[] {
    const n = profile.length;
    const out: RingStep[] = [];
    let nextA = 0; // накопление за внутренним краем текущего кольца
    for (let k = n; k >= 1; k--) {
        const a = Math.max(0, Math.min(1, profile[k - 1]));
        const paint = nextA >= 1 ? 0 : Math.max(0, 1 - (1 - a) / (1 - nextA));
        out.push({ radius: (GLOW_BASE_PX * k) / n, paintAlpha: paint });
        nextA = a;
    }
    return out;
}

/**
 * Профиль прорези тьмы: накопленная сила стирания тьмы изнутри наружу
 * (7 ступеней: центр — тьма снята полностью, край — едва тронута).
 */
export const HOLE_PROFILE: readonly number[] = [1, 0.95, 0.82, 0.62, 0.4, 0.2, 0.08];

/** Профиль тона: приглушённое ядро, максимум на среднем кольце (6 ступеней). */
export const TONE_PROFILE: readonly number[] = [0.35, 0.45, 0.4, 0.3, 0.18, 0.08];

/** Ступени текстуры прорези (erase-карта силы тьмы). */
export function holeStepAlphas(): RingStep[] {
    return stepAlphas(HOLE_PROFILE);
}

/** Ступени текстуры тона (аддитивная подсветка вокруг прорези). */
export function toneStepAlphas(): RingStep[] {
    return stepAlphas(TONE_PROFILE);
}

/** Шаг квантования силы прорези: меньше перерисовок RT, мерцание «ступеньками». */
export const HOLE_ALPHA_STEPS = 8;

/** Квантование альфы стирания до 1/HOLE_ALPHA_STEPS — детерминизм bake. */
export function quantizeHoleAlpha(alpha: number): number {
    const a = Math.max(0, Math.min(1, alpha));
    return Math.round(a * HOLE_ALPHA_STEPS) / HOLE_ALPHA_STEPS;
}

/** Лерп ambient-цвета по каналам: k=0 → from, k=1 → to. */
export function lerpAmbient(from: number, to: number, k: number): number {
    return lerpColor(from, to, k);
}

/** Яркость ambient-цвета: dimColor(c, 1) === c, dimColor(c, 0) === 0x000000. */
export function dimColor(color: number, brightness: number): number {
    const k = Math.max(0, Math.min(1, brightness));
    const r = Math.round(((color >> 16) & 0xff) * k);
    const g = Math.round(((color >> 8) & 0xff) * k);
    const b = Math.round((color & 0xff) * k);
    return (r << 16) | (g << 8) | b;
}

/**
 * Границы суток в часах: рассвет dawn..day (ночь → день),
 * день day..dusk, закат dusk..night (день → ночь), ночь night..dawn.
 */
export interface DayNightSpec {
    dawn: number;
    day: number;
    dusk: number;
    night: number;
}

/** Дефолтная кривая игры: рассвет 06–08, закат 18–20, ночь 20–06. */
export const DEFAULT_DAY_NIGHT: DayNightSpec = { dawn: 6, day: 8, dusk: 18, night: 20 };

/** Доля перехода день→ночь, на которой пик золотого часа. */
export const DUSK_PEAK = 0.45;

/** Дефолтный закатный тон (тёпло-золотой) — игра может переопределить. */
export const DEFAULT_DUSK_COLOR = 0xe8c090;

/**
 * Трёхопорный микс суток по фактору ночи k: день → золотой час → ночь
 * (рассвет проходит через тот же тёплый тон — это ожидаемо). Без к≥1/≤0
 * — крайние опоры; непрерывность и монотонность по k сохранены.
 */
export function dayNightAmbient(
    day: number,
    night: number,
    k: number,
    duskColor: number = DEFAULT_DUSK_COLOR
): number {
    if (k <= 0) return day;
    if (k >= 1) return night;
    if (k < DUSK_PEAK) return lerpAmbient(day, duskColor, k / DUSK_PEAK);
    return lerpAmbient(duskColor, night, (k - DUSK_PEAK) / (1 - DUSK_PEAK));
}

/**
 * Фактор ночи 0..1 по времени суток (часы, mod 24): 0 — день, 1 — ночь,
 * линейный переход на рассвете и закате. Непрерывен на стыке 24/0.
 */
export function dayNightFactor(tHours: number, spec: DayNightSpec = DEFAULT_DAY_NIGHT): number {
    const t = ((tHours % 24) + 24) % 24;
    if (t >= spec.night || t < spec.dawn) return 1;
    if (t < spec.day) return 1 - (t - spec.dawn) / (spec.day - spec.dawn);
    if (t < spec.dusk) return 0;
    return (t - spec.dusk) / (spec.night - spec.dusk);
}