// 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 checkbox toggle widget supporting fluent configuration.
pub struct Checkbox {
  text : String
  checked : Bool
} derive(Eq, Debug)

///|
/// Create a new checkbox widget.
pub fn Checkbox::new(text : String, checked : Bool) -> Checkbox {
  { text, checked, }
}

///|
/// Reference-bound checkbox widget for uniform `ui.add(...)` dispatch.
pub struct CheckboxRef {
  text : String
  target : Ref[Bool]
}

///|
/// Create a checkbox bound directly to a `Ref[Bool]`.
pub fn Checkbox::from_ref(text : String, target : Ref[Bool]) -> CheckboxRef {
  { text, target, }
}

///|
pub impl @core.Widget for CheckboxRef with fn ui(self, ctx : UIContext) -> Response {
  let (new_state, resp) = Checkbox::new(self.text, self.target.val).show(ctx)
  self.target.val = new_state
  resp
}

///|
/// Render the checkbox and return the updated checked state and interaction response.
pub fn Checkbox::show(self : Checkbox, ctx : UIContext) -> (Bool, Response) {
  let scale = ctx.style.scale
  let box_size = 18.0 * scale
  let gap = ctx.style.spacing_sm * scale
  let font_nm = ctx.style.font_normal * scale
  let text_size = ctx.measure_text(self.text, font_nm)
  let w = box_size + gap + text_size.x
  let h = (ctx.style.control_h_sm - ctx.style.radius_xs) * scale
  let (id, rect, resp) = ctx.allocate_space(@math.Vec2::new(w, h))
  ctx.register_focusable(id)
  if resp.hovered {
    ctx.set_cursor_icon("pointer")
  }
  let is_focused = resp.has_focus() || ctx.has_focus(id)
  let key_toggle = is_focused &&
    !ctx.input.is_key_consumed(Key::Space) &&
    !ctx.input.is_key_consumed(Key::Enter) &&
    (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)
  }
  let new_state = if resp.clicked || key_toggle {
    !self.checked
  } else {
    self.checked
  }

  let box_rect = @math.Rect::new(
    rect.x,
    rect.y + (h - box_size) * 0.5,
    box_size,
    box_size,
  )

  let theme = ctx.theme
  let radius = ctx.style.radius_sm * scale
  if self.checked {
    ctx.painter().add_rect(box_rect, theme.accent_primary, radius)
    let stroke_color = if is_focused {
      theme.border_focus
    } else {
      theme.accent_pressed
    }
    let stroke_w = if is_focused { 1.5 * scale } else { 1.0 * scale }
    ctx.painter().add_rect_stroke(box_rect, stroke_color, stroke_w, radius)
    let inner = box_rect.shrink(4.0 * scale)
    ctx.painter().add_rect(inner, theme.text_inverse, 2.0 * scale)
  } else {
    let box_bg = if resp.hovered { theme.bg_subtle } else { theme.bg_window }
    let box_border = if is_focused {
      theme.border_focus
    } else if resp.hovered {
      theme.accent_primary
    } else {
      theme.border_default
    }
    let stroke_w = if is_focused { 1.5 * scale } else { 1.0 * scale }
    ctx.painter().add_rect(box_rect, box_bg, radius)
    ctx.painter().add_rect_stroke(box_rect, box_border, stroke_w, radius)
  }
  if is_focused {
    ctx
    .painter()
    .add_rect_stroke(
      box_rect.expand(2.0 * scale),
      theme.border_focus,
      1.5 * scale,
      radius + 2.0 * scale,
    )
  }

  let text_x = rect.x + box_size + gap
  let text_y = rect.y + (h - font_nm) * 0.5
  let text_color = if resp.hovered {
    theme.text_primary
  } else {
    theme.text_body
  }
  ctx
  .painter()
  .add_text(@math.Vec2::new(text_x, text_y), self.text, font_nm, text_color)

  (new_state, resp)
}

///|
/// Render an interactive toggle checkbox.
pub fn checkbox(
  ctx : UIContext,
  text : String,
  checked : Bool,
) -> (Bool, Response) {
  Checkbox::new(text, checked).show(ctx)
}

///|
/// Standalone radio button option widget supporting fluent configuration.
pub struct Radio {
  label : String
  selected : Bool
} derive(Eq, Debug)

///|
/// Create a new radio button widget.
pub fn Radio::new(label : String, selected : Bool) -> Radio {
  { label, selected, }
}

