<template>
  <div class="min-h-screen p-3 sm:p-6 bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 transition-colors duration-300">
    <div class="max-w-7xl mx-auto">
      <div class="flex flex-col sm:flex-row justify-between items-center mb-4 sm:mb-8 gap-2">
        <h1 class="text-2xl sm:text-4xl font-bold text-gray-900 dark:text-white tracking-tight text-center sm:text-left">Spritesheet generator</h1>
        <dark-mode-toggle />
      </div>
      <div class="flex flex-wrap justify-center gap-4 mb-4 sm:mb-8">
        <a href="https://gitea.adhd.sh/root/spritesheet-generator" target="_blank" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors" data-rybbit-event="source-link"> <i class="fab fa-github"></i> Source </a>
        <a href="https://discord.gg/JTev3nzeDa" target="_blank" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors" data-rybbit-event="discord-link"> <i class="fab fa-discord"></i> Discord </a>
        <a href="#" @click.prevent="openHelpModal" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors" data-rybbit-event="help-link"> <i class="fas fa-question-circle"></i> Help </a>
        <a href="#" @click.prevent="openFeedbackModal" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors" data-rybbit-event="feedback-link"> <i class="fas fa-comment-dots"></i> Feedback </a>
      </div>

      <div class="bg-white dark:bg-gray-800 rounded-2xl shadow-soft dark:shadow-gray-900/30 p-4 sm:p-8 transition-colors duration-300">
        <div class="flex flex-col sm:flex-row justify-between items-center mb-4 sm:mb-6 gap-3">
          <h2 class="text-xl font-semibold text-gray-800 dark:text-gray-100">Upload sprites</h2>
          <button
            @click="openJSONImportDialog"
            class="w-full sm:w-auto px-4 py-2 bg-indigo-500 hover:bg-indigo-600 dark:bg-indigo-600 dark:hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center sm:justify-start space-x-2"
            data-rybbit-event="import-json"
          >
            <i class="fas fa-file-import"></i>
            <span>Import JSON</span>
          </button>
        </div>
        <file-uploader @upload-sprites="handleSpritesUpload" />
        <input ref="jsonFileInput" type="file" accept=".json,application/json" class="hidden" @change="handleJSONFileChange" />
        <div v-if="!sprites.length" class="mt-8">
          <div class="mt-2 leading-relaxed space-y-2">
            <p>Create spritesheets for your game development and animation projects with our completely free, open-source spritesheet generator.</p>
            <p>This powerful online tool lets you upload individual sprite images and automatically arrange them into optimized sprite sheets with customizable layouts - perfect for indie developers, animators, and studios of any size.</p>
            <p class="font-bold text-2xl pb-3 pt-2">How it works:</p>
            <video controls playsinline class="w-full h-full object-contain rounded-lg shadow-md" title="Spritesheet generator tutorial" aria-label="Spritesheet generator tutorial">
              <source src="@/assets/tut2.mp4" type="video/mp4" />
            </video>
          </div>
        </div>

        <div v-if="sprites.length > 0" class="mt-8">
          <div class="flex flex-wrap items-center justify-center sm:justify-start gap-3 sm:gap-6 mb-6 sm:mb-8">
            <div class="flex items-center space-x-1">
              <label for="columns" class="text-gray-700 dark:text-gray-200 font-medium">Columns:</label>
              <input
                id="columns"
                type="number"
                v-model.number="columns"
                min="1"
                max="10"
                class="w-20 px-3 py-2 border border-gray-200 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent outline-none transition-all text-base"
              />
            </div>

            <!-- Add mass position buttons -->
            <div class="flex flex-wrap items-center justify-center gap-2">
              <button @click="alignSprites('left')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Left" data-rybbit-event="align-left">
                <i class="fas fa-arrow-left"></i>
              </button>
              <button @click="alignSprites('center')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Center" data-rybbit-event="align-center">
                <i class="fas fa-arrows-left-right"></i>
              </button>
              <button @click="alignSprites('right')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Right" data-rybbit-event="align-right">
                <i class="fas fa-arrow-right"></i>
              </button>
              <button @click="alignSprites('top')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Top" data-rybbit-event="align-top">
                <i class="fas fa-arrow-up"></i>
              </button>
              <button @click="alignSprites('middle')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Middle" data-rybbit-event="align-middle">
                <i class="fas fa-arrows-up-down"></i>
              </button>
              <button @click="alignSprites('bottom')" class="p-3 sm:p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors" title="Align Bottom" data-rybbit-event="align-bottom">
                <i class="fas fa-arrow-down"></i>
              </button>
            </div>

            <button @click="downloadSpritesheet" class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center space-x-2" data-rybbit-event="download-spritesheet">
              <i class="fas fa-download"></i>
              <span>Download spritesheet</span>
            </button>

            <button
              @click="exportSpritesheetJSON"
              class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-purple-500 hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center space-x-2"
              data-rybbit-event="export-json"
            >
              <i class="fas fa-file-code"></i>
              <span>Export as JSON</span>
            </button>

            <button @click="openGifFpsModal" class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-amber-500 hover:bg-amber-600 dark:bg-amber-600 dark:hover:bg-amber-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center space-x-2" data-rybbit-event="download-gif">
              <i class="fas fa-film"></i>
              <span>Download as GIF</span>
            </button>

            <button @click="downloadAsZip" class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-teal-500 hover:bg-teal-600 dark:bg-teal-600 dark:hover:bg-teal-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center space-x-2" data-rybbit-event="download-zip">
              <i class="fas fa-file-archive"></i>
              <span>Download as ZIP</span>
            </button>

            <button @click="openPreviewModal" class="w-full sm:w-auto px-6 py-3 sm:py-2.5 bg-green-500 hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center space-x-2" data-rybbit-event="preview-animation">
              <i class="fas fa-play"></i>
              <span>Preview animation</span>
            </button>
          </div>

          <sprite-canvas :sprites="sprites" :columns="columns" @update-sprite="updateSpritePosition" @update-sprite-cell="updateSpriteCell" @remove-sprite="removeSprite" @replace-sprite="replaceSprite" @add-sprite="addSprite" @add-sprite-with-resize="addSpriteWithResize" />
        </div>
      </div>
    </div>

    <Modal :is-open="isPreviewModalOpen" @close="closePreviewModal" title="Animation preview">
      <sprite-preview :sprites="sprites" :columns="columns" @update-sprite="updateSpritePosition" />
    </Modal>

    <HelpModal :is-open="isHelpModalOpen" @close="closeHelpModal" />
    <FeedbackModal :is-open="isFeedbackModalOpen" @close="closeFeedbackModal" />
    <SpritesheetSplitter :is-open="isSpritesheetSplitterOpen" :image-url="spritesheetImageUrl" :image-file="spritesheetImageFile" @close="closeSpritesheetSplitter" @split="handleSplitSpritesheet" />
    <GifFpsModal :is-open="isGifFpsModalOpen" @close="closeGifFpsModal" @confirm="downloadAsGif" :default-fps="10" />

    <!-- One-time feedback popup -->
    <div v-if="showFeedbackPopup" class="fixed inset-0 backdrop-blur-sm flex items-center justify-center z-50">
      <div class="bg-white dark:bg-gray-800 rounded-xl p-6 max-w-md mx-4 shadow-xl border border-gray-600">
        <div class="text-center">
          <div class="text-4xl mb-4">💬</div>
          <h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3">Help us improve!</h3>
          <p class="text-gray-600 dark:text-gray-300 mb-6">We'd love to hear your thoughts about the spritesheet generator. Would you like to share your feedback?</p>
          <div class="flex gap-3 justify-center">
            <button @click="handleFeedbackPopupResponse(false)" class="px-4 py-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors">Maybe later</button>
            <button @click="handleFeedbackPopupResponse(true)" class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors">Share feedback</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
  import { ref, watch, onMounted, onUnmounted } from 'vue';
  import FileUploader from './components/FileUploader.vue';
  import SpriteCanvas from './components/SpriteCanvas.vue';
  import Modal from './components/utilities/Modal.vue';
  import SpritePreview from './components/SpritePreview.vue';
  import HelpModal from './components/HelpModal.vue';
  import FeedbackModal from './components/FeedbackModal.vue';
  import SpritesheetSplitter from './components/SpritesheetSplitter.vue';
  import GifFpsModal from './components/GifFpsModal.vue';
  import DarkModeToggle from './components/utilities/DarkModeToggle.vue';
  import GIF from 'gif.js';
  import gifWorkerUrl from 'gif.js/dist/gif.worker.js?url';
  import JSZip from 'jszip';

  interface Sprite {
    id: string;
    file: File;
    img: HTMLImageElement;
    url: string;
    width: number;
    height: number;
    x: number;
    y: number;
  }

  interface SpriteFile {
    file: File;
    x: number;
    y: number;
    width: number;
    height: number;
  }

  const sprites = ref<Sprite[]>([]);
  const columns = ref(4);
  // Clamp and coerce columns to a safe range [1..10]
  watch(columns, val => {
    const num = typeof val === 'number' ? val : parseInt(String(val));
    const safe = Number.isFinite(num) && num >= 1 ? Math.min(num, 10) : 1;
    if (safe !== columns.value) columns.value = safe;
  });
  const isPreviewModalOpen = ref(false);
  const isHelpModalOpen = ref(false);
  const isFeedbackModalOpen = ref(false);
  const isSpritesheetSplitterOpen = ref(false);
  const isGifFpsModalOpen = ref(false);
  const jsonFileInput = ref<HTMLInputElement | null>(null);
  const spritesheetImageUrl = ref('');
  const spritesheetImageFile = ref<File | null>(null);
  const showFeedbackPopup = ref(false);

  const handleSpritesUpload = (files: File[]) => {
    // Check if any of the files is a JSON file
    const jsonFile = files.find(file => file.type === 'application/json' || file.name.endsWith('.json'));

    if (jsonFile) {
      // If it's a JSON file, try to import it
      importSpritesheetJSON(jsonFile);
      return;
    }

    // Check if it's a single image file that might be a spritesheet
    if (files.length === 1 && files[0].type.startsWith('image/')) {
      const file = files[0];
      const url = URL.createObjectURL(file);

      // Load the image to check its dimensions
      const img = new Image();
      img.onload = () => {
        // Ask the user if they want to split the spritesheet
        if (confirm('This looks like it might be a spritesheet. Would you like to split it into individual sprites?')) {
          // Open the spritesheet splitter
          spritesheetImageUrl.value = url;
          spritesheetImageFile.value = file;
          isSpritesheetSplitterOpen.value = true;
          return;
        }

        // If the user doesn't want to split or it's not large enough, process as a single sprite
        processImageFiles([file]);
      };
      img.src = url;
      return;
    }

    // Process multiple image files normally
    processImageFiles(files);
  };

  // Extract the image processing logic to a separate function for reuse
  const processImageFiles = (files: File[]) => {
    Promise.all(
      files.map(file => {
        return new Promise<Sprite>(resolve => {
          const url = URL.createObjectURL(file);
          const img = new Image();
          img.onload = () => {
            resolve({
              id: crypto.randomUUID(),
              file,
              img,
              url,
              width: img.width,
              height: img.height,
              x: 0,
              y: 0,
            });
          };
          img.src = url;
        });
      })
    ).then(newSprites => {
      sprites.value = [...sprites.value, ...newSprites];
    });
  };

  const updateSpritePosition = (id: string, x: number, y: number) => {
    const spriteIndex = sprites.value.findIndex(sprite => sprite.id === id);
    if (spriteIndex !== -1) {
      // Ensure integer positions for pixel-perfect rendering
      sprites.value[spriteIndex].x = Math.floor(x);
      sprites.value[spriteIndex].y = Math.floor(y);
    }
  };

  const downloadSpritesheet = () => {
    if (sprites.value.length === 0) {
      alert('Please upload or import sprites before downloading the spritesheet.');
      return;
    }

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    if (!ctx) return;

    // Find max dimensions
    let maxWidth = 0;
    let maxHeight = 0;

    sprites.value.forEach(sprite => {
      if (sprite.width > maxWidth) maxWidth = sprite.width;
      if (sprite.height > maxHeight) maxHeight = sprite.height;
    });

    // Set canvas size
    const rows = Math.ceil(sprites.value.length / columns.value);
    canvas.width = maxWidth * columns.value;
    canvas.height = maxHeight * rows;

    // Disable image smoothing for pixel-perfect rendering
    ctx.imageSmoothingEnabled = false;

    // Draw sprites with integer positions
    sprites.value.forEach((sprite, index) => {
      const col = index % columns.value;
      const row = Math.floor(index / columns.value);

      const cellX = Math.floor(col * maxWidth);
      const cellY = Math.floor(row * maxHeight);

      ctx.drawImage(sprite.img, Math.floor(cellX + sprite.x), Math.floor(cellY + sprite.y));
    });

    // Create download link with PNG format
    const link = document.createElement('a');
    link.download = 'spritesheet.png';
    link.href = canvas.toDataURL('image/png', 1.0); // Use maximum quality
    link.click();
  };

  // Preview modal control
  const openPreviewModal = () => {
    if (sprites.value.length === 0) {
      alert('Please upload or import sprites to preview an animation.');
      return;
    }
    isPreviewModalOpen.value = true;
  };

  const closePreviewModal = () => {
    isPreviewModalOpen.value = false;
  };

  // Help modal control
  const openHelpModal = () => {
    isHelpModalOpen.value = true;
  };

  const closeHelpModal = () => {
    isHelpModalOpen.value = false;
  };

  // Feedback modal control
  const openFeedbackModal = () => {
    isFeedbackModalOpen.value = true;
  };

  const closeFeedbackModal = () => {
    isFeedbackModalOpen.value = false;
  };

  // Spritesheet splitter modal control
  const closeSpritesheetSplitter = () => {
    isSpritesheetSplitterOpen.value = false;
    // Clean up the URL object to prevent memory leaks
    if (spritesheetImageUrl.value) {
      URL.revokeObjectURL(spritesheetImageUrl.value);
      spritesheetImageUrl.value = '';
    }
    spritesheetImageFile.value = null;
  };

  // GIF FPS modal control
  const openGifFpsModal = () => {
    if (sprites.value.length === 0) {
      alert('Please upload or import sprites before generating a GIF.');
      return;
    }
    isGifFpsModalOpen.value = true;
  };

  const closeGifFpsModal = () => {
    isGifFpsModalOpen.value = false;
  };

  // Handle the split spritesheet result
  const handleSplitSpritesheet = (spriteFiles: SpriteFile[]) => {
    // Process sprite files with their positions
    Promise.all(
      spriteFiles.map(spriteFile => {
        return new Promise<Sprite>(resolve => {
          const url = URL.createObjectURL(spriteFile.file);
          const img = new Image();
          img.onload = () => {
            resolve({
              id: crypto.randomUUID(),
              file: spriteFile.file,
              img,
              url,
              width: img.width,
              height: img.height,
              x: 0, // Start at top-left of cell; ignore splitter bounding-box offset for display
              y: 0, // Start at top-left of cell; ignore splitter bounding-box offset for display
            });
          };
          img.src = url;
        });
      })
    ).then(newSprites => {
      sprites.value = [...sprites.value, ...newSprites];
    });
  };

  // Export spritesheet as JSON with base64 images
  const exportSpritesheetJSON = async () => {
    if (sprites.value.length === 0) {
      alert('Nothing to export. Please add sprites first.');
      return;
    }

    // Create an array to store sprite data with base64 images
    const spritesData = await Promise.all(
      sprites.value.map(async (sprite, index) => {
        // Create a canvas for each sprite to get its base64 data
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        if (!ctx) return null;

        // Set canvas size to match the sprite
        canvas.width = sprite.width;
        canvas.height = sprite.height;

        // Draw the sprite
        ctx.drawImage(sprite.img, 0, 0);

        // Get base64 data
        const base64Data = canvas.toDataURL('image/png');

        return {
          id: sprite.id,
          width: sprite.width,
          height: sprite.height,
          x: sprite.x,
          y: sprite.y,
          base64: base64Data,
          name: sprite.file.name,
        };
      })
    );

    // Create JSON object with all necessary data
    const jsonData = {
      columns: columns.value,
      sprites: spritesData.filter(Boolean), // Remove any null values
    };

    // Convert to JSON string
    const jsonString = JSON.stringify(jsonData, null, 2);

    // Create download link
    const blob = new Blob([jsonString], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.download = 'spritesheet.json';
    link.href = url;
    link.click();

    // Clean up
    URL.revokeObjectURL(url);
  };

  // Open file dialog for JSON import
  const openJSONImportDialog = () => {
    jsonFileInput.value?.click();
  };

  // Handle JSON file selection
  const handleJSONFileChange = (event: Event) => {
    const input = event.target as HTMLInputElement;
    if (input.files && input.files.length > 0) {
      const jsonFile = input.files[0];
      importSpritesheetJSON(jsonFile);
      // Reset input value so uploading the same file again will trigger the event
      if (jsonFileInput.value) jsonFileInput.value.value = '';
    }
  };

  // Import spritesheet from JSON
  const importSpritesheetJSON = async (jsonFile: File) => {
    try {
      const jsonText = await jsonFile.text();
      const jsonData = JSON.parse(jsonText);

      if (!jsonData.sprites || !Array.isArray(jsonData.sprites)) {
        throw new Error('Invalid JSON format: missing sprites array');
      }

      // Set columns if available
      if (jsonData.columns && typeof jsonData.columns === 'number') {
        columns.value = jsonData.columns;
      }

      // Process each sprite
      // Replace current sprites with imported ones
      // Revoke existing blob: URLs to avoid memory leaks
      if (sprites.value.length) {
        sprites.value.forEach(s => {
          if (s.url && s.url.startsWith('blob:')) {
            try {
              URL.revokeObjectURL(s.url);
            } catch {}
          }
        });
      }

      sprites.value = await Promise.all(
        jsonData.sprites.map(async (spriteData: any) => {
          return new Promise<Sprite>(resolve => {
            // Create image from base64
            const img = new Image();
            img.onload = () => {
              // Create a file from the base64 data
              const byteString = atob(spriteData.base64.split(',')[1]);
              const mimeType = spriteData.base64.split(',')[0].split(':')[1].split(';')[0];
              const ab = new ArrayBuffer(byteString.length);
              const ia = new Uint8Array(ab);

              for (let i = 0; i < byteString.length; i++) {
                ia[i] = byteString.charCodeAt(i);
              }

              const blob = new Blob([ab], { type: mimeType });
              const fileName = spriteData.name || `sprite-${spriteData.id}.png`;
              const file = new File([blob], fileName, { type: mimeType });

              resolve({
                id: spriteData.id || crypto.randomUUID(),
                file,
                img,
                url: spriteData.base64,
                width: spriteData.width,
                height: spriteData.height,
                x: spriteData.x || 0,
                y: spriteData.y || 0,
              });
            };
            img.src = spriteData.base64;
          });
        })
      );
    } catch (error) {
      console.error('Error importing JSON:', error);
      alert('Failed to import JSON file. Please check the file format.');
    }
  };

  // Add new alignment function
  const alignSprites = (position: 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom') => {
    if (sprites.value.length === 0) return;

    // Find max dimensions for the current column layout
    let maxWidth = 0;
    let maxHeight = 0;
    sprites.value.forEach(sprite => {
      maxWidth = Math.max(maxWidth, sprite.width);
      maxHeight = Math.max(maxHeight, sprite.height);
    });

    sprites.value = sprites.value.map((sprite, index) => {
      let x = sprite.x;
      let y = sprite.y;

      switch (position) {
        case 'left':
          x = 0;
          break;
        case 'center':
          x = Math.floor((maxWidth - sprite.width) / 2);
          break;
        case 'right':
          x = Math.floor(maxWidth - sprite.width);
          break;
        case 'top':
          y = 0;
          break;
        case 'middle':
          y = Math.floor((maxHeight - sprite.height) / 2);
          break;
        case 'bottom':
          y = Math.floor(maxHeight - sprite.height);
          break;
      }

      // Ensure integer positions for pixel-perfect rendering
      return { ...sprite, x: Math.floor(x), y: Math.floor(y) };
    });

    // Force redraw of the preview canvas
    setTimeout(() => {
      const event = new Event('forceRedraw');
      window.dispatchEvent(event);
    }, 0);
  };

  const updateSpriteCell = (id: string, newIndex: number) => {
    // Find the current index of the sprite
    const currentIndex = sprites.value.findIndex(sprite => sprite.id === id);
    if (currentIndex === -1) return;

    // If we're trying to move to the same position, do nothing
    if (currentIndex === newIndex) return;

    // Create a copy of the sprites array
    const newSprites = [...sprites.value];

    // Perform a swap between the two positions
    if (newIndex < sprites.value.length) {
      // Get references to both sprites
      const movingSprite = { ...newSprites[currentIndex] };
      const targetSprite = { ...newSprites[newIndex] };

      // Swap them
      newSprites[currentIndex] = targetSprite;
      newSprites[newIndex] = movingSprite;
    } else {
      // If dragging to an empty cell (beyond the array length)
      // Use the original reordering logic
      const [movedSprite] = newSprites.splice(currentIndex, 1);
      newSprites.splice(newIndex, 0, movedSprite);
    }

    // Update the sprites array
    sprites.value = newSprites;
  };

  const removeSprite = (id: string) => {
    const spriteIndex = sprites.value.findIndex(sprite => sprite.id === id);
    if (spriteIndex !== -1) {
      const sprite = sprites.value[spriteIndex];
      // Revoke the blob URL to prevent memory leaks
      if (sprite.url && sprite.url.startsWith('blob:')) {
        try {
          URL.revokeObjectURL(sprite.url);
        } catch {}
      }
      // Remove the sprite from the array
      sprites.value.splice(spriteIndex, 1);
    }
  };

  const replaceSprite = (id: string, file: File) => {
    const spriteIndex = sprites.value.findIndex(sprite => sprite.id === id);

    if (spriteIndex !== -1) {
      const oldSprite = sprites.value[spriteIndex];

      // Revoke the old blob URL to prevent memory leaks
      if (oldSprite.url && oldSprite.url.startsWith('blob:')) {
        try {
          URL.revokeObjectURL(oldSprite.url);
        } catch {}
      }

      // Create new sprite from the replacement file
      const url = URL.createObjectURL(file);
      const img = new Image();
      img.onload = () => {
        const newSprite: Sprite = {
          id: oldSprite.id, // Keep the same ID
          file,
          img,
          url,
          width: img.width,
          height: img.height,
          x: oldSprite.x, // Keep the same position
          y: oldSprite.y,
        };
        // Create a new array to trigger Vue's reactivity
        const newSprites = [...sprites.value];
        newSprites[spriteIndex] = newSprite;
        sprites.value = newSprites;
      };
      img.onerror = () => {
        console.error('Failed to load replacement image:', file.name);
        URL.revokeObjectURL(url);
      };
      img.src = url;
    }
  };

  const addSprite = (file: File) => {
    const url = URL.createObjectURL(file);
    const img = new Image();
    img.onload = () => {
      const newSprite: Sprite = {
        id: crypto.randomUUID(),
        file,
        img,
        url,
        width: img.width,
        height: img.height,
        x: 0,
        y: 0,
      };
      sprites.value = [...sprites.value, newSprite];
    };
    img.onerror = () => {
      console.error('Failed to load new sprite image:', file.name);
      URL.revokeObjectURL(url);
    };
    img.src = url;
  };

  const addSpriteWithResize = (file: File) => {
    const url = URL.createObjectURL(file);
    const img = new Image();
    img.onload = () => {
      // Find current max dimensions
      let maxWidth = 0;
      let maxHeight = 0;
      sprites.value.forEach(sprite => {
        maxWidth = Math.max(maxWidth, sprite.width);
        maxHeight = Math.max(maxHeight, sprite.height);
      });

      // Create new sprite
      const newSprite: Sprite = {
        id: crypto.randomUUID(),
        file,
        img,
        url,
        width: img.width,
        height: img.height,
        x: 0,
        y: 0,
      };

      // Calculate new max dimensions after adding the new sprite
      const newMaxWidth = Math.max(maxWidth, img.width);
      const newMaxHeight = Math.max(maxHeight, img.height);

      // Resize existing sprites if the new image is larger
      if (img.width > maxWidth || img.height > maxHeight) {
        // Update all existing sprites to center them in the new larger cells
        sprites.value = sprites.value.map(sprite => {
          let newX = sprite.x;
          let newY = sprite.y;

          // Adjust x position if width increased
          if (img.width > maxWidth) {
            const widthDiff = newMaxWidth - maxWidth;
            // Try to keep the sprite in the same relative position
            const relativeX = maxWidth > 0 ? sprite.x / maxWidth : 0;
            newX = Math.floor(relativeX * newMaxWidth);
            // Make sure it doesn't go out of bounds
            newX = Math.max(0, Math.min(newX, newMaxWidth - sprite.width));
          }

          // Adjust y position if height increased
          if (img.height > maxHeight) {
            const heightDiff = newMaxHeight - maxHeight;
            const relativeY = maxHeight > 0 ? sprite.y / maxHeight : 0;
            newY = Math.floor(relativeY * newMaxHeight);
            newY = Math.max(0, Math.min(newY, newMaxHeight - sprite.height));
          }

          return { ...sprite, x: newX, y: newY };
        });
      }

      // Add the new sprite
      sprites.value = [...sprites.value, newSprite];

      // Force redraw of the canvas
      setTimeout(() => {
        const event = new Event('forceRedraw');
        window.dispatchEvent(event);
      }, 0);
    };
    img.onerror = () => {
      console.error('Failed to load new sprite image:', file.name);
      URL.revokeObjectURL(url);
    };
    img.src = url;
  };

  // Download as GIF with specified FPS
  const downloadAsGif = (fps: number) => {
    if (sprites.value.length === 0) {
      alert('Please upload or import sprites before generating a GIF.');
      return;
    }

    // Find max dimensions
    let maxWidth = 0;
    let maxHeight = 0;

    sprites.value.forEach(sprite => {
      if (sprite.width > maxWidth) maxWidth = sprite.width;
      if (sprite.height > maxHeight) maxHeight = sprite.height;
    });

    // Create a canvas for rendering frames
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    if (!ctx) return;

    // Set canvas size to just fit one sprite cell
    canvas.width = maxWidth;
    canvas.height = maxHeight;

    // Disable image smoothing for pixel-perfect rendering
    ctx.imageSmoothingEnabled = false;

    // Create GIF encoder
    const gif = new GIF({
      workers: 2,
      quality: 10,
      width: maxWidth,
      height: maxHeight,
      workerScript: gifWorkerUrl,
    });

    // Add each sprite as a frame
    sprites.value.forEach(sprite => {
      // Clear canvas
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      // Draw grid background (cell)
      ctx.fillStyle = '#f9fafb';
      ctx.fillRect(0, 0, maxWidth, maxHeight);

      // Draw sprite
      ctx.drawImage(sprite.img, Math.floor(sprite.x), Math.floor(sprite.y));

      // Add frame to GIF
      gif.addFrame(ctx, { copy: true, delay: 1000 / fps });
    });

    // Generate GIF
    gif.on('finished', (blob: Blob) => {
      // Create download link
      const url = URL.createObjectURL(blob);
      const link = document.createElement('a');
      link.download = 'animation.gif';
      link.href = url;
      link.click();

      // Clean up
      URL.revokeObjectURL(url);
    });

    gif.render();
  };

  // Check for one-time feedback popup on mount
  onMounted(() => {
    const hasShownFeedbackPopup = localStorage.getItem('hasShownFeedbackPopup');
    if (!hasShownFeedbackPopup) {
      // Show popup after a short delay to let the page load
      setTimeout(() => {
        showFeedbackPopup.value = true;
      }, 3000);
    }
  });

  // Handle feedback popup response
  const handleFeedbackPopupResponse = (showModal: boolean) => {
    showFeedbackPopup.value = false;
    localStorage.setItem('hasShownFeedbackPopup', 'true');

    if (showModal) {
      openFeedbackModal();
    }
  };

  // Revoke blob URLs on unmount to avoid memory leaks
  onUnmounted(() => {
    sprites.value.forEach(s => {
      if (s.url && s.url.startsWith('blob:')) {
        try {
          URL.revokeObjectURL(s.url);
        } catch {}
      }
    });
  });

  // Download as ZIP with each cell individually
  const downloadAsZip = async () => {
    if (sprites.value.length === 0) {
      alert('Please upload or import sprites before downloading a ZIP.');
      return;
    }

    // Create a new ZIP file
    const zip = new JSZip();

    // Find max dimensions
    let maxWidth = 0;
    let maxHeight = 0;

    sprites.value.forEach(sprite => {
      if (sprite.width > maxWidth) maxWidth = sprite.width;
      if (sprite.height > maxHeight) maxHeight = sprite.height;
    });

    // Create a canvas for rendering individual sprites
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    if (!ctx) return;

    // Set canvas size to just fit one sprite cell
    canvas.width = maxWidth;
    canvas.height = maxHeight;

    // Disable image smoothing for pixel-perfect rendering
    ctx.imageSmoothingEnabled = false;

    // Add each sprite as an individual file
    sprites.value.forEach((sprite, index) => {
      // Clear canvas
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      // Draw grid background (cell)
      ctx.fillStyle = '#f9fafb';
      ctx.fillRect(0, 0, maxWidth, maxHeight);

      // Draw sprite
      ctx.drawImage(sprite.img, Math.floor(sprite.x), Math.floor(sprite.y));

      // Convert to PNG data URL
      const dataURL = canvas.toDataURL('image/png');

      // Convert data URL to binary data
      const binaryData = atob(dataURL.split(',')[1]);
      const arrayBuffer = new ArrayBuffer(binaryData.length);
      const uint8Array = new Uint8Array(arrayBuffer);

      for (let i = 0; i < binaryData.length; i++) {
        uint8Array[i] = binaryData.charCodeAt(i);
      }

      // Add to ZIP file with clearer naming
      const baseName = sprite.file?.name ? sprite.file.name.replace(/\s+/g, '_') : `sprite_${index + 1}.png`;
      const name = `${index + 1}_${baseName}`;
      zip.file(name, uint8Array);
    });

    // Generate ZIP file
    const content = await zip.generateAsync({ type: 'blob' });

    // Create download link
    const url = URL.createObjectURL(content);
    const link = document.createElement('a');
    link.download = 'sprites.zip';
    link.href = url;
    link.click();

    // Clean up
    URL.revokeObjectURL(url);
  };
</script>
