import { markRaw } from 'vue'
import type { BoundingBox, MosaicRegion, Point, RegionMask } from '@/types/mosaic'

export interface RegionMaskSummary {
  pixelCount: number
  boundingBox: BoundingBox
  labelPosition: Point
}

export function resolveRegionLabelPosition(
  region: MosaicRegion,
  useUserClickPositionForLabels: boolean,
): Point {
  return useUserClickPositionForLabels
    ? region.userClickPosition ?? region.labelPosition
    : region.labelPosition
}

export function createRegionMask(width: number, height: number, runs: number[]): RegionMask {
  return markRaw({ width, height, runs: markRaw(Uint32Array.from(runs)) })
}

export function subtractRegionMask(source: RegionMask, removed: RegionMask): RegionMask | null {
  const removedByRow = new Map<number, Array<[number, number]>>()
  forEachMaskRun(removed, (y, xStart, xEnd) => {
    const row = removedByRow.get(y) ?? []
    row.push([xStart, xEnd])
    removedByRow.set(y, row)
  })

  const remainingRuns: number[] = []
  forEachMaskRun(source, (y, xStart, xEnd) => {
    let segments: Array<[number, number]> = [[xStart, xEnd]]
    for (const [cutStart, cutEnd] of removedByRow.get(y) ?? []) {
      const nextSegments: Array<[number, number]> = []
      for (const [segmentStart, segmentEnd] of segments) {
        if (cutEnd < segmentStart || cutStart > segmentEnd) {
          nextSegments.push([segmentStart, segmentEnd])
          continue
        }
        if (cutStart > segmentStart) nextSegments.push([segmentStart, cutStart - 1])
        if (cutEnd < segmentEnd) nextSegments.push([cutEnd + 1, segmentEnd])
      }
      segments = nextSegments
    }
    for (const [segmentStart, segmentEnd] of segments) remainingRuns.push(y, segmentStart, segmentEnd)
  })

  return remainingRuns.length ? createRegionMask(source.width, source.height, remainingRuns) : null
}

export function summarizeRegionMask(mask: RegionMask): RegionMaskSummary | null {
  let pixelCount = 0
  let minX = mask.width
  let minY = mask.height
  let maxX = -1
  let maxY = -1
  const runs: Array<[number, number, number]> = []
  forEachMaskRun(mask, (y, xStart, xEnd) => {
    runs.push([y, xStart, xEnd])
    pixelCount += xEnd - xStart + 1
    minX = Math.min(minX, xStart)
    minY = Math.min(minY, y)
    maxX = Math.max(maxX, xEnd)
    maxY = Math.max(maxY, y)
  })
  if (!pixelCount) return null

  const centerY = (minY + maxY) / 2
  const labelRun = runs.reduce((best, run) => {
    const score = run[2] - run[1] - Math.abs(run[0] - centerY) * 0.2
    const bestScore = best[2] - best[1] - Math.abs(best[0] - centerY) * 0.2
    return score > bestScore ? run : best
  }, runs[0] ?? [0, 0, 0])

  return {
    pixelCount,
    boundingBox: { x: minX, y: minY, width: maxX - minX + 1, height: maxY - minY + 1 },
    labelPosition: { x: Math.round((labelRun[1] + labelRun[2]) / 2), y: labelRun[0] },
  }
}

export function forEachMaskRun(
  mask: RegionMask,
  callback: (y: number, xStart: number, xEnd: number) => void,
): void {
  for (let index = 0; index < mask.runs.length; index += 3) {
    callback(mask.runs[index] ?? 0, mask.runs[index + 1] ?? 0, mask.runs[index + 2] ?? 0)
  }
}

export function buildOccupancy(width: number, height: number, masks: RegionMask[]): Uint8Array {
  const occupancy = new Uint8Array(width * height)
  for (const mask of masks) {
    forEachMaskRun(mask, (y, xStart, xEnd) => {
      occupancy.fill(1, y * width + xStart, y * width + xEnd + 1)
    })
  }
  return occupancy
}

export function maskContains(mask: RegionMask, x: number, y: number): boolean {
  for (let index = 0; index < mask.runs.length; index += 3) {
    const runY = mask.runs[index] ?? -1
    if (runY > y) return false
    if (runY === y && x >= (mask.runs[index + 1] ?? 0) && x <= (mask.runs[index + 2] ?? -1)) {
      return true
    }
  }
  return false
}
