// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

// The named colours of CSS Color 4 ยง6.1, sorted so that a name is found by
// halving the table rather than by walking it.
//
// Each value is the colour packed as 0xRRGGBB. Names are lowercase here and
// compared case-insensitively on the way in, because CSS keywords are case
// insensitive and a theme file written by hand is not going to agree about it.

///|
let names : Array[(StringView, Int32)] = [
  ("aliceblue", 0xF0F8FF),
  ("antiquewhite", 0xFAEBD7),
  ("aqua", 0x00FFFF),
  ("aquamarine", 0x7FFFD4),
  ("azure", 0xF0FFFF),
  ("beige", 0xF5F5DC),
  ("bisque", 0xFFE4C4),
  ("black", 0x000000),
  ("blanchedalmond", 0xFFEBCD),
  ("blue", 0x0000FF),
  ("blueviolet", 0x8A2BE2),
  ("brown", 0xA52A2A),
  ("burlywood", 0xDEB887),
  ("cadetblue", 0x5F9EA0),
  ("chartreuse", 0x7FFF00),
  ("chocolate", 0xD2691E),
  ("coral", 0xFF7F50),
  ("cornflowerblue", 0x6495ED),
  ("cornsilk", 0xFFF8DC),
  ("crimson", 0xDC143C),
  ("cyan", 0x00FFFF),
  ("darkblue", 0x00008B),
  ("darkcyan", 0x008B8B),
  ("darkgoldenrod", 0xB8860B),
  ("darkgray", 0xA9A9A9),
  ("darkgreen", 0x006400),
  ("darkgrey", 0xA9A9A9),
  ("darkkhaki", 0xBDB76B),
  ("darkmagenta", 0x8B008B),
  ("darkolivegreen", 0x556B2F),
  ("darkorange", 0xFF8C00),
  ("darkorchid", 0x9932CC),
  ("darkred", 0x8B0000),
  ("darksalmon", 0xE9967A),
  ("darkseagreen", 0x8FBC8F),
  ("darkslateblue", 0x483D8B),
  ("darkslategray", 0x2F4F4F),
  ("darkslategrey", 0x2F4F4F),
  ("darkturquoise", 0x00CED1),
  ("darkviolet", 0x9400D3),
  ("deeppink", 0xFF1493),
  ("deepskyblue", 0x00BFFF),
  ("dimgray", 0x696969),
  ("dimgrey", 0x696969),
  ("dodgerblue", 0x1E90FF),
  ("firebrick", 0xB22222),
  ("floralwhite", 0xFFFAF0),
  ("forestgreen", 0x228B22),
  ("fuchsia", 0xFF00FF),
  ("gainsboro", 0xDCDCDC),
  ("ghostwhite", 0xF8F8FF),
  ("gold", 0xFFD700),
  ("goldenrod", 0xDAA520),
  ("gray", 0x808080),
  ("green", 0x008000),
  ("greenyellow", 0xADFF2F),
  ("grey", 0x808080),
  ("honeydew", 0xF0FFF0),
  ("hotpink", 0xFF69B4),
  ("indianred", 0xCD5C5C),
  ("indigo", 0x4B0082),
  ("ivory", 0xFFFFF0),
  ("khaki", 0xF0E68C),
  ("lavender", 0xE6E6FA),
  ("lavenderblush", 0xFFF0F5),
  ("lawngreen", 0x7CFC00),
  ("lemonchiffon", 0xFFFACD),
  ("lightblue", 0xADD8E6),
  ("lightcoral", 0xF08080),
  ("lightcyan", 0xE0FFFF),
  ("lightgoldenrodyellow", 0xFAFAD2),
  ("lightgray", 0xD3D3D3),
  ("lightgreen", 0x90EE90),
  ("lightgrey", 0xD3D3D3),
  ("lightpink", 0xFFB6C1),
  ("lightsalmon", 0xFFA07A),
  ("lightseagreen", 0x20B2AA),
  ("lightskyblue", 0x87CEFA),
  ("lightslategray", 0x778899),
  ("lightslategrey", 0x778899),
  ("lightsteelblue", 0xB0C4DE),
  ("lightyellow", 0xFFFFE0),
  ("lime", 0x00FF00),
  ("limegreen", 0x32CD32),
  ("linen", 0xFAF0E6),
  ("magenta", 0xFF00FF),
  ("maroon", 0x800000),
  ("mediumaquamarine", 0x66CDAA),
  ("mediumblue", 0x0000CD),
  ("mediumorchid", 0xBA55D3),
  ("mediumpurple", 0x9370DB),
  ("mediumseagreen", 0x3CB371),
  ("mediumslateblue", 0x7B68EE),
  ("mediumspringgreen", 0x00FA9A),
  ("mediumturquoise", 0x48D1CC),
  ("mediumvioletred", 0xC71585),
  ("midnightblue", 0x191970),
  ("mintcream", 0xF5FFFA),
  ("mistyrose", 0xFFE4E1),
  ("moccasin", 0xFFE4B5),
  ("navajowhite", 0xFFDEAD),
  ("navy", 0x000080),
  ("oldlace", 0xFDF5E6),
  ("olive", 0x808000),
  ("olivedrab", 0x6B8E23),
  ("orange", 0xFFA500),
  ("orangered", 0xFF4500),
  ("orchid", 0xDA70D6),
  ("palegoldenrod", 0xEEE8AA),
  ("palegreen", 0x98FB98),
  ("paleturquoise", 0xAFEEEE),
  ("palevioletred", 0xDB7093),
  ("papayawhip", 0xFFEFD5),
  ("peachpuff", 0xFFDAB9),
  ("peru", 0xCD853F),
  ("pink", 0xFFC0CB),
  ("plum", 0xDDA0DD),
  ("powderblue", 0xB0E0E6),
  ("purple", 0x800080),
  ("rebeccapurple", 0x663399),
  ("red", 0xFF0000),
  ("rosybrown", 0xBC8F8F),
  ("royalblue", 0x4169E1),
  ("saddlebrown", 0x8B4513),
  ("salmon", 0xFA8072),
  ("sandybrown", 0xF4A460),
  ("seagreen", 0x2E8B57),
  ("seashell", 0xFFF5EE),
  ("sienna", 0xA0522D),
  ("silver", 0xC0C0C0),
  ("skyblue", 0x87CEEB),
  ("slateblue", 0x6A5ACD),
  ("slategray", 0x708090),
  ("slategrey", 0x708090),
  ("snow", 0xFFFAFA),
  ("springgreen", 0x00FF7F),
  ("steelblue", 0x4682B4),
  ("tan", 0xD2B48C),
  ("teal", 0x008080),
  ("thistle", 0xD8BFD8),
  ("tomato", 0xFF6347),
  ("turquoise", 0x40E0D0),
  ("violet", 0xEE82EE),
  ("wheat", 0xF5DEB3),
  ("white", 0xFFFFFF),
  ("whitesmoke", 0xF5F5F5),
  ("yellow", 0xFFFF00),
  ("yellowgreen", 0x9ACD32),
]

