/**
 * Чистая математика one-shot эффектов: сэмплы состояния (радиус/альфа/смещение)
 * в момент t. Без Pixi — тестируется в Vitest headless; адаптеры в этом каталоге
 * только применяют сэмплы к вьюхам.
 */

import { backOut, linear, quadOut, type EaseFn } from '../../core/easing';

/** Спецификация кольца/волны; repeat > 1 — телеграф зоны. */
export interface FxRingSpec {
    /** Цвет обводки (0xRRGGBB). */
    color: number;
    /** Стартовый радиус, px. */
    from: number;
    /** Конечный радиус, px. */
    to: number;
    /** Секунд на один проход. */
    duration: number;
    /** Альфа в начале/конце прохода (по умолчанию 1 → 0). */
    alphaFrom?: number;
    alphaTo?: number;
    /** Толщина обводки, px (по умолчанию 1). */
    width?: number;
    /** Сглаживание радиуса (по умолчанию linear). */
    ease?: EaseFn;
    /** Число проходов (по умолчанию 1; Infinity — зацикленный телеграф). */
    repeat?: number;
    /** Пауза между проходами, сек (по умолчанию 0). */
    gap?: number;
    /** Форма зоны: круг или «footprint»-ромб (квадрат, повёрнутый на 45°). */
    shape?: 'circle' | 'diamond';
    /** Цвет заливки зоны (телеграф); 0 — без заливки. */
    fill?: number;
    /** Альфа заливки (по умолчанию 0.25). */
    fillAlpha?: number;
}

/** Состояние кольца в момент t. */
export interface FxRingSample {
    radius: number;
    alpha: number;
}

/** Радиус и альфа кольца в момент t; repeat > 1 — проходы с паузой gap. */
export function sampleRing(t: number, spec: FxRingSpec): FxRingSample {
    const dur = Math.max(spec.duration, 1e-6);
    const gap = spec.gap ?? 0;
    const repeat = spec.repeat ?? 1;
    const cycle = dur + gap;
    const total = repeat === Infinity ? Infinity : repeat * cycle - gap;
    // Прошли все проходы — стоим в конце последнего (mod не должен завернуть границу).
    const ended = total !== Infinity && t >= total;
    const k = repeat === Infinity ? t % cycle : Math.min(t, total);
    const u = ended ? 1 : Math.min(1, Math.max(0, (k % cycle) / dur));
    const ease = spec.ease ?? linear;
    const aFrom = spec.alphaFrom ?? 1;
    const aTo = spec.alphaTo ?? 0;
    return {
        radius: spec.from + (spec.to - spec.from) * ease(u),
        alpha: aFrom + (aTo - aFrom) * u
    };
}

/** Полная длительность эффекта, сек (Infinity при repeat: Infinity). */
export function ringDuration(spec: FxRingSpec): number {
    const repeat = spec.repeat ?? 1;
    if (repeat === Infinity) return Infinity;
    return repeat * (spec.duration + (spec.gap ?? 0)) - (spec.gap ?? 0);
}

/** Спецификация всплывающего текста (урон, тосты, подборы). */
export interface FxFloatSpec {
    /** Полная длительность, сек. */
    duration: number;
    /** Плато после подъёма, сек (тосты — «держать, пока читается»). */
    hold?: number;
    /** Подъём вверх, px (по умолчанию 8; отрицательный — вниз). */
    rise?: number;
    /** Дрейф по X, px (число урона «уезжает» вбок). */
    driftX?: number;
    /** Сек нарастания альфы в начале (по умолчанию 0.06). */
    fadeIn?: number;
    /** Сек затухания в конце (по умолчанию 0.4). */
    fadeOut?: number;
    /** Ease подъёма (по умолчанию quadOut). */
    ease?: EaseFn;
    /** Пик овершута масштаба (0 — без «пружинки»). */
    pop?: number;
    /** Стартовый масштаб при pop (по умолчанию 0.6). */
    fromScale?: number;
}

