// 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.

///|
/// Standalone continuous floating-point slider widget supporting fluent configuration.
pub struct Slider {
  label : String
  value : Double
  min : Double
  max : Double
  step : Double?
} derive(Eq, Debug)

///|
/// Create a new slider widget.
pub fn Slider::new(
  label : String,
  value : Double,
  min : Double,
  max : Double,
) -> Slider {
  { label, value, min, max, step: None, }
}

///|
/// Configure an optional stepping increment for keyboard and drag adjustment.
pub fn Slider::step(self : Slider, step : Double) -> Slider {
  { ..self, step: Some(step), }
}

///|
/// Reference-bound slider widget for uniform `ui.add(...)` dispatch.
pub struct SliderRef {
  label : String
  target : Ref[Double]
  min : Double
  max : Double
  step : Double?
}

///|
/// Create a slider bound directly to a `Ref[Double]` variable.
pub fn Slider::from_ref(
  label : String,
  target : Ref[Double],
  min : Double,
  max : Double,
) -> SliderRef {
  { label, target, min, max, step: None, }
}

///|
/// Configure stepping increment on reference-bound slider.
pub fn SliderRef::step(self : SliderRef, step : Double) -> SliderRef {
  { ..self, step: Some(step), }
}

///|
/// Render the reference-bound slider, writing the updated value to target ref.
pub impl @core.Widget for SliderRef with fn ui(self, ctx : UIContext) -> Response {
  let slider = match self.step {
    Some(s) =>
      Slider::new(self.label, self.target.val, self.min, self.max).step(s)
    None => Slider::new(self.label, self.target.val, self.min, self.max)
  }
  let (new_val, resp) = slider.show(ctx)
  self.target.val = new_val
  resp
}

