// Палитра цветов-меток проекта (0.63, пересобрана в 0.72): 8 базовых тонов,
// различимых на глаз, плюс ряд нейтральных, всего 27 оттенков — клеток меньше,
// но любые две различимы (минимальная попарная разница по CIELAB — ΔE 12.5).
// Порядок стабилен — по нему строится сетка выбора, поэтому цвета генерируем
// детерминированно, а не держим списком hex-литералов.
// В БД уходит HEX #rrggbb (бэкенд проверяет только формат, не палитру).

/** Базовые тона: оттенки по кругу через 28..90°, цвета кита, где они есть. */
export const BASE_COLORS = [
  '#f7768e', // розово-красный, 349° (error кита)
  '#ff9e64', // оранжевый, 25° (accent кита)
  // 0.72: коричневый — своя база, коричневого в ките нет. Это тот же оранжевый
  // тон (28°), но тёмный и приглушённый (l 38%, s 45%): в сетке он стоит сразу
  // за оранжевым и видно, что коричневый — не «тёмный оранжевый», а свой цвет
  '#8c5e35', // коричневый, 28°
  // 0.69: жёлтого в ките нет. Warning #e0af68 — оттенок 35°, рядом с оранжевым
  // #ff9e64 (25°) он читался вторым оранжевым, а не жёлтым; поэтому база своя
  '#e5cf5a', // жёлтый, 50°
  '#9ece6a', // зелёный, 95° (success кита)
  // циан — из палитры темы, на которой построен кит: своего токена у него нет
  '#2ac3de', // циан, 187°
  '#7aa2f7', // синий, 219° (secondary кита)
  '#bb9af7', // сиреневый, 267° (info кита)
] as const
// 0.72 из базы убран голубой #7dcfff (199°): он стоял в 12° от циана и в 20° от
// синего — три холодных клетки читались одним и тем же синим. Остальные тонá
// разведены не меньше чем на 32°, и это разные по светлоте цвета.

// Ступени считаются от светлоты базового цвета, а не заданы абсолютно: базовые
// идут от 38% (коричневый) до 79% (сиреневый), и абсолютная ступень то попадала
// в сам базовый, то совпадала с соседней. Светлый шаг — 18%, тёмный — 24%
// (в 0.63..0.71 ступени шли через 8%, и половина сетки выглядела одинаково).
// Ступень берём, только если её удалось отвести от базового на MIN_SHIFT
// процентов светлоты: у цвета, стоящего у предела, своей ступени просто нет, и
// ряд выходит короче — это честнее, чем клетка, не отличимая от соседней.
const LIGHT_SHIFT = 18
const DARK_SHIFT = 24
const MIN_SHIFT = 14
// Тёмную ступень чуть приглушаем: на тёмной подложке насыщенность при низкой
// светлоте читается как неон (тёмно-красная ступень выходила ярче базовой) —
// 0.8 оставляет цвет тем же тоном, но спокойнее
const DARK_SAT = 0.8
// Пределы светлоты ступеней: ниже 36% цвет на тёмной подложке кита выглядит
// чёрным, выше 88% — белым, пастель теряет тон. Приглушённых вариантов (тот же
// тон с половинной насыщенностью) в 0.72 нет: именно они и давали «слишком
// одинаковые цвета» — и рядом со светлой ступенью того же ряда, и рядом с
// приглушёнными соседних тонов
const LIGHT_CAP = 88
const DARK_FLOOR = 36

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, DARK_FLOOR, LIGHT_CAP) / 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 shadeRow(base: string): string[] {
  const [h, s, l] = hexToHsl(base)
  const out = [base]
  for (const target of [l + LIGHT_SHIFT, l - DARK_SHIFT]) {
    const isDark = target < l
    const hex = hslToHex(h, isDark ? s * DARK_SAT : s, target)
    // светлоту меряем у результата: clamp внутри hslToHex мог съесть ступень
    if (Math.abs(hexToHsl(hex)[2] - l) >= MIN_SHIFT) out.push(hex)
  }
  return out
}

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

// Ряд нейтральных (0.69): белый → тёмно-серый равными ступенями по светлоте.
// Оттенка у серого нет, из базового цвета его не вывести — ступени заданы своей
// шкалой; светлоту здесь не зажимаем, как у цветных рядов: нужен чистый белый
// конец. В 0.72 ступеней 5 через 16% (было 12 через 8% — соседние серые не
// отличались), и шкала кончается на #5c5c5c, а не на чёрном: чёрная метка на
// тёмной карточке кита не видна, то есть была бы выбором без результата
const GREY_LEVELS = [100, 84, 68, 52, 36] as const

/** Ступень серого: каналы равны, значение — от светлоты (0..100). */
function grey(level: number): string {
  const channel = Math.round((clamp(level, 0, 100) / 100) * 255)
    .toString(16)
    .padStart(2, '0')
  return `#${channel.repeat(3)}`
}

/** Сетка палитры: 8 рядов по базовым тонам + нейтральные, 27 оттенков. */
export const PALETTE_GROUPS: PaletteGroup[] = [
  ...BASE_COLORS.map((base) => ({ base, shades: shadeRow(base) })),
  // нейтральные — последним рядом: цветные метки узнаются быстрее, а серые нужны
  // редко и в конце сетки не мешают
  { base: 'grey', shades: GREY_LEVELS.map(grey) },
]

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

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