/** Состояние текста в момент t (x/y — смещение от стартовой точки). */
export interface FxFloatSample {
    x: number;
    y: number;
    alpha: number;
    scale: number;
}

/** Положение, альфа и масштаб всплывающего текста в момент t. */
export function sampleFloat(t: number, spec: FxFloatSpec): FxFloatSample {
    const rise = spec.rise ?? 8;
    const fadeIn = spec.fadeIn ?? 0.06;
    const fadeOut = spec.fadeOut ?? 0.4;
    const hold = spec.hold ?? 0;
    const travel = Math.max(spec.duration - hold, 1e-6);
    const u = Math.min(1, Math.max(0, (t - hold) / travel));
    const ease = spec.ease ?? quadOut;
    const alphaIn = Math.min(1, t / Math.max(fadeIn, 1e-6));
    const alphaOut = Math.min(1, (spec.duration - t) / Math.max(fadeOut, 1e-6));
    const pop = spec.pop ?? 0;
    const fromScale = spec.fromScale ?? 0.6;
    const scale = pop > 0
        ? fromScale + (1 - fromScale) * Math.min(1, t / Math.max(pop, 1e-6))
        : 1;
    return {
        x: (spec.driftX ?? 0) * u,
        y: 0 - rise * ease(u), // 0 − (…) вместо -(…): не даём «минус ноль» на старте
        alpha: Math.max(0, Math.min(alphaIn, alphaOut)),
        scale
    };
}

/**
 * Время чтения строки (для тостов): base + perChar*len, не больше max.
 * Дефолты совпадают с историческим тостом демо: 1.4 + 0.045/символ, не дольше 6.
 */
export function readSeconds(
    text: string,
    opts?: { base?: number; perChar?: number; max?: number }
): number {
    const base = opts?.base ?? 1.4;
    const perChar = opts?.perChar ?? 0.045;
    const max = opts?.max ?? 6;
    return Math.min(max, base + text.length * perChar);
}

/** Спецификация растворения/проявления произвольной вьюхи. */
export interface FxFadeSpec {
    /** Секунд до конца. */
    duration: number;
    /** Целевая альфа (по умолчанию 0 — растворение). */
    to?: number;
    /** Задержка старта, сек. */
    delay?: number;
    /** Ease альфы (по умолчанию quadOut). */
    ease?: EaseFn;
}

/** Альфа вьюхи в момент t: from → to за duration после delay. */
export function sampleFade(t: number, from: number, spec: FxFadeSpec): number {
    const delay = spec.delay ?? 0;
    const u = Math.min(1, Math.max(0, (t - delay) / Math.max(spec.duration, 1e-6)));
    const ease = spec.ease ?? quadOut;
    const to = spec.to ?? 0;
    return from + (to - from) * ease(u);
}

/** Спецификация появления с овершутом (pop/squash). */
export interface FxPopSpec {
    /** Секунд (по умолчанию 0.25). */
    duration?: number;
    /** Стартовый множитель масштаба (по умолчанию 0.5). */
    fromScale?: number;
    /** Пик овершута (по умолчанию 1.12; 1 — без пружинки). */
    overshoot?: number;
    /** Ease масштаба (по умолчанию backOut). */
    ease?: EaseFn;
}

/** Множитель масштаба в момент t (1 в покое): from → overshoot → 1. */
export function samplePop(t: number, spec: FxPopSpec = {}): number {
    const dur = spec.duration ?? 0.25;
    const from = spec.fromScale ?? 0.5;
    const over = spec.overshoot ?? 1.12;
    const ease = spec.ease ?? backOut;
    const u = Math.min(1, Math.max(0, t / Math.max(dur, 1e-6)));
    if (u >= 1) return 1;
    // Две фазы: разгон from → overshoot (70% времени), затем плавный возврат к 1.
    const peakT = 0.7;
    if (u < peakT) {
        return from + (over - from) * ease(u / peakT);
    }
    const settle = (u - peakT) / (1 - peakT);
    return over + (1 - over) * settle;
}