/*
    copyright (c) 2025 Tino Mettler

    darktable is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    darktable is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this software.  If not, see <http://www.gnu.org/licenses/>.
*/

const container = document.querySelector('.slide-container');
const slides = {
    prev: document.getElementById('prev'),
    current: document.getElementById('current'),
    next: document.getElementById('next')
};
const prevArrow = document.getElementById('prevArrow');
const nextArrow = document.getElementById('nextArrow');

let startX = 0;
let startY = 0;
let isDragging = false; // For swipe navigation
let isZoomed = false;
let isPanning = false; // Panning in zoomed state
let hasPanned = false;
let translateX = 0;
let translateY = 0;
let currentScale = 1;
let img;

let baseWidth, baseHeight;  // Image dimensions before zoom

let scaledWidth, scaledHeight;

const images = gallery_data.images;

function updateCounter(index) {
    const counter = document.getElementById('counter');
    counter.textContent = (index + 1) + ' / ' + images.length;
}

function updateBoundaries() {
    containerRect = container.getBoundingClientRect();
    // Calculate the actual scaled dimensions based on the base (pre-zoom) size
    scaledWidth = baseWidth * currentScale;
    scaledHeight = baseHeight * currentScale;
}

function limitPanning(proposedX, proposedY) {
    const maxX = 0;
    const minX = baseWidth - scaledWidth;

    const maxY = 0;
    const minY = baseHeight - scaledHeight;

    let constrainedX = proposedX;
    let constrainedY = proposedY;

    // Apply constraints
    if (scaledWidth > baseWidth) {
        // Image wider than original - constrain panning
        constrainedX = Math.max(minX, Math.min(maxX, proposedX));
    } else {
        // Image narrower than original - center it
        constrainedX = (baseWidth - scaledWidth) / 2;
    }

    if (scaledHeight > baseHeight) {
        // Image taller than original - constrain panning
        constrainedY = Math.max(minY, Math.min(maxY, proposedY));
    } else {
        // Image shorter than original - center it
        constrainedY = (baseHeight - scaledHeight) / 2;
    }

    return {
        x: constrainedX,
        y: constrainedY
    };
}

