/**
 * Gradient stop editor — canvas preview, draggable markers, stop list, presets.
 *
 * Extracted from color-strips.js. Self-contained module that manages
 * gradient stops state and renders into the CSS editor modal DOM.
 */

import { t } from '../core/i18n.ts';

/* ── Types ─────────────────────────────────────────────────────── */

interface GradientStop {
    position: number;
    color: number[];
    colorRight: number[] | null;
}

interface GradientDragState {
    idx: number;
    trackRect: DOMRect;
}

interface GradientPresetStop {
    position: number;
    color: number[];
    color_right?: number[];
}

interface CustomPreset {
    name: string;
    stops: GradientPresetStop[];
}

/* ── Color conversion utilities ───────────────────────────────── */

export function rgbArrayToHex(rgb: number[]): string {
    if (!Array.isArray(rgb) || rgb.length !== 3) return '#ffffff';
    return '#' + rgb.map(v => Math.max(0, Math.min(255, v)).toString(16).padStart(2, '0')).join('');
}

/** Convert a CSS hex string like "#rrggbb" to an [R, G, B] array. */
export function hexToRgbArray(hex: string): number[] {
    const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
    return m ? [parseInt(m[1], 16), parseInt(m[2], 16), parseInt(m[3], 16)] : [255, 255, 255];
}

/* ── State ────────────────────────────────────────────────────── */

/**
 * Internal state: array of stop objects.
 * Each stop: { position: float 0–1, color: [R,G,B], colorRight: [R,G,B]|null }
 */
let _gradientStops: GradientStop[] = [];
let _gradientSelectedIdx: number = -1;
let _gradientDragging: GradientDragState | null = null;
let _gradientOnChange: (() => void) | null = null;
let _idPrefix: string = '';

/** Set an ID prefix for DOM elements (e.g. 'ge-' to find 'ge-gradient-canvas'). */
export function gradientSetIdPrefix(prefix: string): void { _idPrefix = prefix; }

function _el(id: string): HTMLElement | null { return document.getElementById(_idPrefix + id); }

/** Set a callback that fires whenever stops change. */
export function gradientSetOnChange(fn: (() => void) | null): void { _gradientOnChange = fn; }

/** Read-only accessor for save/dirty-check from the parent module. */
export function getGradientStops(): GradientStop[] {
    return _gradientStops;
}

/* ── Interpolation (mirrors Python backend exactly) ───────────── */

function _gradientInterpolate(stops: GradientStop[], pos: number): number[] {
    if (!stops.length) return [128, 128, 128];
    const sorted = [...stops].sort((a, b) => a.position - b.position);

    if (pos <= sorted[0].position) return sorted[0].color.slice();

    const last = sorted[sorted.length - 1];
    if (pos >= last.position) return (last.colorRight || last.color).slice();

    for (let i = 0; i < sorted.length - 1; i++) {
        const a = sorted[i];
        const b = sorted[i + 1];
        if (a.position <= pos && pos <= b.position) {
            const span = b.position - a.position;
            const t2 = span > 0 ? (pos - a.position) / span : 0;
            const lc = a.colorRight || a.color;
            const rc = b.color;
            return lc.map((c, j) => Math.round(c + t2 * (rc[j] - c)));
        }
    }
    return [128, 128, 128];
}

/* ── Init ─────────────────────────────────────────────────────── */

export function gradientInit(stops: GradientPresetStop[]): void {
    _gradientStops = stops.map(s => ({
        position: parseFloat(String(s.position ?? 0)),
        color: (Array.isArray(s.color) && s.color.length === 3) ? [...s.color] : [255, 255, 255],
        colorRight: (Array.isArray(s.color_right) && s.color_right.length === 3) ? [...s.color_right] : null,
    }));
    _gradientSelectedIdx = _gradientStops.length > 0 ? 0 : -1;
    _gradientDragging = null;
    _gradientSetupTrackClick();
    gradientRenderAll();
}

/* ── Presets ──────────────────────────────────────────────────── */

