// 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 collapsible header container supporting fluent configuration.
pub struct CollapsingHeader {
  id_salt : String
  title : String
  default_open : Bool
} derive(Eq, Debug)

///|
/// Create a new collapsing header container.
pub fn CollapsingHeader::new(
  id_salt : String,
  title : String,
) -> CollapsingHeader {
  { id_salt, title, default_open: false, }
}

///|
/// Configure default open state on initial display.
pub fn CollapsingHeader::default_open(
  self : CollapsingHeader,
  default_open : Bool,
) -> CollapsingHeader {
  { ..self, default_open, }
}

///|
/// Show the collapsible header and execute content closure if expanded.
pub fn CollapsingHeader::show(
  self : CollapsingHeader,
  ctx : UIContext,
  content : (UIContext) -> Unit,
) -> Response {
  collapsing_header(ctx, self.id_salt, self.title, self.default_open, content)
}

///|
/// Render a collapsible section header with expand/collapse arrow indicator and nested content.
pub fn collapsing_header(
  ctx : UIContext,
  id_salt : String,
  title : String,
  default_open : Bool,
  content : (UIContext) -> Unit,
) -> Response {
  let header_id = ctx.id_stack.derive_id(id_salt)
  let scale = ctx.style.scale
  let w = ctx.available_width()
  let h = (ctx.style.control_h_sm + ctx.style.radius_xs) * scale

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

  let is_focused = resp.has_focus() || ctx.has_focus(id)
  let mut is_open = ctx.is_collapsing_open(header_id, default_open)
  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 is_focused {
    if ctx.input.key_pressed(Key::ArrowRight) && !is_open {
      is_open = true
      ctx.set_collapsing_open(header_id, is_open)
      ctx.input.consume_key(Key::ArrowRight)
    }
    if ctx.input.key_pressed(Key::ArrowLeft) && is_open {
      is_open = false
      ctx.set_collapsing_open(header_id, is_open)
      ctx.input.consume_key(Key::ArrowLeft)
    }
  }

  if resp.clicked() || key_toggle {
    is_open = !is_open
    ctx.set_collapsing_open(header_id, is_open)
  }

  if resp.hovered() {
    ctx.set_cursor_icon("pointer")
  }
  let theme = ctx.theme
  let bg_color = if resp.hovered() { theme.bg_surface } else { theme.bg_subtle }
  let border_color = if is_focused {
    theme.border_focus
  } else if resp.hovered() {
    theme.border_default
  } else {
    theme.border_muted
  }
  let header_radius = ctx.style.radius_sm * scale
  ctx.painter().add_rect(rect, bg_color, header_radius)
  ctx.painter().add_rect_stroke(rect, border_color, 1.0 * scale, header_radius)
  if is_focused {
    ctx
    .painter()
    .add_rect_stroke(
      rect.expand(2.0 * scale),
      theme.border_focus,
      1.5 * scale,
      header_radius + 2.0 * scale,
    )
  }

  let arrow_color = if is_open {
    theme.accent_primary
  } else {
    theme.text_muted
  }
  let arrow_cx = rect.x + 12.0 * scale
  let arrow_cy = rect.y + h * 0.5
  let arrow_stroke = 1.5 * scale
  if is_open {
    let p1 = @math.Vec2::new(arrow_cx - 3.5 * scale, arrow_cy - 1.5 * scale)
    let p2 = @math.Vec2::new(arrow_cx, arrow_cy + 2.0 * scale)
    let p3 = @math.Vec2::new(arrow_cx + 3.5 * scale, arrow_cy - 1.5 * scale)
    ctx.painter().add_line(p1, p2, arrow_color, arrow_stroke)
    ctx.painter().add_line(p2, p3, arrow_color, arrow_stroke)
  } else {
    let p1 = @math.Vec2::new(arrow_cx - 1.5 * scale, arrow_cy - 3.5 * scale)
    let p2 = @math.Vec2::new(arrow_cx + 2.0 * scale, arrow_cy)
    let p3 = @math.Vec2::new(arrow_cx - 1.5 * scale, arrow_cy + 3.5 * scale)
    ctx.painter().add_line(p1, p2, arrow_color, arrow_stroke)
    ctx.painter().add_line(p2, p3, arrow_color, arrow_stroke)
  }

  let font_sz = ctx.style.font_normal * scale
  let title_x = rect.x + 24.0 * scale
  let title_y = rect.y + (rect.h - font_sz) * 0.5
  let text_color = if is_open {
    theme.text_strong
  } else if resp.hovered() {
    theme.text_primary
  } else {
    theme.text_body
  }
  ctx
  .painter()
  .add_text(@math.Vec2::new(title_x, title_y), title, font_sz, text_color)

  if is_open {
    let prev_cursor = ctx.cursor()
    let prev_avail_w = ctx.available_width()
    let indent = ctx.style.spacing_lg * scale

    ctx.set_cursor(@math.Vec2::new(prev_cursor.x + indent, prev_cursor.y))
    ctx.set_available_width(prev_avail_w - indent)

    content(ctx)

    let final_y = ctx.cursor().y
    ctx.set_cursor(@math.Vec2::new(prev_cursor.x, final_y))
    ctx.set_available_width(prev_avail_w)
  }

  resp
}

