// Copyright 2026 Ling71671
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// Convert HSV color components (h in [0, 360], s in [0, 1], v in [0, 1]) to standard RGB.
pub fn hsv_to_rgb(h : Double, s : Double, v : Double) -> (Int, Int, Int) {
  let h_clamped = (h % 360.0 + 360.0) % 360.0
  let s_clamped = if s < 0.0 { 0.0 } else if s > 1.0 { 1.0 } else { s }
  let v_clamped = if v < 0.0 { 0.0 } else if v > 1.0 { 1.0 } else { v }

  let c = v_clamped * s_clamped
  let h_sector = h_clamped / 60.0
  let x = c * (1.0 - (h_sector % 2.0 - 1.0).abs())
  let m = v_clamped - c

  let (r1, g1, b1) = if h_sector < 1.0 {
    (c, x, 0.0)
  } else if h_sector < 2.0 {
    (x, c, 0.0)
  } else if h_sector < 3.0 {
    (0.0, c, x)
  } else if h_sector < 4.0 {
    (0.0, x, c)
  } else if h_sector < 5.0 {
    (x, 0.0, c)
  } else {
    (c, 0.0, x)
  }

  let r = ((r1 + m) * 255.0 + 0.5).to_int()
  let g = ((g1 + m) * 255.0 + 0.5).to_int()
  let b = ((b1 + m) * 255.0 + 0.5).to_int()
  (r, g, b)
}

///|
/// Convert RGB integer components (0..255) to HSV color representation.
pub fn rgb_to_hsv(r : Int, g : Int, b : Int) -> (Double, Double, Double) {
  let rf = r.to_double() / 255.0
  let gf = g.to_double() / 255.0
  let bf = b.to_double() / 255.0

  let cmax = if rf > gf {
    if rf > bf {
      rf
    } else {
      bf
    }
  } else if gf > bf {
    gf
  } else {
    bf
  }

  let cmin = if rf < gf {
    if rf < bf {
      rf
    } else {
      bf
    }
  } else if gf < bf {
    gf
  } else {
    bf
  }

  let delta = cmax - cmin
  let v = cmax
  let s = if cmax > 0.00001 { delta / cmax } else { 0.0 }

  let h = if delta < 0.00001 {
    0.0
  } else if cmax == rf {
    let mut val = 60.0 * ((gf - bf) / delta)
    if val < 0.0 {
      val = val + 360.0
    }
    val
  } else if cmax == gf {
    60.0 * ((bf - rf) / delta) + 120.0
  } else {
    60.0 * ((rf - gf) / delta) + 240.0
  }

  (h, s, v)
}

///|
fn hex_byte(val : Int) -> String {
  let hex_digits = "0123456789ABCDEF"
  let hi = (val >> 4) & 0xF
  let lo = val & 0xF
  let hi_s = hex_digits[hi:hi + 1].to_owned()
  let lo_s = hex_digits[lo:lo + 1].to_owned()
  hi_s + lo_s
}

