import { THEME_CHANGE_EVENT } from '../../core/components/ThemeToggle';
import { IMAGE_RATIOS, MAX_ZOOM, MIN_ZOOM, applyFrame, parseFrame, stringifyFrame } from './imageFrame';
import type { ImageFrame } from './imageFrame';

type Variant = 'light' | 'dark';

/**
 * Panneau flottant de cadrage d'un slot `[data-editable-image]` : clic / glisser sur l'image =
 * point focal, curseur = zoom, select = ratio (commun Été / Hiver, absent si
 * `data-frame-ratio="false"`). Un seul panneau, réutilisé pour tous les slots.
 *
 * Le cadrage édité est celui de l'image affichée dans le thème actif (Hiver sans image propre
 * = image Été affichée = cadrage Été), cf `frameStyleVars()`.
 */
export class ImageFrameEditor {
    private panel: HTMLElement;
    private title: HTMLElement;
    private zoomInput: HTMLInputElement;
    private ratioRow: HTMLElement;
    private ratioSelect: HTMLSelectElement;
    private field: HTMLElement | null = null;
    private variant: Variant = 'light';
    private wasDraggable = false;
    private dragging = false;

    constructor(private readonly onChange: () => void) {
        this.panel = document.createElement('div');
        this.panel.className = 'image-frame-editor';
        this.panel.hidden = true;
        this.panel.innerHTML = `
            <p class="image-frame-editor__title"></p>
            <p class="image-frame-editor__hint">Cliquez ou glissez sur l'image pour choisir la partie visible.</p>
            <label class="image-frame-editor__row">Zoom
                <input type="range" min="${MIN_ZOOM}" max="${MAX_ZOOM}" step="0.05" data-frame-zoom>
            </label>
            <label class="image-frame-editor__row" data-frame-ratio-row>Format (commun Été / Hiver)
                <select data-frame-ratio-select>
                    <option value="">Format d'origine</option>
                    ${IMAGE_RATIOS.map(ratio => `<option value="${ratio}">${ratio.replace('/', ' / ')}</option>`).join('')}
                </select>
            </label>
            <div class="image-frame-editor__actions">
                <button type="button" class="image-frame-editor__btn image-frame-editor__btn--secondary" data-frame-reset>Réinitialiser</button>
                <button type="button" class="image-frame-editor__btn" data-frame-done>Terminer</button>
            </div>
        `;
        document.body.appendChild(this.panel);

        this.title = this.panel.querySelector('.image-frame-editor__title')!;
        this.zoomInput = this.panel.querySelector('[data-frame-zoom]')!;
        this.ratioRow = this.panel.querySelector('[data-frame-ratio-row]')!;
        this.ratioSelect = this.panel.querySelector('[data-frame-ratio-select]')!;

        this.zoomInput.addEventListener('input', () => this.updateFrame({ zoom: Number(this.zoomInput.value) }));
        this.ratioSelect.addEventListener('change', () => {
            if (!this.field) return;
            this.field.dataset.imageRatio = this.ratioSelect.value;
            this.commit();
        });
        this.panel.querySelector('[data-frame-reset]')!.addEventListener('click', () => this.reset());
        this.panel.querySelector('[data-frame-done]')!.addEventListener('click', () => this.close());

        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') this.close();
        });
        // The edited variant depends on the active theme: re-target rather than edit the wrong one.
        document.addEventListener(THEME_CHANGE_EVENT, () => this.close());
    }

    public open(field: HTMLElement): void {
        this.close();
        this.field = field;
        this.variant = ImageFrameEditor.displayedVariant(field);

        this.wasDraggable = field.draggable;
        field.draggable = false; // gallery slots: native drag & drop would swallow the focal-point drag
        field.classList.add('is-framing');
        field.addEventListener('pointerdown', this.onPointerDown);
        field.addEventListener('pointermove', this.onPointerMove);
        field.addEventListener('pointerup', this.onPointerUp);

        this.title.textContent = ImageFrameEditor.label(field);
        this.zoomInput.value = String(this.currentFrame().zoom ?? MIN_ZOOM);
        this.ratioRow.hidden = field.dataset.frameRatio === 'false';
        this.ratioSelect.value = field.dataset.imageRatio ?? '';
        this.panel.hidden = false;
    }

    public close(): void {
        if (!this.field) return;
        const field = this.field;
        field.draggable = this.wasDraggable;
        field.classList.remove('is-framing');
        field.removeEventListener('pointerdown', this.onPointerDown);
        field.removeEventListener('pointermove', this.onPointerMove);
        field.removeEventListener('pointerup', this.onPointerUp);
        this.field = null;
        this.dragging = false;
        this.panel.hidden = true;
    }

    /** Libellé du bouton « Cadrer » du tooltip, pour le thème actif. */
    public static label(field: HTMLElement): string {
        if (field.dataset.mediaIdDark === undefined) return "⤢ Cadrer l'image";

        const isDark = ImageFrameEditor.isDarkTheme();
        const displayed = ImageFrameEditor.displayedVariant(field);
        const season = displayed === 'dark' ? 'Hiver' : 'Été';
        const shownIn = (displayed === 'dark') !== isDark ? ` (affichée en ${isDark ? 'Hiver' : 'Été'})` : '';

        return `⤢ Cadrer l'image ${season}${shownIn}`;
    }

    /** Variante dont l'image est affichée dans le thème actif (même fallback que le rendu). */
    private static displayedVariant(field: HTMLElement): Variant {
        const hasLight = !!field.dataset.mediaId;
        const hasDark = !!field.dataset.mediaIdDark;
        if (ImageFrameEditor.isDarkTheme()) return hasDark ? 'dark' : 'light';
        return hasLight || !hasDark ? 'light' : 'dark';
    }

    private static isDarkTheme(): boolean {
        return document.documentElement.dataset.theme === 'dark';
    }

    private readonly onPointerDown = (e: PointerEvent): void => {
        if ((e.target as HTMLElement).closest('.editable-image__tooltip, button')) return;
        e.preventDefault();
        this.dragging = true;
        this.field?.setPointerCapture(e.pointerId);
        this.setFocalPoint(e);
    };

    private readonly onPointerMove = (e: PointerEvent): void => {
        if (this.dragging) this.setFocalPoint(e);
    };

    private readonly onPointerUp = (): void => {
        this.dragging = false;
    };

    private setFocalPoint(e: PointerEvent): void {
        if (!this.field) return;
        const rect = this.field.getBoundingClientRect();
        if (rect.width === 0 || rect.height === 0) return;
        const percent = (value: number): number => Math.round(Math.max(0, Math.min(100, value * 100)) * 100) / 100;

        this.updateFrame({
            x: percent((e.clientX - rect.left) / rect.width),
            y: percent((e.clientY - rect.top) / rect.height),
        });
    }

    private currentFrame(): ImageFrame {
        if (!this.field) return {};
        const raw = this.variant === 'dark' ? this.field.dataset.imageFrameDark : this.field.dataset.imageFrame;
        return parseFrame(raw) ?? {};
    }

    private updateFrame(changes: ImageFrame): void {
        if (!this.field) return;
        const frame = stringifyFrame({ ...this.currentFrame(), ...changes });
        if (this.variant === 'dark') {
            this.field.dataset.imageFrameDark = frame;
        } else {
            this.field.dataset.imageFrame = frame;
        }
        this.commit();
    }

    private reset(): void {
        if (!this.field) return;
        if (this.variant === 'dark') {
            this.field.dataset.imageFrameDark = '';
        } else {
            this.field.dataset.imageFrame = '';
        }
        this.zoomInput.value = String(MIN_ZOOM);
        this.commit();
    }

    private commit(): void {
        if (!this.field) return;
        applyFrame(this.field);
        this.onChange();
    }
}
