// Палитра цветов-меток проекта (0.63): ~100 оттенков, выведенных из 8 базовых
// цветов кита. Порядок стабилен — по нему строится сетка выбора, поэтому цвета
// генерируем детерминированно, а не держим списком из сотни hex-литералов.
// В БД уходит HEX #rrggbb (бэкенд проверяет только формат, не палитру).

/** Базовые цвета кита (gnexus-ui-kit: accent/info/success/warning/danger…). */
export const BASE_COLORS = [
  '#7aa2f7', // синий
  '#7dcfff', // голубой
  '#2ac3de', // циан
  '#bb9af7', // сиреневый
  '#9ece6a', // зелёный
  '#e0af68', // песочный
  '#f7768e', // розово-красный
  '#ff9e64', // оранжевый
] as const

// Ступени светлоты (в процентах, абсолютные — колонки сетки сопоставимы между
// рядами) и множители насыщенности. Светлота держится в 36..84%, иначе оттенок
// нечитаем на тёмной подложке кита.
const LIGHT_LEVELS = [78, 70, 62, 54, 46, 38] as const
const SAT_STEPS = [1, 0.72] as const

function clamp(value: number, low: number, high: number): number {
  return Math.min(high, Math.max(low, value))
}

/** #rrggbb → [h, s, l] (h 0..360, s/l 0..100). */
function hexToHsl(hex: string): [number, number, number] {
  const r = parseInt(hex.slice(1, 3), 16) / 255
  const g = parseInt(hex.slice(3, 5), 16) / 255
  const b = parseInt(hex.slice(5, 7), 16) / 255
  const max = Math.max(r, g, b)
  const min = Math.min(r, g, b)
  const l = (max + min) / 2
  const d = max - min
  if (d === 0) return [0, 0, l * 100]
  const s = d / (1 - Math.abs(2 * l - 1))
  let h: number
  if (max === r) h = ((g - b) / d) % 6
  else if (max === g) h = (b - r) / d + 2
  else h = (r - g) / d + 4
  return [(((h * 60) % 360) + 360) % 360, s * 100, l * 100]
}

/** [h, s, l] → #rrggbb в нижнем регистре (как нормализует бэкенд). */
function hslToHex(h: number, s: number, l: number): string {
  const sat = clamp(s, 0, 100) / 100
  const light = clamp(l, 36, 84) / 100
  const c = (1 - Math.abs(2 * light - 1)) * sat
  const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
  const m = light - c / 2
  const [r, g, b] =
    h < 60 ? [c, x, 0]
    : h < 120 ? [x, c, 0]
    : h < 180 ? [0, c, x]
    : h < 240 ? [0, x, c]
    : h < 300 ? [x, 0, c]
    : [c, 0, x]
  const channel = (v: number) => Math.round((v + m) * 255).toString(16).padStart(2, '0')
  return `#${channel(r)}${channel(g)}${channel(b)}`
}

// Грубые «корзины» по каналам: соседние ступени дают почти одинаковые цвета
// (у насыщенных базовых насыщенность упирается в предел) — такие дубликаты
// в сетку не берём, ряды остаются различимыми глазом.
function bucket(hex: string): string {
  return [1, 3, 5].map((i) => Math.round(parseInt(hex.slice(i, i + 2), 16) / 14)).join('-')
}

/** Оттенки одного базового цвета — строка сетки выбора (сам базовый первым). */
function shadeRow(base: string): string[] {
  // светлоту базового не берём: ступени заданы абсолютно, чтобы колонки рядов совпадали
  const [h, s] = hexToHsl(base)
  const out = [base]
  const seen = new Set([bucket(base)])
  for (const level of LIGHT_LEVELS) {
    for (const k of SAT_STEPS) {
      // насыщенность не ниже 30% — иначе оттенок выглядит серым
      const hex = hslToHex(h, Math.max(s * k, 30), level)
      const key = bucket(hex)
      if (seen.has(key)) continue
      seen.add(key)
      out.push(hex)
    }
  }
  return out
}

export interface PaletteGroup {
  base: string
  shades: string[]
}

/** Сетка палитры: 8 рядов по базовым цветам кита, ~100 оттенков всего. */
export const PALETTE_GROUPS: PaletteGroup[] = BASE_COLORS.map((base) => ({
  base,
  shades: shadeRow(base),
}))

/** Плоский список всех оттенков (проверка «цвет из палитры»). */
export const PALETTE: string[] = PALETTE_GROUPS.flatMap((group) => group.shades)

/** Цвет пришёл из палитры? (произвольный #rrggbb тоже валиден — показываем отдельно) */
export function isKnownColor(color: string | null): boolean {
  return !!color && PALETTE.includes(color.toLowerCase())
}