export const GRADIENT_PRESETS = {
    rainbow: [
        { position: 0.0,  color: [255, 0, 0] },
        { position: 0.17, color: [255, 165, 0] },
        { position: 0.33, color: [255, 255, 0] },
        { position: 0.5,  color: [0, 255, 0] },
        { position: 0.67, color: [0, 100, 255] },
        { position: 0.83, color: [75, 0, 130] },
        { position: 1.0,  color: [148, 0, 211] },
    ],
    sunset: [
        { position: 0.0,  color: [255, 60, 0] },
        { position: 0.3,  color: [255, 120, 20] },
        { position: 0.6,  color: [200, 40, 80] },
        { position: 0.8,  color: [120, 20, 120] },
        { position: 1.0,  color: [40, 10, 60] },
    ],
    ocean: [
        { position: 0.0,  color: [0, 10, 40] },
        { position: 0.3,  color: [0, 60, 120] },
        { position: 0.6,  color: [0, 140, 180] },
        { position: 0.8,  color: [100, 220, 240] },
        { position: 1.0,  color: [200, 240, 255] },
    ],
    forest: [
        { position: 0.0,  color: [0, 40, 0] },
        { position: 0.3,  color: [0, 100, 20] },
        { position: 0.6,  color: [60, 180, 30] },
        { position: 0.8,  color: [140, 220, 50] },
        { position: 1.0,  color: [220, 255, 80] },
    ],
    fire: [
        { position: 0.0,  color: [0, 0, 0] },
        { position: 0.25, color: [80, 0, 0] },
        { position: 0.5,  color: [255, 40, 0] },
        { position: 0.75, color: [255, 160, 0] },
        { position: 1.0,  color: [255, 255, 60] },
    ],
    lava: [
        { position: 0.0,  color: [0, 0, 0] },
        { position: 0.3,  color: [120, 0, 0] },
        { position: 0.6,  color: [255, 60, 0] },
        { position: 0.8,  color: [255, 160, 40] },
        { position: 1.0,  color: [255, 255, 120] },
    ],
    aurora: [
        { position: 0.0,  color: [0, 20, 40] },
        { position: 0.25, color: [0, 200, 100] },
        { position: 0.5,  color: [0, 100, 200] },
        { position: 0.75, color: [120, 0, 200] },
        { position: 1.0,  color: [0, 200, 140] },
    ],
    ice: [
        { position: 0.0,  color: [255, 255, 255] },
        { position: 0.3,  color: [180, 220, 255] },
        { position: 0.6,  color: [80, 160, 255] },
        { position: 0.85, color: [20, 60, 180] },
        { position: 1.0,  color: [10, 20, 80] },
    ],
    warm: [
        { position: 0.0,  color: [255, 255, 80] },
        { position: 0.33, color: [255, 160, 0] },
        { position: 0.67, color: [255, 60, 0] },
        { position: 1.0,  color: [160, 0, 0] },
    ],
    cool: [
        { position: 0.0,  color: [0, 255, 200] },
        { position: 0.33, color: [0, 120, 255] },
        { position: 0.67, color: [60, 0, 255] },
        { position: 1.0,  color: [120, 0, 180] },
    ],
    neon: [
        { position: 0.0,  color: [255, 0, 200] },
        { position: 0.25, color: [0, 255, 255] },
        { position: 0.5,  color: [0, 255, 50] },
        { position: 0.75, color: [255, 255, 0] },
        { position: 1.0,  color: [255, 0, 100] },
    ],
    pastel: [
        { position: 0.0,  color: [255, 180, 180] },
        { position: 0.2,  color: [255, 220, 160] },
        { position: 0.4,  color: [255, 255, 180] },
        { position: 0.6,  color: [180, 255, 200] },
        { position: 0.8,  color: [180, 200, 255] },
        { position: 1.0,  color: [220, 180, 255] },
    ],
};

/**
 * Build a gradient preview from GRADIENT_PRESETS entry (array of {position, color:[r,g,b]}).
 */
export function gradientPresetStripHTML(stops: GradientPresetStop[], w: number = 80, h: number = 16): string {
    const css = stops.map(s => `rgb(${s.color.join(',')}) ${(s.position * 100).toFixed(0)}%`).join(', ');
    return `<span style="display:inline-block;width:${w}px;height:${h}px;border-radius:3px;background:linear-gradient(to right,${css});flex-shrink:0"></span>`;
}

export function applyGradientPreset(key: string): void {
    if (!key || !GRADIENT_PRESETS[key]) return;
    gradientInit(GRADIENT_PRESETS[key]);
}

/* ── Render ───────────────────────────────────────────────────── */

export function gradientRenderAll(): void {
    _gradientRenderCanvas();
    _gradientRenderMarkers();
    _gradientRenderStopList();
    if (_gradientOnChange) _gradientOnChange();
}