function handleZoom(e) {
    // If we were actually panning, don't zoom
    if (hasPanned) {
        isPanning = false;
        hasPanned = false;
        return;
    }

    // If we're zoomed and haven't panned, zoom out
    if (isZoomed) {
        img.classList.add('zooming');
        translateX = 0;
        translateY = 0;
        currentScale = 1;
        img.style.transform = 'none';
        container.classList.remove('zoomed');
        isZoomed = false;
        updateNavigationState();

        setTimeout(() => {
            img.classList.remove('zooming');
        }, 200);
        return;
    }

    // Zoom in at clicked/tapped point
    if (!isZoomed) {
        const rect = img.getBoundingClientRect();
        const x = e.clientX - rect.left;
        const y = e.clientY - rect.top;

        // Store the base (pre-zoom) dimensions and offset
        baseWidth = rect.width;
        baseHeight = rect.height;

        // Calculate the scale for 1:1 pixel zoom
        currentScale = img.naturalWidth / rect.width;

        // Calculate the translation needed to keep clicked point under cursor
        // After scaling, the point at (x, y) will be at (x * currentScale, y * currentScale)
        // We want it to remain at (e.clientX - rect.left, e.clientY - rect.top)
        translateX = e.clientX - rect.left - (x * currentScale);
        translateY = e.clientY - rect.top - (y * currentScale);

        updateBoundaries();
        const limited = limitPanning(translateX, translateY);
        translateX = limited.x;
        translateY = limited.y;

        img.classList.add('zooming');
        img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentScale})`;
        container.classList.add('zoomed');

        setTimeout(() => {
            img.classList.remove('zooming');
        }, 200);
    }
    isZoomed = true;
    updateNavigationState();
}

function createImageElement(imageData) {
    if (!imageData) return null;
    const img = new Image();
    img.src = imageData.filename;
    img.width = imageData.width;
    img.height = imageData.height;
    img.addEventListener('dragstart', (e) => e.preventDefault());
    return img;
}

function loadSlides() {
    slides.prev.innerHTML = '';
    slides.current.innerHTML = '';
    slides.next.innerHTML = '';

    if (currentIndex > 0) {
        const prevImg = createImageElement(images[currentIndex - 1]);
        if (prevImg) slides.prev.appendChild(prevImg);
    }

    const currentImg = createImageElement(images[currentIndex]);
    if (currentImg) {
        img = currentImg;
        slides.current.appendChild(currentImg);
    }

    if (currentIndex < images.length - 1) {
        const nextImg = createImageElement(images[currentIndex + 1]);
        if (nextImg) slides.next.appendChild(nextImg);
    }

    updateCounter(currentIndex)
    updateNavigationState();
}

function updateNavigationState() {
    prevArrow.style.visibility = (currentIndex === 0 || isZoomed) ? 'hidden' : 'visible';
    nextArrow.style.visibility = (currentIndex === images.length - 1 || isZoomed) ? 'hidden' : 'visible';
}

async function showPreviousImage() {
    if (currentIndex > 0 && !isZoomed) {
        currentIndex--;
        container.style.transition = 'transform 0.3s ease-out';
        container.style.transform = 'translateX(0%)';
        await waitForTransition();
        container.style.transition = 'none';
        container.style.transform = 'translateX(-33.333%)';
        loadSlides();
    }
}

async function showNextImage() {
    if (currentIndex < images.length - 1 && !isZoomed) {
        currentIndex++;
        container.style.transition = 'transform 0.3s ease-out';
        container.style.transform = 'translateX(-66.666%)';
        await waitForTransition();
        container.style.transition = 'none';
        container.style.transform = 'translateX(-33.333%)';
        loadSlides();
    }
}

function handleTouchStart(e) {
    if (isZoomed) return;
    startX = e.touches[0].clientX;
    isDragging = true;
    container.style.transition = 'none';
}

function handleTouchMove(e) {
    if (!isDragging || isZoomed) return;

    const currentX = e.touches[0].clientX;
    const diff = currentX - startX;
    const baseOffset = -33.333;
    const percentMoved = (diff / window.innerWidth) * 33.333;

    container.style.transform = `translateX(${baseOffset + percentMoved}%)`;
}

async function handleTouchEnd(e) {
    if (!isDragging)
        if(isZoomed) {
            handleZoom(e);
            return;
        }
    isDragging = false;

    const endX = e.changedTouches[0].clientX;
    const diff = endX - startX;
    const threshold = window.innerWidth * 0.2;

    container.style.transition = 'transform 0.3s ease-out';

    if (diff > threshold && currentIndex > 0) {
        await showPreviousImage();
    } else if (diff < -threshold && currentIndex < images.length - 1) {
        await showNextImage();
    } else {
        container.style.transform = 'translateX(-33.333%)';
    }
}

function handleKeyDown(e) {
    if (isZoomed) return;

    if (e.code === 'Space' || e.code === 'ArrowRight') {
        e.preventDefault();
        showNextImage();
    } else if (e.code === 'Backspace' || e.code === 'ArrowLeft') {
        e.preventDefault();
        showPreviousImage();
    }
}

function waitForTransition() {
    return new Promise(resolve => {
        container.addEventListener('transitionend', resolve, { once: true });
    });
}

// Initialize
loadSlides();

// Navigation event listeners
container.addEventListener('touchstart', handleTouchStart);
container.addEventListener('touchmove', handleTouchMove);
container.addEventListener('touchend', handleTouchEnd);
container.addEventListener('click', handleZoom);

document.addEventListener('keydown', handleKeyDown);
prevArrow.addEventListener('click', showPreviousImage);
nextArrow.addEventListener('click', showNextImage);

// Mouse panning event listeners
container.addEventListener('mousedown', function(e) {
    if (isZoomed && e.target.tagName === 'IMG') {
        isPanning = true;
        hasPanned = false;
        updateBoundaries();
        startX = e.clientX - translateX;
        startY = e.clientY - translateY;
        e.preventDefault();
        container.style.cursor = 'grabbing';
    }
});

window.addEventListener('mousemove', function(e) {
    if (isPanning && isZoomed) {
        const proposedX = e.clientX - startX;
        const proposedY = e.clientY - startY;

        const limited = limitPanning(proposedX, proposedY);
        translateX = limited.x;
        translateY = limited.y;

        const img = slides.current.querySelector('img');
        img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentScale})`;
        hasPanned = true;
    }
});

window.addEventListener('mouseup', function() {
    if (isPanning) {
        isPanning = false;
        container.style.cursor = isZoomed ? 'zoom-out' : 'zoom-in';
    }
});

// Touch panning event listeners
container.addEventListener('touchstart', function(e) {
    if (isZoomed) {
        isPanning = true;
        hasPanned = false;
        updateBoundaries();
        const touch = e.touches[0];
        startX = touch.clientX - translateX;
        startY = touch.clientY - translateY;
        e.preventDefault();
    }
});

container.addEventListener('touchmove', function(e) {
    if (isPanning && isZoomed) {
        const touch = e.touches[0];
        const proposedX = touch.clientX - startX;
        const proposedY = touch.clientY - startY;

        const limited = limitPanning(proposedX, proposedY);
        translateX = limited.x;
        translateY = limited.y;

        const img = slides.current.querySelector('img');
        img.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentScale})`;
        hasPanned = true;
        e.preventDefault();
    }
});

container.addEventListener('touchend', function(e) {
    if (isPanning) {
        isPanning = false;
    }
});
