/**
 * Résolution de la date de capture, par ordre de priorité :
 *  1. champ `capturedAt` fourni par le client (si valide)
 *  2. convention de nommage {YYYYMMDD-HHmmss}_{id}.{ext} (uploads de l'app)
 *  3. EXIF (lecture du début du fichier) — fichiers déposés hors app
 *  4. mtime du fichier
 */
const FILENAME_PATTERN = /^(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})_/;
export function parseFileNameDate(name) {
    const m = name.match(FILENAME_PATTERN);
    if (!m)
        return null;
    const d = new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]), Number(m[4]), Number(m[5]), Number(m[6])));
    return Number.isNaN(d.getTime()) ? null : d;
}
/** Format EXIF "YYYY:MM:DD HH:MM:SS" (heure locale) → Date UTC. */
export function parseExifDateString(value) {
    const m = value.match(/^(\d{4}):(\d{2}):(\d{2})[ T](\d{2}):(\d{2}):(\d{2})/);
    if (!m)
        return null;
    const d = new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]), Number(m[4]), Number(m[5]), Number(m[6])));
    return Number.isNaN(d.getTime()) ? null : d;
}
/**
 * Extrait DateTimeOriginal/DateTimeDigitized/DateTime d'un buffer JPEG.
 * Recherche le marqueur APP1 "Exif\0\0", puis parse le TIFF (IFD0 → ExifIFD).
 * Retourne la valeur brute ("YYYY:MM:DD HH:MM:SS") ou null.
 */
export function parseExifDateTime(buffer) {
    if (buffer.length < 8 || buffer[0] !== 0xff || buffer[1] !== 0xd8)
        return null; // SOI
    let offset = 2;
    while (offset + 4 <= buffer.length) {
        if (buffer[offset] !== 0xff)
            return null;
        const marker = buffer[offset + 1];
        if (marker === 0xe1) {
            // APP1 — longueur inclut les 2 octets de longueur
            const len = buffer.readUInt16BE(offset + 2);
            const payloadStart = offset + 4;
            const payloadEnd = payloadStart + len - 2;
            if (payloadEnd <= buffer.length &&
                buffer.subarray(payloadStart, payloadStart + 6).toString("ascii") === "Exif\0\0") {
                return parseTiffDateTime(buffer.subarray(payloadStart + 6, payloadEnd));
            }
            offset = payloadEnd;
            continue;
        }
        if (marker === 0xda)
            return null; // SOS — plus d'APP1
        // autres marqueurs : longueur suivante
        if (offset + 4 > buffer.length)
            return null;
        const len = buffer.readUInt16BE(offset + 2);
        if (len < 2)
            return null;
        offset += 2 + len;
    }
    return null;
}
function parseTiffDateTime(tiff) {
    if (tiff.length < 8)
        return null;
    const littleEndian = tiff[0] === 0x49 && tiff[1] === 0x49; // "II"
    const bigEndian = tiff[0] === 0x4d && tiff[1] === 0x4d; // "MM"
    if (!littleEndian && !bigEndian)
        return null;
    if (readU16(tiff, 2, littleEndian) !== 42)
        return null;
    const ifd0Offset = readU32(tiff, 4, littleEndian);
    if (ifd0Offset === null)
        return null;
    // IFD0 : cherche ExifIFDPointer (0x8769)
    const exifOffset = scanIfd(tiff, ifd0Offset, littleEndian, 0x8769);
    if (exifOffset === null)
        return null;
    // ExifIFD : cherche DateTimeOriginal (0x9003), DateTimeDigitized (0x9004), DateTime (0x0132)
    for (const tag of [0x9003, 0x9004, 0x0132]) {
        const value = readAsciiIfdValue(tiff, exifOffset, littleEndian, tag);
        if (value)
            return value;
    }
    return null;
}
function readU16(buf, offset, le) {
    return le ? buf.readUInt16LE(offset) : buf.readUInt16BE(offset);
}
function readU32(buf, offset, le) {
    if (offset + 4 > buf.length)
        return null;
    return le ? buf.readUInt32LE(offset) : buf.readUInt32BE(offset);
}
/** Parcourt une IFD et retourne la valeur (offset absolu) du tag demandé. */
function scanIfd(buf, ifdOffset, le, wantedTag) {
    if (ifdOffset + 2 > buf.length)
        return null;
    const count = readU16(buf, ifdOffset, le);
    const entriesStart = ifdOffset + 2;
    for (let i = 0; i < count; i++) {
        const entryOffset = entriesStart + i * 12;
        if (entryOffset + 12 > buf.length)
            return null;
        const tag = readU16(buf, entryOffset, le);
        if (tag === wantedTag) {
            // type=4 (LONG) : valeur inline 4 octets ; sinon offset
            const type = readU16(buf, entryOffset + 2, le);
            const valueOffset = entryOffset + 8;
            if (type === 4 || type === 3) {
                return readU32(buf, valueOffset, le);
            }
            return readU32(buf, valueOffset, le); // ASCII/other : offset
        }
    }
    return null;
}
/** Lit une valeur ASCII (type 2) depuis une IFD donnée. */
function readAsciiIfdValue(buf, ifdOffset, le, wantedTag) {
    if (ifdOffset + 2 > buf.length)
        return null;
    const count = readU16(buf, ifdOffset, le);
    const entriesStart = ifdOffset + 2;
    for (let i = 0; i < count; i++) {
        const entryOffset = entriesStart + i * 12;
        if (entryOffset + 12 > buf.length)
            return null;
        const tag = readU16(buf, entryOffset, le);
        if (tag !== wantedTag)
            continue;
        const type = readU16(buf, entryOffset + 2, le);
        if (type !== 2)
            return null; // ASCII
        const valueCount = readU32(buf, entryOffset + 4, le) ?? 0;
        if (valueCount <= 4) {
            // valeur inline (4 octets max)
            return buf.subarray(entryOffset + 8, entryOffset + 8 + valueCount).toString("ascii").replace(/\0.*$/, "");
        }
        const strOffset = readU32(buf, entryOffset + 8, le);
        if (strOffset === null)
            return null;
        const end = Math.min(strOffset + valueCount, buf.length);
        return buf.subarray(strOffset, end).toString("ascii").replace(/\0.*$/, "");
    }
    return null;
}
/**
 * Résout la date de capture d'un fichier.
 * @param name nom du fichier
 * @param mtime date du serveur (mtime du fichier uploadé)
 * @param readExif callback optionnel qui lit le début du fichier (JPEG)
 */
export async function resolveCapturedAt(name, mtime, readExif) {
    const fromName = parseFileNameDate(name);
    if (fromName)
        return { date: fromName, source: "filename" };
    // Pour un upload, l'EXIF prime sur le mtime (le mtime est l'heure de
    // copie, pas de capture).
    if (readExif) {
        try {
            const head = await readExif();
            const exif = parseExifDateTime(head);
            const fromExif = exif ? parseExifDateString(exif) : null;
            if (fromExif)
                return { date: fromExif, source: "exif" };
        }
        catch {
            /* EXIF illisible — on retombe sur mtime */
        }
    }
    if (mtime && !Number.isNaN(mtime.getTime())) {
        return { date: mtime, source: "mtime" };
    }
    return { date: mtime ?? new Date(0), source: "mtime" };
}
//# sourceMappingURL=capture-date.js.map