///|
/// Render a full interactive 2D HSV color picker panel featuring a 2D sat/val square,
/// rainbow hue slider, alpha opacity slider, and dual-ring high-contrast grip.
pub fn color_picker(
  ctx : UIContext,
  id_salt : String,
  color : @color.Color,
  show_alpha? : Bool = true,
) -> (@color.Color, Response) {
  let scale = ctx.style.scale
  let width = 180.0 * scale
  let height = (if show_alpha { 230.0 } else { 206.0 }) * scale

  ctx.push_id(id_salt)
  let (_panel_id, rect, resp) = ctx.allocate_space(
    @math.Vec2::new(width, height),
  )
  ctx.pop_id()

  // Panel background card
  let card_radius = ctx.style.radius_md * scale
  let inner_radius = ctx.style.radius_sm * scale
  ctx.painter().add_rect(rect, @color.Color::bg_surface(), card_radius)
  ctx
  .painter()
  .add_rect_stroke(
    rect,
    @color.Color::border_default(),
    1.0 * scale,
    card_radius,
  )

  let pad = 10.0 * scale
  let sq_x = rect.x + pad
  let sq_y = rect.y + pad
  let sq_w = rect.w - pad * 2.0
  let sq_h = 126.0 * scale
  let sq_rect = @math.Rect::new(sq_x, sq_y, sq_w, sq_h)

  let (init_h, init_s, init_v) = rgb_to_hsv(color.r, color.g, color.b)
  let panel_id = ctx.id_stack.derive_id("color_picker::" + id_salt)
  let mut h = if init_s > 0.001 && init_v > 0.001 {
    ctx
    .memory()
    .set_double(panel_id, init_h, tag="hue", frame=ctx.frame_counter())
    init_h
  } else {
    ctx.memory().get_double_or_default(panel_id, init_h, tag="hue")
  }
  let mut s = init_s
  let mut v = init_v
  let mut a = color.a

  // 1. 2D Saturation/Value Area Interaction
  let sv_id = ctx.id_stack.derive_id("cp_sv::" + id_salt)
  let sv_hovered = ctx.is_hovered(sq_rect)
  if sv_hovered || ctx.active_id() == sv_id {
    ctx.set_cursor_icon("crosshair")
  }
  if sv_hovered && ctx.input.mouse_pressed {
    ctx.set_active_id(sv_id)
  }

  if ctx.active_id() == sv_id {
    ctx.set_wants_capture_mouse(true)
    if ctx.input.mouse_down {
      let raw_s = (ctx.input.mouse_pos.x - sq_x) / sq_w
      let raw_v = 1.0 - (ctx.input.mouse_pos.y - sq_y) / sq_h
      s = if raw_s < 0.0 { 0.0 } else if raw_s > 1.0 { 1.0 } else { raw_s }
      v = if raw_v < 0.0 { 0.0 } else if raw_v > 1.0 { 1.0 } else { raw_v }
    }
    if ctx.input.mouse_released {
      ctx.set_active_id(Id::zero())
    }
  }

  // Draw 2D Sat/Val Surface using smooth continuous gradients
  let (pure_r, pure_g, pure_b) = hsv_to_rgb(h, 1.0, 1.0)
  ctx
  .painter()
  .add_rect(sq_rect, @color.Color::rgb(pure_r, pure_g, pure_b), inner_radius)
  ctx
  .painter()
  .add_linear_gradient(
    sq_rect,
    @math.Vec2::new(sq_x, sq_y),
    @math.Vec2::new(sq_x + sq_w, sq_y),
    @color.Color::rgba(255, 255, 255, 255),
    @color.Color::rgba(255, 255, 255, 0),
    inner_radius,
  )
  ctx
  .painter()
  .add_linear_gradient(
    sq_rect,
    @math.Vec2::new(sq_x, sq_y),
    @math.Vec2::new(sq_x, sq_y + sq_h),
    @color.Color::rgba(0, 0, 0, 0),
    @color.Color::rgba(0, 0, 0, 255),
    inner_radius,
  )

  // Border outline for SV square
  ctx
  .painter()
  .add_rect_stroke(
    sq_rect,
    @color.Color::border_muted(),
    1.0 * scale,
    inner_radius,
  )

  // Dual-ring cursor on SV area
  let cur_x = sq_x + s * sq_w
  let cur_y = sq_y + (1.0 - v) * sq_h
  let cur_center = @math.Vec2::new(cur_x, cur_y)
  ctx
  .painter()
  .add_circle_stroke(
    cur_center,
    5.0 * scale,
    @color.Color::bg_window(),
    1.6 * scale,
  )
  ctx
  .painter()
  .add_circle_stroke(
    cur_center,
    3.5 * scale,
    @color.Color::text_primary(),
    1.2 * scale,
  )

  // 2. Hue Rainbow Slider
  let hue_y = sq_y + sq_h + 8.0 * scale
  let hue_h = 12.0 * scale
  let hue_rect = @math.Rect::new(sq_x, hue_y, sq_w, hue_h)
  let hue_id = ctx.id_stack.derive_id("cp_hue::" + id_salt)

  let hue_hovered = ctx.is_hovered(hue_rect)
  if hue_hovered || ctx.active_id() == hue_id {
    ctx.set_cursor_icon("ew-resize")
  }
  if hue_hovered && ctx.input.mouse_pressed {
    ctx.set_active_id(hue_id)
  }

  if ctx.active_id() == hue_id {
    ctx.set_wants_capture_mouse(true)
    if ctx.input.mouse_down {
      let raw_h = (ctx.input.mouse_pos.x - sq_x) / sq_w * 360.0
      h = if raw_h < 0.0 { 0.0 } else if raw_h > 360.0 { 360.0 } else { raw_h }
      ctx.memory().set_double(panel_id, h, tag="hue", frame=ctx.frame_counter())
    }
    if ctx.input.mouse_released {
      ctx.set_active_id(Id::zero())
    }
  }

  // Draw Continuous Rainbow Multi-Stop Gradient Slices
  let rainbow_stops : Array[(Double, Double, @color.Color, @color.Color)] = [
    (0.0, 60.0, @color.Color::rgb(255, 0, 0), @color.Color::rgb(255, 255, 0)),
    (60.0, 120.0, @color.Color::rgb(255, 255, 0), @color.Color::rgb(0, 255, 0)),
    (120.0, 180.0, @color.Color::rgb(0, 255, 0), @color.Color::rgb(0, 255, 255)),
    (180.0, 240.0, @color.Color::rgb(0, 255, 255), @color.Color::rgb(0, 0, 255)),
    (240.0, 300.0, @color.Color::rgb(0, 0, 255), @color.Color::rgb(255, 0, 255)),
    (300.0, 360.0, @color.Color::rgb(255, 0, 255), @color.Color::rgb(255, 0, 0)),
  ]
  let seg_w = sq_w / 6.0
  for idx, item in rainbow_stops {
    let (_, _, c_start, c_end) = item
    let seg_x = sq_x + idx.to_double() * seg_w
    let seg_rect = @math.Rect::new(seg_x, hue_y, seg_w + 0.5, hue_h)
    let p_start = @math.Vec2::new(seg_x, hue_y)
    let p_end = @math.Vec2::new(seg_x + seg_w, hue_y)
    let corner_r = if idx == 0 || idx == 5 { inner_radius } else { 0.0 }
    ctx
    .painter()
    .add_linear_gradient(seg_rect, p_start, p_end, c_start, c_end, corner_r)
  }
  ctx
  .painter()
  .add_rect_stroke(
    hue_rect,
    @color.Color::border_muted(),
    1.0 * scale,
    inner_radius,
  )

  // Hue Slider Thumb
  let hue_thumb_x = sq_x + h / 360.0 * sq_w
  let hue_thumb_center = @math.Vec2::new(hue_thumb_x, hue_y + hue_h * 0.5)
  ctx
  .painter()
  .add_circle(hue_thumb_center, 6.0 * scale, @color.Color::bg_window())
  ctx
  .painter()
  .add_circle_stroke(
    hue_thumb_center,
    6.0 * scale,
    @color.Color::border_default(),
    1.0 * scale,
  )

  // 3. Alpha Slider (Optional)
  if show_alpha {
    let alpha_y = hue_y + hue_h + 8.0 * scale
    let alpha_h = 12.0 * scale
    let alpha_rect = @math.Rect::new(sq_x, alpha_y, sq_w, alpha_h)
    let alpha_id = ctx.id_stack.derive_id("cp_alpha::" + id_salt)

    let alpha_hovered = ctx.is_hovered(alpha_rect)
    if alpha_hovered || ctx.active_id() == alpha_id {
      ctx.set_cursor_icon("ew-resize")
    }
    if alpha_hovered && ctx.input.mouse_pressed {
      ctx.set_active_id(alpha_id)
    }

    if ctx.active_id() == alpha_id {
      ctx.set_wants_capture_mouse(true)
      if ctx.input.mouse_down {
        let raw_a = (ctx.input.mouse_pos.x - sq_x) / sq_w * 255.0
        let val_a = raw_a.to_int()
        a = if val_a < 0 { 0 } else if val_a > 255 { 255 } else { val_a }
      }
      if ctx.input.mouse_released {
        ctx.set_active_id(Id::zero())
      }
    }

    // Alpha Bar Background & Continuous Gradient
    ctx.painter().add_rect(alpha_rect, @color.Color::bg_subtle(), inner_radius)
    let (cur_r, cur_g, cur_b) = hsv_to_rgb(h, s, v)
    let c_start = @color.Color::rgba(cur_r, cur_g, cur_b, 0)
    let c_end = @color.Color::rgba(cur_r, cur_g, cur_b, 255)
    let p_start = @math.Vec2::new(sq_x, alpha_y)
    let p_end = @math.Vec2::new(sq_x + sq_w, alpha_y)
    ctx
    .painter()
    .add_linear_gradient(
      alpha_rect, p_start, p_end, c_start, c_end, inner_radius,
    )
    ctx
    .painter()
    .add_rect_stroke(
      alpha_rect,
      @color.Color::border_muted(),
      1.0 * scale,
      inner_radius,
    )

    // Alpha Slider Thumb
    let alpha_thumb_x = sq_x + a.to_double() / 255.0 * sq_w
    let alpha_thumb_center = @math.Vec2::new(
      alpha_thumb_x,
      alpha_y + alpha_h * 0.5,
    )
    ctx
    .painter()
    .add_circle(alpha_thumb_center, 6.0 * scale, @color.Color::bg_window())
    ctx
    .painter()
    .add_circle_stroke(
      alpha_thumb_center,
      6.0 * scale,
      @color.Color::border_default(),
      1.0 * scale,
    )
  }

  // 4. Color Swatch & Hex Display
  let (final_r, final_g, final_b) = hsv_to_rgb(h, s, v)
  let updated_color = @color.Color::rgba(final_r, final_g, final_b, a)

  let footer_y = if show_alpha {
    hue_y + hue_h + 8.0 * scale + 12.0 * scale + 8.0 * scale
  } else {
    hue_y + hue_h + 8.0 * scale
  }
  let swatch_rect = @math.Rect::new(sq_x, footer_y, 22.0 * scale, 22.0 * scale)
  ctx.painter().add_rect(swatch_rect, updated_color, inner_radius)
  ctx
  .painter()
  .add_rect_stroke(
    swatch_rect,
    @color.Color::border_default(),
    1.0 * scale,
    inner_radius,
  )

  let hex_text = if show_alpha {
    "#" +
    hex_byte(final_r) +
    hex_byte(final_g) +
    hex_byte(final_b) +
    hex_byte(a)
  } else {
    "#" + hex_byte(final_r) + hex_byte(final_g) + hex_byte(final_b)
  }

  let font_sz = ctx.style.font_small * scale
  let hex_pos = @math.Vec2::new(
    sq_x + 30.0 * scale,
    footer_y + (22.0 * scale - font_sz) * 0.5,
  )
  ctx
  .painter()
  .add_text(hex_pos, hex_text, font_sz, @color.Color::text_strong())

  (updated_color, resp)
}

