<div id="timeline">
    <div class="inputs">
        <input type="text" name="deb" id="deb" class="timepicker">
        <input type="text" name="fin" id="fin" class="timepicker">
    </div>
    <div class="timeline-container">
        <div class="period" style="left: 0%; width: 20%">
            <div class="period-handle left"></div>
            <div class="period-handle right"></div>
            <div class="period-time">Time</div>
        </div>
    </div>
</div>
<script>


    const timelineContainer = document.querySelector(".timeline-container");
    const periodTimeLine = document.querySelector(".period");
    const t_start = {{ t_start }};
    const t_mid = {{ t_mid }};
    const t_end = {{ t_end }};

    const tick_time = 60 / {{ tick_time }};
    const tick_delay = 1 / tick_time;

    const period_default = 2;

    let handleMoving = false;

    // Création des graduations de la timeline
    // On créé des grandes graduations pour les heures
    // On créé des petites graduations pour les "tick"
    function createTicks() {
        let i = t_start;

        while (i <= t_end) {
            // création d'un tick Heure (grand)
            const hourTick = document.createElement("div");
            hourTick.classList.add("tick", "hour");
            hourTick.style.left = `${((i - t_start) / (t_end - t_start)) * 100}%`;
            timelineContainer.appendChild(hourTick);
            // on ajoute un label pour l'heure (ex : 12:00)
            const tickLabel = document.createElement("div");
            tickLabel.classList.add("tick-label");
            tickLabel.style.left = `${((i - t_start) / (t_end - t_start)) * 100}%`;
            tickLabel.textContent = numberToTime(i);
            timelineContainer.appendChild(tickLabel);
            // Si on est pas à la fin, on ajoute les graduations intermédiaires
            if (i < t_end) {
                let j = Math.floor(i + 1);

                while (i < j) {
                    i += tick_delay;

                    if (i <= t_end) {
                        // création d'un tick (petit)
                        const quarterTick = document.createElement("div");
                        quarterTick.classList.add("tick", "quarter");
                        quarterTick.style.left = `${computePercentage(i, t_start)}%`;
                        timelineContainer.appendChild(quarterTick);
                    }

                }
                i = j;
            } else {
                i++;
            }
        }
    }
    // Convertit un nombre en heure
    // ex : 12.5 => "12:30"
    function numberToTime(num) {
        const integer = Math.floor(num);
        const decimal = Math.round((num % 1) * 60);

        let dec = `:${decimal}`;
        if (decimal < 10) {
            dec = `:0${decimal}`;
        }

        let int = `${integer}`;
        if (integer < 10) {
            int = `0${integer}`;
        }

        return int + dec;

    }
    // Arrondi un nombre au tick le plus proche
    function snapToQuarter(value) {
        return Math.round(value * tick_time) / tick_time;
    }
    // Mise à jour des valeurs des timepickers
    // En fonction des valeurs de la timeline
    function updatePeriodTimeLabel() {
        const values = getPeriodValues();
        const deb = numberToTime(values[0])
        const fin = numberToTime(values[1])
        const text = `${deb} - ${fin}`
        periodTimeLine.querySelector('.period-time').textContent = text;

        //Mise à jour des inputs
        try{
            $('#deb').val(deb);
            $('#fin').val(fin);
        }catch{}

    }

    // Gestion des évènements de la timeline
    // - Déplacement des poignées
    // - Déplacement de la période
    function timelineMainEvent(event) {

        // Position de départ de l'événement (souris ou tactile)
        const startX = (event.clientX || event.changedTouches[0].clientX);

        // Vérifie si l'événement concerne une poignée de période
        if (event.target.classList.contains("period-handle")) {
            // Initialisation des valeurs de départ
            const startWidth = parseFloat(periodTimeLine.style.width);
            const startLeft = parseFloat(periodTimeLine.style.left);
            const isLeftHandle = event.target.classList.contains("left");
            handleMoving = true;

            // Fonction de déplacement de la poignée
            const onMouseMove = (moveEvent) => {

                if (!handleMoving) return;

                // Calcul du déplacement en pixels
                const deltaX = (moveEvent.clientX || moveEvent.changedTouches[0].clientX) - startX;
                const containerWidth = timelineContainer.clientWidth;
                // Calcul de la nouvelle largeur en pourcentage
                const newWidth = startWidth + ((isLeftHandle ? -deltaX : deltaX) / containerWidth) * 100;

                if (isLeftHandle) {
                    // Si la poignée gauche est déplacée, ajuste également la position gauche
                    const newLeft = startLeft + (deltaX / containerWidth) * 100;
                    adjustPeriodPosition(newLeft, newWidth);
                } else {
                    adjustPeriodPosition(parseFloat(periodTimeLine.style.left), newWidth);
                }

                // Met à jour l'étiquette de temps de la période
                updatePeriodTimeLabel();
            };

            // Fonction de relâchement de la souris ou du tactile
            // - Alignement des poignées sur les ticks
            // - Appel des callbacks
            // - Sauvegarde des valeurs dans le local storage
            // - Réinitialisation de la variable de déplacement des poignées
            const mouseUp = () => {
                snapHandlesToQuarters();
                timelineContainer.removeEventListener("mousemove", onMouseMove);
                handleMoving = false;
                func_call();
                savePeriodInLocalStorage();
            };

            // Ajoute les écouteurs d'événement pour le déplacement et le relâchement
            timelineContainer.addEventListener("mousemove", onMouseMove);
            timelineContainer.addEventListener("touchmove", onMouseMove);
            document.addEventListener("mouseup", mouseUp, { once: true });
            document.addEventListener("touchend", mouseUp, { once: true });

        // Vérifie si l'événement concerne la période elle-même
        } else if (event.target === periodTimeLine) {

            const startLeft = parseFloat(periodTimeLine.style.left);

            // Fonction de déplacement de la période
            const onMouseMove = (moveEvent) => {
                if (handleMoving) return;
                const deltaX = (moveEvent.clientX || moveEvent.changedTouches[0].clientX) - startX;
                const containerWidth = timelineContainer.clientWidth;
                // Calcul de la nouvelle position gauche en pourcentage
                const newLeft = startLeft + (deltaX / containerWidth) * 100;

                adjustPeriodPosition(newLeft, parseFloat(periodTimeLine.style.width));
                updatePeriodTimeLabel();
            };

            // Fonction de relâchement de la souris ou du tactile
            // - Alignement des poignées sur les ticks
            // - Appel des callbacks
            // - Sauvegarde des valeurs dans le local storage
            const mouseUp = () => {
                snapHandlesToQuarters();
                timelineContainer.removeEventListener("mousemove", onMouseMove);
                func_call();
                savePeriodInLocalStorage();
            };

            // Ajoute les écouteurs d'événement pour le déplacement et le relâchement
            timelineContainer.addEventListener("mousemove", onMouseMove);
            timelineContainer.addEventListener("touchmove", onMouseMove);
            document.addEventListener("mouseup", mouseUp, { once: true });
            document.addEventListener("touchend", mouseUp, { once: true });
        }
    }


    let func_call = () => { };

    // Fonction initialisant la timeline
    // La fonction "callback" est appelée à chaque modification de la période
    function setupTimeLine(callback) {
        func_call = callback;
        timelineContainer.addEventListener("mousedown", (e) => { timelineMainEvent(e) });
        timelineContainer.addEventListener("touchstart", (e) => { timelineMainEvent(e) });

        // Initialisation des timepickers (à gauche de la timeline)
        // lors d'un changement, cela met à jour la timeline
        const updateFromInputs = ()=>{
            let deb = $('#deb').val();
            let fin = $('#fin').val();
            if (deb != '' && fin != '') {
                deb = fromTime(deb);
                fin = fromTime(fin);
                try {
                    setPeriodValues(deb, fin);
                } catch {
                    setPeriodValues(...getPeriodValues());
                }
            }
        }

        $('#deb').data('TimePicker').options.change = updateFromInputs;
        $('#fin').data('TimePicker').options.change = updateFromInputs;

        // actualise l'affichage des inputs avec les valeurs de la timeline
        updatePeriodTimeLabel();
    }
    // Ajuste la position de la période en fonction de la nouvelle position et largeur
    // Vérifie que la période ne dépasse pas les limites de la timeline
    function adjustPeriodPosition(newLeft, newWidth) {

        const snappedLeft = snapToQuarter(newLeft);
        const snappedWidth = snapToQuarter(newWidth);
        const minLeft = 0;
        const maxLeft = 100 - snappedWidth;

        const clampedLeft = Math.min(Math.max(snappedLeft, minLeft), maxLeft);

        periodTimeLine.style.left = `${clampedLeft}%`;
        periodTimeLine.style.width = `${snappedWidth}%`;
    }
    // Récupère les valeurs de la période
    function getPeriodValues() {
        // On prend les pourcentages
        const leftPercentage = parseFloat(periodTimeLine.style.left);
        const widthPercentage = parseFloat(periodTimeLine.style.width);

        // On calcule l'inverse des pourcentages pour obtenir les heures
        const startHour = (leftPercentage / 100) * (t_end - t_start) + t_start;
        const endHour = ((leftPercentage + widthPercentage) / 100) * (t_end - t_start) + t_start;
        // On les arrondit aux ticks les plus proches
        const startValue = snapToQuarter(startHour);
        const endValue = snapToQuarter(endHour);
        
        // on verifie que les valeurs sont bien dans les bornes
        const computedValues = [Math.max(startValue, t_start), Math.min(t_end, endValue)];
        
        // si les valeurs sont hors des bornes, on les ajuste
        if (computedValues[0] > t_end || computedValues[1] < t_start) {
            return [t_start, Math.min(t_end, t_start + period_default)];
        }
        // Si la période est trop petite, on l'agrandit artificiellement (il faut au moins 1 tick de largeur)
        if (computedValues[1] - computedValues[0] <= tick_delay && computedValues[1] < t_end - tick_delay) {
            computedValues[1] += tick_delay;
        }

        return computedValues;
    }
    // Met à jour les valeurs de la période
    // Met à jour l'affichage de la timeline
    // Appelle les callbacks associés
    function setPeriodValues(deb, fin) {
        if (fin < deb) {
            throw new RangeError(`le paramètre 'deb' doit être inférieur au paramètre 'fin' ([${deb};${fin}])`)
        }

        if (deb < 0 || fin < 0) {
            throw new RangeError(`Les paramètres doivent être des entiers positifis ([${deb};${fin}])`)
        }

        deb = snapToQuarter(deb);
        fin = snapToQuarter(fin);
        let leftPercentage = computePercentage(deb, t_start);
        let widthPercentage = computePercentage(fin, deb);
        periodTimeLine.style.left = `${leftPercentage}%`;
        periodTimeLine.style.width = `${widthPercentage}%`;

        snapHandlesToQuarters();
        updatePeriodTimeLabel()
        func_call();
        savePeriodInLocalStorage();
    }
    // Aligne les poignées de la période sur les ticks les plus proches
    // ex : 12h39 => 12h45 (si les ticks sont à 15min)
    // evite aussi les dépassements de la timeline (max et min)
    function snapHandlesToQuarters() {
        const periodValues = getPeriodValues();
        let lef = Math.min(computePercentage(Math.abs(periodValues[0]), t_start), computePercentage(Math.abs(t_end), tick_delay));
        if (lef < 0) {
            lef = 0;
        }
        const left = `${lef}%`;

        let wid = Math.max(computePercentage(Math.abs(periodValues[1]), Math.abs(periodValues[0])), computePercentage(tick_delay, 0));
        if (wid > 100) {
            wid = 100;
        }
        const width = `${wid}%`
        periodTimeLine.style.left = left;
        periodTimeLine.style.width = width;

        updatePeriodTimeLabel()
    }
    // Retourne le pourcentage d'une valeur par rapport à t_start et t_end
    // ex : 12h par rapport à 8h et 20h => 25%
    function computePercentage(a, b) {
        return ((a - b) / (t_end - t_start)) * 100;
    }
    // Convertit une heure (string) en nombre
    // ex : "12:30" => 12.5
    function fromTime(time, separator = ":") {
        const [hours, minutes] = time.split(separator).map((el) => Number(el))
        return hours + minutes / 60
    }
    // Renvoie les valeurs de la période sous forme de date
    // Les heures sont récupérées depuis la timeline
    // la date est récupérée depuis un champ "#date" (datepicker)
    function getPeriodAsDate(){
        let [deb, fin] = getPeriodValues();
        deb = numberToTime(deb);
        fin = numberToTime(fin);    

        const dateStr = $("#date")
        .datepicker("getDate")
        .format("yyyy-mm-dd")
        .substring(0, 10); // récupération que de la date, pas des heures

        return {
            deb: new Date(`${dateStr}T${deb}`),
            fin: new Date(`${dateStr}T${fin}`)
        }
    }
    // Sauvegarde les valeurs de la période dans le local storage
    function savePeriodInLocalStorage(){
        const dates = getPeriodValues();
        localStorage.setItem("sco-timeline-values", JSON.stringify(dates));
    }

    // Récupère les valeurs de la période depuis le local storage
    // Si elles n'existent pas, on les initialise avec les valeurs par défaut
    function loadPeriodFromLocalStorage(){
        const dates = JSON.parse(localStorage.getItem("sco-timeline-values"));
        if(dates){
            setPeriodValues(...dates);
        }else{
            setPeriodValues(t_start, t_start + period_default);
        }
    }
    // == Initialisation par défaut de la timeline ==

    createTicks(); // création des graduations

    loadPeriodFromLocalStorage(); // chargement des valeurs si disponible

    // Si on donne les heures en appelant le template alors on met à jour la timeline
    {% if heures %}
    let [heure_deb, heure_fin] = [{{ heures | safe }}]
    if (heure_deb != '' && heure_fin != '') {
        heure_deb = fromTime(heure_deb);
        heure_fin = fromTime(heure_fin);
        setPeriodValues(heure_deb, heure_fin)
    }
    {% endif %}