///|
/// Render the slider and return the updated value alongside the interaction response.
pub fn Slider::show(self : Slider, ctx : UIContext) -> (Double, Response) {
  let scale = ctx.style.scale
  let font_size = ctx.style.font_normal * scale
  let font_sm = ctx.style.font_small * scale
  let label_size = ctx.measure_text(self.label, font_size)
  let label_w = if self.label.length() > 0 {
    label_size.x + 12.0 * scale
  } else {
    0.0
  }
  let track_w = 120.0 * scale

  let clamped_min = if self.min < self.max { self.min } else { self.max }
  let clamped_max = if self.max > self.min { self.max } else { self.min }
  let range = clamped_max - clamped_min
  let safe_range = if range > 0.000001 { range } else { 1.0 }

  let format_val = fn(v : Double) -> String {
    if safe_range >= 10.0 {
      v.to_int().to_string()
    } else {
      let int_part = v.to_int()
      let diff = v - int_part.to_double()
      let abs_diff = if diff < 0.0 { -diff } else { diff }
      let frac_part = (abs_diff * 100.0).to_int()
      let frac_str = if frac_part < 10 {
        "0" + frac_part.to_string()
      } else {
        frac_part.to_string()
      }
      int_part.to_string() + "." + frac_str
    }
  }

  let min_str = format_val(clamped_min)
  let max_str = format_val(clamped_max)
  let min_text_w = ctx.measure_text(min_str, font_sm).x
  let max_text_w = ctx.measure_text(max_str, font_sm).x
  let raw_val_w = if min_text_w > max_text_w { min_text_w } else { max_text_w }
  let base_val_w = 40.0 * scale
  let val_w = if raw_val_w + 12.0 * scale > base_val_w {
    raw_val_w + 12.0 * scale
  } else {
    base_val_w
  }

  let total_w = label_w + track_w + val_w + 8.0 * scale
  let h = ctx.style.control_h_sm * scale
  let (id, rect, resp) = ctx.allocate_space(@math.Vec2::new(total_w, h))
  ctx.register_focusable(id)

  let track_h = 6.0 * scale
  let track_rect = @math.Rect::new(
    rect.x + label_w,
    rect.y + (h - track_h) * 0.5,
    track_w,
    track_h,
  )

  // Hit-testing on interactive track zone
  let touch_rect = @math.Rect::new(
    track_rect.x - 4.0 * scale,
    rect.y,
    track_w + 8.0 * scale,
    h,
  )
  let is_hovered = ctx.is_hovered(touch_rect)
  if is_hovered && ctx.input.mouse_pressed {
    ctx.set_active_id(id)
  }

  let is_active = ctx.active_id() == id
  if is_hovered || is_active {
    ctx.set_cursor_icon("ew-resize")
  }

  let mut new_val = self.value
  if is_active {
    if ctx.input.mouse_down {
      let mouse_x = ctx.input.mouse_pos.x
      let mut pct = (mouse_x - track_rect.x) / track_rect.w
      if pct < 0.0 {
        pct = 0.0
      } else if pct > 1.0 {
        pct = 1.0
      }
      new_val = clamped_min + pct * safe_range
      if self.step is Some(s) {
        if s > 0.000001 {
          let steps_cnt = ((new_val - clamped_min) / s + 0.5)
            .to_int()
            .to_double()
          new_val = clamped_min + steps_cnt * s
          if new_val > clamped_max {
            new_val = clamped_max
          }
        }
      }
    } else {
      ctx.set_active_id(@core.Id::zero())
    }
  }

  // Keyboard navigation when focused
  let is_focused = resp.has_focus()
  if is_focused {
    let step = match self.step {
      Some(s) => s
      None =>
        if safe_range >= 100.0 {
          1.0
        } else if safe_range >= 10.0 {
          0.5
        } else {
          0.05
        }
    }
    if ctx.input.key_pressed(Key::ArrowLeft) ||
      ctx.input.key_pressed(Key::ArrowDown) {
      new_val = new_val - step
      if new_val < clamped_min {
        new_val = clamped_min
      }
      ctx.input.consume_key(Key::ArrowLeft)
      ctx.input.consume_key(Key::ArrowDown)
    }
    if ctx.input.key_pressed(Key::ArrowRight) ||
      ctx.input.key_pressed(Key::ArrowUp) {
      new_val = new_val + step
      if new_val > clamped_max {
        new_val = clamped_max
      }
      ctx.input.consume_key(Key::ArrowRight)
      ctx.input.consume_key(Key::ArrowUp)
    }
    if ctx.input.key_pressed(Key::Home) {
      new_val = clamped_min
      ctx.input.consume_key(Key::Home)
    }
    if ctx.input.key_pressed(Key::End) {
      new_val = clamped_max
      ctx.input.consume_key(Key::End)
    }
    let page_step = step * 10.0
    if ctx.input.key_pressed(Key::PageUp) {
      new_val = new_val + page_step
      if new_val > clamped_max {
        new_val = clamped_max
      }
      ctx.input.consume_key(Key::PageUp)
    }
    if ctx.input.key_pressed(Key::PageDown) {
      new_val = new_val - page_step
      if new_val < clamped_min {
        new_val = clamped_min
      }
      ctx.input.consume_key(Key::PageDown)
    }
  }

  // Draw Label
  let theme = ctx.theme
  if self.label.length() > 0 {
    ctx
    .painter()
    .add_text(
      @math.Vec2::new(rect.x, rect.y + (h - font_size) * 0.5),
      self.label,
      font_size,
      theme.text_primary,
    )
  }

  let track_radius = 3.0 * scale

  // Draw Recessed Track Background
  ctx.painter().add_rect(track_rect, theme.bg_surface, track_radius)
  ctx
  .painter()
  .add_rect_stroke(track_rect, theme.border_muted, 1.0 * scale, track_radius)

  // Draw Discrete Ticks (25%, 50%, 75%)
  let tick_col = theme.border_default
  for t in [0.25, 0.5, 0.75] {
    let tick_x = track_rect.x + track_rect.w * t
    let tick_y = track_rect.y + track_rect.h * 0.5
    ctx
    .painter()
    .add_circle(@math.Vec2::new(tick_x, tick_y), 1.5 * scale, tick_col)
  }

  // Draw Active Progress Fill
  let mut current_pct = (new_val - clamped_min) / safe_range
  if current_pct < 0.0 {
    current_pct = 0.0
  } else if current_pct > 1.0 {
    current_pct = 1.0
  }
  let active_w = track_rect.w * current_pct
  if active_w > 0.0 {
    let fill_rect = @math.Rect::new(
      track_rect.x,
      track_rect.y,
      active_w,
      track_rect.h,
    )
    ctx.painter().add_rect(fill_rect, theme.accent_primary, track_radius)
  }

  // Compute Value Text
  let val_text = format_val(new_val)

  // Draw White Ceramic Thumb Handle with Cobalt Border
  let thumb_cx = track_rect.x + active_w
  let thumb_cy = track_rect.y + track_rect.h * 0.5
  let radius_sm = ctx.style.radius_sm * scale

  let thumb_r = if is_active {
    7.0 * scale
  } else if is_hovered || is_focused {
    6.5 * scale
  } else {
    5.5 * scale
  }
  ctx
  .painter()
  .add_circle(@math.Vec2::new(thumb_cx, thumb_cy), thumb_r, theme.bg_window)
  let thumb_stroke = if is_focused {
    theme.border_focus
  } else {
    theme.accent_primary
  }
  let thumb_stroke_w = if is_focused { 2.0 * scale } else { 1.5 * scale }
  ctx
  .painter()
  .add_circle_stroke(
    @math.Vec2::new(thumb_cx, thumb_cy),
    thumb_r,
    thumb_stroke,
    thumb_stroke_w,
  )

  // Draw Floating HUD Bubble above Thumb when active
  if is_active {
    let bubble_size = ctx.measure_text(val_text, font_sm)
    let bubble_w = bubble_size.x + 14.0 * scale
    let bubble_h = 20.0 * scale
    let bubble_x = thumb_cx - bubble_w * 0.5
    let bubble_y = thumb_cy - thumb_r - bubble_h - 4.0 * scale
    let bubble_rect = @math.Rect::new(bubble_x, bubble_y, bubble_w, bubble_h)
    ctx.painter().add_rect(bubble_rect, theme.bg_window, radius_sm)
    ctx
    .painter()
    .add_rect_stroke(bubble_rect, theme.border_default, 1.0 * scale, radius_sm)
    let bubble_text_pos = @math.Vec2::new(
      bubble_x + 7.0 * scale,
      bubble_y + (bubble_h - font_sm) * 0.5,
    )
    ctx
    .painter()
    .add_text(bubble_text_pos, val_text, font_sm, theme.text_primary)
  }

  // Draw Value Text in row
  ctx
  .painter()
  .add_text(
    @math.Vec2::new(
      track_rect.x + track_w + 10.0 * scale,
      rect.y + (h - font_sm) * 0.5,
    ),
    val_text,
    font_sm,
    theme.text_secondary,
  )

  (new_val, resp)
}