function _gradientRenderCanvas(): void {
    const canvas = _el('gradient-canvas') as HTMLCanvasElement | null;
    if (!canvas) return;

    // Sync canvas pixel width to its CSS display width
    const W = Math.max(1, Math.round(canvas.offsetWidth || 300));
    if (canvas.width !== W) canvas.width = W;

    const ctx = canvas.getContext('2d')!;
    const H = canvas.height;
    const imgData = ctx.createImageData(W, H);

    for (let x = 0; x < W; x++) {
        const pos = W > 1 ? x / (W - 1) : 0;
        const [r, g, b] = _gradientInterpolate(_gradientStops, pos);
        for (let y = 0; y < H; y++) {
            const idx = (y * W + x) * 4;
            imgData.data[idx]     = r;
            imgData.data[idx + 1] = g;
            imgData.data[idx + 2] = b;
            imgData.data[idx + 3] = 255;
        }
    }
    ctx.putImageData(imgData, 0, 0);
}

function _gradientRenderMarkers(): void {
    const track = _el('gradient-markers-track');
    if (!track) return;
    track.innerHTML = '';

    _gradientStops.forEach((stop, idx) => {
        const marker = document.createElement('div');
        marker.className = 'gradient-marker' + (idx === _gradientSelectedIdx ? ' selected' : '');
        marker.style.left = `${stop.position * 100}%`;
        marker.style.background = rgbArrayToHex(stop.color);
        marker.title = `${(stop.position * 100).toFixed(0)}%`;

        marker.addEventListener('mousedown', (e) => {
            e.preventDefault();
            e.stopPropagation();
            _gradientSelectedIdx = idx;
            _gradientStartDrag(e, idx);
            _gradientRenderMarkers();
            _gradientRenderStopList();
        });

        track.appendChild(marker);
    });
}

/**
 * Update the selected stop index and reflect it via CSS classes only —
 * no DOM rebuild, so in-flight click events on child elements are preserved.
 */
function _gradientSelectStop(idx: number): void {
    _gradientSelectedIdx = idx;
    document.querySelectorAll('.gradient-stop-row').forEach((r, i) => r.classList.toggle('selected', i === idx));
    document.querySelectorAll('.gradient-marker').forEach((m, i) => m.classList.toggle('selected', i === idx));
}

function _gradientRenderStopList(): void {
    const list = _el('gradient-stops-list');
    if (!list) return;
    list.innerHTML = '';

    _gradientStops.forEach((stop, idx) => {
        const row = document.createElement('div');
        row.className = 'gradient-stop-row' + (idx === _gradientSelectedIdx ? ' selected' : '');

        const hasBidir = !!stop.colorRight;
        const rightColor = stop.colorRight || stop.color;

        row.innerHTML = `
            <input type="number" class="gradient-stop-pos" value="${stop.position.toFixed(2)}"
                   min="0" max="1" step="0.01" title="${t('color_strip.gradient.position')}">
            <input type="color" class="gradient-stop-color" value="${rgbArrayToHex(stop.color)}"
                   title="Left color">
            <button type="button" class="btn btn-sm gradient-stop-bidir-btn${hasBidir ? ' active' : ''}"
                    title="${t('color_strip.gradient.bidir.hint')}">↔</button>
            <input type="color" class="gradient-stop-color-right" value="${rgbArrayToHex(rightColor)}"
                   style="display:${hasBidir ? 'inline-block' : 'none'}" title="Right color">
            <span class="gradient-stop-spacer"></span>
            <button type="button" class="btn btn-sm btn-danger gradient-stop-remove-btn"
                    title="Remove stop"${_gradientStops.length <= 2 ? ' disabled' : ''}>✕</button>
        `;

        // Select row on mousedown — CSS-only update so child click events are not interrupted
        row.addEventListener('mousedown', () => _gradientSelectStop(idx));

        // Position
        const posInput = row.querySelector('.gradient-stop-pos')!;
        posInput.addEventListener('change', (e) => {
            const target = e.target as HTMLInputElement;
            const val = Math.min(1, Math.max(0, parseFloat(target.value) || 0));
            target.value = val.toFixed(2);
            _gradientStops[idx].position = val;
            gradientRenderAll();
        });
        posInput.addEventListener('focus', () => _gradientSelectStop(idx));

        // Left color
        row.querySelector('.gradient-stop-color')!.addEventListener('input', (e) => {
            const val = (e.target as HTMLInputElement).value;
            _gradientStops[idx].color = hexToRgbArray(val);
            const markers = document.querySelectorAll('.gradient-marker');
            if (markers[idx]) (markers[idx] as HTMLElement).style.background = val;
            _gradientRenderCanvas();
        });

        // Bidirectional toggle
        row.querySelector('.gradient-stop-bidir-btn')!.addEventListener('click', (e) => {
            e.stopPropagation();
            _gradientStops[idx].colorRight = _gradientStops[idx].colorRight
                ? null
                : [..._gradientStops[idx].color];
            _gradientRenderStopList();
            _gradientRenderCanvas();
        });

        // Right color
        row.querySelector('.gradient-stop-color-right')!.addEventListener('input', (e) => {
            _gradientStops[idx].colorRight = hexToRgbArray((e.target as HTMLInputElement).value);
            _gradientRenderCanvas();
        });

        // Remove
        row.querySelector('.btn-danger')!.addEventListener('click', (e) => {
            e.stopPropagation();
            if (_gradientStops.length > 2) {
                _gradientStops.splice(idx, 1);
                if (_gradientSelectedIdx >= _gradientStops.length) {
                    _gradientSelectedIdx = _gradientStops.length - 1;
                }
                gradientRenderAll();
            }
        });

        list.appendChild(row);
    });
}

