import type { LabColor, RGBColor } from '@/types/mosaic'

const clampByte = (value: number) => Math.max(0, Math.min(255, Math.round(value)))

export function rgbToHex(color: RGBColor): string {
  const channel = (value: number) => clampByte(value).toString(16).padStart(2, '0')
  return `#${channel(color.r)}${channel(color.g)}${channel(color.b)}`.toUpperCase()
}

export function rgbToXyz(color: RGBColor): { x: number; y: number; z: number } {
  const linearize = (value: number) => {
    const channel = clampByte(value) / 255
    return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4
  }
  const r = linearize(color.r)
  const g = linearize(color.g)
  const b = linearize(color.b)
  return {
    x: (r * 0.4124564 + g * 0.3575761 + b * 0.1804375) * 100,
    y: (r * 0.2126729 + g * 0.7151522 + b * 0.072175) * 100,
    z: (r * 0.0193339 + g * 0.119192 + b * 0.9503041) * 100,
  }
}

export function xyzToLab(xyz: { x: number; y: number; z: number }): LabColor {
  const transform = (value: number) => {
    const delta = 6 / 29
    return value > delta ** 3 ? Math.cbrt(value) : value / (3 * delta ** 2) + 4 / 29
  }
  const x = transform(xyz.x / 95.047)
  const y = transform(xyz.y / 100)
  const z = transform(xyz.z / 108.883)
  return { l: 116 * y - 16, a: 500 * (x - y), b: 200 * (y - z) }
}

export function rgbToLab(color: RGBColor): LabColor {
  return xyzToLab(rgbToXyz(color))
}

export function deltaE76(first: LabColor, second: LabColor): number {
  return Math.hypot(first.l - second.l, first.a - second.a, first.b - second.b)
}

export function relativeLuminance(color: RGBColor): number {
  const linearize = (value: number) => {
    const channel = clampByte(value) / 255
    return channel <= 0.03928 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4
  }
  return 0.2126 * linearize(color.r) + 0.7152 * linearize(color.g) + 0.0722 * linearize(color.b)
}

export function colorDistance(first: RGBColor, second: RGBColor): number {
  return Math.hypot(first.r - second.r, first.g - second.g, first.b - second.b)
}

export function averageColors(colors: Array<{ color: RGBColor; weight: number }>): RGBColor {
  const total = colors.reduce((sum, item) => sum + item.weight, 0) || 1
  return {
    r: Math.round(colors.reduce((sum, item) => sum + item.color.r * item.weight, 0) / total),
    g: Math.round(colors.reduce((sum, item) => sum + item.color.g * item.weight, 0) / total),
    b: Math.round(colors.reduce((sum, item) => sum + item.color.b * item.weight, 0) / total),
  }
}