</script>
<style>

    #timeline {
        display: flex;
        justify-content: start;
    }

    .inputs {
        display: flex;
        flex-direction: column;
        justify-content: space-between;
        gap: 5px;
        margin-bottom: 10px;
        width: 5em;
    }

    .timeline-container {
        width: 75%;
        margin-left: 25px;
        background-color: white;
        border-radius: 15px;
        position: relative;
        height: 40px;
        margin-bottom: 25px;
    }

    /* ... */
    .tick {
        position: absolute;
        bottom: 0;
        width: 1px;
        background-color: rgba(0, 0, 0, 0.5);
    }

    .tick.hour {
        height: 100%;
    }

    .tick.quarter {
        height: 50%;
    }

    .tick-label {
        position: absolute;
        bottom: 0;
        font-size: 12px;
        text-align: center;
        transform: translateY(100%) translateX(-50%);
        user-select: none;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
    }


    .period {
        position: absolute;
        height: 100%;
        background-color: var(--color-secondary);
        border-radius: 15px;
    }

    .period-handle {
        position: absolute;
        top: 0;
        bottom: 0;
        width: 8px;
        border-radius: 0 4px 4px 0;
        cursor: col-resize;
    }

    .period-handle.right {
        right: 0;
        border-radius: 4px 0 0 4px;
    }

    .period .period-time {
        display: none;
        position: absolute;
        left: calc(50% - var(--w)/2 - 5px);
        justify-content: center;
        align-content: center;
        top: calc(-60% - 10px);
        --w: 10em;
        width: var(--w);
    }

    .period:hover .period-time {
        display: flex;

        background-color: var(--color-secondary);
        border-radius: 15px;
        padding: 5px;
    }
</style>