/* ── Add Stop ─────────────────────────────────────────────────── */

export function gradientAddStop(position?: number): void {
    if (position === undefined) {
        // Find the largest gap between adjacent stops and place in the middle
        const sorted = [..._gradientStops].sort((a, b) => a.position - b.position);
        let maxGap = 0, gapMid = 0.5;
        for (let i = 0; i < sorted.length - 1; i++) {
            const gap = sorted[i + 1].position - sorted[i].position;
            if (gap > maxGap) {
                maxGap = gap;
                gapMid = (sorted[i].position + sorted[i + 1].position) / 2;
            }
        }
        position = sorted.length >= 2 ? Math.round(gapMid * 100) / 100 : 0.5;
    }
    position = Math.min(1, Math.max(0, position));
    const color = _gradientInterpolate(_gradientStops, position);
    _gradientStops.push({ position, color, colorRight: null });
    _gradientSelectedIdx = _gradientStops.length - 1;
    gradientRenderAll();
}

/* ── Drag ─────────────────────────────────────────────────────── */

function _gradientStartDrag(e: MouseEvent, idx: number): void {
    const track = _el('gradient-markers-track');
    if (!track) return;
    _gradientDragging = { idx, trackRect: track.getBoundingClientRect() };

    const onMove = (me: MouseEvent): void => {
        if (!_gradientDragging) return;
        const { trackRect } = _gradientDragging;
        const pos = Math.min(1, Math.max(0, (me.clientX - trackRect.left) / trackRect.width));
        _gradientStops[_gradientDragging.idx].position = Math.round(pos * 100) / 100;
        gradientRenderAll();
    };

    const onUp = (): void => {
        _gradientDragging = null;
        document.removeEventListener('mousemove', onMove);
        document.removeEventListener('mouseup', onUp);
    };

    document.addEventListener('mousemove', onMove);
    document.addEventListener('mouseup', onUp);
}

/* ── Custom presets (localStorage) ───────────────────────────── */

const _CUSTOM_PRESETS_KEY = 'custom_gradient_presets';

/** Load custom presets from localStorage. Returns an array of { name, stops }. */
export function loadCustomGradientPresets(): CustomPreset[] {
    try {
        return JSON.parse(localStorage.getItem(_CUSTOM_PRESETS_KEY) || '[]');
    } catch {
        return [];
    }
}

/** Save the current gradient stops as a named custom preset. */
export function saveCurrentAsCustomPreset(name: string): void {
    if (!name) return;
    const stops = _gradientStops.map(s => ({
        position: s.position,
        color: [...s.color],
        ...(s.colorRight ? { color_right: [...s.colorRight] } : {}),
    }));
    const presets = loadCustomGradientPresets();
    // Replace if same name exists
    const idx = presets.findIndex(p => p.name === name);
    if (idx >= 0) presets[idx] = { name, stops };
    else presets.push({ name, stops });
    localStorage.setItem(_CUSTOM_PRESETS_KEY, JSON.stringify(presets));
}

/** Delete a custom preset by name. */
export function deleteCustomGradientPreset(name: string): void {
    const presets = loadCustomGradientPresets().filter(p => p.name !== name);
    localStorage.setItem(_CUSTOM_PRESETS_KEY, JSON.stringify(presets));
}

/* ── Track click → add stop ───────────────────────────────────── */

function _gradientSetupTrackClick(): void {
    const track = _el('gradient-markers-track');
    if (!track || (track as any)._gradientClickBound) return;
    (track as any)._gradientClickBound = true;

    track.addEventListener('click', (e) => {
        if (_gradientDragging) return;
        const rect = track.getBoundingClientRect();
        const pos = Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width));
        // Ignore clicks very close to an existing marker
        const tooClose = _gradientStops.some(s => Math.abs(s.position - pos) < 0.03);
        if (!tooClose) {
            gradientAddStop(Math.round(pos * 100) / 100);
        }
    });
}
