
import exifr from 'exifr';
import { formatInTimeZone } from 'date-fns-tz';

const EXIF_PARSE_OPTIONS = {
    pick: [
        'DateTimeOriginal',
        'SubSecTimeOriginal',
        'OffsetTimeOriginal',
        'DateTimeDigitized',
        'SubSecTimeDigitized',
        'OffsetTimeDigitized',
        'CreateDate',
        'SubSecTime',
        'OffsetTime',
    ],
    translateValues: false,
    reviveValues: false,
    silentErrors: true,
};

const EXIF_CAPTURE_FIELDS = [
    { value: 'DateTimeOriginal', subsec: 'SubSecTimeOriginal', offset: 'OffsetTimeOriginal' },
    { value: 'DateTimeDigitized', subsec: 'SubSecTimeDigitized', offset: 'OffsetTimeDigitized' },
    { value: 'CreateDate', subsec: 'SubSecTime', offset: 'OffsetTime' },
];

const ABSOLUTE_TIMESTAMP_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?(?:\.(\d+))?(Z|[+\-]\d{2}:\d{2})$/;
const FLOATING_TIMESTAMP_RE = /^(\d{4})[:\-](\d{2})[:\-](\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?(?:\.(\d+))?)?(?:\s*(Z|[+\-]\d{2}:\d{2}))?$/;
const MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

const cleanTimestampValue = (value) => {
    if (value === null || value === undefined) return '';
    return String(value).replace(/\0/g, '').replace(/^Taken:\s*/i, '').trim();
};

const buildExifTimestamp = (dateValue, subsecValue, offsetValue) => {
    const base = cleanTimestampValue(dateValue);
    if (!base) return null;

    let timestamp = base;
    const subsec = cleanTimestampValue(subsecValue);
    const offset = cleanTimestampValue(offsetValue);

    if (subsec && !timestamp.includes('.')) {
        timestamp += `.${subsec}`;
    }

    if (offset && !/(Z|[+\-]\d{2}:\d{2})$/i.test(timestamp)) {
        timestamp += ` ${offset}`;
    }

    return timestamp;
};

const extractCaptureTimestamp = (exifData) => {
    if (!exifData) return null;

    for (const field of EXIF_CAPTURE_FIELDS) {
        const timestamp = buildExifTimestamp(exifData[field.value], exifData[field.subsec], exifData[field.offset]);
        if (timestamp) return timestamp;
    }

    return null;
};

const isValidDateParts = (year, month, day, hour = 0, minute = 0, second = 0) => {
    if (month < 1 || month > 12 || day < 1 || day > 31) return false;
    if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59) return false;

    const probe = new Date(Date.UTC(year, month - 1, day, hour, minute, second));
    return (
        probe.getUTCFullYear() === year &&
        probe.getUTCMonth() === month - 1 &&
        probe.getUTCDate() === day &&
        probe.getUTCHours() === hour &&
        probe.getUTCMinutes() === minute &&
        probe.getUTCSeconds() === second
    );
};

const parseAbsoluteTimestamp = (value) => {
    const cleaned = cleanTimestampValue(value);
    const match = cleaned.match(ABSOLUTE_TIMESTAMP_RE);
    if (!match) return null;

    const [, y, m, d, hh, mm, ss = '00', fraction = '', timezone] = match;
    const year = Number(y);
    const month = Number(m);
    const day = Number(d);
    const hour = Number(hh);
    const minute = Number(mm);
    const second = Number(ss);
    const millisecond = Number((fraction || '').slice(0, 3).padEnd(3, '0'));

    if (!isValidDateParts(year, month, day, hour, minute, second)) return null;

    let utcMillis = Date.UTC(year, month - 1, day, hour, minute, second, millisecond);

    if (timezone !== 'Z') {
        const sign = timezone[0] === '-' ? -1 : 1;
        const [offsetHours, offsetMinutes] = timezone.slice(1).split(':').map(Number);
        const offsetMillis = sign * ((offsetHours * 60) + offsetMinutes) * 60 * 1000;
        utcMillis -= offsetMillis;
    }

    const date = new Date(utcMillis);
    return Number.isNaN(date.getTime()) ? null : date;
};