///|
/// Standalone dropdown selection menu supporting fluent configuration.
pub struct ComboBox {
  id_salt : String
  label : String
  selected_index : Int
  options : Array[String]
} derive(Eq, Debug)

///|
/// Create a new combo box widget.
pub fn ComboBox::new(
  id_salt : String,
  selected_index : Int,
  options : Array[String],
) -> ComboBox {
  { id_salt, label: "", selected_index, options, }
}

///|
/// Configure label for combo box.
pub fn ComboBox::label(self : ComboBox, label : String) -> ComboBox {
  { ..self, label, }
}

///|
/// Show the combo box and return (new_selected_index, response).
pub fn ComboBox::show(self : ComboBox, ctx : UIContext) -> (Int, Response) {
  combo_box(ctx, self.id_salt, self.label, self.selected_index, self.options)
}

///|
/// Render a dropdown selection menu in Studio Light.
/// Returns a tuple of (new_selected_index, response).
pub fn combo_box(
  ctx : UIContext,
  id_salt : String,
  label : String,
  selected_index : Int,
  options : Array[String],
) -> (Int, Response) {
  let combo_id = ctx.id_stack.derive_id(id_salt)
  let scale = ctx.style.scale
  let combo_font_sz = ctx.style.font_normal * scale
  let label_size = if !label.is_empty() {
    ctx.measure_text(label, combo_font_sz).x + 8.0 * scale
  } else {
    0.0
  }
  let box_w = 140.0 * scale
  let total_w = label_size + box_w
  let 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(total_w, h))
  ctx.pop_id()
  ctx.register_focusable(id)

  let box_rect = @math.Rect::new(rect.x + label_size, rect.y, box_w, h)
  let box_hovered = ctx.is_hovered(box_rect)
  if box_hovered {
    ctx.set_cursor_icon("pointer")
  }

  let mut current_idx = selected_index
  let is_open = ctx.open_combo_id() == combo_id
  let is_focused = resp.has_focus() || ctx.has_focus(id)

  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 (box_hovered && ctx.input.mouse_pressed()) || key_toggle {
    if is_open {
      ctx.set_open_combo_id(@core.Id::zero())
    } else {
      ctx.set_open_combo_id(combo_id)
    }
  }

  if is_open {
    if ctx.input.key_pressed(Key::Escape) {
      ctx.set_open_combo_id(@core.Id::zero())
      ctx.input.consume_key(Key::Escape)
    }
    if ctx.input.key_pressed(Key::ArrowDown) {
      if current_idx < options.length() - 1 {
        current_idx = current_idx + 1
      }
      ctx.input.consume_key(Key::ArrowDown)
    }
    if ctx.input.key_pressed(Key::ArrowUp) {
      if current_idx > 0 {
        current_idx = current_idx - 1
      }
      ctx.input.consume_key(Key::ArrowUp)
    }
    if ctx.input.key_pressed(Key::Enter) {
      ctx.set_open_combo_id(@core.Id::zero())
      ctx.input.consume_key(Key::Enter)
    }
  }

  let theme = ctx.theme
  if !label.is_empty() {
    let label_y = rect.y + (h - combo_font_sz) * 0.5
    ctx
    .painter()
    .add_text(
      @math.Vec2::new(rect.x, label_y),
      label,
      combo_font_sz,
      theme.text_primary,
    )
  }

  let bg_color = if box_hovered || is_open {
    theme.bg_subtle
  } else {
    theme.bg_window
  }
  let border_color = if is_focused || is_open {
    theme.border_focus
  } else if box_hovered {
    theme.accent_primary
  } else {
    theme.border_default
  }

  let box_radius = ctx.style.radius_sm * scale
  ctx.painter().add_rect(box_rect, bg_color, box_radius)
  let stroke_w = if is_focused || is_open { 1.5 * scale } else { 1.0 * scale }
  ctx.painter().add_rect_stroke(box_rect, border_color, stroke_w, box_radius)

  let selected_str = if current_idx >= 0 && current_idx < options.length() {
    options[current_idx]
  } else {
    ""
  }
  let text_y = box_rect.y + (h - combo_font_sz) * 0.5
  ctx
  .painter()
  .add_text(
    @math.Vec2::new(box_rect.x + 8.0 * scale, text_y),
    selected_str,
    combo_font_sz,
    theme.text_primary,
  )

  let chevron_cx = box_rect.x + box_w - 14.0 * scale
  let chevron_cy = box_rect.y + h * 0.5
  let chevron_col = if is_open {
    theme.accent_primary
  } else if box_hovered {
    theme.text_primary
  } else {
    theme.text_muted
  }
  if is_open {
    let p1 = @math.Vec2::new(chevron_cx - 3.5 * scale, chevron_cy + 1.5 * scale)
    let p2 = @math.Vec2::new(chevron_cx, chevron_cy - 2.0 * scale)
    let p3 = @math.Vec2::new(chevron_cx + 3.5 * scale, chevron_cy + 1.5 * scale)
    ctx.painter().add_line(p1, p2, chevron_col, 1.5 * scale)
    ctx.painter().add_line(p2, p3, chevron_col, 1.5 * scale)
  } else {
    let p1 = @math.Vec2::new(chevron_cx - 3.5 * scale, chevron_cy - 1.5 * scale)
    let p2 = @math.Vec2::new(chevron_cx, chevron_cy + 2.0 * scale)
    let p3 = @math.Vec2::new(chevron_cx + 3.5 * scale, chevron_cy - 1.5 * scale)
    ctx.painter().add_line(p1, p2, chevron_col, 1.5 * scale)
    ctx.painter().add_line(p2, p3, chevron_col, 1.5 * scale)
  }

  if is_open {
    let item_h = 24.0 * scale
    let total_options_h = item_h * options.length().to_double()
    let max_menu_h = 240.0 * scale
    let menu_h = if total_options_h > max_menu_h {
      max_menu_h
    } else {
      total_options_h
    }

    let screen_h = match ctx.current_clip() {
      Some(c) => if c.h > 0.0 { c.y + c.h } else { 600.0 * scale }
      None => 600.0 * scale
    }
    let mut menu_y = box_rect.y + box_rect.h + 2.0 * scale
    if menu_y + menu_h > screen_h && box_rect.y - menu_h - 2.0 * scale >= 0.0 {
      menu_y = box_rect.y - menu_h - 2.0 * scale
    }
    let menu_rect = @math.Rect::new(box_rect.x, menu_y, box_w, menu_h)

    ctx.begin_foreground()
    ctx.block_hover(menu_rect)

    let combo_scroll_id = ctx.id_stack.derive_id(id_salt + "::combo_scroll")
    let mut combo_scroll_y = ctx.get_scroll_offset(combo_scroll_id)
    let max_scroll = if total_options_h > menu_h {
      total_options_h - menu_h
    } else {
      0.0
    }
    if ctx.is_hovered(menu_rect) && ctx.input.scroll_delta.y != 0.0 {
      combo_scroll_y = combo_scroll_y - ctx.input.scroll_delta.y
    }
    if combo_scroll_y < 0.0 {
      combo_scroll_y = 0.0
    }
    if combo_scroll_y > max_scroll {
      combo_scroll_y = max_scroll
    }
    ctx.set_scroll_offset(combo_scroll_id, combo_scroll_y)

    if ctx.input.mouse_pressed() && !ctx.is_hovered(menu_rect) && !box_hovered {
      ctx.set_open_combo_id(@core.Id::zero())
      ctx.end_foreground()
      return (current_idx, resp)
    }

    let shadow_rect = @math.Rect::new(
      menu_rect.x,
      menu_rect.y + 2.0 * scale,
      menu_rect.w,
      menu_rect.h,
    )
    let menu_radius = ctx.style.radius_md * scale
    let item_radius = ctx.style.radius_sm * scale
    let item_font_sz = ctx.style.font_small * scale
    ctx.painter().add_rect(shadow_rect, theme.shadow, menu_radius)
    ctx.painter().add_rect(menu_rect, theme.bg_window, item_radius)
    ctx
    .painter()
    .add_rect_stroke(menu_rect, theme.border_default, 1.0 * scale, item_radius)

    ctx.push_clip(menu_rect)

    for i = 0; i < options.length(); i = i + 1 {
      let item_rect = @math.Rect::new(
        menu_rect.x,
        menu_rect.y + item_h * i.to_double() - combo_scroll_y,
        box_w,
        item_h,
      )
      let item_hovered = ctx.is_hovered(item_rect)
      if item_hovered {
        ctx.set_cursor_icon("pointer")
        ctx.painter().add_rect(item_rect, theme.bg_surface, item_radius)
        if ctx.input.mouse_pressed() {
          current_idx = i
          ctx.set_open_combo_id(@core.Id::zero())
        }
      }
      let item_text_col = if item_hovered {
        theme.accent_primary
      } else if i == current_idx {
        theme.accent_primary
      } else {
        theme.text_body
      }
      ctx
      .painter()
      .add_text(
        @math.Vec2::new(
          item_rect.x + 8.0 * scale,
          item_rect.y + (item_h - item_font_sz) * 0.5,
        ),
        options[i],
        item_font_sz,
        item_text_col,
      )

      if i == current_idx {
        let chk_x = item_rect.x + box_w - 14.0 * scale
        let chk_y = item_rect.y + item_h * 0.5
        let p1 = @math.Vec2::new(chk_x - 4.0 * scale, chk_y)
        let p2 = @math.Vec2::new(chk_x - 1.5 * scale, chk_y + 2.5 * scale)
        let p3 = @math.Vec2::new(chk_x + 3.5 * scale, chk_y - 2.5 * scale)
        ctx.painter().add_line(p1, p2, theme.accent_primary, 1.5 * scale)
        ctx.painter().add_line(p2, p3, theme.accent_primary, 1.5 * scale)
      }
    }

    ctx.pop_clip()
    ctx.end_foreground()
  }

  (current_idx, resp)
}

