/**
 * GAZE Gallery Enhancement System
 * Core Controller Module
 * 
 * Manages gallery views, slideshow modes, and viewer interactions.
 * Provides a unified API for all gallery features.
 */

(function() {
  'use strict';

  // ============================================================
  // Configuration & Constants
  // ============================================================
  
  const CONFIG = {
    // Layout settings
    layouts: {
      grid: { minWidth: 160, maxWidth: 210, gap: 8 },
      masonry: { columnWidth: 280, gap: 12, minColumns: 2, maxColumns: 6 },
      justified: { targetHeight: 240, minHeight: 180, maxHeight: 320, gap: 8 },
      mosaic: { columnWidth: 200, minColumns: 3, maxColumns: 8 }
    },
    
    // Slideshow settings
    slideshow: {
      intervals: [3000, 5000, 8000, 15000],
      defaultInterval: 5000,
      transitions: ['fade', 'slide', 'zoom', 'cube'],
      defaultTransition: 'fade'
    },
    
    // Viewer settings
    viewer: {
      zoomMin: 0.5,
      zoomMax: 5,
      zoomStep: 0.1,
      panSensitivity: 1,
      preloadCount: 2
    },
    
    // Animation durations (ms)
    animations: {
      transition: 300,
      kenBurns: 8000,
      ambient: 500
    },
    
    // Storage keys
    storage: {
      viewMode: 'gaze-gallery-view',
      slideshowSettings: 'gaze-gallery-slideshow',
      favorites: 'gaze-gallery-favorites'
    }
  };

  // ============================================================
  // Gallery State
  // ============================================================
  
  const state = {
    // Current view mode
    viewMode: 'grid', // grid, masonry, justified, mosaic
    
    // Slideshow state
    slideshow: {
      active: false,
      mode: null, // cinema, classic, showcase, ambient
      currentIndex: 0,
      interval: CONFIG.slideshow.defaultInterval,
      transition: CONFIG.slideshow.defaultTransition,
      timer: null,
      paused: false,
      shuffled: false,
      shuffleOrder: []
    },
    
    // Image data
    images: [],
    filteredImages: [],
    
    // Viewer state
    viewer: {
      open: false,
      currentIndex: 0,
      zoom: 1,
      panX: 0,
      panY: 0,
      isDragging: false
    },
    
    // Comparison mode
    comparison: {
      active: false,
      imageA: null,
      imageB: null,
      mode: 'slider' // slider, split, onion, difference
    },
    
    // Discovery mode
    discovery: {
      discovered: new Set(),
      favorites: new Set(),
      sessionStart: null
    },
    
    // DOM references
    dom: {
      container: null,
      controls: null
    }
  };

  // ============================================================
  // Event Emitter
  // ============================================================
  
  const events = {
    listeners: {},
    
    on(event, callback) {
      if (!this.listeners[event]) this.listeners[event] = [];
      this.listeners[event].push(callback);
      return () => this.off(event, callback);
    },
    
    off(event, callback) {
      if (!this.listeners[event]) return;
      this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
    },
    
    emit(event, data) {
      if (!this.listeners[event]) return;
      this.listeners[event].forEach(cb => cb(data));
    }
  };

  // ============================================================
  // Image Data Manager
  // ============================================================
  
  const imageManager = {
    /**
     * Initialize with gallery cards from DOM
     */
    initFromDOM(container) {
      const cards = container.querySelectorAll('.gallery-card');
      state.images = Array.from(cards).map((card, index) => ({
        index,
        src: card.dataset.src,
        path: card.dataset.path,
        category: card.dataset.category,
        slug: card.dataset.slug,
        name: card.dataset.name,
        element: card,
        loaded: false,
        aspectRatio: null
      }));
      state.filteredImages = [...state.images];
      return state.images;
    },
    
    /**
     * Get image by index
     */
    get(index) {
      return state.filteredImages[index] || null;
    },
    
    /**
     * Get total count
     */
    count() {
      return state.filteredImages.length;
    },
    
    /**
     * Preload images around current index
     */
    async preload(centerIndex, count = CONFIG.viewer.preloadCount) {
      const total = state.filteredImages.length;
      const toLoad = [];
      
      for (let i = -count; i <= count; i++) {
        let idx = (centerIndex + i + total) % total;
        const img = state.filteredImages[idx];
        if (img && !img.loaded) {
          toLoad.push(this.loadImage(img));
        }
      }
      
      await Promise.all(toLoad);
    },
    
    /**
     * Load single image and get dimensions
     */
    loadImage(imageData) {
      return new Promise((resolve) => {
        if (imageData.loaded) {
          resolve(imageData);
          return;
        }
        
        const img = new Image();
        img.onload = () => {
          imageData.loaded = true;
          imageData.aspectRatio = img.width / img.height;
          imageData.width = img.width;
          imageData.height = img.height;
          resolve(imageData);
        };
        img.onerror = () => {
          imageData.loaded = true;
          imageData.aspectRatio = 1;
          resolve(imageData);
        };
        img.src = imageData.src;
      });
    },
    
    /**
     * Filter images by criteria
     */
    filter(criteria) {
      if (!criteria || Object.keys(criteria).length === 0) {
        state.filteredImages = [...state.images];
      } else {
        state.filteredImages = state.images.filter(img => {
          if (criteria.category && img.category !== criteria.category) return false;
          if (criteria.slug && img.slug !== criteria.slug) return false;
          return true;
        });
      }
      events.emit('imagesFiltered', state.filteredImages);
      return state.filteredImages;
    },
    
    /**
     * Get random unvisited image (for discovery mode)
     */
    getRandomUnvisited() {
      const unvisited = state.filteredImages.filter(
        (_, i) => !state.discovery.discovered.has(i)
      );
      if (unvisited.length === 0) {
        // Reset discovery
        state.discovery.discovered.clear();
        return state.filteredImages[Math.floor(Math.random() * state.filteredImages.length)];
      }
      return unvisited[Math.floor(Math.random() * unvisited.length)];
    }
  };

  // ============================================================
  // Layout Engine
  // ============================================================
  
  const layoutEngine = {
    /**
     * Switch to a layout mode
     */
    setLayout(mode) {
      if (!['grid', 'masonry', 'justified', 'mosaic'].includes(mode)) {
        console.warn(`Unknown layout mode: ${mode}`);
        return;
      }
      
      state.viewMode = mode;
      localStorage.setItem(CONFIG.storage.viewMode, mode);
      
      // Remove all layout classes
      const container = state.dom.container;
      if (container) {
        container.classList.remove('layout-grid', 'layout-masonry', 'layout-justified', 'layout-mosaic');
        container.classList.add(`layout-${mode}`);
      }
      
      // Apply layout
      this.apply(mode);
      events.emit('layoutChanged', mode);
    },
    
    /**
     * Apply current layout
     */
    apply(mode = state.viewMode) {
      switch (mode) {
        case 'grid':
          this.applyGrid();
          break;
        case 'masonry':
          this.applyMasonry();
          break;
        case 'justified':
          this.applyJustified();
          break;
        case 'mosaic':
          this.applyMosaic();
          break;
      }
    },
    
    /**
     * Grid layout (enhanced default)
     */
    applyGrid() {
      const container = state.dom.container;
      if (!container) return;

      // Reset container styles set by other layouts
      container.style.height = '';
      container.style.position = '';
      container.style.gap = '';

      // Reset any custom positioning on cards and their children
      state.images.forEach(img => {
        if (img.element) {
          img.element.style.cssText = '';

          // Reset child element inline styles set by mosaic/other layouts
          const imgEl = img.element.querySelector('img');
          if (imgEl) imgEl.style.cssText = '';
          const badge = img.element.querySelector('.cat-badge');
          if (badge) badge.style.cssText = '';
          const overlay = img.element.querySelector('.overlay');
          if (overlay) overlay.style.cssText = '';
        }
      });
    },
    
    /**
     * Masonry layout
     */
    async applyMasonry() {
      const container = state.dom.container;
      if (!container) return;
      
      const config = CONFIG.layouts.masonry;
      const containerWidth = container.offsetWidth;
      const columnCount = Math.min(
        config.maxColumns,
        Math.max(config.minColumns, Math.floor(containerWidth / config.columnWidth))
      );
      const columnWidth = (containerWidth - (columnCount - 1) * config.gap) / columnCount;
      
      // Track column heights
      const columnHeights = new Array(columnCount).fill(0);
      
      // Position each image
      for (const imgData of state.filteredImages) {
        if (!imgData.element) continue;
        
        // Load image to get aspect ratio
        if (!imgData.loaded) {
          await imageManager.loadImage(imgData);
        }
        
        // Find shortest column
        const shortestColumn = columnHeights.indexOf(Math.min(...columnHeights));
        const left = shortestColumn * (columnWidth + config.gap);
        const top = columnHeights[shortestColumn];
        
        // Calculate height based on aspect ratio
        const height = columnWidth / (imgData.aspectRatio || 1);
        
        // Position element
        imgData.element.style.cssText = `
          position: absolute;
          left: ${left}px;
          top: ${top}px;
          width: ${columnWidth}px;
          height: auto;
        `;
        
        // Update column height
        columnHeights[shortestColumn] += height + config.gap;
      }
      
      // Set container height
      container.style.height = `${Math.max(...columnHeights)}px`;
      container.style.position = 'relative';
    },
    
    /**
     * Justified layout (row-based)
     */
    async applyJustified() {
      const container = state.dom.container;
      if (!container) return;
      
      const config = CONFIG.layouts.justified;
      const containerWidth = container.offsetWidth;
      
      // Load all images first to get aspect ratios
      await Promise.all(state.filteredImages.map(img => imageManager.loadImage(img)));
      
      // Build rows using linear partition
      const rows = [];
      let currentRow = [];
      let currentRowWidth = 0;
      
      for (const imgData of state.filteredImages) {
        const scaledWidth = config.targetHeight * (imgData.aspectRatio || 1);
        
        if (currentRowWidth + scaledWidth > containerWidth && currentRow.length > 0) {
          rows.push(this.justifyRow(currentRow, containerWidth, config));
          currentRow = [];
          currentRowWidth = 0;
        }
        
        currentRow.push(imgData);
        currentRowWidth += scaledWidth + config.gap;
      }
      
      // Handle last row
      if (currentRow.length > 0) {
        rows.push(this.justifyRow(currentRow, containerWidth, config, true));
      }
      
      // Position images
      let top = 0;
      for (const row of rows) {
        let left = 0;
        for (const item of row.items) {
          item.imgData.element.style.cssText = `
            position: absolute;
            left: ${left}px;
            top: ${top}px;
            width: ${item.width}px;
            height: ${row.height}px;
          `;
          item.imgData.element.querySelector('img').style.cssText = `
            width: 100%;
            height: 100%;
            object-fit: cover;
          `;
          left += item.width + config.gap;
        }
        top += row.height + config.gap;
      }
      
      container.style.height = `${top}px`;
      container.style.position = 'relative';
    },
    
    /**
     * Calculate justified row dimensions
     */
    justifyRow(images, containerWidth, config, isLast = false) {
      const gap = config.gap;
      const totalGap = (images.length - 1) * gap;
      const availableWidth = containerWidth - totalGap;
      
      // Calculate row aspect ratio
      const rowAspectRatio = images.reduce((sum, img) => sum + (img.aspectRatio || 1), 0);
      let rowHeight = availableWidth / rowAspectRatio;
      
      // Clamp height
      rowHeight = Math.min(config.maxHeight, Math.max(config.minHeight, rowHeight));
      
      // For last row, don't stretch if too few images
      if (isLast && images.length < 3) {
        rowHeight = config.targetHeight;
      }
      
      return {
        height: rowHeight,
        items: images.map(imgData => ({
          imgData,
          width: rowHeight * (imgData.aspectRatio || 1)
        }))
      };
    },
    
    /**
     * Mosaic layout - seamless grid with no gaps or rounded corners
     */
    async applyMosaic() {
      const container = state.dom.container;
      if (!container) return;
      
      const config = CONFIG.layouts.mosaic;
      const containerWidth = container.offsetWidth;
      
      // Calculate column count based on container width
      const columnCount = Math.min(
        config.maxColumns,
        Math.max(config.minColumns, Math.floor(containerWidth / config.columnWidth))
      );
      
      const itemWidth = containerWidth / columnCount;
      const itemHeight = itemWidth; // Square tiles (1:1 aspect ratio)
      
      // Position each image in a strict grid
      state.filteredImages.forEach((imgData, index) => {
        if (!imgData.element) return;
        
        const col = index % columnCount;
        const row = Math.floor(index / columnCount);
        
        imgData.element.style.cssText = `
          position: absolute;
          left: ${col * itemWidth}px;
          top: ${row * itemHeight}px;
          width: ${itemWidth}px;
          height: ${itemHeight}px;
          margin: 0;
          padding: 0;
          border-radius: 0;
        `;
        
        // Ensure images fill completely with no gaps
        const imgEl = imgData.element.querySelector('img');
        if (imgEl) {
          imgEl.style.cssText = `
            width: 100%;
            height: 100%;
            object-fit: cover;
            border-radius: 0;
            display: block;
          `;
        }
        
        // Hide badges and overlays for seamless look
        const badge = imgData.element.querySelector('.cat-badge');
        if (badge) badge.style.display = 'none';
        const overlay = imgData.element.querySelector('.overlay');
        if (overlay) overlay.style.opacity = '0';
      });
      
      // Calculate total height
      const rowCount = Math.ceil(state.filteredImages.length / columnCount);
      container.style.height = `${rowCount * itemHeight}px`;
      container.style.position = 'relative';
      container.style.gap = '0';
    }
  };

  // ============================================================
  // Enhanced Viewer (Lightbox)
  // ============================================================
  
  const viewer = {
    /**
     * Open viewer at specific index
     */
    open(index) {
      state.viewer.currentIndex = index;
      state.viewer.open = true;
      state.viewer.zoom = 1;
      state.viewer.panX = 0;
      state.viewer.panY = 0;
      
      this.render();
      this.show();
      imageManager.preload(index);
      
      events.emit('viewerOpened', { index, image: imageManager.get(index) });
    },
    
    /**
     * Close viewer
     */
    close() {
      state.viewer.open = false;
      this.hide();
      events.emit('viewerClosed');
    },
    
    /**
     * Navigate to next/previous
     */
    navigate(direction) {
      const total = imageManager.count();
      if (total === 0) return;
      
      let newIndex = state.viewer.currentIndex + direction;
      if (newIndex < 0) newIndex = total - 1;
      if (newIndex >= total) newIndex = 0;
      
      state.viewer.currentIndex = newIndex;
      state.viewer.zoom = 1;
      state.viewer.panX = 0;
      state.viewer.panY = 0;
      
      this.render();
      imageManager.preload(newIndex);
      
      events.emit('viewerNavigated', { index: newIndex, image: imageManager.get(newIndex) });
    },
    
    /**
     * Zoom to level
     */
    setZoom(level, centerX, centerY) {
      const oldZoom = state.viewer.zoom;
      state.viewer.zoom = Math.min(CONFIG.viewer.zoomMax, Math.max(CONFIG.viewer.zoomMin, level));
      
      // Adjust pan to zoom toward center point
      if (centerX !== undefined && centerY !== undefined) {
        const scale = state.viewer.zoom / oldZoom;
        state.viewer.panX = centerX - (centerX - state.viewer.panX) * scale;
        state.viewer.panY = centerY - (centerY - state.viewer.panY) * scale;
      }
      
      this.applyTransform();
      events.emit('viewerZoomed', state.viewer.zoom);
    },
    
    /**
     * Handle zoom wheel
     */
    handleWheel(e) {
      e.preventDefault();
      const delta = e.deltaY > 0 ? -CONFIG.viewer.zoomStep : CONFIG.viewer.zoomStep;
      const rect = e.currentTarget.getBoundingClientRect();
      const centerX = e.clientX - rect.left - rect.width / 2;
      const centerY = e.clientY - rect.top - rect.height / 2;
      this.setZoom(state.viewer.zoom + delta, centerX, centerY);
    },
    
    /**
     * Pan the image
     */
    pan(dx, dy) {
      if (state.viewer.zoom <= 1) return;
      
      state.viewer.panX += dx * CONFIG.viewer.panSensitivity;
      state.viewer.panY += dy * CONFIG.viewer.panSensitivity;
      
      this.applyTransform();
    },
    
    /**
     * Reset zoom and pan
     */
    resetTransform() {
      state.viewer.zoom = 1;
      state.viewer.panX = 0;
      state.viewer.panY = 0;
      this.applyTransform();
    },
    
    /**
     * Apply current transform to image
     */
    applyTransform() {
      const img = document.getElementById('gaze-viewer-image');
      if (!img) return;
      
      img.style.transform = `translate(${state.viewer.panX}px, ${state.viewer.panY}px) scale(${state.viewer.zoom})`;
    },
    
    /**
     * Create/update viewer DOM
     */
    render() {
      let modal = document.getElementById('gaze-viewer');
      if (!modal) {
        modal = this.createDOM();
      }
      
      const imgData = imageManager.get(state.viewer.currentIndex);
      if (!imgData) return;
      
      // Update image
      const img = document.getElementById('gaze-viewer-image');
      img.src = imgData.src;
      img.alt = imgData.name || 'Gallery image';
      
      // Update counter
      const counter = document.getElementById('gaze-viewer-counter');
      counter.textContent = `${state.viewer.currentIndex + 1} / ${imageManager.count()}`;
      
      // Update info
      const info = document.getElementById('gaze-viewer-info-content');
      info.innerHTML = `
        <div class="viewer-info-title">${imgData.name || 'Unknown'}</div>
        <div class="viewer-info-meta">
          <span class="viewer-info-category">${imgData.category || ''}</span>
          ${imgData.slug ? `<span class="viewer-info-slug">${imgData.slug}</span>` : ''}
        </div>
      `;

      // Update Open Button Link
      const openBtn = document.getElementById('gaze-viewer-open-btn');
      if (openBtn) {
        if (imgData.category === 'characters') {
          openBtn.onclick = () => window.location.href = '/character/' + imgData.slug;
          openBtn.textContent = 'Open';
        } else if (imgData.category === 'checkpoints') {
          openBtn.onclick = () => window.location.href = '/checkpoint/' + imgData.slug;
          openBtn.textContent = 'Open';
        } else {
          openBtn.onclick = () => window.location.href = '/generator?' + imgData.category.replace(/s$/, '') + '=' + encodeURIComponent(imgData.slug);
          openBtn.textContent = 'Generator';
        }
      }
      
      // Reset transform
      this.applyTransform();
    },
    
    /**
     * Create viewer DOM structure
     */
    createDOM() {
      const modal = document.createElement('div');
      modal.id = 'gaze-viewer';
      modal.className = 'gaze-viewer';
      modal.innerHTML = `
        <div class="gaze-viewer-backdrop"></div>
        <div class="gaze-viewer-content">
          <!-- Main image area -->
          <div class="gaze-viewer-stage">
            <img id="gaze-viewer-image" src="" alt="" class="gaze-viewer-img">
          </div>
          
          <!-- Navigation arrows -->
          <button class="gaze-viewer-nav gaze-viewer-prev" aria-label="Previous">
            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <polyline points="15 18 9 12 15 6"></polyline>
            </svg>
          </button>
          <button class="gaze-viewer-nav gaze-viewer-next" aria-label="Next">
            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
              <polyline points="9 18 15 12 9 6"></polyline>
            </svg>
          </button>
          
          <!-- Top toolbar -->
          <div class="gaze-viewer-toolbar">
            <div class="gaze-viewer-toolbar-left">
              <span id="gaze-viewer-counter" class="gaze-viewer-counter">1 / 1</span>
            </div>
            <div class="gaze-viewer-toolbar-center">
              <button class="gaze-viewer-btn" id="gaze-viewer-zoom-out" aria-label="Zoom out">
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                  <circle cx="11" cy="11" r="8"></circle>
                  <line x1="8" y1="11" x2="14" y2="11"></line>
                </svg>
              </button>
              <span id="gaze-viewer-zoom-level" class="gaze-viewer-zoom-level">100%</span>
              <button class="gaze-viewer-btn" id="gaze-viewer-zoom-in" aria-label="Zoom in">
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                  <circle cx="11" cy="11" r="8"></circle>
                  <line x1="11" y1="8" x2="11" y2="14"></line>
                  <line x1="8" y1="11" x2="14" y2="11"></line>
                </svg>
              </button>
              <button class="gaze-viewer-btn" id="gaze-viewer-reset" aria-label="Reset zoom">
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                  <path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
                </svg>
              </button>
            </div>
            <div class="gaze-viewer-toolbar-right">
              <button class="gaze-viewer-btn" id="gaze-viewer-info-toggle" aria-label="Toggle info">
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                  <circle cx="12" cy="12" r="10"></circle>
                  <line x1="12" y1="16" x2="12" y2="12"></line>
                  <line x1="12" y1="8" x2="12.01" y2="8"></line>
                </svg>
              </button>
              <button class="gaze-viewer-btn" id="gaze-viewer-slideshow" aria-label="Start slideshow">
                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                  <polygon points="5 3 19 12 5 21 5 3"></polygon>
                </svg>
              </button>
              <button class="gaze-viewer-btn gaze-viewer-close" id="gaze-viewer-close" aria-label="Close">
                <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                  <line x1="18" y1="6" x2="6" y2="18"></line>
                  <line x1="6" y1="6" x2="18" y2="18"></line>
                </svg>
              </button>
            </div>
          </div>
          
          <!-- Info panel -->
          <div class="gaze-viewer-info" id="gaze-viewer-info">
            <div id="gaze-viewer-info-content"></div>
            <div class="gaze-viewer-info-actions">
              <button class="btn btn-sm btn-primary" id="gaze-viewer-prompt-btn">View Prompt</button>
              <button class="btn btn-sm btn-outline-light" id="gaze-viewer-open-btn">Open</button>
              <button class="btn btn-sm btn-outline-danger" id="gaze-viewer-delete-btn">Delete</button>
            </div>
          </div>
          
          <!-- Thumbnail strip -->
          <div class="gaze-viewer-thumbs" id="gaze-viewer-thumbs">
            <div class="gaze-viewer-thumbs-track" id="gaze-viewer-thumbs-track"></div>
          </div>
        </div>
      `;
      
      document.body.appendChild(modal);
      this.attachEventListeners(modal);
      
      return modal;
    },
    
    /**
     * Attach event listeners to viewer
     */
    attachEventListeners(modal) {
      // Close button
      modal.querySelector('.gaze-viewer-close').addEventListener('click', () => this.close());
      
      // Backdrop click
      modal.querySelector('.gaze-viewer-backdrop').addEventListener('click', () => this.close());
      
      // Navigation
      modal.querySelector('.gaze-viewer-prev').addEventListener('click', () => this.navigate(-1));
      modal.querySelector('.gaze-viewer-next').addEventListener('click', () => this.navigate(1));
      
      // Zoom controls
      modal.querySelector('#gaze-viewer-zoom-in').addEventListener('click', () => {
        this.setZoom(state.viewer.zoom + 0.25);
      });
      modal.querySelector('#gaze-viewer-zoom-out').addEventListener('click', () => {
        this.setZoom(state.viewer.zoom - 0.25);
      });
      modal.querySelector('#gaze-viewer-reset').addEventListener('click', () => {
        this.resetTransform();
      });
      
      // Info toggle
      modal.querySelector('#gaze-viewer-info-toggle').addEventListener('click', () => {
        modal.querySelector('.gaze-viewer-info').classList.toggle('open');
      });
      
      // Image wheel zoom
      const img = modal.querySelector('.gaze-viewer-img');
      img.addEventListener('wheel', (e) => this.handleWheel(e), { passive: false });
      
      // Image drag to pan
      let isDragging = false;
      let lastX, lastY;
      
      img.addEventListener('mousedown', (e) => {
        if (state.viewer.zoom > 1) {
          isDragging = true;
          lastX = e.clientX;
          lastY = e.clientY;
          img.style.cursor = 'grabbing';
        }
      });
      
      document.addEventListener('mousemove', (e) => {
        if (isDragging) {
          const dx = e.clientX - lastX;
          const dy = e.clientY - lastY;
          this.pan(dx, dy);
          lastX = e.clientX;
          lastY = e.clientY;
        }
      });
      
      document.addEventListener('mouseup', () => {
        isDragging = false;
        img.style.cursor = state.viewer.zoom > 1 ? 'grab' : 'zoom-in';
      });
      
      // Double-click to toggle zoom
      img.addEventListener('dblclick', () => {
        if (state.viewer.zoom > 1) {
          this.resetTransform();
        } else {
          this.setZoom(2);
        }
      });
      
      // Slideshow button
      modal.querySelector('#gaze-viewer-slideshow').addEventListener('click', () => {
        slideshowController.start('classic');
      });
      
      // Prompt button
      modal.querySelector('#gaze-viewer-prompt-btn').addEventListener('click', () => {
        const imgData = imageManager.get(state.viewer.currentIndex);
        if (imgData && window.showPrompt) {
          window.showPrompt(imgData.path, imgData.name, imgData.category, imgData.slug);
        }
      });
      
      // Delete button
      modal.querySelector('#gaze-viewer-delete-btn').addEventListener('click', () => {
        const imgData = imageManager.get(state.viewer.currentIndex);
        if (imgData && window.openDeleteModal) {
          window.openDeleteModal(imgData.path, imgData.name);
          viewer.close();
        }
      });
    },
    
    /**
     * Show the viewer
     */
    show() {
      const modal = document.getElementById('gaze-viewer');
      if (modal) {
        modal.classList.add('open');
        document.body.style.overflow = 'hidden';
      }
    },
    
    /**
     * Hide the viewer
     */
    hide() {
      const modal = document.getElementById('gaze-viewer');
      if (modal) {
        modal.classList.remove('open');
        document.body.style.overflow = '';
      }
    },
    
    /**
     * Build thumbnail strip
     */
    buildThumbnails() {
      const track = document.getElementById('gaze-viewer-thumbs-track');
      if (!track) return;
      
      track.innerHTML = state.filteredImages.map((img, i) => `
        <div class="gaze-viewer-thumb ${i === state.viewer.currentIndex ? 'active' : ''}" 
             data-index="${i}">
          <img src="${img.src}" alt="" loading="lazy">
        </div>
      `).join('');
      
      // Attach click handlers
      track.querySelectorAll('.gaze-viewer-thumb').forEach(thumb => {
        thumb.addEventListener('click', () => {
          const index = parseInt(thumb.dataset.index);
          state.viewer.currentIndex = index;
          this.render();
          this.updateThumbnailActive();
        });
      });
    },
    
    /**
     * Update active thumbnail
     */
    updateThumbnailActive() {
      const track = document.getElementById('gaze-viewer-thumbs-track');
      if (!track) return;
      
      track.querySelectorAll('.gaze-viewer-thumb').forEach((thumb, i) => {
        thumb.classList.toggle('active', i === state.viewer.currentIndex);
      });
      
      // Scroll active into view
      const active = track.querySelector('.gaze-viewer-thumb.active');
      if (active) {
        active.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
      }
    }
  };

  // ============================================================
  // Slideshow Controller
  // ============================================================
  
  const slideshowController = {
    /**
     * Start slideshow in specified mode
     */
    start(mode = 'classic') {
      state.slideshow.active = true;
      state.slideshow.mode = mode;
      state.slideshow.paused = false;
      
      // If viewer isn't open, open it
      if (!state.viewer.open) {
        viewer.open(0);
      }
      
      // Setup based on mode
      switch (mode) {
        case 'cinema':
          this.setupCinemaMode();
          break;
        case 'classic':
          this.setupClassicMode();
          break;
        case 'showcase':
          this.setupShowcaseMode();
          break;
        case 'ambient':
          this.setupAmbientMode();
          break;
      }
      
      // Start timer
      this.startTimer();
      
      events.emit('slideshowStarted', { mode });
    },
    
    /**
     * Stop slideshow
     */
    stop() {
      state.slideshow.active = false;
      state.slideshow.mode = null;
      this.stopTimer();
      this.cleanupModes();
      events.emit('slideshowStopped');
    },
    
    /**
     * Pause/resume
     */
    toggle() {
      if (!state.slideshow.active) return;
      
      state.slideshow.paused = !state.slideshow.paused;
      
      if (state.slideshow.paused) {
        this.stopTimer();
      } else {
        this.startTimer();
      }
      
      events.emit('slideshowToggled', { paused: state.slideshow.paused });
    },
    
    /**
     * Go to next slide
     */
    next() {
      if (state.slideshow.shuffled) {
        // Use shuffle order
        const currentOrderIndex = state.slideshow.shuffleOrder.indexOf(state.viewer.currentIndex);
        const nextOrderIndex = (currentOrderIndex + 1) % state.slideshow.shuffleOrder.length;
        state.viewer.currentIndex = state.slideshow.shuffleOrder[nextOrderIndex];
        viewer.render();
      } else {
        viewer.navigate(1);
      }
      
      // Apply transition effect
      this.applyTransition();
    },
    
    /**
     * Toggle shuffle
     */
    toggleShuffle() {
      state.slideshow.shuffled = !state.slideshow.shuffled;
      
      if (state.slideshow.shuffled) {
        // Generate shuffle order
        state.slideshow.shuffleOrder = [...Array(imageManager.count()).keys()]
          .sort(() => Math.random() - 0.5);
      }
      
      events.emit('slideshowShuffleToggled', { shuffled: state.slideshow.shuffled });
    },
    
    /**
     * Set interval
     */
    setInterval(ms) {
      state.slideshow.interval = ms;
      if (state.slideshow.active && !state.slideshow.paused) {
        this.stopTimer();
        this.startTimer();
      }
    },
    
    /**
     * Set transition type
     */
    setTransition(type) {
      state.slideshow.transition = type;
    },
    
    /**
     * Start auto-advance timer
     */
    startTimer() {
      this.stopTimer();
      state.slideshow.timer = setInterval(() => this.next(), state.slideshow.interval);
    },
    
    /**
     * Stop timer
     */
    stopTimer() {
      if (state.slideshow.timer) {
        clearInterval(state.slideshow.timer);
        state.slideshow.timer = null;
      }
    },
    
    /**
     * Apply current transition effect
     */
    applyTransition() {
      const img = document.getElementById('gaze-viewer-image');
      if (!img) return;
      
      const transition = state.slideshow.transition;
      img.classList.remove('transition-fade', 'transition-slide', 'transition-zoom', 'transition-cube');
      
      // Trigger reflow
      void img.offsetWidth;
      
      img.classList.add(`transition-${transition}`);
    },
    
    /**
     * Setup Cinema mode
     */
    setupCinemaMode() {
      const viewerEl = document.getElementById('gaze-viewer');
      if (viewerEl) {
        viewerEl.classList.add('cinema-mode');
        
        // Add ambient glow container
        if (!document.getElementById('gaze-ambient-glow')) {
          const glow = document.createElement('div');
          glow.id = 'gaze-ambient-glow';
          glow.className = 'gaze-ambient-glow';
          viewerEl.querySelector('.gaze-viewer-content').prepend(glow);
        }
        
        // Extract colors for ambient glow
        this.updateAmbientGlow();
      }
    },
    
    /**
     * Setup Classic mode
     */
    setupClassicMode() {
      const viewerEl = document.getElementById('gaze-viewer');
      if (viewerEl) {
        viewerEl.classList.add('classic-mode');
      }
    },
    
    /**
     * Setup Showcase mode
     */
    setupShowcaseMode() {
      const viewerEl = document.getElementById('gaze-viewer');
      if (viewerEl) {
        viewerEl.classList.add('showcase-mode');
        
        // Add frame element
        if (!document.getElementById('gaze-showcase-frame')) {
          const frame = document.createElement('div');
          frame.id = 'gaze-showcase-frame';
          frame.className = 'gaze-showcase-frame';
          viewerEl.querySelector('.gaze-viewer-stage').appendChild(frame);
        }
      }
    },
    
    /**
     * Setup Ambient mode
     */
    setupAmbientMode() {
      const viewerEl = document.getElementById('gaze-viewer');
      if (viewerEl) {
        viewerEl.classList.add('ambient-mode');
        
        // Add particle container
        if (!document.getElementById('gaze-particles')) {
          const particles = document.createElement('div');
          particles.id = 'gaze-particles';
          particles.className = 'gaze-particles';
          viewerEl.querySelector('.gaze-viewer-content').prepend(particles);
          
          // Initialize particles
          this.createParticles();
        }
        
        // Longer intervals for ambient
        state.slideshow.interval = 15000;
        this.startTimer();
      }
    },
    
    /**
     * Create floating particles
     */
    createParticles() {
      const container = document.getElementById('gaze-particles');
      if (!container) return;
      
      const colors = ['#8b7eff', '#c084fc', '#60a5fa', '#ffffff'];
      const count = 40;
      
      for (let i = 0; i < count; i++) {
        const particle = document.createElement('div');
        particle.className = 'gaze-particle';
        particle.style.cssText = `
          left: ${Math.random() * 100}%;
          top: ${Math.random() * 100}%;
          width: ${2 + Math.random() * 6}px;
          height: ${2 + Math.random() * 6}px;
          background: ${colors[Math.floor(Math.random() * colors.length)]};
          opacity: ${0.1 + Math.random() * 0.5};
          animation-delay: ${Math.random() * 10}s;
          animation-duration: ${15 + Math.random() * 20}s;
        `;
        container.appendChild(particle);
      }
    },
    
    /**
     * Update ambient glow colors from current image
     */
    async updateAmbientGlow() {
      const imgData = imageManager.get(state.viewer.currentIndex);
      if (!imgData) return;
      
      const glowEl = document.getElementById('gaze-ambient-glow');
      if (!glowEl) return;
      
      try {
        const colors = await this.extractDominantColors(imgData.src);
        glowEl.style.background = `
          radial-gradient(ellipse at 20% 20%, ${colors[0]}40 0%, transparent 50%),
          radial-gradient(ellipse at 80% 20%, ${colors[1]}30 0%, transparent 50%),
          radial-gradient(ellipse at 50% 80%, ${colors[2]}35 0%, transparent 50%)
        `;
      } catch (e) {
        console.warn('Could not extract colors:', e);
      }
    },
    
    /**
     * Extract dominant colors from image
     */
    extractDominantColors(src) {
      return new Promise((resolve) => {
        const img = new Image();
        img.crossOrigin = 'anonymous';
        img.onload = () => {
          const canvas = document.createElement('canvas');
          const ctx = canvas.getContext('2d');
          canvas.width = 50;
          canvas.height = 50;
          ctx.drawImage(img, 0, 0, 50, 50);
          
          const imageData = ctx.getImageData(0, 0, 50, 50).data;
          const colors = [];
          
          // Sample colors from different regions
          const regions = [
            [10, 10], [40, 10], [25, 40]
          ];
          
          for (const [x, y] of regions) {
            const i = (y * 50 + x) * 4;
            colors.push(`rgb(${imageData[i]}, ${imageData[i+1]}, ${imageData[i+2]})`);
          }
          
          resolve(colors);
        };
        img.onerror = () => resolve(['#8b7eff', '#c084fc', '#60a5fa']);
        img.src = src;
      });
    },
    
    /**
     * Cleanup mode-specific elements
     */
    cleanupModes() {
      const viewerEl = document.getElementById('gaze-viewer');
      if (viewerEl) {
        viewerEl.classList.remove('cinema-mode', 'classic-mode', 'showcase-mode', 'ambient-mode');
      }
      
      // Remove particles
      const particles = document.getElementById('gaze-particles');
      if (particles) particles.remove();
      
      // Remove ambient glow
      const glow = document.getElementById('gaze-ambient-glow');
      if (glow) glow.remove();
      
      // Remove frame
      const frame = document.getElementById('gaze-showcase-frame');
      if (frame) frame.remove();
    }
  };

  // ============================================================
  // Comparison Mode
  // ============================================================
  
  const comparisonMode = {
    /**
     * Enter comparison mode
     */
    enter(imageA, imageB) {
      state.comparison.active = true;
      state.comparison.imageA = imageA;
      state.comparison.imageB = imageB;
      
      this.render();
      events.emit('comparisonStarted', { imageA, imageB });
    },
    
    /**
     * Exit comparison mode
     */
    exit() {
      state.comparison.active = false;
      this.cleanup();
      events.emit('comparisonEnded');
    },
    
    /**
     * Set comparison mode type
     */
    setMode(mode) {
      state.comparison.mode = mode;
      this.render();
    },
    
    /**
     * Render comparison view
     */
    render() {
      let modal = document.getElementById('gaze-comparison');
      if (!modal) {
        modal = this.createDOM();
      }
      
      const { imageA, imageB, mode } = state.comparison;
      
      modal.className = `gaze-comparison mode-${mode}`;
      modal.classList.add('open');
      
      // Update images
      document.getElementById('gaze-compare-a').src = imageA.src;
      document.getElementById('gaze-compare-b').src = imageB.src;
      
      document.body.style.overflow = 'hidden';
    },
    
    /**
     * Create comparison DOM
     */
    createDOM() {
      const modal = document.createElement('div');
      modal.id = 'gaze-comparison';
      modal.className = 'gaze-comparison';
      modal.innerHTML = `
        <div class="gaze-comparison-backdrop"></div>
        <div class="gaze-comparison-content">
          <div class="gaze-comparison-images">
            <div class="gaze-compare-pane gaze-compare-pane-a">
              <img id="gaze-compare-a" src="" alt="Image A">
            </div>
            <div class="gaze-compare-divider" id="gaze-compare-divider">
              <div class="gaze-compare-handle"></div>
            </div>
            <div class="gaze-compare-pane gaze-compare-pane-b">
              <img id="gaze-compare-b" src="" alt="Image B">
            </div>
          </div>
          
          <div class="gaze-comparison-controls">
            <div class="gaze-comparison-modes">
              <button class="gaze-compare-mode-btn active" data-mode="slider">Slider</button>
              <button class="gaze-compare-mode-btn" data-mode="split">Split</button>
              <button class="gaze-compare-mode-btn" data-mode="onion">Onion</button>
            </div>
            <input type="range" id="gaze-compare-opacity" min="0" max="100" value="50" class="d-none">
            <button class="gaze-compare-close">✕ Close</button>
          </div>
        </div>
      `;
      
      document.body.appendChild(modal);
      this.attachEventListeners(modal);
      
      return modal;
    },
    
    /**
     * Attach event listeners
     */
    attachEventListeners(modal) {
      // Close
      modal.querySelector('.gaze-comparison-backdrop').addEventListener('click', () => this.exit());
      modal.querySelector('.gaze-compare-close').addEventListener('click', () => this.exit());
      
      // Mode buttons
      modal.querySelectorAll('.gaze-compare-mode-btn').forEach(btn => {
        btn.addEventListener('click', () => {
          modal.querySelectorAll('.gaze-compare-mode-btn').forEach(b => b.classList.remove('active'));
          btn.classList.add('active');
          this.setMode(btn.dataset.mode);
        });
      });
      
      // Slider drag
      const divider = modal.querySelector('#gaze-compare-divider');
      let isDragging = false;
      
      divider.addEventListener('mousedown', () => isDragging = true);
      document.addEventListener('mouseup', () => isDragging = false);
      document.addEventListener('mousemove', (e) => {
        if (!isDragging || state.comparison.mode !== 'slider') return;
        
        const rect = modal.querySelector('.gaze-comparison-images').getBoundingClientRect();
        const percent = ((e.clientX - rect.left) / rect.width) * 100;
        const clamped = Math.min(95, Math.max(5, percent));
        
        divider.style.left = `${clamped}%`;
        modal.querySelector('.gaze-compare-pane-a').style.width = `${clamped}%`;
      });
      
      // Onion opacity
      const opacitySlider = modal.querySelector('#gaze-compare-opacity');
      opacitySlider.addEventListener('input', () => {
        const opacity = opacitySlider.value / 100;
        modal.querySelector('#gaze-compare-b').style.opacity = opacity;
      });
    },
    
    /**
     * Cleanup
     */
    cleanup() {
      const modal = document.getElementById('gaze-comparison');
      if (modal) {
        modal.classList.remove('open');
        document.body.style.overflow = '';
      }
    }
  };

  // ============================================================
  // Keyboard Controller
  // ============================================================
  
  const keyboardController = {
    /**
     * Initialize keyboard shortcuts
     */
    init() {
      document.addEventListener('keydown', (e) => this.handleKeydown(e));
    },
    
    /**
     * Handle keydown event
     */
    handleKeydown(e) {
      // Skip if typing in input
      if (e.target.matches('input, textarea, select')) return;
      
      const key = e.key.toLowerCase();
      
      // Viewer shortcuts (when viewer is open)
      if (state.viewer.open) {
        switch (key) {
          case 'escape':
            if (state.slideshow.active) {
              slideshowController.stop();
            } else {
              viewer.close();
            }
            e.preventDefault();
            break;
          case 'arrowleft':
            viewer.navigate(-1);
            e.preventDefault();
            break;
          case 'arrowright':
            viewer.navigate(1);
            e.preventDefault();
            break;
          case 'arrowup':
            viewer.setZoom(state.viewer.zoom + 0.25);
            e.preventDefault();
            break;
          case 'arrowdown':
            viewer.setZoom(state.viewer.zoom - 0.25);
            e.preventDefault();
            break;
          case ' ':
            if (state.slideshow.active) {
              slideshowController.toggle();
            } else {
              slideshowController.start('classic');
            }
            e.preventDefault();
            break;
          case 'f':
            this.toggleFullscreen();
            e.preventDefault();
            break;
          case 'i':
            document.querySelector('.gaze-viewer-info')?.classList.toggle('open');
            e.preventDefault();
            break;
          case '0':
            viewer.resetTransform();
            e.preventDefault();
            break;
          case 'r':
            if (state.slideshow.active) {
              slideshowController.toggleShuffle();
            }
            e.preventDefault();
            break;
        }
        
        // Number keys 1-5 for slideshow speed
        if (state.slideshow.active && /[1-5]/.test(key)) {
          const speeds = [2000, 3000, 5000, 8000, 15000];
          slideshowController.setInterval(speeds[parseInt(key) - 1]);
          e.preventDefault();
        }
      }
      
      // Gallery shortcuts (when viewer is closed)
      if (!state.viewer.open) {
        switch (key) {
          case 'g':
            layoutEngine.setLayout('grid');
            e.preventDefault();
            break;
          case 'm':
            layoutEngine.setLayout('masonry');
            e.preventDefault();
            break;
          case 'j':
            layoutEngine.setLayout('justified');
            e.preventDefault();
            break;
          case 'a':
            layoutEngine.setLayout('mosaic');
            e.preventDefault();
            break;
          case 's':
            viewer.open(0);
            slideshowController.start('cinema');
            e.preventDefault();
            break;
        }
      }
    },
    
    /**
     * Toggle fullscreen
     */
    toggleFullscreen() {
      if (!document.fullscreenElement) {
        const viewer = document.getElementById('gaze-viewer');
        if (viewer) {
          viewer.requestFullscreen().catch(console.error);
        }
      } else {
        document.exitFullscreen().catch(console.error);
      }
    }
  };

  // ============================================================
  // Public API
  // ============================================================
  
  const GalleryCore = {
    // Configuration
    CONFIG,
    
    // State (read-only)
    get state() { return state; },
    
    // Event handling
    on: events.on.bind(events),
    off: events.off.bind(events),
    
    /**
     * Initialize the gallery system
     */
    init(containerSelector = '.gallery-grid') {
      const container = document.querySelector(containerSelector);
      if (!container) {
        console.warn('Gallery container not found:', containerSelector);
        return;
      }
      
      state.dom.container = container;
      
      // Load images from DOM
      imageManager.initFromDOM(container);
      
      // Restore saved view mode
      const savedMode = localStorage.getItem(CONFIG.storage.viewMode);
      if (savedMode) {
        state.viewMode = savedMode;
      }
      
      // Initialize keyboard shortcuts
      keyboardController.init();
      
      // Apply current layout
      layoutEngine.apply();
      
      // Setup click handlers on cards
      this.attachCardListeners();
      
      console.log(`Gallery initialized with ${imageManager.count()} images`);
      events.emit('initialized', { imageCount: imageManager.count() });
      
      return this;
    },
    
    /**
     * Attach click listeners to gallery cards
     */
    attachCardListeners() {
      state.images.forEach((img, index) => {
        if (img.element) {
          img.element.addEventListener('click', (e) => {
            // Don't open viewer if in selection mode
            const grid = img.element.closest('.gallery-grid');
            if (grid && grid.classList.contains('selection-mode')) return;

            // Don't open viewer if clicking action buttons
            if (e.target.closest('button, a, input')) return;
            viewer.open(index);
          });
        }
      });
    },
    
    // Layout methods
    setLayout: layoutEngine.setLayout.bind(layoutEngine),
    getLayout: () => state.viewMode,
    
    // Viewer methods
    openViewer: viewer.open.bind(viewer),
    closeViewer: viewer.close.bind(viewer),
    
    // Slideshow methods
    startSlideshow: slideshowController.start.bind(slideshowController),
    stopSlideshow: slideshowController.stop.bind(slideshowController),
    toggleSlideshow: slideshowController.toggle.bind(slideshowController),
    setSlideshowInterval: slideshowController.setInterval.bind(slideshowController),
    setSlideshowTransition: slideshowController.setTransition.bind(slideshowController),
    
    // Comparison methods
    startComparison: comparisonMode.enter.bind(comparisonMode),
    stopComparison: comparisonMode.exit.bind(comparisonMode),
    
    // Image management
    getImage: imageManager.get.bind(imageManager),
    getImageCount: imageManager.count.bind(imageManager),
    filterImages: imageManager.filter.bind(imageManager),
    
    // Discovery mode
    getRandomImage: imageManager.getRandomUnvisited.bind(imageManager),
    markDiscovered: (index) => state.discovery.discovered.add(index),
    toggleFavorite: (index) => {
      if (state.discovery.favorites.has(index)) {
        state.discovery.favorites.delete(index);
      } else {
        state.discovery.favorites.add(index);
      }
      events.emit('favoriteToggled', { index, isFavorite: state.discovery.favorites.has(index) });
    }
  };

  // Expose to global scope
  window.GalleryCore = GalleryCore;

})();
