<template>
  <Modal :is-open="isOpen" @close="cancel" title="Split spritesheet">
    <div class="space-y-6">
      <div class="flex flex-col space-y-4">
        <div class="flex items-center justify-center mb-4">
          <img :src="imageUrl" alt="Spritesheet" class="max-w-full max-h-48 sm:max-h-64 border border-gray-300 dark:border-gray-600 rounded-lg" :style="settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}" />
        </div>

        <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
          <div class="space-y-2">
            <label for="detection-method" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Detection Method</label>
            <select
              id="detection-method"
              v-model="detectionMethod"
              class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-400 dark:focus:border-blue-400"
              data-rybbit-event="spritesheet-detection-method"
            >
              <option value="irregular">Auto-detect</option>
              <option value="manual">Manual (specify rows and columns)</option>
            </select>
          </div>

          <div v-if="detectionMethod === 'auto' || detectionMethod === 'irregular'" class="space-y-2">
            <label for="sensitivity" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Detection Sensitivity</label>
            <input type="range" id="sensitivity" v-model="sensitivity" min="1" max="100" class="w-full dark:accent-blue-400" data-rybbit-event="spritesheet-sensitivity" />
            <div class="text-xs text-gray-500 dark:text-gray-400 flex justify-between">
              <span>Low</span>
              <span>High</span>
            </div>
          </div>

          <div v-if="detectionMethod === 'manual'" class="space-y-2">
            <label for="rows" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Rows</label>
            <input
              type="number"
              id="rows"
              v-model.number="rows"
              min="1"
              class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-400 dark:focus:border-blue-400"
              data-rybbit-event="spritesheet-rows"
            />
          </div>

          <div v-if="detectionMethod === 'manual'" class="space-y-2">
            <label for="columns" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Columns</label>
            <input
              type="number"
              id="columns"
              v-model.number="columns"
              min="1"
              class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-400 dark:focus:border-blue-400"
              data-rybbit-event="spritesheet-columns"
            />
          </div>
        </div>

        <div class="space-y-2">
          <div class="flex items-center">
            <input type="checkbox" id="remove-empty" v-model="removeEmpty" class="h-4 w-4 text-blue-600 dark:text-blue-400 focus:ring-blue-500 dark:focus:ring-blue-400 border-gray-300 dark:border-gray-600 rounded" data-rybbit-event="spritesheet-remove-empty" />
            <label for="remove-empty" class="ml-2 block text-sm text-gray-700 dark:text-gray-300"> Remove empty sprites (transparent/background color) </label>
          </div>
        </div>

        <div v-if="previewSprites.length > 0" class="space-y-2">
          <h3 class="text-sm font-medium text-gray-700 dark:text-gray-300">Preview ({{ previewSprites.length }} sprites)</h3>
          <div class="grid grid-cols-3 sm:grid-cols-6 md:grid-cols-8 gap-2 max-h-96 overflow-y-auto p-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800">
            <div v-for="(sprite, index) in previewSprites" :key="index" class="relative border border-gray-300 dark:border-gray-600 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center" :style="{ width: '80px', height: '80px' }">
              <img :src="sprite.url" alt="Sprite preview" class="max-w-full max-h-full" :style="settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}" />
            </div>
          </div>
        </div>
      </div>

      <div class="flex flex-col-reverse sm:flex-row sm:justify-end space-y-3 space-y-reverse sm:space-y-0 sm:space-x-3">
        <button
          @click="cancel"
          class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-gray-800"
          data-rybbit-event="spritesheet-cancel"
        >
          Cancel
        </button>
        <button
          @click="confirm"
          class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-gray-800"
          :disabled="previewSprites.length === 0 || isProcessing"
          data-rybbit-event="spritesheet-split"
        >
          Split spritesheet
        </button>
      </div>
    </div>
  </Modal>
</template>