///|
/// Standalone horizontal tab bar container supporting fluent configuration.
pub struct TabBar {
  id_str : String
  tabs : Array[String]
  selected_idx : Int
} derive(Eq, Debug)

///|
/// Create a new tab bar widget.
pub fn TabBar::new(
  id_str : String,
  tabs : Array[String],
  selected_idx : Int,
) -> TabBar {
  { id_str, tabs, selected_idx, }
}

///|
/// Show the tab bar and return (new_selected_idx, response).
pub fn TabBar::show(self : TabBar, ctx : UIContext) -> (Int, Response) {
  tab_bar(ctx, self.id_str, self.tabs, self.selected_idx)
}

///|
/// Render a segmented horizontal tab bar in Studio Light.
/// Returns a tuple of (new_selected_index, response).
pub fn tab_bar(
  ctx : UIContext,
  _id_str : String,
  tabs : Array[String],
  selected_idx : Int,
) -> (Int, Response) {
  let scale = ctx.style.scale
  let font_size = ctx.style.font_normal * scale
  let tab_pad = ctx.style.spacing_md * scale
  let mut total_w = 0.0
  for tab in tabs {
    total_w = total_w + ctx.measure_text(tab, font_size).x + tab_pad * 2.0
  }
  let h = (ctx.style.control_h_sm + ctx.style.spacing_xs) * scale
  let (id, rect, resp) = ctx.allocate_space(@math.Vec2::new(total_w, h))
  ctx.register_focusable(id)

  let mut current_selected = selected_idx
  let is_focused = resp.has_focus() || ctx.has_focus(id)

  if is_focused && tabs.length() > 0 {
    if ctx.input.key_pressed(Key::ArrowLeft) {
      current_selected = if current_selected > 0 {
        current_selected - 1
      } else {
        tabs.length() - 1
      }
      ctx.input.consume_key(Key::ArrowLeft)
    }
    if ctx.input.key_pressed(Key::ArrowRight) {
      current_selected = if current_selected < tabs.length() - 1 {
        current_selected + 1
      } else {
        0
      }
      ctx.input.consume_key(Key::ArrowRight)
    }
    if ctx.input.key_pressed(Key::Home) {
      current_selected = 0
      ctx.input.consume_key(Key::Home)
    }
    if ctx.input.key_pressed(Key::End) {
      current_selected = tabs.length() - 1
      ctx.input.consume_key(Key::End)
    }
  }

  let theme = ctx.theme
  let container_radius = ctx.style.radius_md * scale
  let tab_radius = ctx.style.radius_sm * scale
  ctx.painter().add_rect(rect, theme.bg_surface, container_radius)
  ctx
  .painter()
  .add_rect_stroke(
    rect,
    if is_focused {
      theme.border_focus
    } else {
      theme.border_muted
    },
    1.0 * scale,
    container_radius,
  )
  if is_focused {
    ctx
    .painter()
    .add_rect_stroke(
      rect.expand(2.0 * scale),
      theme.border_focus,
      1.5 * scale,
      container_radius + 2.0 * scale,
    )
  }

  let mut cur_x = rect.x
  for i = 0; i < tabs.length(); i = i + 1 {
    let tab_text = tabs[i]
    let text_w = ctx.measure_text(tab_text, font_size).x
    let tab_w = text_w + tab_pad * 2.0
    let tab_rect = @math.Rect::new(cur_x, rect.y, tab_w, h)
    let is_hovered = ctx.is_hovered(tab_rect)
    let is_selected = i == current_selected

    if is_hovered {
      ctx.set_cursor_icon("pointer")
    }
    if is_hovered && ctx.input.mouse_pressed() {
      current_selected = i
    }

    if is_selected {
      let active_rect = tab_rect.shrink(2.0 * scale)
      ctx.painter().add_rect(active_rect, theme.bg_window, tab_radius)
      ctx
      .painter()
      .add_rect_stroke(
        active_rect,
        theme.border_default,
        1.0 * scale,
        tab_radius,
      )
    } else if is_hovered {
      let hover_rect = tab_rect.shrink(2.0 * scale)
      ctx.painter().add_rect(hover_rect, theme.bg_subtle, tab_radius)
    }

    let text_col = if is_selected {
      theme.text_primary
    } else if is_hovered {
      theme.text_primary
    } else {
      theme.text_muted
    }

    let text_x = tab_rect.x + (tab_w - text_w) * 0.5
    let text_y = tab_rect.y + (h - font_size) * 0.5
    ctx
    .painter()
    .add_text(@math.Vec2::new(text_x, text_y), tab_text, font_size, text_col)

    cur_x = cur_x + tab_w
  }

  (current_selected, resp)
}