///|
/// Render a compact color picker button which opens a floating popup color picker when clicked.
pub fn color_picker_button(
  ctx : UIContext,
  id_salt : String,
  color : @color.Color,
  show_alpha? : Bool = true,
) -> (@color.Color, Response) {
  let scale = ctx.style.scale
  let font_sz = ctx.style.font_small * scale
  let hex_text = if show_alpha {
    "#" +
    hex_byte(color.r) +
    hex_byte(color.g) +
    hex_byte(color.b) +
    hex_byte(color.a)
  } else {
    "#" + hex_byte(color.r) + hex_byte(color.g) + hex_byte(color.b)
  }

  let txt_sz = ctx.measure_text(hex_text, font_sz)
  let btn_w = (20.0 + 8.0 + 16.0) * scale + txt_sz.x
  let btn_h = (ctx.style.control_h_sm + ctx.style.spacing_xs) * scale

  ctx.push_id(id_salt)
  let (id, rect, resp) = ctx.allocate_space(@math.Vec2::new(btn_w, btn_h))
  ctx.pop_id()
  ctx.register_focusable(id)

  let is_focused = resp.has_focus() || ctx.has_focus(id)
  let is_hovered = ctx.is_hovered(rect)
  let btn_bg = if is_hovered {
    @color.Color::bg_hover()
  } else {
    @color.Color::bg_surface()
  }
  let btn_radius = ctx.style.radius_sm * scale
  ctx.painter().add_rect(rect, btn_bg, btn_radius)
  ctx
  .painter()
  .add_rect_stroke(
    rect,
    if is_focused {
      @color.Color::border_focus()
    } else {
      @color.Color::border_default()
    },
    1.0,
    btn_radius,
  )
  if is_focused {
    ctx
    .painter()
    .add_rect_stroke(
      rect.expand(2.0 * scale),
      @color.Color::border_focus(),
      1.5,
      btn_radius + 2.0 * scale,
    )
  }

  // Color Swatch Pill
  let swatch_size = 16.0 * scale
  let swatch_pad = 6.0 * scale
  let swatch_rect = @math.Rect::new(
    rect.x + swatch_pad,
    rect.y + (btn_h - swatch_size) * 0.5,
    swatch_size,
    swatch_size,
  )
  ctx.painter().add_rect(swatch_rect, color, btn_radius)
  ctx
  .painter()
  .add_rect_stroke(swatch_rect, @color.Color::border_muted(), 1.0, btn_radius)

  // Hex Text
  let text_y = rect.y + (btn_h - font_sz) * 0.5
  ctx
  .painter()
  .add_text(
    @math.Vec2::new(rect.x + swatch_pad * 2.0 + swatch_size, text_y),
    hex_text,
    font_sz,
    @color.Color::text_body(),
  )

  let is_open = ctx.open_combo_id() == id
  let mut next_color = color

  let key_toggle = is_focused &&
    (ctx.input.key_pressed(Key::Space) || ctx.input.key_pressed(Key::Enter))
  if key_toggle {
    ctx.input.consume_key(Key::Space)
    ctx.input.consume_key(Key::Enter)
  }

  if resp.clicked || key_toggle {
    if is_open {
      ctx.set_open_combo_id(Id::zero())
    } else {
      ctx.set_open_combo_id(id)
    }
  }

  if is_open {
    ctx.begin_foreground()
    let popup_pos = @math.Vec2::new(rect.x, rect.y + btn_h + 4.0 * scale)
    let prev_cursor = ctx.cursor()
    ctx.set_cursor(popup_pos)
    let (c, _) = color_picker(ctx, id_salt + "::popup", color, show_alpha~)
    next_color = c
    ctx.set_cursor(prev_cursor)
    ctx.end_foreground()

    if ctx.input.key_pressed(Key::Escape) {
      ctx.set_open_combo_id(Id::zero())
      ctx.input.consume_key(Key::Escape)
    }
  }

  (next_color, resp)
}