///|
/// Where a name sits in the table, if it is one.
fn find_name(name : StringView) -> Int32? {
  let mut low = 0
  let mut high = names.length()
  while low < high {
    let middle = (low + high) / 2
    let order = names[middle].0.lexical_compare(name)
    if order < 0 {
      low = middle + 1
    } else if order > 0 {
      high = middle
    } else {
      return Some(middle)
    }
  }
  None
}

///|
/// The colour a CSS keyword names, or `None` if it names nothing.
///
/// `transparent` and `currentcolor` are not here: one is a colour with no alpha
/// and the other is not a colour yet, and both are handled where a declaration
/// is read rather than in the table of names.
pub fn Color::of_name(name : StringView) -> Color? {
  match find_name(name.to_lower()) {
    Some(index) => {
      let packed = names[index].1
      Some(
        Color::rgb((packed >> 16) & 0xFF, (packed >> 8) & 0xFF, packed & 0xFF),
      )
    }
    None => None
  }
}

///|
/// The name this colour has, if it has one, and the shortest such name where
/// several do: `aqua` and `cyan` are the same colour, and four letters beat
/// four letters only by coming first in the table.
///
/// A colour that is not one of the 148 opaque names has no name, however close
/// it looks: a name is a spelling, not an approximation.
pub fn Color::name(self : Color) -> String? {
  if self.alpha != 1.0 {
    return None
  }
  let srgb = self.to_srgb()
  let packed = (byte_of(srgb.x) << 16) |
    (byte_of(srgb.y) << 8) |
    byte_of(srgb.z)
  let mut best : String? = None
  for entry in names {
    if entry.1 == packed {
      let candidate = entry.0
      best = match best {
        None => Some(candidate.to_owned())
        Some(current) =>
          if candidate.length() < current.length() {
            Some(candidate.to_owned())
          } else {
            Some(current)
          }
      }
    }
  }
  best
}

///|
/// All the names, in the order the table keeps them.
pub fn names_all() -> Array[StringView] {
  names.map(fn(entry) { entry.0 })
}

///|
/// The number of names, which CSS Color 4 fixes at 148.
pub fn names_count() -> Int32 {
  names.length()
}