/**
 * Temps relatif et dates absolues, pour les listes et les panneaux de détail.
 *
 * Règle de l'application : le RELATIF dans les listes (« il y a 4 min »),
 * l'ABSOLU dans le détail et dans l'infobulle (« 23/09/2026 14:02:11 »).
 *
 * Fonctions PURES : `now` se passe en argument. Le harnais Vitest du dépôt
 * tourne en environnement `node` ; une horloge lue en dur rendrait les tests
 * dépendants de l'heure à laquelle on les lance.
 *
 * Les dates sont rendues dans le fuseau du NAVIGATEUR : c'est le jour
 * calendaire de la personne qui lit qui décide de « aujourd'hui » et « hier ».
 * Le format de date affiché est jj/mm/aaaa dans les deux langues, comme
 * partout ailleurs dans le Manager.
 */

type Lang = 'fr' | 'en';

const JUST_NOW: Record<Lang, string> = {
    fr: "à l'instant",
    en: 'just now',
};

/** Sous ce seuil, un chiffre en secondes n'apprend rien : « à l'instant ». */
const JUST_NOW_SECONDS = 45;

/** Au-delà de ce nombre de jours, le relatif cède la place à la date. */
const MAX_RELATIVE_DAYS = 6;

function lang(locale: string | null | undefined): Lang {
    return typeof locale === 'string' && locale.toLowerCase().startsWith('en') ? 'en' : 'fr';
}

function parse(iso: string): Date | null {
    const date = new Date(iso);

    return Number.isNaN(date.getTime()) ? null : date;
}

function pad(value: number): string {
    return String(value).padStart(2, '0');
}

/** Minuit local du jour de `date`, en millisecondes. */
function startOfLocalDay(date: Date): number {
    return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
}

/**
 * Écart en jours CALENDAIRES (locaux) entre deux instants — positif si `date`
 * est avant `now`. Arrondi : un changement d'heure glisse d'une heure, pas
 * d'une journée.
 */
function calendarDaysBetween(date: Date, now: Date): number {
    return Math.round((startOfLocalDay(now) - startOfLocalDay(date)) / 86_400_000);
}

function capitalize(value: string): string {
    return value.charAt(0).toLocaleUpperCase() + value.slice(1);
}

/** `2026-09-23T14:02:11+01:00` → `23/09/2026` (fuseau du navigateur). */
export function formatDateFr(iso: string): string {
    const date = parse(iso);
    if (!date) return iso;

    return `${pad(date.getDate())}/${pad(date.getMonth() + 1)}/${date.getFullYear()}`;
}

/** `2026-09-23T14:02:11+01:00` → `23/09/2026 14:02:11` (fuseau du navigateur). */
export function formatDateTimeFr(iso: string): string {
    const date = parse(iso);
    if (!date) return iso;

    return `${formatDateFr(iso)} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
}

/**
 * « à l'instant », « il y a 4 min », « il y a 2 h », « hier », « il y a 3 jours »,
 * puis la date jj/mm/aaaa au-delà de six jours.
 *
 * Minutes et heures sont TRONQUÉES : à 1 h 59, il s'est écoulé « au moins
 * 1 h », pas 2. Les jours se comptent en jours calendaires : une action d'hier
 * à 23 h lue ce matin à 8 h est « hier », pas « il y a 9 h »… sauf si moins de
 * 24 h se sont écoulées, où l'heure est plus précise que le jour.
 */
export function formatRelative(iso: string, locale: string, now: Date = new Date()): string {
    const date = parse(iso);
    if (!date) return iso;

    const l = lang(locale);
    const elapsedSeconds = (now.getTime() - date.getTime()) / 1000;
    const absSeconds = Math.abs(elapsedSeconds);
    const sign = elapsedSeconds >= 0 ? -1 : 1;

    if (absSeconds < JUST_NOW_SECONDS) {
        return JUST_NOW[l];
    }

    const short = new Intl.RelativeTimeFormat(l, { numeric: 'always', style: 'short' });

    const minutes = Math.max(1, Math.floor(absSeconds / 60));
    if (minutes < 60) {
        return short.format(sign * minutes, 'minute');
    }

    const hours = Math.floor(absSeconds / 3600);
    if (hours < 24) {
        return short.format(sign * hours, 'hour');
    }

    const days = Math.abs(calendarDaysBetween(date, now));
    if (days <= MAX_RELATIVE_DAYS) {
        const long = new Intl.RelativeTimeFormat(l, { numeric: 'auto', style: 'long' });

        return long.format(sign * Math.max(1, days), 'day');
    }

    return formatDateFr(iso);
}

/**
 * En-tête d'un groupe de jour dans un flux :
 * « Aujourd'hui · mercredi 23/09/2026 », « Hier · mardi 22/09/2026 »,
 * sinon « Lundi 21/09/2026 ».
 *
 * « Aujourd'hui » / « Hier » viennent d'`Intl.RelativeTimeFormat` : ils
 * suivent la langue sans passer par un fichier de traduction.
 */
export function formatDayHeader(iso: string, locale: string, now: Date = new Date()): string {
    const date = parse(iso);
    if (!date) return iso;

    const l = lang(locale);
    const weekday = new Intl.DateTimeFormat(l, { weekday: 'long' }).format(date);
    const dayLabel = `${weekday} ${formatDateFr(iso)}`;
    const days = calendarDaysBetween(date, now);

    if (days === 0 || days === 1) {
        const relative = new Intl.RelativeTimeFormat(l, { numeric: 'auto' }).format(-days, 'day');

        return `${capitalize(relative)} · ${dayLabel}`;
    }

    return capitalize(dayLabel);
}

/** Clé de regroupement par jour calendaire local : `aaaa-mm-jj`. */
export function localDayKey(iso: string): string {
    const date = parse(iso);
    if (!date) return iso;

    return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
}
