export type Crop = {
  x: number
  y: number
  width: number
  height: number
}

export type AnalyzeResult = {
  frames: number
  crop: Crop
}

export const analyzeSprite = (img: HTMLImageElement): AnalyzeResult => {
  const { data, width, height } = getImageData(img)

  // Find columns with content (non-transparent pixels)
  const colHasContent = new Array(width).fill(false)

  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      // pixel data is [R,G,B,A,R,G,B,A,...], so (y*width+x)*4+3 gets the alpha byte
      const alpha = data[(y * width + x) * 4 + 3]
      if (alpha > 0) {
        colHasContent[x] = true
      }
    }
  }

  const frames = detectFrames(colHasContent, width)
  const crop = calculateCrop(img, frames)

  return { frames, crop }
}

export const calculateCrop = (img: HTMLImageElement, frames: number): Crop => {
  const { data, width, height } = getImageData(img)
  const frameWidth = Math.floor(width / frames)

  // For each frame, find the bounding box, then union them all
  let unionMinX = Infinity
  let unionMinY = Infinity
  let unionMaxX = -Infinity
  let unionMaxY = -Infinity

  for (let f = 0; f < frames; f++) {
    const frameStartX = f * frameWidth
    const frameEndX = frameStartX + frameWidth
    const bounds = getFrameBounds(data, width, height, frameStartX, frameEndX)

    if (bounds) {
      // Convert to frame-local coordinates
      const localMinX = bounds.minX - frameStartX
      const localMaxX = bounds.maxX - frameStartX

      unionMinX = Math.min(unionMinX, localMinX)
      unionMinY = Math.min(unionMinY, bounds.minY)
      unionMaxX = Math.max(unionMaxX, localMaxX)
      unionMaxY = Math.max(unionMaxY, bounds.maxY)
    }
  }

  // Handle case where no content found
  if (unionMinX === Infinity) {
    return { x: 0, y: 0, width: frameWidth, height }
  }

  return {
    x: unionMinX,
    y: unionMinY,
    width: unionMaxX - unionMinX + 1,
    height: unionMaxY - unionMinY + 1,
  }
}

const getImageData = (img: HTMLImageElement) => {
  const canvas = document.createElement("canvas")
  canvas.width = img.width
  canvas.height = img.height
  const ctx = canvas.getContext("2d")!
  ctx.drawImage(img, 0, 0)
  return ctx.getImageData(0, 0, img.width, img.height)
}

const detectFrames = (colHasContent: boolean[], width: number): number => {
  // Find regions, requiring gaps of at least 10px to separate frames
  const minGapWidth = 10
  const regions: { start: number; end: number }[] = []
  let inRegion = false
  let regionStart = 0
  let gapStart = -1

  for (let x = 0; x < width; x++) {
    if (colHasContent[x]) {
      if (!inRegion) {
        inRegion = true
        regionStart = x
      }
      gapStart = -1
    } else if (inRegion) {
      if (gapStart === -1) {
        gapStart = x
      }
      if (x - gapStart >= minGapWidth) {
        regions.push({ start: regionStart, end: gapStart - 1 })
        inRegion = false
        gapStart = -1
      }
    }
  }
  if (inRegion) {
    regions.push({ start: regionStart, end: gapStart === -1 ? width - 1 : gapStart - 1 })
  }

  if (regions.length === 0) {
    throw new Error("No content found in spritesheet")
  }

  // Check if regions are roughly evenly spaced
  const avgWidth = width / regions.length
  const tolerance = avgWidth * 0.4

  for (let i = 0; i < regions.length; i++) {
    const expectedCenter = avgWidth * i + avgWidth / 2
    const actualCenter = (regions[i].start + regions[i].end) / 2
    if (Math.abs(expectedCenter - actualCenter) > tolerance) {
      throw new Error("Frames are not evenly spaced")
    }
  }

  return regions.length
}

const getFrameBounds = (
  data: Uint8ClampedArray,
  width: number,
  height: number,
  startX: number,
  endX: number
): { minX: number; minY: number; maxX: number; maxY: number } | undefined => {
  let minX = Infinity
  let minY = Infinity
  let maxX = -Infinity
  let maxY = -Infinity

  for (let y = 0; y < height; y++) {
    for (let x = startX; x < endX; x++) {
      const alpha = data[(y * width + x) * 4 + 3]
      if (alpha > 0) {
        minX = Math.min(minX, x)
        minY = Math.min(minY, y)
        maxX = Math.max(maxX, x)
        maxY = Math.max(maxY, y)
      }
    }
  }

  if (minX === Infinity) return undefined

  return { minX, minY, maxX, maxY }
}