const parseFloatingTimestamp = (value) => {
    const cleaned = cleanTimestampValue(value);
    const match = cleaned.match(FLOATING_TIMESTAMP_RE);
    if (!match) return null;

    const [, y, m, d, hh, mm, ss = '00', , offset = null] = match;
    const year = Number(y);
    const month = Number(m);
    const day = Number(d);
    const hasTime = hh !== undefined && mm !== undefined;
    const hour = hasTime ? Number(hh) : 0;
    const minute = hasTime ? Number(mm) : 0;
    const second = hasTime ? Number(ss) : 0;

    if (!isValidDateParts(year, month, day, hour, minute, second)) return null;

    return { year, month, day, hour, minute, offset, hasTime };
};

const formatFloatingTimestamp = ({ year, month, day, hour, minute, offset, hasTime }) => {
    const monthLabel = MONTH_NAMES[month - 1];
    if (!monthLabel) return 'Date unknown';

    let formatted = `${monthLabel} ${day}, ${year}`;
    if (hasTime) {
        const meridiem = hour >= 12 ? 'PM' : 'AM';
        const hour12 = hour % 12 || 12;
        const minuteLabel = String(minute).padStart(2, '0');
        formatted += ` at ${hour12}:${minuteLabel} ${meridiem}`;
    }

    if (offset) {
        formatted += offset === 'Z' ? ' UTC' : ` UTC${offset}`;
    }

    return `Taken: ${formatted}`;
};

/**
 * Extracts the actual capture timestamp from a File object's EXIF metadata.
 * Returns the raw metadata value so camera-local capture time is preserved.
 *
 * @param {File|Blob} file
 * @returns {Promise<string|null>}
 */
export async function getPhotoDate(file) {
    if (!file) return null;

    try {
        const exifData = await exifr.parse(file, EXIF_PARSE_OPTIONS);
        return extractCaptureTimestamp(exifData);
    } catch (e) {
        console.warn('Failed to extract EXIF date:', e);
    }

    return null;
}

/**
 * Extracts the actual capture timestamp from a remote image URL's EXIF metadata.
 * Returns the raw metadata value so camera-local capture time is preserved.
 *
 * @param {string} url
 * @returns {Promise<string|null>}
 */
export async function getPhotoDateFromUrl(url) {
    if (!url) return null;

    try {
        const response = await fetch(url);
        if (!response.ok) return null;

        const blob = await response.blob();
        const exifData = await exifr.parse(blob, EXIF_PARSE_OPTIONS);
        return extractCaptureTimestamp(exifData);
    } catch (e) {
        console.warn('Failed to extract EXIF from URL:', url, e);
    }

    return null;
}

/**
 * Formats photo capture metadata for display without shifting floating EXIF times.
 * Legacy ISO timestamps with explicit timezone offsets are normalized to EST/EDT.
 *
 * @param {Date|string} date
 * @returns {string}
 */
export function formatPhotoTimestamp(date) {
    if (!date) return 'Date unknown';

    try {
        if (date instanceof Date) {
            if (Number.isNaN(date.getTime())) return 'Date unknown';
            return `Taken: ${formatInTimeZone(date, 'America/New_York', "MMM d, yyyy 'at' h:mm a zzz")}`;
        }

        const absoluteDate = parseAbsoluteTimestamp(date);
        if (absoluteDate) {
            return `Taken: ${formatInTimeZone(absoluteDate, 'America/New_York', "MMM d, yyyy 'at' h:mm a zzz")}`;
        }

        const floatingTimestamp = parseFloatingTimestamp(date);
        if (floatingTimestamp) {
            return formatFloatingTimestamp(floatingTimestamp);
        }

        return 'Date unknown';
    } catch (e) {
        console.error('Error formatting photo timestamp', e);
        return 'Date unknown';
    }
}