///|
/// Render the radio button option.
pub fn Radio::show(self : Radio, ctx : UIContext) -> (Bool, Response) {
  let scale = ctx.style.scale
  let circle_r = 7.0 * scale
  let gap = ctx.style.spacing_sm * scale
  let font_nm = ctx.style.font_normal * scale
  let text_size = ctx.measure_text(self.label, font_nm)
  let total_w = circle_r * 2.0 + gap + text_size.x
  let total_h = (ctx.style.control_h_sm - ctx.style.radius_xs) * scale
  let (id, rect, resp) = ctx.allocate_space(@math.Vec2::new(total_w, total_h))
  if resp.hovered {
    ctx.set_cursor_icon("pointer")
  }
  ctx.register_focusable(id)
  let is_focused = resp.has_focus() || ctx.has_focus(id)
  let key_toggle = is_focused &&
    !ctx.input.is_key_consumed(Key::Space) &&
    !ctx.input.is_key_consumed(Key::Enter) &&
    (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)
  }

  let new_selected = if resp.clicked() || key_toggle {
    true
  } else {
    self.selected
  }

  let center = @math.Vec2::new(
    rect.x + circle_r + 1.0 * scale,
    rect.y + total_h * 0.5,
  )

  let theme = ctx.theme
  let outer_color = if is_focused || self.selected {
    theme.accent_primary
  } else if resp.hovered {
    theme.accent_primary
  } else {
    theme.border_default
  }

  let bg_circle_color = if resp.hovered {
    theme.bg_subtle
  } else {
    theme.bg_window
  }

  ctx.painter().add_circle(center, circle_r, bg_circle_color)
  let stroke_w = if is_focused { 2.0 * scale } else { 1.5 * scale }
  ctx.painter().add_circle_stroke(center, circle_r, outer_color, stroke_w)
  if is_focused {
    ctx
    .painter()
    .add_circle_stroke(
      center,
      circle_r + 2.0 * scale,
      theme.border_focus,
      1.5 * scale,
    )
  }

  if self.selected {
    ctx.painter().add_circle(center, circle_r * 0.5, theme.accent_primary)
  }

  let text_pos = @math.Vec2::new(
    rect.x + circle_r * 2.0 + gap + 1.0 * scale,
    rect.y + (total_h - font_nm) * 0.5,
  )
  let text_color = if resp.hovered {
    theme.text_primary
  } else {
    theme.text_body
  }
  ctx.painter().add_text(text_pos, self.label, font_nm, text_color)

  (new_selected, resp)
}

///|
/// Render an interactive radio button option.
pub fn radio(
  ctx : UIContext,
  label : String,
  selected : Bool,
) -> (Bool, Response) {
  Radio::new(label, selected).show(ctx)
}

///|
/// Standalone toggle switch pill widget supporting fluent configuration.
pub struct Toggle {
  text : String
  checked : Bool
} derive(Eq, Debug)

///|
/// Create a new toggle switch pill widget.
pub fn Toggle::new(text : String, checked : Bool) -> Toggle {
  { text, checked, }
}

///|
/// Reference-bound toggle switch widget for uniform `ui.add(...)` dispatch.
pub struct ToggleRef {
  text : String
  target : Ref[Bool]
}

///|
/// Create a toggle switch bound directly to a `Ref[Bool]`.
pub fn Toggle::from_ref(text : String, target : Ref[Bool]) -> ToggleRef {
  { text, target, }
}

///|
pub impl @core.Widget for ToggleRef with fn ui(self, ctx : UIContext) -> Response {
  let (new_state, resp) = Toggle::new(self.text, self.target.val).show(ctx)
  self.target.val = new_state
  resp
}

///|
/// Render the toggle switch pill and return updated checked state alongside interaction response.
pub fn Toggle::show(self : Toggle, ctx : UIContext) -> (Bool, Response) {
  let scale = ctx.style.scale
  let font_nm = ctx.style.font_normal * scale
  let switch_w = 36.0 * scale
  let switch_h = 20.0 * scale
  let gap = ctx.style.spacing_sm * scale
  let text_size = ctx.measure_text(self.text, font_nm)
  let total_w = switch_w + gap + text_size.x
  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)
  if resp.hovered {
    ctx.set_cursor_icon("pointer")
  }
  let is_focused = resp.has_focus() || ctx.has_focus(id)
  let key_toggle = is_focused &&
    !ctx.input.is_key_consumed(Key::Space) &&
    !ctx.input.is_key_consumed(Key::Enter) &&
    (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)
  }
  let new_state = if resp.clicked || key_toggle {
    !self.checked
  } else {
    self.checked
  }

  let switch_rect = @math.Rect::new(
    rect.x,
    rect.y + (h - switch_h) * 0.5,
    switch_w,
    switch_h,
  )

  let theme = ctx.theme
  let bg_color = if self.checked {
    if resp.hovered {
      theme.accent_pressed
    } else {
      theme.accent_primary
    }
  } else if resp.hovered {
    theme.border_default
  } else {
    theme.border_muted
  }

  ctx.painter().add_rect(switch_rect, bg_color, switch_h * 0.5)
  if is_focused {
    ctx
    .painter()
    .add_rect_stroke(
      switch_rect.expand(2.0 * scale),
      theme.border_focus,
      1.5 * scale,
      switch_h * 0.5 + 2.0 * scale,
    )
  }

  let thumb_radius = (switch_h - 4.0 * scale) * 0.5
  let thumb_cx = if self.checked {
    switch_rect.x + switch_w - 2.0 * scale - thumb_radius
  } else {
    switch_rect.x + 2.0 * scale + thumb_radius
  }
  let thumb_cy = switch_rect.y + switch_h * 0.5
  let thumb_rect = @math.Rect::new(
    thumb_cx - thumb_radius,
    thumb_cy - thumb_radius,
    thumb_radius * 2.0,
    thumb_radius * 2.0,
  )
  ctx.painter().add_rect(thumb_rect, theme.bg_window, thumb_radius)

  let text_x = rect.x + switch_w + gap
  let text_y = rect.y + (h - font_nm) * 0.5
  let text_color = if resp.hovered {
    theme.text_primary
  } else {
    theme.text_body
  }
  ctx
  .painter()
  .add_text(@math.Vec2::new(text_x, text_y), self.text, font_nm, text_color)

  (new_state, resp)
}

///|
/// Render an interactive toggle pill switch.
pub fn toggle(
  ctx : UIContext,
  text : String,
  checked : Bool,
) -> (Bool, Response) {
  Toggle::new(text, checked).show(ctx)
}