import type {
  ColorGroup,
  MosaicRegion,
  MosaicSettings,
  ProjectDimensions,
  ProjectFile,
} from '@/types/mosaic'
import { relativeLuminance, rgbToHex } from './colors'
import { forEachMaskRun, resolveRegionLabelPosition } from './regionMask'

export function downloadBlob(blob: Blob, filename: string): void {
  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = filename
  link.hidden = true
  document.body.append(link)
  try {
    link.click()
  } finally {
    window.setTimeout(() => {
      link.remove()
      URL.revokeObjectURL(url)
    }, 1000)
  }
}

export async function canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {
  return new Promise((resolve, reject) => {
    canvas.toBlob((blob) => (blob ? resolve(blob) : reject(new Error('PNG sa nepodarilo vytvoriť.'))), 'image/png')
  })
}

export function renderExportCanvas(
  source: CanvasImageSource,
  dimensions: ProjectDimensions,
  regions: MosaicRegion[],
  groups: ColorGroup[],
  includeOverlay: boolean,
  useUserClickPositionForLabels = false,
): HTMLCanvasElement {
  const canvas = document.createElement('canvas')
  canvas.width = dimensions.width
  canvas.height = dimensions.height
  const context = canvas.getContext('2d')
  if (!context) throw new Error('Prehliadač nepodporuje Canvas 2D.')
  context.drawImage(source, 0, 0, dimensions.width, dimensions.height)
  const groupMap = new Map(groups.map((group) => [group.id, group]))

  if (includeOverlay) {
    context.save()
    context.globalAlpha = 0.24
    for (const region of regions) {
      const group = groupMap.get(region.groupId)
      if (!group) continue
      context.fillStyle = rgbToHex(group.color)
      forEachMaskRun(region.mask, (y, xStart, xEnd) => context.fillRect(xStart, y, xEnd - xStart + 1, 1))
    }
    context.restore()
  }

  const fontSize = Math.max(14, Math.round(Math.min(dimensions.width, dimensions.height) / 55))
  context.textAlign = 'center'
  context.textBaseline = 'middle'
  context.font = `700 ${fontSize}px system-ui, sans-serif`
  for (const region of regions) {
    const group = groupMap.get(region.groupId)
    if (!group) continue
    const labelPosition = resolveRegionLabelPosition(region, useUserClickPositionForLabels)
    const label = String(group.number)
    context.lineWidth = Math.max(2, fontSize / 7)
    context.strokeStyle = relativeLuminance(group.color) > 0.45 ? 'rgba(255,255,255,.9)' : 'rgba(0,0,0,.8)'
    context.fillStyle = relativeLuminance(group.color) > 0.45 ? '#111827' : '#FFFFFF'
    context.strokeText(label, labelPosition.x, labelPosition.y)
    context.fillText(label, labelPosition.x, labelPosition.y)
  }
  return canvas
}

const csvCell = (value: string | number) => `"${String(value).replaceAll('"', '""')}"`

export function createPaletteCsv(groups: ColorGroup[], regions: MosaicRegion[]): string {
  const header = ['Poradie', 'Číslo skupiny', 'HEX', 'R', 'G', 'B', 'Lab L', 'Lab a', 'Lab b', 'Počet plôch', 'Počet pixelov']
  const regionMap = new Map(regions.map((region) => [region.id, region]))
  const rows = groups.map((group, index) => {
    const pixels = group.regionIds.reduce((sum, id) => sum + (regionMap.get(id)?.pixelCount ?? 0), 0)
    return [
      index + 1,
      group.number,
      rgbToHex(group.color),
      group.color.r,
      group.color.g,
      group.color.b,
      group.labColor.l.toFixed(2),
      group.labColor.a.toFixed(2),
      group.labColor.b.toFixed(2),
      group.regionIds.length,
      pixels,
    ].map(csvCell).join(',')
  })
  return `\uFEFF${[header.map(csvCell).join(','), ...rows].join('\r\n')}`
}

async function blobToDataUrl(blob: Blob): Promise<string> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.onload = () => resolve(String(reader.result))
    reader.onerror = () => reject(reader.error ?? new Error('Súbor sa nepodarilo načítať.'))
    reader.readAsDataURL(blob)
  })
}

export async function createProjectFile(input: {
  imageName: string
  imageBlob: Blob | null
  dimensions: ProjectDimensions
  settings: MosaicSettings
  regions: MosaicRegion[]
  groups: ColorGroup[]
  includeSmallImage?: boolean
}): Promise<ProjectFile> {
  const project: ProjectFile = {
    version: 1,
    createdAt: new Date().toISOString(),
    imageName: input.imageName,
    dimensions: { ...input.dimensions },
    settings: { ...input.settings },
    regions: input.regions.map((region) => ({
      ...region,
      color: { ...region.color },
      labColor: { ...region.labColor },
      boundingBox: { ...region.boundingBox },
      labelPosition: { ...region.labelPosition },
      userClickPosition: region.userClickPosition ? { ...region.userClickPosition } : undefined,
      mask: { width: region.mask.width, height: region.mask.height, runs: Array.from(region.mask.runs) },
    })),
    groups: input.groups.map((group) => ({
      ...group,
      color: { ...group.color },
      labColor: { ...group.labColor },
      regionIds: [...group.regionIds],
    })),
  }
  if (input.includeSmallImage && input.imageBlob && input.imageBlob.size <= 5 * 1024 * 1024) {
    project.imageDataUrl = await blobToDataUrl(input.imageBlob)
  }
  return project
}

export function isProjectFile(value: unknown): value is ProjectFile {
  if (!value || typeof value !== 'object') return false
  const project = value as Partial<ProjectFile>
  return project.version === 1 &&
    typeof project.imageName === 'string' &&
    typeof project.dimensions?.width === 'number' &&
    typeof project.dimensions.height === 'number' &&
    Array.isArray(project.regions) && Array.isArray(project.groups) &&
    typeof project.settings?.floodTolerance === 'number'
}
