/**
 * Cadrage d'une image éditable (point focal + zoom par thème, ratio commun au slot).
 * Miroir exact de `src/Twig/ImageFrameExtension.php` : mêmes variables CSS, mêmes
 * bornes, même fallback (le cadrage suit l'image affichée), pour que l'aperçu live de
 * l'éditeur corresponde au rendu serveur. Consommé par `_image-frame.scss`.
 */

export interface ImageFrame {
    x?: number;
    y?: number;
    zoom?: number;
}

/** `null` = ratio du design. Même liste que `App\Validator\Constraints\ImageFrame::RATIOS`. */
export const IMAGE_RATIOS = ['1/1', '4/5', '3/4', '2/3', '3/2', '4/3', '16/9'] as const;

export const MIN_ZOOM = 1;
export const MAX_ZOOM = 3;
const DEFAULT_POSITION = 50;

const FRAME_VARS = ['--img-x', '--img-y', '--img-zoom', '--img-dx', '--img-dy', '--img-dzoom', '--img-ratio'];

export function parseFrame(raw: string | undefined): ImageFrame | null {
    if (!raw) return null;
    try {
        const parsed: unknown = JSON.parse(raw);
        return parsed !== null && typeof parsed === 'object' ? (parsed as ImageFrame) : null;
    } catch {
        return null;
    }
}

/** Sérialise un cadrage pour `data-image-frame*` : '' quand il vaut le cadrage par défaut. */
export function stringifyFrame(frame: ImageFrame | null): string {
    return frame && frameVars(frame, '').length > 0 ? JSON.stringify(frame) : '';
}

export function isKnownRatio(ratio: string | undefined): boolean {
    return (IMAGE_RATIOS as readonly string[]).includes(ratio ?? '');
}

export function frameStyleVars(
    frame: ImageFrame | null,
    darkFrame: ImageFrame | null,
    hasLight: boolean,
    hasDark: boolean,
    ratio: string | null
): Array<[string, string]> {
    const light = hasLight ? frame : (hasDark ? darkFrame : null);
    const dark = hasDark ? darkFrame : light;

    const vars = [...frameVars(light, ''), ...frameVars(dark, 'd')];
    if (ratio !== null && isKnownRatio(ratio)) {
        vars.push(['--img-ratio', ratio]);
    }
    return vars;
}

/** Applique sur le slot l'état porté par ses `data-*` (mêmes attributs que le rendu Twig). */
export function applyFrame(field: HTMLElement): void {
    const ratio = field.dataset.frameRatio !== 'false' && isKnownRatio(field.dataset.imageRatio)
        ? field.dataset.imageRatio!
        : null;
    const vars = frameStyleVars(
        parseFrame(field.dataset.imageFrame),
        parseFrame(field.dataset.imageFrameDark),
        !!field.dataset.mediaId,
        !!field.dataset.mediaIdDark,
        ratio
    );

    FRAME_VARS.forEach(name => field.style.removeProperty(name));
    vars.forEach(([name, value]) => field.style.setProperty(name, value));
    field.toggleAttribute('data-img-ratio', ratio !== null);
}

function frameVars(frame: ImageFrame | null, prefix: string): Array<[string, string]> {
    if (!frame) return [];

    const vars: Array<[string, string]> = [];
    (['x', 'y'] as const).forEach(axis => {
        const value = clamp(frame[axis], 0, 100, DEFAULT_POSITION);
        if (value !== DEFAULT_POSITION) vars.push([`--img-${prefix}${axis}`, `${format(value)}%`]);
    });
    const zoom = clamp(frame.zoom, MIN_ZOOM, MAX_ZOOM, MIN_ZOOM);
    if (zoom !== MIN_ZOOM) vars.push([`--img-${prefix}zoom`, format(zoom)]);

    return vars;
}

function clamp(value: unknown, min: number, max: number, fallback: number): number {
    return typeof value === 'number' && Number.isFinite(value) ? Math.max(min, Math.min(max, value)) : fallback;
}

function format(value: number): string {
    return String(Math.round(value * 100) / 100);
}