///|
/// Render a continuous floating-point slider.
pub fn slider(
  ctx : UIContext,
  label : String,
  value : Double,
  min : Double,
  max : Double,
  step? : Double,
) -> (Double, Response) {
  let s = Slider::new(label, value, min, max)
  let s2 = match step {
    Some(st) => s.step(st)
    None => s
  }
  s2.show(ctx)
}

///|
/// Render an integer step slider.
pub fn slider_int(
  ctx : UIContext,
  label : String,
  value : Int,
  min : Int,
  max : Int,
  step? : Int,
) -> (Int, Response) {
  let st = match step {
    Some(s) => Some(s.to_double())
    None => None
  }
  let (new_val, resp) = slider(
    ctx,
    label,
    value.to_double(),
    min.to_double(),
    max.to_double(),
    step?=st,
  )
  let rounded = if new_val >= 0.0 {
    (new_val + 0.5).to_int()
  } else {
    (new_val - 0.5).to_int()
  }
  (rounded, resp)
}

///|
/// Render a compact numerical drag-value box.
pub fn drag_value(
  ctx : UIContext,
  label : String,
  value : Double,
  speed? : Double = 0.1,
  min? : Double = 0.0,
  max? : Double = 0.0,
) -> (Double, Response) {
  let scale = ctx.style.scale
  let font_nm = ctx.style.font_normal * scale
  let font_sm = ctx.style.font_small * scale
  let radius_sm = ctx.style.radius_sm * scale
  let box_w = 64.0 * scale
  let h = (ctx.style.control_h_sm - ctx.style.radius_xs) * scale

  let label_size = ctx.measure_text(label, font_nm)
  let label_w = if label.length() > 0 {
    label_size.x + 8.0 * scale
  } else {
    0.0
  }
  let total_w = label_w + box_w
  let (id, rect, resp) = ctx.allocate_space(@math.Vec2::new(total_w, h))
  ctx.register_focusable(id)

  let box_rect = @math.Rect::new(rect.x + label_w, rect.y, box_w, h)
  let is_hovered = ctx.is_hovered(box_rect)
  if is_hovered && ctx.input.mouse_pressed {
    ctx.set_active_id(id)
  }

  let is_active = ctx.active_id() == id
  if is_hovered || is_active {
    ctx.set_cursor_icon("ew-resize")
  }
  let mut new_val = value

  let base_speed = if speed > 0.0001 { speed } else { 1.0 }
  let multiplier = if ctx.input.modifiers.shift {
    0.1
  } else if ctx.input.modifiers.ctrl {
    10.0
  } else {
    1.0
  }
  let step = base_speed * multiplier

  // 1. Keyboard Arrow Left / Right when focused
  let is_focused = resp.has_focus()
  if is_focused {
    if ctx.input.key_pressed(Key::ArrowLeft) ||
      ctx.input.key_pressed(Key::ArrowDown) {
      new_val = new_val - step
      ctx.input.consume_key(Key::ArrowLeft)
      ctx.input.consume_key(Key::ArrowDown)
    }
    if ctx.input.key_pressed(Key::ArrowRight) ||
      ctx.input.key_pressed(Key::ArrowUp) {
      new_val = new_val + step
      ctx.input.consume_key(Key::ArrowRight)
      ctx.input.consume_key(Key::ArrowUp)
    }
    if min < max {
      if new_val < min {
        new_val = min
      } else if new_val > max {
        new_val = max
      }
    }
  }

  // 2. Pointer drag tracking
  if is_active {
    if ctx.input.mouse_down {
      let delta_x = ctx.input.mouse_delta.x
      new_val = new_val + delta_x * step
      if min < max {
        if new_val < min {
          new_val = min
        } else if new_val > max {
          new_val = max
        }
      }
    } else {
      ctx.set_active_id(@core.Id::zero())
    }
  }

  // 3. Draw Label
  let theme = ctx.theme
  if label.length() > 0 {
    ctx
    .painter()
    .add_text(
      @math.Vec2::new(rect.x, rect.y + (h - font_nm) * 0.5),
      label,
      font_nm,
      theme.text_primary,
    )
  }

  // 4. Draw Box Background & Mercury Progress Fill
  let bg_color = if is_active {
    theme.bg_surface
  } else if is_hovered {
    theme.bg_window
  } else {
    theme.bg_subtle
  }

  let border_color = if is_focused {
    theme.border_focus
  } else if is_active || is_hovered {
    theme.accent_primary
  } else {
    theme.border_default
  }

  ctx.painter().add_rect(box_rect, bg_color, radius_sm)

  // Mercury Progress Fill inside box
  if min < max {
    let range = max - min
    let pct = (new_val - min) / range
    let safe_pct = if pct < 0.0 { 0.0 } else if pct > 1.0 { 1.0 } else { pct }
    let fill_w = box_rect.w * safe_pct
    if fill_w > 0.0 {
      let mercury_rect = @math.Rect::new(
        box_rect.x,
        box_rect.y,
        fill_w,
        box_rect.h,
      )
      ctx.painter().add_rect(mercury_rect, theme.accent_soft, radius_sm)
    }
  }

  ctx.painter().add_rect_stroke(box_rect, border_color, 1.0 * scale, radius_sm)
  if is_focused {
    ctx
    .painter()
    .add_rect_stroke(
      box_rect.expand(2.0 * scale),
      theme.border_focus,
      1.5 * scale,
      radius_sm + 2.0 * scale,
    )
  }

  // Format value to 1 decimal place or integer
  let rounded_int = (new_val * 10.0).to_int()
  let whole = rounded_int / 10
  let frac = (rounded_int % 10).abs()
  let val_str = whole.to_string() + "." + frac.to_string()

  let val_size = ctx.measure_text(val_str, font_sm)
  let text_x = box_rect.x + (box_w - val_size.x) * 0.5
  let text_y = box_rect.y + (h - font_sm) * 0.5
  ctx
  .painter()
  .add_text(
    @math.Vec2::new(text_x, text_y),
    val_str,
    font_sm,
    if is_active {
      theme.accent_primary
    } else {
      theme.text_primary
    },
  )

  (new_val, resp)
}