/*
 * Upload navigateur → Amazon S3 SANS CORS.
 *
 * On dépose le fichier par un POST multipart en mode `no-cors`. Un POST multipart/form-data
 * est une « simple request » : pas de préflight CORS, donc le bucket peut rester sans règle
 * CORS. La réponse est opaque (statut illisible) mais le POST aboutit et S3 stocke l'objet
 * (policy signée + success_action_status=201) → on considère le succès dès que le fetch se
 * résout sans erreur réseau. C'est le mécanisme validé en navigateur sur app-hub.
 *
 * ⚠️ Ancienne approche (abandonnée) : <form> + <iframe> + success_action_redirect vers /s3/done,
 * dont on lisait l'URL pour détecter le succès. Fragile : derrière le proxy OVH, le schéma
 * généré (http) pouvait différer de la page (https) → iframe bloquée en mixed-content / lecture
 * cross-origin impossible → TOUTES les photos comptées en échec alors qu'elles étaient bien
 * déposées. Le mode no-cors n'a pas de redirection retour, donc aucune dépendance au schéma.
 */

/** Convertit un base64 (sans préfixe data:) en Blob. */
export function base64ToBlob(base64, type = 'image/jpeg') {
    const bin = atob(base64);
    const len = bin.length;
    const bytes = new Uint8Array(len);
    for (let i = 0; i < len; i++) bytes[i] = bin.charCodeAt(i);
    return new Blob([bytes], { type });
}

/** Petite pause (ms). */
function delay(ms) {
    return new Promise((r) => setTimeout(r, ms));
}

/**
 * Téléverse un fichier vers S3 par un POST multipart en mode no-cors.
 * Réessaie automatiquement en cas d'échec réseau / délai (réseau mobile instable :
 * sur un gros scan en 4G, quelques POST échouent ponctuellement → une 2e/3e tentative
 * les rattrape). La policy signée reste valable 15 min, donc on peut réutiliser `fields`.
 * @param {object} p
 * @param {string} p.url           URL du bucket (action du POST)
 * @param {object} p.fields        champs signés (policy, signature, key, …)
 * @param {File}   p.file          fichier à envoyer
 * @param {number} [p.timeoutMs]   délai max avant abandon (par tentative)
 * @param {number} [p.retries]     nombre de nouvelles tentatives après le 1er échec
 * @returns {Promise<void>} résout si le dépôt aboutit, rejette si toutes les tentatives échouent
 */
export async function s3FormUpload({ url, fields, file, timeoutMs = 60000, retries = 2 }) {
    const fd = new FormData();
    // Les champs signés AVANT le fichier (S3 exige que `file` soit le dernier champ).
    for (const [k, v] of Object.entries(fields)) fd.append(k, v);
    fd.append('file', file, (file && file.name) ? file.name : 'file');

    let lastErr = null;
    for (let attempt = 0; attempt <= retries; attempt++) {
        if (attempt > 0) await delay(700 * attempt); // backoff court et croissant
        const ctrl = new AbortController();
        const timer = setTimeout(() => ctrl.abort(), timeoutMs);
        try {
            await fetch(url, { method: 'POST', mode: 'no-cors', body: fd, signal: ctrl.signal });
            clearTimeout(timer);
            return; // succès (réponse opaque) → on s'arrête
        } catch (e) {
            clearTimeout(timer);
            lastErr = e;
        }
    }
    throw new Error(lastErr && lastErr.name === 'AbortError' ? 'Délai d’upload S3 dépassé' : 'Échec réseau du dépôt sur S3');
}

/**
 * Compresse une image (Blob/File) en JPEG base64 (sans préfixe data:), bornée à maxDim px.
 * Utilisé pour le remplacement de photo en mode S3 (côté navigateur).
 * @returns {Promise<string>}
 */
export function compressToJpegBase64(blob, maxDim = 1280, quality = 0.82) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        const url = URL.createObjectURL(blob);
        img.onload = () => {
            let { width, height } = img;
            if (width > maxDim || height > maxDim) {
                if (width >= height) {
                    height = Math.round((height * maxDim) / width);
                    width = maxDim;
                } else {
                    width = Math.round((width * maxDim) / height);
                    height = maxDim;
                }
            }
            const canvas = document.createElement('canvas');
            canvas.width = width;
            canvas.height = height;
            canvas.getContext('2d').drawImage(img, 0, 0, width, height);
            URL.revokeObjectURL(url);
            resolve(canvas.toDataURL('image/jpeg', quality).split(',')[1]);
        };
        img.onerror = () => { URL.revokeObjectURL(url); reject(new Error('Image illisible')); };
        img.src = url;
    });
}

/**
 * Demande au serveur une POST policy S3 signée pour une image.
 * Réessaie sur échec réseau ou erreur serveur transitoire (5xx) — le réseau mobile
 * peut faire échouer ponctuellement cet appel aussi. Les refus déterministes (4xx :
 * 401 session expirée, 403, 409 S3 désactivé…) ne sont PAS réessayés (inutile).
 * @returns {Promise<{url:string, fields:object, key:string}>}
 */
export async function s3Sign({ sessionId, imageId, label = '', createdAt = '', retries = 2 }) {
    let lastErr = null;
    for (let attempt = 0; attempt <= retries; attempt++) {
        if (attempt > 0) await delay(500 * attempt);
        let res;
        try {
            res = await fetch('/api/s3/sign', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ sessionId, imageId, label, createdAt }),
            });
        } catch (e) {
            lastErr = e; // échec réseau → on retente
            continue;
        }
        if (res.ok) return res.json();
        const data = await res.json().catch(() => ({}));
        const err = new Error(data.error || `signature S3 refusée (${res.status})`);
        if (res.status < 500) throw err; // refus déterministe → inutile de retenter
        lastErr = err; // 5xx → on retente
    }
    throw lastErr || new Error('signature S3 refusée');
}
