(function() {
  'use strict';

  // ---- Storage keys ----
  var STORAGE_PRESET = 'mps_sandbox_preset';
  var STORAGE_SLIDERS = 'mps_sandbox_sliders';
  var STORAGE_PANEL = 'mps_sandbox_panel';
  var STORAGE_MODE = 'mps_sandbox_mode'; // 'preset' or 'custom'

  // ---- Filter presets ----
  // Each: { name, category, css } where css is the CSS filter string
  var PRESETS = [
    // Basic
    { name: 'None',           category: 'Basic',     css: 'none' },
    { name: 'Grayscale',      category: 'Basic',     css: 'grayscale(100%)' },
    { name: 'Sepia',          category: 'Basic',     css: 'sepia(100%)' },
    { name: 'Invert',         category: 'Basic',     css: 'invert(100%)' },

    // Warm
    { name: 'Warm',           category: 'Warm',      css: 'sepia(30%) saturate(140%) brightness(105%)' },
    { name: 'Sunset',         category: 'Warm',      css: 'sepia(50%) hue-rotate(-15deg) saturate(150%)' },
    { name: 'Golden',         category: 'Warm',      css: 'sepia(60%) brightness(110%) contrast(90%)' },
    { name: 'Amber',          category: 'Warm',      css: 'sepia(40%) saturate(160%) hue-rotate(-10deg) brightness(108%)' },

    // Cool
    { name: 'Cool',           category: 'Cool',      css: 'sepia(20%) hue-rotate(180deg) saturate(120%)' },
    { name: 'Arctic',         category: 'Cool',      css: 'sepia(30%) hue-rotate(200deg) brightness(110%)' },
    { name: 'Moonlight',      category: 'Cool',      css: 'sepia(40%) hue-rotate(220deg) saturate(80%) brightness(105%)' },
    { name: 'Frost',          category: 'Cool',      css: 'sepia(25%) hue-rotate(190deg) brightness(115%) contrast(95%)' },

    // Dramatic
    { name: 'Hi-Contrast',    category: 'Dramatic',  css: 'contrast(180%) saturate(120%)' },
    { name: 'Noir',           category: 'Dramatic',  css: 'grayscale(100%) contrast(150%) brightness(90%)' },
    { name: 'Faded',          category: 'Dramatic',  css: 'contrast(80%) saturate(60%) brightness(110%)' },
    { name: 'Vintage',        category: 'Dramatic',  css: 'sepia(40%) contrast(90%) brightness(105%) saturate(80%)' },

    // Color shifts
    { name: 'Red Shift',      category: 'Color',     css: 'sepia(100%) hue-rotate(-30deg) saturate(300%)' },
    { name: 'Green Shift',    category: 'Color',     css: 'sepia(100%) hue-rotate(85deg) saturate(300%)' },
    { name: 'Blue Shift',     category: 'Color',     css: 'sepia(100%) hue-rotate(200deg) saturate(300%)' },
    { name: 'Purple Haze',    category: 'Color',     css: 'sepia(100%) hue-rotate(265deg) saturate(200%)' },
    { name: 'Cyan',           category: 'Color',     css: 'sepia(100%) hue-rotate(145deg) saturate(300%)' },

    // Instagram-style
    { name: 'Clarendon',      category: 'Instagram', css: 'contrast(120%) saturate(125%)' },
    { name: 'Juno',           category: 'Instagram', css: 'sepia(10%) contrast(110%) saturate(150%) brightness(105%)' },
    { name: 'Lark',           category: 'Instagram', css: 'contrast(90%) brightness(115%) saturate(85%)' },
    { name: 'Gingham',        category: 'Instagram', css: 'brightness(105%) hue-rotate(350deg) saturate(80%)' },
    { name: 'Nashville',      category: 'Instagram', css: 'sepia(25%) contrast(120%) brightness(105%) saturate(120%) hue-rotate(-15deg)' },
    { name: 'Valencia',       category: 'Instagram', css: 'sepia(15%) contrast(110%) brightness(108%) saturate(120%)' },
    { name: 'Walden',         category: 'Instagram', css: 'brightness(110%) saturate(160%) sepia(30%) hue-rotate(350deg)' },

    // SVG filter presets
    { name: 'Duo Blue',       category: 'SVG',       css: 'url(#sandbox-duotone-blue)' },
    { name: 'Duo Green',      category: 'SVG',       css: 'url(#sandbox-duotone-green)' },
    { name: 'Film Grain',     category: 'SVG',       css: 'url(#sandbox-grain)' },
    { name: 'Vignette',       category: 'SVG',       css: 'url(#sandbox-vignette)' },
  ];

  // ---- Slider definitions ----
  var SLIDERS = [
    { id: 'brightness', label: 'Brightness', min: 0,   max: 200, dflt: 100, unit: '%',   fn: 'brightness' },
    { id: 'contrast',   label: 'Contrast',   min: 0,   max: 200, dflt: 100, unit: '%',   fn: 'contrast' },
    { id: 'saturate',   label: 'Saturation',  min: 0,   max: 300, dflt: 100, unit: '%',   fn: 'saturate' },
    { id: 'hue',        label: 'Hue',         min: 0,   max: 360, dflt: 0,   unit: 'deg', fn: 'hue-rotate' },
    { id: 'blur',       label: 'Blur',        min: 0,   max: 20,  dflt: 0,   unit: 'px',  fn: 'blur' },
    { id: 'sepia',      label: 'Sepia',       min: 0,   max: 100, dflt: 0,   unit: '%',   fn: 'sepia' },
    { id: 'grayscale2', label: 'Grayscale',   min: 0,   max: 100, dflt: 0,   unit: '%',   fn: 'grayscale' },
  ];

  // ---- State ----
  var currentPresetIndex = 0; // 0 = None
  var sliderValues = {};
  var filterMode = 'preset'; // 'preset' or 'custom'
  var mediaSelector = 'img, video';

  // ---- DOM refs ----
  var toolbar = document.getElementById('sandbox-toolbar');
  var toggleBtn = document.getElementById('sandbox-toggle');
  var panel = document.getElementById('sandbox-panel');
  var closeBtn = document.getElementById('sandbox-close');
  var presetsContainer = document.getElementById('sandbox-presets');
  var slidersContainer = document.getElementById('sandbox-sliders');
  var actionsContainer = document.getElementById('sandbox-actions');

  if (!toggleBtn || !panel) return;

  // Does the user have an S3 bucket configured?
  var hasBucket = toolbar && toolbar.getAttribute('data-has-bucket') === '1';
  var lastExportBlob = null;
  var lastExportFilename = null;
  var lastExportContentType = null;

  // ---- Face detection state ----
  var faceMeshLoaded = false;
  var faceMeshModule = null;
  var faceCamera = null;
  var eyeGlowColor = '#00ff00';
  var faceEffectActive = false;
  var faceCanvasOverlays = [];

  // ========================================
  // Initialization
  // ========================================
  function init() {
    resetSliderValues();
    buildPresetButtons();
    buildSliders();
    buildActions();
    buildFaceSection();
    enableCrossOrigin();
    restoreState();
    setupEvents();
  }

  // Set crossOrigin on all images so canvas export can read pixels.
  // The CDN already serves Access-Control-Allow-Origin: * but the
  // browser only sends the Origin header when crossOrigin is set.
  function enableCrossOrigin() {
    var imgs = document.querySelectorAll('img');
    for (var i = 0; i < imgs.length; i++) {
      if (!imgs[i].crossOrigin) {
        imgs[i].crossOrigin = 'anonymous';
      }
    }
  }

  function resetSliderValues() {
    for (var i = 0; i < SLIDERS.length; i++) {
      sliderValues[SLIDERS[i].id] = SLIDERS[i].dflt;
    }
  }

  // ========================================
  // Preset buttons
  // ========================================
  function buildPresetButtons() {
    var currentCategory = '';
    for (var i = 0; i < PRESETS.length; i++) {
      var preset = PRESETS[i];
      if (preset.category !== currentCategory) {
        currentCategory = preset.category;
        var catLabel = document.createElement('div');
        catLabel.className = 'sandbox-preset-category';
        catLabel.textContent = currentCategory;
        presetsContainer.appendChild(catLabel);
      }
      var btn = document.createElement('button');
      btn.className = 'sandbox-preset-btn';
      if (i === currentPresetIndex) btn.classList.add('active');
      btn.textContent = preset.name;
      btn.dataset.index = String(i);
      btn.addEventListener('click', handlePresetClick);
      presetsContainer.appendChild(btn);
    }
  }

  function handlePresetClick(e) {
    var idx = parseInt(e.target.dataset.index, 10);
    selectPreset(idx);
  }

  function selectPreset(idx) {
    currentPresetIndex = idx;
    filterMode = 'preset';

    // Update active button
    var btns = presetsContainer.querySelectorAll('.sandbox-preset-btn');
    for (var i = 0; i < btns.length; i++) {
      btns[i].classList.toggle('active', parseInt(btns[i].dataset.index, 10) === idx);
    }

    // Reset sliders to defaults when selecting a preset
    resetSliderValues();
    updateSliderInputs();

    applyCurrentFilter();
    saveState();
  }

  // ========================================
  // Sliders
  // ========================================
  function buildSliders() {
    var heading = document.createElement('div');
    heading.className = 'sandbox-preset-category';
    heading.textContent = 'Adjustments';
    heading.style.marginBottom = '6px';
    slidersContainer.appendChild(heading);

    for (var i = 0; i < SLIDERS.length; i++) {
      var s = SLIDERS[i];
      var row = document.createElement('div');
      row.className = 'sandbox-slider-row';

      var label = document.createElement('span');
      label.textContent = s.label;

      var input = document.createElement('input');
      input.type = 'range';
      input.id = 'sandbox-slider-' + s.id;
      input.min = String(s.min);
      input.max = String(s.max);
      input.value = String(s.dflt);
      input.dataset.sliderId = s.id;
      input.addEventListener('input', handleSliderChange);

      var valueSpan = document.createElement('span');
      valueSpan.className = 'sandbox-slider-value';
      valueSpan.id = 'sandbox-value-' + s.id;
      valueSpan.textContent = String(s.dflt);

      row.appendChild(label);
      row.appendChild(input);
      row.appendChild(valueSpan);
      slidersContainer.appendChild(row);
    }
  }

  function handleSliderChange(e) {
    var sid = e.target.dataset.sliderId;
    var val = parseInt(e.target.value, 10);
    sliderValues[sid] = val;

    var valueSpan = document.getElementById('sandbox-value-' + sid);
    if (valueSpan) valueSpan.textContent = String(val);

    // If any slider differs from defaults, switch to custom mode
    var isCustom = false;
    for (var i = 0; i < SLIDERS.length; i++) {
      if (sliderValues[SLIDERS[i].id] !== SLIDERS[i].dflt) {
        isCustom = true;
        break;
      }
    }

    if (isCustom) {
      filterMode = 'custom';
    }

    applyCurrentFilter();
    saveState();
  }

  function updateSliderInputs() {
    for (var i = 0; i < SLIDERS.length; i++) {
      var s = SLIDERS[i];
      var input = document.getElementById('sandbox-slider-' + s.id);
      var valueSpan = document.getElementById('sandbox-value-' + s.id);
      if (input) input.value = String(sliderValues[s.id]);
      if (valueSpan) valueSpan.textContent = String(sliderValues[s.id]);
    }
  }

  // ========================================
  // Filter application
  // ========================================
  function getCurrentFilterCSS() {
    var parts = [];

    // Start with preset if in preset mode and not "None"
    if (filterMode === 'preset' && currentPresetIndex > 0) {
      parts.push(PRESETS[currentPresetIndex].css);
    }

    // Add slider adjustments
    for (var i = 0; i < SLIDERS.length; i++) {
      var s = SLIDERS[i];
      var val = sliderValues[s.id];
      if (val !== s.dflt) {
        parts.push(s.fn + '(' + val + s.unit + ')');
      }
    }

    if (parts.length === 0) return 'none';
    return parts.join(' ');
  }

  function applyCurrentFilter() {
    var css = getCurrentFilterCSS();
    var elements = document.querySelectorAll(mediaSelector);
    for (var i = 0; i < elements.length; i++) {
      elements[i].style.filter = (css === 'none') ? '' : css;
    }
  }

  function clearAllFilters() {
    var elements = document.querySelectorAll(mediaSelector);
    for (var i = 0; i < elements.length; i++) {
      elements[i].style.filter = '';
    }
  }

  // Public function for watch mode SPA re-application
  window.sandboxReapply = function() {
    // Small delay to let new DOM settle
    setTimeout(function() {
      enableCrossOrigin();
      applyCurrentFilter();
    }, 50);
  };

  // ========================================
  // Actions (reset, export)
  // ========================================
  function buildActions() {
    // Reset button
    var resetBtn = document.createElement('button');
    resetBtn.className = 'sandbox-action-btn';
    resetBtn.textContent = 'Reset All';
    resetBtn.addEventListener('click', function() {
      currentPresetIndex = 0;
      filterMode = 'preset';
      resetSliderValues();
      updateSliderInputs();

      var btns = presetsContainer.querySelectorAll('.sandbox-preset-btn');
      for (var i = 0; i < btns.length; i++) {
        btns[i].classList.toggle('active', parseInt(btns[i].dataset.index, 10) === 0);
      }

      clearAllFilters();
      saveState();
    });

    // Apply to page toggle
    var pageBtn = document.createElement('button');
    pageBtn.className = 'sandbox-action-btn';
    pageBtn.textContent = 'Full Page';
    pageBtn.title = 'Toggle: apply filter to entire page or just media';
    pageBtn.addEventListener('click', function() {
      if (mediaSelector === 'img, video') {
        mediaSelector = 'img, video, .product-description, .product-right, section.content';
        pageBtn.textContent = 'Media Only';
      } else {
        mediaSelector = 'img, video';
        pageBtn.textContent = 'Full Page';
        // Clear non-media elements
        var all = document.querySelectorAll('.product-description, .product-right, section.content');
        for (var i = 0; i < all.length; i++) {
          all[i].style.filter = '';
        }
      }
      applyCurrentFilter();
    });

    // Export image button
    var exportImgBtn = document.createElement('button');
    exportImgBtn.className = 'sandbox-action-btn sandbox-export-btn';
    exportImgBtn.textContent = 'Export Image';
    exportImgBtn.addEventListener('click', exportImage);

    // Export video button
    var exportVidBtn = document.createElement('button');
    exportVidBtn.className = 'sandbox-action-btn sandbox-export-btn';
    exportVidBtn.textContent = 'Export Video';
    exportVidBtn.addEventListener('click', exportVideo);

    actionsContainer.appendChild(resetBtn);
    actionsContainer.appendChild(pageBtn);
    actionsContainer.appendChild(exportImgBtn);
    actionsContainer.appendChild(exportVidBtn);

    // Upload to bucket button — only when user has S3 credentials
    if (hasBucket) {
      var uploadBtn = document.createElement('button');
      uploadBtn.className = 'sandbox-action-btn sandbox-export-btn';
      uploadBtn.textContent = 'Upload to Bucket';
      uploadBtn.title = 'Upload the last exported artifact to your S3 bucket';
      uploadBtn.addEventListener('click', function() {
        if (!lastExportBlob) {
          showStatus('Export an image or video first, then upload.');
          return;
        }
        uploadToBucket(lastExportBlob, lastExportFilename, lastExportContentType);
      });
      actionsContainer.appendChild(uploadBtn);
    }
  }

  // ========================================
  // Image export (canvas)
  // ========================================
  function exportImage() {
    var img = document.querySelector('img.product-main');
    if (!img) {
      img = document.querySelector('.product-images img');
    }
    if (!img) {
      showStatus('No image found to export.');
      return;
    }

    showStatus('Preparing export...');

    var exportImg = new Image();
    exportImg.crossOrigin = 'anonymous';
    exportImg.onload = function() {
      var canvas = document.createElement('canvas');
      canvas.width = exportImg.naturalWidth;
      canvas.height = exportImg.naturalHeight;
      var ctx = canvas.getContext('2d');

      // Apply the CSS filter to canvas context
      ctx.filter = getCurrentFilterCSS();
      ctx.drawImage(exportImg, 0, 0);

      canvas.toBlob(function(blob) {
        if (!blob) {
          showStatus('Export failed — could not generate image.');
          return;
        }
        lastExportBlob = blob;
        lastExportFilename = 'sandbox-export.png';
        lastExportContentType = 'image/png';
        downloadBlob(blob, 'sandbox-export.png');
        if (hasBucket) {
          showStatus('Image exported. Click "Upload to Bucket" to save to your storage.');
        } else {
          showStatus('Image exported.');
        }
      }, 'image/png');
    };
    exportImg.onerror = function() {
      showStatus('Export failed — cross-origin access denied by CDN.');
    };
    exportImg.src = img.src;
  }

  // ========================================
  // Video export (MediaRecorder + canvas)
  // ========================================
  var videoRecorder = null;
  var videoChunks = [];
  var videoExportActive = false;

  function exportVideo() {
    var video = document.querySelector('#watch-video');
    if (!video) {
      video = document.querySelector('video.product-main');
    }
    if (!video) {
      video = document.querySelector('.product-images video');
    }
    if (!video) {
      showStatus('No video found to export.');
      return;
    }

    if (videoExportActive) {
      // Stop recording
      stopVideoExport();
      return;
    }

    videoExportActive = true;
    showStatus('Recording... click "Export Video" again to stop.');

    var canvas = document.createElement('canvas');
    canvas.width = video.videoWidth || 1280;
    canvas.height = video.videoHeight || 720;
    var ctx = canvas.getContext('2d');

    var stream = canvas.captureStream(30);

    // Try to capture audio from the video element
    try {
      if (video.captureStream) {
        var audioTracks = video.captureStream().getAudioTracks();
        for (var i = 0; i < audioTracks.length; i++) {
          stream.addTrack(audioTracks[i]);
        }
      }
    } catch (e) {
      // Audio capture may fail cross-origin; continue without audio
    }

    var mimeType = 'video/webm';
    if (!MediaRecorder.isTypeSupported(mimeType)) {
      mimeType = 'video/webm;codecs=vp8';
    }

    videoChunks = [];
    videoRecorder = new MediaRecorder(stream, { mimeType: mimeType });

    videoRecorder.ondataavailable = function(e) {
      if (e.data && e.data.size > 0) videoChunks.push(e.data);
    };

    videoRecorder.onstop = function() {
      var blob = new Blob(videoChunks, { type: mimeType });
      lastExportBlob = blob;
      lastExportFilename = 'sandbox-export.webm';
      lastExportContentType = mimeType;
      downloadBlob(blob, 'sandbox-export.webm');
      videoExportActive = false;
      if (hasBucket) {
        showStatus('Video exported. Click "Upload to Bucket" to save to your storage.');
      } else {
        showStatus('Video exported.');
      }
    };

    videoRecorder.start(100); // Collect data every 100ms

    function drawFrame() {
      if (!videoExportActive) return;
      if (video.paused || video.ended) {
        stopVideoExport();
        return;
      }
      ctx.filter = getCurrentFilterCSS();
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
      requestAnimationFrame(drawFrame);
    }

    drawFrame();
    if (video.paused) video.play();
  }

  function stopVideoExport() {
    videoExportActive = false;
    if (videoRecorder && videoRecorder.state !== 'inactive') {
      videoRecorder.stop();
    }
  }

  // ========================================
  // Upload to user's S3 bucket
  // ========================================
  function uploadToBucket(blob, filename, contentType) {
    showStatus('Requesting upload URL...');

    fetch('/u/sandbox/upload', {
      method: 'POST',
      headers: {'Content-Type': 'application/x-www-form-urlencoded'},
      body: 'filename=' + encodeURIComponent(filename)
          + '&content_type=' + encodeURIComponent(contentType),
    })
    .then(function(r) { return r.json(); })
    .then(function(data) {
      if (data.error) {
        showStatus(data.error);
        return;
      }

      showStatus('Uploading to bucket...');

      var formData = new FormData();
      var fields = data.presigned.fields;
      Object.keys(fields).forEach(function(k) {
        formData.append(k, fields[k]);
      });
      formData.append('file', blob, filename);

      return fetch(data.presigned.url, {
        method: 'POST',
        body: formData,
      });
    })
    .then(function(response) {
      if (!response) return; // error case already handled
      if (response.ok || response.status === 204) {
        showStatus('Uploaded to your bucket.');
      } else {
        showStatus('Upload failed (HTTP ' + response.status + '). Check bucket permissions.');
      }
    })
    .catch(function(err) {
      showStatus('Upload failed: ' + (err.message || err));
    });
  }

  // ========================================
  // Face detection (MediaPipe) — on demand
  // ========================================
  function buildFaceSection() {
    var section = document.createElement('div');
    section.className = 'sandbox-face-section';

    var heading = document.createElement('div');
    heading.className = 'sandbox-preset-category';
    heading.textContent = 'Face Effects (machine learning)';
    section.appendChild(heading);

    var loadBtn = document.createElement('button');
    loadBtn.className = 'sandbox-action-btn';
    loadBtn.style.width = '100%';
    loadBtn.textContent = 'Load Face Detection';
    loadBtn.addEventListener('click', function() {
      loadBtn.textContent = 'Loading model...';
      loadBtn.disabled = true;
      loadFaceMesh().then(function() {
        loadBtn.style.display = 'none';
        buildFaceControls(section);
      }).catch(function(err) {
        loadBtn.textContent = 'Failed — Retry';
        loadBtn.disabled = false;
        showStatus('Face detection failed to load: ' + (err.message || err));
      });
    });

    section.appendChild(loadBtn);
    actionsContainer.parentNode.appendChild(section);
  }

  function loadFaceMesh() {
    if (faceMeshLoaded) return Promise.resolve();

    return new Promise(function(resolve, reject) {
      // Load MediaPipe Face Mesh
      var script = document.createElement('script');
      script.src = 'https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh@0.4/face_mesh.js';
      script.onload = function() {
        // Also load camera utils for video
        var camScript = document.createElement('script');
        camScript.src = 'https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils@0.3/camera_utils.js';
        camScript.onload = function() {
          try {
            /* global FaceMesh */
            faceMeshModule = new window.FaceMesh({
              locateFile: function(file) {
                return 'https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh@0.4/' + file;
              }
            });
            faceMeshModule.setOptions({
              maxNumFaces: 4,
              refineLandmarks: true,
              minDetectionConfidence: 0.5,
              minTrackingConfidence: 0.5,
            });
            faceMeshLoaded = true;
            resolve();
          } catch (e) {
            reject(e);
          }
        };
        camScript.onerror = function() { reject(new Error('Failed to load camera utils')); };
        document.head.appendChild(camScript);
      };
      script.onerror = function() { reject(new Error('Failed to load face mesh library')); };
      document.head.appendChild(script);
    });
  }

  function buildFaceControls(section) {
    var controls = document.createElement('div');
    controls.className = 'sandbox-face-controls';

    // Eye glow button
    var glowBtn = document.createElement('button');
    glowBtn.className = 'sandbox-action-btn';
    glowBtn.textContent = 'Eye Glow';
    glowBtn.addEventListener('click', function() {
      toggleFaceEffect('glow');
    });

    // Face mask button (guy fawkes style)
    var maskBtn = document.createElement('button');
    maskBtn.className = 'sandbox-action-btn';
    maskBtn.textContent = 'Face Mask';
    maskBtn.addEventListener('click', function() {
      toggleFaceEffect('mask');
    });

    controls.appendChild(glowBtn);
    controls.appendChild(maskBtn);

    // Color picker for eye glow
    var colorRow = document.createElement('div');
    colorRow.className = 'sandbox-color-row';
    var colorLabel = document.createElement('span');
    colorLabel.textContent = 'Glow Color';
    var colorInput = document.createElement('input');
    colorInput.type = 'color';
    colorInput.value = eyeGlowColor;
    colorInput.addEventListener('input', function(e) {
      eyeGlowColor = e.target.value;
    });
    colorRow.appendChild(colorLabel);
    colorRow.appendChild(colorInput);

    section.appendChild(controls);
    section.appendChild(colorRow);

    showStatus('Face detection ready. Click "Eye Glow" or "Face Mask" on a page with images or video.');
  }

  var activeFaceEffect = null; // 'glow', 'mask', or null

  function toggleFaceEffect(effect) {
    if (activeFaceEffect === effect) {
      // Turn off
      activeFaceEffect = null;
      cleanupFaceOverlays();
      showStatus('Face effect disabled.');
      return;
    }

    activeFaceEffect = effect;

    // Try to apply to visible image first
    var img = document.querySelector('img.product-main') || document.querySelector('.product-images img');
    var video = document.querySelector('#watch-video') || document.querySelector('video.product-main');

    if (video && !video.paused) {
      startVideoFaceEffect(video);
    } else if (img) {
      applyImageFaceEffect(img);
    } else {
      showStatus('No image or video found for face detection.');
      activeFaceEffect = null;
    }
  }

  function applyImageFaceEffect(img) {
    showStatus('Detecting faces...');

    // Create a temporary canvas to feed to MediaPipe
    var tempCanvas = document.createElement('canvas');
    var tempImg = new Image();
    tempImg.crossOrigin = 'anonymous';
    tempImg.onload = function() {
      tempCanvas.width = tempImg.naturalWidth;
      tempCanvas.height = tempImg.naturalHeight;
      var tempCtx = tempCanvas.getContext('2d');
      tempCtx.drawImage(tempImg, 0, 0);

      faceMeshModule.onResults(function(results) {
        drawFaceEffects(results, img, tempImg.naturalWidth, tempImg.naturalHeight);
      });

      faceMeshModule.send({ image: tempCanvas }).then(function() {
        showStatus('Face detection complete.');
      }).catch(function() {
        showStatus('Face detection failed on this image.');
      });
    };
    tempImg.onerror = function() {
      showStatus('Cannot load image for face detection — cross-origin denied.');
    };
    tempImg.src = img.src;
  }

  function drawFaceEffects(results, targetElement, origW, origH) {
    if (!results.multiFaceLandmarks || results.multiFaceLandmarks.length === 0) {
      showStatus('No faces detected.');
      return;
    }

    // Position a canvas overlay on top of the target element
    var rect = targetElement.getBoundingClientRect();
    var canvas = document.createElement('canvas');
    canvas.className = 'sandbox-canvas-overlay';
    canvas.width = rect.width;
    canvas.height = rect.height;
    canvas.style.width = rect.width + 'px';
    canvas.style.height = rect.height + 'px';

    // Position relative to the element's parent
    var parent = targetElement.parentElement;
    if (getComputedStyle(parent).position === 'static') {
      parent.style.position = 'relative';
    }
    parent.appendChild(canvas);
    faceCanvasOverlays.push(canvas);

    var ctx = canvas.getContext('2d');
    var scaleX = rect.width / origW;
    var scaleY = rect.height / origH;

    for (var f = 0; f < results.multiFaceLandmarks.length; f++) {
      var landmarks = results.multiFaceLandmarks[f];

      if (activeFaceEffect === 'glow') {
        drawEyeGlow(ctx, landmarks, origW, origH, scaleX, scaleY);
      } else if (activeFaceEffect === 'mask') {
        drawFaceMask(ctx, landmarks, origW, origH, scaleX, scaleY);
      }
    }
  }

  function drawEyeGlow(ctx, landmarks, origW, origH, scaleX, scaleY) {
    // Left iris center (landmark 468), right iris center (landmark 473)
    var irisIndices = [
      { center: 468, ring: [469, 470, 471, 472] },
      { center: 473, ring: [474, 475, 476, 477] },
    ];

    for (var i = 0; i < irisIndices.length; i++) {
      var iris = irisIndices[i];
      if (!landmarks[iris.center]) continue;

      var cx = landmarks[iris.center].x * origW * scaleX;
      var cy = landmarks[iris.center].y * origH * scaleY;

      // Compute iris radius from ring landmarks
      var radius = 0;
      for (var r = 0; r < iris.ring.length; r++) {
        if (!landmarks[iris.ring[r]]) continue;
        var dx = (landmarks[iris.ring[r]].x - landmarks[iris.center].x) * origW * scaleX;
        var dy = (landmarks[iris.ring[r]].y - landmarks[iris.center].y) * origH * scaleY;
        radius = Math.max(radius, Math.sqrt(dx * dx + dy * dy));
      }
      if (radius < 2) radius = 8; // fallback

      var glowRadius = radius * 3;

      // Parse hex color to rgba
      var r_val = parseInt(eyeGlowColor.slice(1, 3), 16);
      var g_val = parseInt(eyeGlowColor.slice(3, 5), 16);
      var b_val = parseInt(eyeGlowColor.slice(5, 7), 16);

      var gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, glowRadius);
      gradient.addColorStop(0, 'rgba(' + r_val + ',' + g_val + ',' + b_val + ',0.9)');
      gradient.addColorStop(0.3, 'rgba(' + r_val + ',' + g_val + ',' + b_val + ',0.6)');
      gradient.addColorStop(0.6, 'rgba(' + r_val + ',' + g_val + ',' + b_val + ',0.2)');
      gradient.addColorStop(1, 'rgba(' + r_val + ',' + g_val + ',' + b_val + ',0)');

      ctx.fillStyle = gradient;
      ctx.beginPath();
      ctx.arc(cx, cy, glowRadius, 0, Math.PI * 2);
      ctx.fill();
    }
  }

  function drawFaceMask(ctx, landmarks, origW, origH, scaleX, scaleY) {
    // Draw a stylized guy fawkes-inspired mask using face landmarks
    // This draws geometric shapes over the face using key landmark positions

    // Face oval outline
    var faceOutline = [10, 338, 297, 332, 284, 251, 389, 356, 454, 323, 361, 288,
                       397, 365, 379, 378, 400, 377, 152, 148, 176, 149, 150, 136,
                       172, 58, 132, 93, 234, 127, 162, 21, 54, 103, 67, 109, 10];
    ctx.beginPath();
    for (var i = 0; i < faceOutline.length; i++) {
      var lm = landmarks[faceOutline[i]];
      if (!lm) continue;
      var x = lm.x * origW * scaleX;
      var y = lm.y * origH * scaleY;
      if (i === 0) ctx.moveTo(x, y);
      else ctx.lineTo(x, y);
    }
    ctx.closePath();
    ctx.fillStyle = 'rgba(240, 230, 210, 0.85)';
    ctx.fill();
    ctx.strokeStyle = '#333';
    ctx.lineWidth = 2;
    ctx.stroke();

    // Eyes — dark almond shapes
    var leftEye = [33, 246, 161, 160, 159, 158, 157, 173, 133, 155, 154, 153, 145, 144, 163, 7, 33];
    var rightEye = [362, 398, 384, 385, 386, 387, 388, 466, 263, 249, 390, 373, 374, 380, 381, 382, 362];
    drawEyeShape(ctx, landmarks, leftEye, origW, origH, scaleX, scaleY);
    drawEyeShape(ctx, landmarks, rightEye, origW, origH, scaleX, scaleY);

    // Eyebrows — arched dark lines
    var leftBrow = [70, 63, 105, 66, 107, 55, 65, 52, 53, 46];
    var rightBrow = [300, 293, 334, 296, 336, 285, 295, 282, 283, 276];
    drawBrowLine(ctx, landmarks, leftBrow, origW, origH, scaleX, scaleY);
    drawBrowLine(ctx, landmarks, rightBrow, origW, origH, scaleX, scaleY);

    // Mustache shape (stylized)
    var moustacheTop = [164, 167, 165, 92, 186, 57, 43, 106, 182, 83, 18, 313, 406, 335, 273, 287, 410, 322, 391, 393, 164];
    ctx.beginPath();
    for (var j = 0; j < moustacheTop.length; j++) {
      var ml = landmarks[moustacheTop[j]];
      if (!ml) continue;
      var mx = ml.x * origW * scaleX;
      var my = ml.y * origH * scaleY;
      if (j === 0) ctx.moveTo(mx, my);
      else ctx.lineTo(mx, my);
    }
    ctx.closePath();
    ctx.fillStyle = '#222';
    ctx.fill();

    // Goatee
    var chin = landmarks[152];
    var lipBottom = landmarks[17];
    if (chin && lipBottom) {
      var goateeX = chin.x * origW * scaleX;
      var goateeY = (lipBottom.y * origH * scaleY + chin.y * origH * scaleY) / 2;
      var goateeW = 15 * scaleX;
      var goateeH = (chin.y - lipBottom.y) * origH * scaleY * 0.8;
      ctx.beginPath();
      ctx.ellipse(goateeX, goateeY, goateeW, goateeH, 0, 0, Math.PI * 2);
      ctx.fillStyle = '#222';
      ctx.fill();
    }

    // Smile lines
    var smileLeft = landmarks[61];
    var smileRight = landmarks[291];
    if (smileLeft && smileRight) {
      ctx.beginPath();
      ctx.arc(
        smileLeft.x * origW * scaleX,
        smileLeft.y * origH * scaleY,
        8 * scaleX, 0.3, 1.2
      );
      ctx.strokeStyle = '#555';
      ctx.lineWidth = 1.5;
      ctx.stroke();

      ctx.beginPath();
      ctx.arc(
        smileRight.x * origW * scaleX,
        smileRight.y * origH * scaleY,
        8 * scaleX, 1.9, 2.8
      );
      ctx.stroke();
    }
  }

  function drawEyeShape(ctx, landmarks, indices, origW, origH, scaleX, scaleY) {
    ctx.beginPath();
    for (var i = 0; i < indices.length; i++) {
      var lm = landmarks[indices[i]];
      if (!lm) continue;
      var x = lm.x * origW * scaleX;
      var y = lm.y * origH * scaleY;
      if (i === 0) ctx.moveTo(x, y);
      else ctx.lineTo(x, y);
    }
    ctx.closePath();
    ctx.fillStyle = '#111';
    ctx.fill();
  }

  function drawBrowLine(ctx, landmarks, indices, origW, origH, scaleX, scaleY) {
    ctx.beginPath();
    for (var i = 0; i < indices.length; i++) {
      var lm = landmarks[indices[i]];
      if (!lm) continue;
      var x = lm.x * origW * scaleX;
      var y = lm.y * origH * scaleY;
      if (i === 0) ctx.moveTo(x, y);
      else ctx.lineTo(x, y);
    }
    ctx.strokeStyle = '#222';
    ctx.lineWidth = 3 * scaleX;
    ctx.stroke();
  }

  function startVideoFaceEffect(video) {
    showStatus('Running face detection on video...');

    // Create a canvas overlay on the video
    var container = video.parentElement;
    var canvas = document.createElement('canvas');
    canvas.className = 'sandbox-canvas-overlay';
    canvas.style.width = '100%';
    canvas.style.height = '100%';

    if (getComputedStyle(container).position === 'static') {
      container.style.position = 'relative';
    }
    container.appendChild(canvas);
    faceCanvasOverlays.push(canvas);

    var ctx = canvas.getContext('2d');

    faceMeshModule.onResults(function(results) {
      canvas.width = video.videoWidth || video.clientWidth;
      canvas.height = video.videoHeight || video.clientHeight;

      ctx.clearRect(0, 0, canvas.width, canvas.height);

      if (!results.multiFaceLandmarks || results.multiFaceLandmarks.length === 0) return;

      var scaleX = canvas.width / (video.videoWidth || canvas.width);
      var scaleY = canvas.height / (video.videoHeight || canvas.height);
      var origW = video.videoWidth || canvas.width;
      var origH = video.videoHeight || canvas.height;

      for (var f = 0; f < results.multiFaceLandmarks.length; f++) {
        if (activeFaceEffect === 'glow') {
          drawEyeGlow(ctx, results.multiFaceLandmarks[f], origW, origH, scaleX, scaleY);
        } else if (activeFaceEffect === 'mask') {
          drawFaceMask(ctx, results.multiFaceLandmarks[f], origW, origH, scaleX, scaleY);
        }
      }
    });

    // Use Camera utility for continuous processing
    if (window.Camera) {
      faceCamera = new window.Camera(video, {
        onFrame: function() {
          if (!activeFaceEffect) {
            if (faceCamera) faceCamera.stop();
            return;
          }
          return faceMeshModule.send({ image: video });
        },
        width: video.videoWidth || 1280,
        height: video.videoHeight || 720,
      });
      faceCamera.start();
    } else {
      // Fallback: manual requestAnimationFrame loop
      function processFrame() {
        if (!activeFaceEffect) return;
        if (video.paused || video.ended) return;
        faceMeshModule.send({ image: video }).then(function() {
          requestAnimationFrame(processFrame);
        });
      }
      processFrame();
    }
  }

  function cleanupFaceOverlays() {
    if (faceCamera) {
      faceCamera.stop();
      faceCamera = null;
    }
    for (var i = 0; i < faceCanvasOverlays.length; i++) {
      if (faceCanvasOverlays[i].parentNode) {
        faceCanvasOverlays[i].parentNode.removeChild(faceCanvasOverlays[i]);
      }
    }
    faceCanvasOverlays = [];
    activeFaceEffect = null;
  }

  // ========================================
  // Panel toggle
  // ========================================
  function setupEvents() {
    toggleBtn.addEventListener('click', function() {
      var isOpen = panel.style.display !== 'none';
      panel.style.display = isOpen ? 'none' : 'block';
      try { localStorage.setItem(STORAGE_PANEL, isOpen ? '0' : '1'); } catch (e) {}
    });

    closeBtn.addEventListener('click', function() {
      panel.style.display = 'none';
      try { localStorage.setItem(STORAGE_PANEL, '0'); } catch (e) {}
    });
  }

  // ========================================
  // State persistence (localStorage)
  // ========================================
  function saveState() {
    try {
      localStorage.setItem(STORAGE_PRESET, String(currentPresetIndex));
      localStorage.setItem(STORAGE_SLIDERS, JSON.stringify(sliderValues));
      localStorage.setItem(STORAGE_MODE, filterMode);
    } catch (e) {}
  }

  function restoreState() {
    try {
      // Restore panel open state
      var panelOpen = localStorage.getItem(STORAGE_PANEL);
      if (panelOpen === '1') {
        panel.style.display = 'block';
      }

      // Restore filter mode
      var savedMode = localStorage.getItem(STORAGE_MODE);
      if (savedMode === 'preset' || savedMode === 'custom') {
        filterMode = savedMode;
      }

      // Restore preset
      var savedPreset = localStorage.getItem(STORAGE_PRESET);
      if (savedPreset !== null) {
        var idx = parseInt(savedPreset, 10);
        if (idx >= 0 && idx < PRESETS.length) {
          currentPresetIndex = idx;
          var btns = presetsContainer.querySelectorAll('.sandbox-preset-btn');
          for (var i = 0; i < btns.length; i++) {
            btns[i].classList.toggle('active', parseInt(btns[i].dataset.index, 10) === idx);
          }
        }
      }

      // Restore sliders
      var savedSliders = localStorage.getItem(STORAGE_SLIDERS);
      if (savedSliders) {
        var parsed = JSON.parse(savedSliders);
        for (var key in parsed) {
          if (parsed.hasOwnProperty(key) && sliderValues.hasOwnProperty(key)) {
            sliderValues[key] = parsed[key];
          }
        }
        updateSliderInputs();
      }

      // Apply restored filter
      applyCurrentFilter();
    } catch (e) {}
  }

  // ========================================
  // Utilities
  // ========================================
  function downloadBlob(blob, filename) {
    var url = URL.createObjectURL(blob);
    var a = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    setTimeout(function() { URL.revokeObjectURL(url); }, 1000);
  }

  function showStatus(msg) {
    // Show a temporary status message in the panel
    var existing = document.querySelector('.sandbox-status');
    if (existing) existing.parentNode.removeChild(existing);

    var status = document.createElement('div');
    status.className = 'sandbox-status';
    status.textContent = msg;
    actionsContainer.parentNode.insertBefore(status, actionsContainer.nextSibling);

    setTimeout(function() {
      if (status.parentNode) status.parentNode.removeChild(status);
    }, 5000);
  }

  // ---- Go ----
  init();

})();