<script setup lang="ts">
  import { ref, watch, onUnmounted } from 'vue';
  import Modal from './utilities/Modal.vue';
  import { useSettingsStore } from '@/stores/useSettingsStore';
  import type { SpriteFile } from '@/types/sprites';

  interface SpritePreview {
    url: string;
    x: number;
    y: number;
    width: number;
    height: number;
    isEmpty: boolean;
  }

  const props = defineProps<{
    isOpen: boolean;
    imageUrl: string;
    imageFile: File | null | undefined;
  }>();

  const emit = defineEmits<{
    (e: 'close'): void;
    (e: 'split', sprites: SpriteFile[]): void; // Change from File[] to SpriteFile[]
  }>();

  // Get settings from store
  const settingsStore = useSettingsStore();

  // State
  const detectionMethod = ref<'manual' | 'auto' | 'irregular'>('irregular');
  const rows = ref(1);
  const columns = ref(1);
  const sensitivity = ref(50);
  const removeEmpty = ref(true);
  const previewSprites = ref<SpritePreview[]>([]);
  const isProcessing = ref(false);
  const imageElement = ref<HTMLImageElement | null>(null);
  const irregularWorker = ref<Worker | null>(null);

  // Cache for sprite detection results
  const detectionCache = new Map<string, SpritePreview[]>();

  // Generate cache key for current detection settings
  function getCacheKey(url: string, method: string, sensitivity: number, removeEmpty: boolean): string {
    return `${url}-${method}-${sensitivity}-${removeEmpty}`;
  }

  // Load the image when the component is mounted or the URL changes
  watch(() => props.imageUrl, loadImage, { immediate: true });

  function loadImage() {
    if (!props.imageUrl) return;

    const img = new Image();
    img.onload = () => {
      imageElement.value = img;

      // Set default rows and columns based on image dimensions
      // This is a simple heuristic - for pixel art, we might want to detect sprite size
      const aspectRatio = img.width / img.height;

      if (aspectRatio > 1) {
        // Landscape orientation - likely more columns than rows
        columns.value = Math.min(Math.ceil(Math.sqrt(aspectRatio * 4)), 8);
        rows.value = Math.ceil(4 / columns.value);
      } else {
        // Portrait orientation - likely more rows than columns
        rows.value = Math.min(Math.ceil(Math.sqrt(4 / aspectRatio)), 8);
        columns.value = Math.ceil(4 / rows.value);
      }

      // Generate initial preview
      generatePreview();
    };
    img.src = props.imageUrl;
  }

  // Generate preview of split sprites with caching
  async function generatePreview() {
    if (!imageElement.value) return;

    // Check cache first
    const cacheKey = getCacheKey(props.imageUrl, detectionMethod.value, sensitivity.value, removeEmpty.value);

    if (detectionCache.has(cacheKey)) {
      previewSprites.value = detectionCache.get(cacheKey)!;
      return;
    }

    isProcessing.value = true;
    previewSprites.value = [];

    try {
      const img = imageElement.value;

      if (detectionMethod.value === 'auto') {
        await autoDetectSprites(img);
      } else if (detectionMethod.value === 'irregular') {
        await detectIrregularSprites(img);
      } else {
        await splitSpritesheet(img, rows.value, columns.value);
      }

      // Cache results (limit cache size to prevent memory issues)
      if (detectionCache.size > 10) {
        const firstKey = detectionCache.keys().next().value;
        detectionCache.delete(firstKey || '');
      }
      detectionCache.set(cacheKey, previewSprites.value);
    } catch (error) {
      console.error('Error generating preview:', error);
    } finally {
      isProcessing.value = false;
    }
  }

  function getSpriteBoundingBox(ctx: CanvasRenderingContext2D, width: number, height: number) {
    const imageData = ctx.getImageData(0, 0, width, height);
    const data = imageData.data;

    let minX = width;
    let minY = height;
    let maxX = 0;
    let maxY = 0;
    let hasContent = false;

    // Scan through all pixels to find the bounding box
    for (let y = 0; y < height; y++) {
      for (let x = 0; x < width; x++) {
        const idx = (y * width + x) * 4;
        // Check if pixel is not transparent (alpha > 0)
        if (data[idx + 3] > 10) {
          // Allow some tolerance for compression artifacts
          minX = Math.min(minX, x);
          minY = Math.min(minY, y);
          maxX = Math.max(maxX, x);
          maxY = Math.max(maxY, y);
          hasContent = true;
        }
      }
    }

    // If no non-transparent pixels found, return null
    if (!hasContent) {
      return null;
    }

    // Return bounding box with a small padding, ensuring it stays within bounds
    const bx = Math.max(0, minX - 1);
    const by = Math.max(0, minY - 1);
    const bw = Math.min(width - bx, maxX - minX + 3); // +1 for inclusive bounds, +2 for padding
    const bh = Math.min(height - by, maxY - minY + 3);

    return {
      x: bx,
      y: by,
      width: Math.max(1, bw),
      height: Math.max(1, bh),
    };
  }

  // Split spritesheet manually based on rows and columns
  async function splitSpritesheet(img: HTMLImageElement, rows: number, columns: number) {
    const safeColumns = Number.isFinite(columns) && columns > 0 ? Math.floor(columns) : 1;
    const safeRows = Number.isFinite(rows) && rows > 0 ? Math.floor(rows) : 1;
    const spriteWidth = Math.max(1, Math.floor(img.width / safeColumns));
    const spriteHeight = Math.max(1, Math.floor(img.height / safeRows));

    const sprites: SpritePreview[] = [];

    // Create a canvas for processing the full sprite
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    // Create a second canvas for the cropped sprite
    const croppedCanvas = document.createElement('canvas');
    const croppedCtx = croppedCanvas.getContext('2d');

    if (!ctx || !croppedCtx) return;

    canvas.width = spriteWidth;
    canvas.height = spriteHeight;

    // Split the image into individual sprites
    for (let row = 0; row < safeRows; row++) {
      for (let col = 0; col < safeColumns; col++) {
        // Clear the canvas
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // Draw the portion of the spritesheet
        ctx.drawImage(img, col * spriteWidth, row * spriteHeight, spriteWidth, spriteHeight, 0, 0, spriteWidth, spriteHeight);

        // Check if the sprite is empty (all transparent or same color)
        const isEmpty = removeEmpty.value ? isCanvasEmpty(ctx, spriteWidth, spriteHeight) : false;

        // If we're not removing empty sprites or the sprite is not empty
        if (!removeEmpty.value || !isEmpty) {
          // Get bounding box of non-transparent pixels
          const boundingBox = getSpriteBoundingBox(ctx, spriteWidth, spriteHeight);

          let url;
          let x = 0; // Default position (will be updated if we have a bounding box)
          let y = 0;
          let width = spriteWidth;
          let height = spriteHeight;

          if (boundingBox) {
            // The key change: preserve the original position where the sprite was found
            x = boundingBox.x;
            y = boundingBox.y;
            width = boundingBox.width;
            height = boundingBox.height;

            // Set dimensions for the cropped sprite
            croppedCanvas.width = boundingBox.width;
            croppedCanvas.height = boundingBox.height;

            // Draw only the non-transparent part
            croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
            croppedCtx.drawImage(canvas, boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height, 0, 0, boundingBox.width, boundingBox.height);

            // Convert to data URL
            url = croppedCanvas.toDataURL('image/png');
          } else {
            // No non-transparent pixels found, use the original sprite
            url = canvas.toDataURL('image/png');
          }

          sprites.push({
            url,
            x,
            y,
            width,
            height,
            isEmpty,
          });
        }
      }
    }

    previewSprites.value = sprites;
  }

  // Auto-detect sprites based on transparency/color differences
  async function autoDetectSprites(img: HTMLImageElement) {
    // This is a simplified implementation
    // A more sophisticated algorithm would analyze the image to find sprite boundaries

    // For now, we'll use a simple approach:
    // 1. Try to detect the sprite size by looking for repeating patterns
    // 2. Then use that size to split the spritesheet

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

    canvas.width = img.width;
    canvas.height = img.height;
    ctx.drawImage(img, 0, 0);

    // Get image data for analysis
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data;

    // Simple detection of sprite size based on transparency patterns
    // This is a very basic implementation and might not work for all spritesheets
    const { detectedWidth, detectedHeight } = detectSpriteSize(data, canvas.width, canvas.height);

    if (detectedWidth > 0 && detectedHeight > 0) {
      // Sanity thresholds to avoid absurdly tiny tiles/huge counts
      const MIN_TILE = 8;
      const MAX_SPRITES = 1024;
      if (detectedWidth < MIN_TILE || detectedHeight < MIN_TILE) {
        // Fallback if tile is too small
        const s = Math.max(1, Math.min(100, sensitivity.value));
        const divisor = 3 + Math.round(s / 20); // 3..8
        const estimatedSize = Math.max(MIN_TILE, Math.floor(Math.min(img.width, img.height) / divisor));
        const estimatedRows = Math.max(1, Math.floor(img.height / estimatedSize));
        const estimatedColumns = Math.max(1, Math.floor(img.width / estimatedSize));
        await splitSpritesheet(img, estimatedRows, estimatedColumns);
        return;
      }

      const detectedRows = Math.max(1, Math.floor(img.height / detectedHeight));
      const detectedColumns = Math.max(1, Math.floor(img.width / detectedWidth));

      // If the detected combination is unreasonably high, fallback to estimate
      if (detectedRows * detectedColumns > MAX_SPRITES) {
        const s = Math.max(1, Math.min(100, sensitivity.value));
        const divisor = 3 + Math.round(s / 20); // 3..8
        const estimatedSize = Math.max(MIN_TILE, Math.floor(Math.min(img.width, img.height) / divisor));
        const estimatedRows = Math.max(1, Math.floor(img.height / estimatedSize));
        const estimatedColumns = Math.max(1, Math.floor(img.width / estimatedSize));
        await splitSpritesheet(img, estimatedRows, estimatedColumns);
        return;
      }

      // Use the detected size to split the spritesheet
      await splitSpritesheet(img, detectedRows, detectedColumns);
    } else {
      // Fallback to manual splitting with a reasonable guess based on sensitivity
      const s = Math.max(1, Math.min(100, sensitivity.value));
      const divisor = 3 + Math.round(s / 20); // 3..8
      const estimatedSize = Math.max(8, Math.floor(Math.min(img.width, img.height) / divisor));
      const estimatedRows = Math.max(1, Math.floor(img.height / estimatedSize));
      const estimatedColumns = Math.max(1, Math.floor(img.width / estimatedSize));

      await splitSpritesheet(img, estimatedRows, estimatedColumns);
    }
  }

  // Helper function to detect sprite size based on transparency/color gutters and edge periodicity
  function detectSpriteSize(data: Uint8ClampedArray, width: number, height: number) {
    // Map sensitivity (1-100) to thresholds:
    // Higher sensitivity -> allows stricter background matching and lower gutter proportion thresholds
    const s = Math.max(1, Math.min(100, sensitivity.value));

    // Background/color thresholds
    const alphaBgThresh = Math.round(255 * (0.15 + (100 - s) * 0.001)); // 15%-25% depending on sensitivity
    const colorTol = Math.round(10 + (100 - s) * 0.8); // 10..90 Euclidean approx
    const gutterPropThresh = 0.92 - s * 0.004; // 0.92 down to ~0.52

    function getPixel(x: number, y: number) {
      const idx = (y * width + x) * 4;
      return [data[idx], data[idx + 1], data[idx + 2], data[idx + 3]] as [number, number, number, number];
    }

    // Estimate background color from corners (median of corners)
    const corners: [number, number, number, number][] = [getPixel(0, 0), getPixel(width - 1, 0), getPixel(0, height - 1), getPixel(width - 1, height - 1)];
    function median(arr: number[]) {
      const a = arr.slice().sort((a, b) => a - b);
      const mid = (a.length - 1) / 2;
      return (a[Math.floor(mid)] + a[Math.ceil(mid)]) / 2;
    }
    const bg = [median(corners.map(c => c[0])), median(corners.map(c => c[1])), median(corners.map(c => c[2])), median(corners.map(c => c[3]))] as [number, number, number, number];

    function isBg(r: number, g: number, b: number, a: number) {
      if (a <= alphaBgThresh) return true;
      const dr = r - bg[0];
      const dg = g - bg[1];
      const db = b - bg[2];
      // Use Manhattan distance approximation for speed
      const manhattan = Math.abs(dr) + Math.abs(dg) + Math.abs(db);
      // Normalize approx to 0..~765 and compare to scaled tolerance
      return manhattan <= colorTol * 3;
    }

    // Sample stride to speed up scanning large sheets
    const rowSample = Math.max(1, Math.floor(height / 64));
    const colSample = Math.max(1, Math.floor(width / 64));

    // Compute background proportion per column and row
    const colBgProp: number[] = new Array(width).fill(0);
    for (let x = 0; x < width; x++) {
      let bgCount = 0;
      let total = 0;
      for (let y = 0; y < height; y += rowSample) {
        const [r, g, b, a] = getPixel(x, y);
        if (isBg(r, g, b, a)) bgCount++;
        total++;
      }
      colBgProp[x] = total > 0 ? bgCount / total : 1;
    }

    const rowBgProp: number[] = new Array(height).fill(0);
    for (let y = 0; y < height; y++) {
      let bgCount = 0;
      let total = 0;
      for (let x = 0; x < width; x += colSample) {
        const [r, g, b, a] = getPixel(x, y);
        if (isBg(r, g, b, a)) bgCount++;
        total++;
      }
      rowBgProp[y] = total > 0 ? bgCount / total : 1;
    }

    function extractRuns(bgProp: number[]): { emptyRuns: [number, number][]; segSizes: number[] } {
      const emptyRuns: [number, number][] = [];
      const segSizes: number[] = [];
      let inEmpty = false;
      let runStart = 0;
      let lastSeparatorEnd = -1;
      for (let i = 0; i < bgProp.length; i++) {
        const isEmpty = bgProp[i] >= gutterPropThresh;
        if (isEmpty && !inEmpty) {
          inEmpty = true;
          runStart = i;
          if (lastSeparatorEnd >= 0) {
            const seg = runStart - lastSeparatorEnd - 1;
            if (seg > 0) segSizes.push(seg);
          }
        } else if (!isEmpty && inEmpty) {
          inEmpty = false;
          emptyRuns.push([runStart, i - 1]);
          lastSeparatorEnd = i - 1;
        }
      }
      if (inEmpty) {
        emptyRuns.push([runStart, bgProp.length - 1]);
        lastSeparatorEnd = bgProp.length - 1;
      }
      // Trailing segment after last empty run
      if (lastSeparatorEnd >= 0 && lastSeparatorEnd < bgProp.length - 1) {
        const seg = bgProp.length - 1 - lastSeparatorEnd;
        if (seg > 0) segSizes.push(seg);
      }
      return { emptyRuns, segSizes };
    }

    function modeWithTolerance(values: number[], tol = 2): number {
      if (values.length === 0) return 0;
      values.sort((a, b) => a - b);
      let bestCount = 0;
      let bestVal = values[0];
      for (let i = 0; i < values.length; i++) {
        const base = values[i];
        let count = 1;
        for (let j = i + 1; j < values.length; j++) {
          if (Math.abs(values[j] - base) <= tol) count++;
          else break;
        }
        if (count > bestCount) {
          bestCount = count;
          bestVal = base;
        }
      }
      return bestVal;
    }

    const colRuns = extractRuns(colBgProp);
    const rowRuns = extractRuns(rowBgProp);

    let detectedWidth = modeWithTolerance(colRuns.segSizes, 2);
    let detectedHeight = modeWithTolerance(rowRuns.segSizes, 2);

    // Fallback: use edge periodicity via autocorrelation if gutters not found
    function edgeAutocorrLength(axis: 'x' | 'y'): number {
      const maxLen = axis === 'x' ? width : height;
      const otherLen = axis === 'x' ? height : width;
      const sampleStepMajor = Math.max(1, Math.floor(maxLen / 512));
      const sampleStepMinor = Math.max(1, Math.floor(otherLen / 64));
      const energy: number[] = new Array(maxLen).fill(0);
      if (axis === 'x') {
        for (let x = 0; x < maxLen - 1; x += sampleStepMajor) {
          let e = 0;
          for (let y = 0; y < otherLen; y += sampleStepMinor) {
            const [r1, g1, b1, a1] = getPixel(x, y);
            const [r2, g2, b2, a2] = getPixel(x + 1, y);
            e += Math.abs(r1 - r2) + Math.abs(g1 - g2) + Math.abs(b1 - b2) + Math.abs(a1 - a2);
          }
          energy[x] = e;
        }
      } else {
        for (let y = 0; y < maxLen - 1; y += sampleStepMajor) {
          let e = 0;
          for (let x = 0; x < otherLen; x += sampleStepMinor) {
            const [r1, g1, b1, a1] = getPixel(x, y);
            const [r2, g2, b2, a2] = getPixel(x, y + 1);
            e += Math.abs(r1 - r2) + Math.abs(g1 - g2) + Math.abs(b1 - b2) + Math.abs(a1 - a2);
          }
          energy[y] = e;
        }
      }
      const minTile = Math.max(3, Math.floor(Math.min(maxLen / 32, 128)));
      const maxTile = Math.max(minTile + 1, Math.floor(Math.min(maxLen / 2, 512)));
      let bestLag = 0;
      let bestVal = -Infinity;
      for (let lag = minTile; lag <= maxTile; lag++) {
        let sum = 0;
        for (let i = 0; i + lag < energy.length; i++) {
          const e1 = energy[i] || 0;
          const e2 = energy[i + lag] || 0;
          sum += e1 * e2;
        }
        if (sum > bestVal) {
          bestVal = sum;
          bestLag = lag;
        }
      }
      return bestLag;
    }

    if (detectedWidth <= 0 || detectedWidth > width) {
      const lagX = edgeAutocorrLength('x');
      if (lagX > 0 && lagX <= width) detectedWidth = lagX;
    }
    if (detectedHeight <= 0 || detectedHeight > height) {
      const lagY = edgeAutocorrLength('y');
      if (lagY > 0 && lagY <= height) detectedHeight = lagY;
    }

    // Sanity checks
    if (!Number.isFinite(detectedWidth) || detectedWidth <= 0 || detectedWidth > width) detectedWidth = 0;
    if (!Number.isFinite(detectedHeight) || detectedHeight <= 0 || detectedHeight > height) detectedHeight = 0;

    return { detectedWidth, detectedHeight };
  }

  // Detect irregular sprites using Web Worker
  async function detectIrregularSprites(img: HTMLImageElement): Promise<void> {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    if (!ctx) throw new Error('Could not get canvas context');

    canvas.width = img.width;
    canvas.height = img.height;
    ctx.drawImage(img, 0, 0);

    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

    // Initialize worker lazily using Vite worker syntax
    if (!irregularWorker.value) {
      try {
        irregularWorker.value = new Worker(new URL('../workers/irregularSpriteDetection.worker.ts', import.meta.url), { type: 'module' });
      } catch (error) {
        console.error('Failed to create worker:', error);
        // Fallback to auto detection if worker fails
        await autoDetectSprites(img);
        return;
      }
    }

    return new Promise<void>((resolve, reject) => {
      const worker = irregularWorker.value!;

      const handleMessage = async (e: MessageEvent) => {
        clearTimeout(timeout);
        worker.removeEventListener('message', handleMessage);
        worker.removeEventListener('error', handleError);

        if (e.data.type === 'spritesDetected') {
          try {
            await processDetectedSprites(img, e.data.sprites, e.data.backgroundColor);
            resolve();
          } catch (error) {
            reject(error);
          }
        }
      };

      // Set timeout for worker processing
      const timeout = setTimeout(() => {
        worker.removeEventListener('message', handleMessage);
        worker.removeEventListener('error', handleError);
        console.warn('Worker timeout, falling back to auto detection');
        // Fallback to auto detection
        autoDetectSprites(img).then(resolve).catch(reject);
      }, 30000); // 30 second timeout

      const handleError = (error: ErrorEvent) => {
        clearTimeout(timeout);
        worker.removeEventListener('message', handleMessage);
        worker.removeEventListener('error', handleError);
        console.error('Worker error, falling back to auto detection:', error);
        // Fallback to auto detection
        autoDetectSprites(img).then(resolve).catch(reject);
      };

      worker.addEventListener('message', handleMessage);
      worker.addEventListener('error', handleError);

      try {
        worker.postMessage({
          type: 'detectIrregularSprites',
          imageData,
          sensitivity: sensitivity.value,
          maxSize: 2048, // Limit processing size for performance
        });
      } catch (error) {
        clearTimeout(timeout);
        console.error('Failed to post message to worker:', error);
        // Fallback to auto detection
        autoDetectSprites(img).then(resolve).catch(reject);
      }
    });
  }

  // Process sprites detected by the worker (optimized)
  async function processDetectedSprites(img: HTMLImageElement, detectedSprites: any[], backgroundColor?: [number, number, number, number]): Promise<void> {
    if (!detectedSprites?.length) {
      previewSprites.value = [];
      return;
    }

    const sprites: SpritePreview[] = [];
    const sourceCanvas = document.createElement('canvas');
    const sourceCtx = sourceCanvas.getContext('2d');
    const spriteCanvas = document.createElement('canvas');
    const spriteCtx = spriteCanvas.getContext('2d');

    if (!sourceCtx || !spriteCtx) return;

    // Setup source canvas once
    sourceCanvas.width = img.width;
    sourceCanvas.height = img.height;
    sourceCtx.drawImage(img, 0, 0);

    // Process sprites in batches to avoid blocking
    const batchSize = 50;
    for (let i = 0; i < detectedSprites.length; i += batchSize) {
      const batch = detectedSprites.slice(i, i + batchSize);

      for (const sprite of batch) {
        const { x, y, width, height } = sprite;

        // Skip invalid sprites
        if (width <= 0 || height <= 0) continue;

        spriteCanvas.width = width;
        spriteCanvas.height = height;
        spriteCtx.clearRect(0, 0, width, height);
        spriteCtx.drawImage(sourceCanvas, x, y, width, height, 0, 0, width, height);

        // Remove background and make transparent for irregular sprites
        if (detectionMethod.value === 'irregular' && backgroundColor) {
          removeBackgroundFromSprite(spriteCtx, width, height, backgroundColor, sensitivity.value);
        }

        const isEmpty = removeEmpty.value ? isCanvasEmpty(spriteCtx, width, height) : false;

        if (!removeEmpty.value || !isEmpty) {
          sprites.push({
            url: spriteCanvas.toDataURL('image/png'),
            x,
            y,
            width,
            height,
            isEmpty,
          });
        }
      }

      // Yield control periodically for large batches
      if (i > 0 && i % 100 === 0) {
        await new Promise(resolve => setTimeout(resolve, 0));
      }
    }

    previewSprites.value = sprites;
  }

  // Remove background color and make it transparent
  function removeBackgroundFromSprite(ctx: CanvasRenderingContext2D, width: number, height: number, backgroundColor: [number, number, number, number], sensitivity: number): void {
    const imageData = ctx.getImageData(0, 0, width, height);
    const data = imageData.data;

    const [bgR, bgG, bgB, bgA] = backgroundColor;
    const colorTolerance = Math.round(50 - sensitivity * 0.45); // Same as worker
    const alphaTolerance = Math.round(40 - sensitivity * 0.35);

    for (let i = 0; i < data.length; i += 4) {
      const r = data[i];
      const g = data[i + 1];
      const b = data[i + 2];
      const a = data[i + 3];

      // Handle fully transparent pixels
      if (a < 10) {
        data[i + 3] = 0; // Make fully transparent
        continue;
      }

      // Calculate color difference using Euclidean distance
      const rDiff = r - bgR;
      const gDiff = g - bgG;
      const bDiff = b - bgB;
      const aDiff = a - bgA;

      const colorDistance = Math.sqrt(rDiff * rDiff + gDiff * gDiff + bDiff * bDiff);
      const alphaDistance = Math.abs(aDiff);

      // If pixel matches background color, make it transparent
      if (colorDistance <= colorTolerance && alphaDistance <= alphaTolerance) {
        data[i + 3] = 0; // Set alpha to 0 (transparent)
      }
    }

    ctx.putImageData(imageData, 0, 0);
  }

  // Check if a canvas is empty (all transparent or same color)
  function isCanvasEmpty(ctx: CanvasRenderingContext2D, width: number, height: number): boolean {
    const imageData = ctx.getImageData(0, 0, width, height);
    const data = imageData.data;

    // Check if all pixels are transparent
    let allTransparent = true;
    let allSameColor = true;

    // Reference values from first pixel
    const firstR = data[0];
    const firstG = data[1];
    const firstB = data[2];
    const firstA = data[3];

    for (let i = 0; i < data.length; i += 4) {
      const alpha = data[i + 3];

      // Check transparency
      if (alpha > 10) {
        // Allow some tolerance for compression artifacts
        allTransparent = false;
      }

      // Check if all pixels are the same color
      if (data[i] !== firstR || data[i + 1] !== firstG || data[i + 2] !== firstB || Math.abs(data[i + 3] - firstA) > 10) {
        allSameColor = false;
      }

      // Early exit if we've determined it's not empty
      if (!allTransparent && !allSameColor) {
        break;
      }
    }

    return allTransparent || allSameColor;
  }

  // Convert preview sprites to actual files
  async function createSpriteFiles(): Promise<SpriteFile[]> {
    const spriteFiles: SpriteFile[] = [];

    for (let i = 0; i < previewSprites.value.length; i++) {
      const sprite = previewSprites.value[i];

      // Convert data URL to blob
      const response = await fetch(sprite.url);
      const blob = await response.blob();

      // Create file from blob
      const fileName = `sprite_${i + 1}.png`;
      const file = new File([blob], fileName, { type: 'image/png' });

      // Create sprite file with position information
      spriteFiles.push({
        file,
        x: sprite.x,
        y: sprite.y,
        width: sprite.width,
        height: sprite.height,
      });
    }

    return spriteFiles;
  }

  // Actions
  function cancel() {
    emit('close');
  }

  async function confirm() {
    if (previewSprites.value.length === 0) return;

    isProcessing.value = true;

    try {
      const files = await createSpriteFiles();
      emit('split', files);
      emit('close');
    } catch (error) {
      console.error('Error creating sprite files:', error);
    } finally {
      isProcessing.value = false;
    }
  }

  // Add these watchers to automatically update preview
  watch([rows, columns, removeEmpty, detectionMethod, sensitivity], () => {
    if (imageElement.value) {
      generatePreview();
    }
  });

  // Clean up worker and cache on component unmount
  onUnmounted(() => {
    if (irregularWorker.value) {
      irregularWorker.value.terminate();
      irregularWorker.value = null;
    }
    detectionCache.clear();
  });
</script>
