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

///|
pub struct CommandItem {
  id : String
  title : String
  category : String
  shortcut : String
} derive(Eq, Debug)

///|
pub fn CommandItem::new(
  id : String,
  title : String,
  category? : String = "",
  shortcut? : String = "",
) -> CommandItem {
  { id, title, category, shortcut, }
}

///|
pub struct CommandPaletteResponse {
  open : Bool
  selected_id : String?
  query : String
  selected_index : Int
} derive(Eq, Debug)

///|
fn char_code_to_lower(code : Int) -> Int {
  if code >= 65 && code <= 90 {
    code + 32
  } else {
    code
  }
}

///|
fn string_contains_ignore_case(haystack : String, needle : String) -> Bool {
  let n_len = needle.length()
  let h_len = haystack.length()
  if n_len == 0 {
    return true
  }
  if n_len > h_len {
    return false
  }
  for i = 0; i <= h_len - n_len; i = i + 1 {
    let mut matched = true
    for j = 0; j < n_len; j = j + 1 {
      let hc = char_code_to_lower(haystack[i + j].to_int())
      let nc = char_code_to_lower(needle[j].to_int())
      if hc != nc {
        matched = false
        break
      }
    }
    if matched {
      return true
    }
  }
  false
}

///|
/// Render a global command palette modal in Studio Light porcelain style.
/// Supports backdrop dimming, live query filtering, arrow key navigation,
/// enter selection, and pointer click dismissal.
pub fn command_palette(
  ctx : UIContext,
  open : Bool,
  query : String,
  commands : Array[CommandItem],
  selected_index? : Int = 0,
  viewport_size? : @math.Vec2 = @math.Vec2::zero(),
) -> CommandPaletteResponse {
  if !open {
    return { open: false, selected_id: None, query, selected_index, }
  }

  // 1. Text input & query update
  let mut new_query = query
  let mut new_selected_index = selected_index

  if ctx.input.key_pressed(Key::Backspace) {
    ctx.reset_caret_blink()
    let len = new_query.length()
    if len > 0 {
      new_query = string_slice(new_query, 0, len - 1)
    }
  }

  let text_in = ctx.input.text_input()
  if !text_in.is_empty() {
    ctx.reset_caret_blink()
    new_query = new_query + text_in
  }

  if new_query != query {
    new_selected_index = 0
  }

  // 2. Filter commands
  let filtered : Array[CommandItem] = []
  for item in commands {
    if new_query.is_empty() ||
      string_contains_ignore_case(item.title, new_query) ||
      string_contains_ignore_case(item.category, new_query) {
      filtered.push(item)
    }
  }
  let filtered_count = filtered.length()

  // 3. Keyboard navigation
  if filtered_count > 0 {
    if new_selected_index >= filtered_count {
      new_selected_index = filtered_count - 1
    }
    if new_selected_index < 0 {
      new_selected_index = 0
    }
    if ctx.input.key_pressed(Key::ArrowDown) {
      new_selected_index = (new_selected_index + 1) % filtered_count
    }
    if ctx.input.key_pressed(Key::ArrowUp) {
      new_selected_index = if new_selected_index <= 0 {
        filtered_count - 1
      } else {
        new_selected_index - 1
      }
    }
  } else {
    new_selected_index = 0
  }

  // 4. Escape & Enter dismissal
  if ctx.input.key_pressed(Key::Escape) {
    return {
      open: false,
      selected_id: None,
      query: new_query,
      selected_index: new_selected_index,
    }
  }

  if ctx.input.key_pressed(Key::Enter) {
    let sel = if filtered_count > 0 && new_selected_index < filtered_count {
      Some(filtered[new_selected_index].id)
    } else {
      None
    }
    return {
      open: false,
      selected_id: sel,
      query: new_query,
      selected_index: new_selected_index,
    }
  }

  // 5. Layout & geometry
  let scale = ctx.style.scale
  let font_sm = ctx.style.font_small * scale
  let font_nm = ctx.style.font_normal * scale
  let font_lg = ctx.style.font_large * scale
  let card_radius = ctx.style.radius_lg * scale
  let row_radius = ctx.style.radius_sm * scale

  let viewport_w = if viewport_size.x > 0.0 {
    viewport_size.x
  } else if ctx.available_width() > 0.0 {
    ctx.available_width()
  } else {
    4000.0
  }
  let viewport_h = if viewport_size.y > 0.0 { viewport_size.y } else { 4000.0 }
  let scrim_rect = @math.Rect::new(0.0, 0.0, viewport_w, viewport_h)

  let card_container_w = if viewport_size.x > 0.0 {
    viewport_size.x
  } else if ctx.available_width() > 0.0 {
    ctx.available_width()
  } else {
    800.0
  }
  let default_palette_w = 540.0 * scale
  let card_w = if card_container_w < default_palette_w + 20.0 * scale {
    card_container_w - 32.0 * scale
  } else {
    default_palette_w
  }
  let card_x = (card_container_w - card_w) * 0.5
  let card_y = 64.0 * scale
  let header_h = (ctx.style.control_h_lg + ctx.style.radius_md) * scale
  let footer_h = (ctx.style.control_h_sm + ctx.style.spacing_xs) * scale
  let row_h = ctx.style.control_h_md * scale
  let cat_h = 24.0 * scale

  let mut content_h = 0.0
  let mut prev_cat = ""
  if filtered_count == 0 {
    content_h = 48.0 * scale
  } else {
    for item in filtered {
      if !item.category.is_empty() && item.category != prev_cat {
        content_h = content_h + cat_h
        prev_cat = item.category
      }
      content_h = content_h + row_h
    }
  }

  let max_content_h = 320.0 * scale
  let visible_content_h = if content_h > max_content_h {
    max_content_h
  } else {
    content_h
  }
  let card_h = header_h + visible_content_h + footer_h + 12.0 * scale
  let card_rect = @math.Rect::new(card_x, card_y, card_w, card_h)

  // Scroll offset tracking
  // The interaction phase runs in the foreground scope: the palette blocked
  // hover over the whole screen one frame earlier, and is_hovered ignores
  // blockers only while the foreground flag is up. Without this the palette
  // would stop reacting to its own rows and wheel from the second frame on.
  ctx.begin_foreground()
  let content_clip_rect = @math.Rect::new(
    card_x,
    card_y + header_h,
    card_w,
    visible_content_h + 12.0 * scale,
  )
  let scroll_id = ctx.id_stack.derive_id("cmd_palette_scroll")
  let mut scroll_y = ctx.get_scroll_offset(scroll_id)
  let max_scroll = if content_h > visible_content_h {
    content_h - visible_content_h
  } else {
    0.0
  }
  if ctx.is_hovered(content_clip_rect) && ctx.input.scroll_delta.y != 0.0 {
    scroll_y = scroll_y - ctx.input.scroll_delta.y
  }
  let selected_top = new_selected_index.to_double() * row_h
  if selected_top < scroll_y {
    scroll_y = selected_top
  } else if selected_top + row_h > scroll_y + visible_content_h {
    scroll_y = selected_top + row_h - visible_content_h
  }
  if scroll_y < 0.0 {
    scroll_y = 0.0
  }
  if scroll_y > max_scroll {
    scroll_y = max_scroll
  }
  ctx.set_scroll_offset(scroll_id, scroll_y)

  // Block hover on underlying canvas
  ctx.block_hover(scrim_rect)

  // Outside click dismissal
  if ctx.input.mouse_pressed && !card_rect.contains(ctx.input.mouse_pos) {
    ctx.end_foreground()
    return {
      open: false,
      selected_id: None,
      query: new_query,
      selected_index: new_selected_index,
    }
  }

  // Layout metrics & elevation offsets
  let row_pad_x = row_radius * scale
  let row_w = card_w - row_pad_x * 2.0
  let elevation_key_y = 8.0 * scale
  let elevation_ambient_y = 2.0 * scale

  // Mouse hover & click on item rows
  let mut cur_row_y = card_y + header_h + row_pad_x - scroll_y
  let mut prev_row_cat = ""
  let mut clicked_id : String? = None

  for idx = 0; idx < filtered_count; idx = idx + 1 {
    let item = filtered[idx]
    if !item.category.is_empty() && item.category != prev_row_cat {
      cur_row_y = cur_row_y + cat_h
      prev_row_cat = item.category
    }
    let row_rect = @math.Rect::new(card_x + row_pad_x, cur_row_y, row_w, row_h)
    if content_clip_rect.contains(ctx.input.mouse_pos) &&
      ctx.is_hovered(row_rect) {
      ctx.set_cursor_icon("pointer")
      new_selected_index = idx
      if ctx.input.mouse_pressed {
        clicked_id = Some(item.id)
      }
    }
    cur_row_y = cur_row_y + row_h
  }

  if clicked_id is Some(id) {
    ctx.end_foreground()
    return {
      open: false,
      selected_id: Some(id),
      query: new_query,
      selected_index: new_selected_index,
    }
  }

  // 6. Rendering into foreground layer
  ctx.end_foreground()
  ctx.begin_foreground()

  // Semi-transparent backdrop scrim
  ctx.painter().add_rect(scrim_rect, @color.Color::bg_scrim(), 0.0)

  // Drop shadows
  ctx
  .painter()
  .add_rect(
    @math.Rect::new(card_x, card_y + elevation_key_y, card_w, card_h),
    @color.Color::shadow_key(),
    card_radius,
  )
  ctx
  .painter()
  .add_rect(
    @math.Rect::new(card_x, card_y + elevation_ambient_y, card_w, card_h),
    @color.Color::shadow_ambient(),
    card_radius,
  )

  // Porcelain card surface and border
  ctx.painter().add_rect(card_rect, @color.Color::bg_window(), card_radius)
  ctx
  .painter()
  .add_rect_stroke(card_rect, @color.Color::border_default(), 1.0, card_radius)

  // Search prompt icon '>'
  ctx
  .painter()
  .add_text(
    @math.Vec2::new(card_x + 16.0 * scale, card_y + 14.0 * scale),
    ">",
    font_lg,
    @color.Color::accent_primary(),
  )

  // Search input text or placeholder
  if new_query.is_empty() {
    ctx
    .painter()
    .add_text(
      @math.Vec2::new(card_x + 36.0 * scale, card_y + 16.0 * scale),
      "键入命令或搜索动作...",
      font_nm,
      @color.Color::text_muted(),
    )
  } else {
    ctx
    .painter()
    .add_text(
      @math.Vec2::new(card_x + 36.0 * scale, card_y + 16.0 * scale),
      new_query,
      font_nm,
      @color.Color::text_primary(),
    )
    let q_size = ctx.measure_text(new_query, font_nm)
    let caret_x = card_x + 36.0 * scale + q_size.x + 2.0 * scale
    if ctx.is_caret_visible() {
      ctx
      .painter()
      .add_line(
        @math.Vec2::new(caret_x, card_y + 16.0 * scale),
        @math.Vec2::new(caret_x, card_y + 31.0 * scale),
        @color.Color::accent_primary(),
        1.5,
      )
    }
  }

  // Right Esc badge
  let esc_w = 32.0 * scale
  let esc_h = 18.0 * scale
  let esc_x = card_x + card_w - esc_w - 14.0 * scale
  let esc_y = card_y + 14.0 * scale
  let esc_rect = @math.Rect::new(esc_x, esc_y, esc_w, esc_h)
  ctx.painter().add_rect(esc_rect, @color.Color::bg_surface(), row_radius)
  ctx
  .painter()
  .add_rect_stroke(esc_rect, @color.Color::border_muted(), 1.0, row_radius)
  ctx
  .painter()
  .add_text(
    @math.Vec2::new(esc_x + 6.0 * scale, esc_y + 3.0 * scale),
    "Esc",
    font_sm,
    @color.Color::text_muted(),
  )

  // Hairline divider below search bar
  ctx
  .painter()
  .add_line(
    @math.Vec2::new(card_x, card_y + header_h),
    @math.Vec2::new(card_x + card_w, card_y + header_h),
    @color.Color::border_muted(),
    1.0,
  )

  // Results Stream
  ctx.push_clip(content_clip_rect)
  let mut draw_y = card_y + header_h + row_pad_x - scroll_y
  let mut prev_draw_cat = ""

  if filtered_count == 0 {
    let empty_text_y = draw_y + (48.0 * scale - font_nm) * 0.5
    ctx
    .painter()
    .add_text(
      @math.Vec2::new(card_x + 20.0 * scale, empty_text_y),
      "未找到匹配命令",
      font_nm,
      @color.Color::text_muted(),
    )
  } else {
    let indicator_h = 20.0 * scale
    let indicator_w = 3.0 * scale
    let text_pad_left = card_x + row_pad_x + 12.0 * scale

    for idx = 0; idx < filtered_count; idx = idx + 1 {
      let item = filtered[idx]
      if !item.category.is_empty() && item.category != prev_draw_cat {
        let cat_y = draw_y + (cat_h - font_sm) * 0.5
        ctx
        .painter()
        .add_text(
          @math.Vec2::new(card_x + 16.0 * scale, cat_y),
          item.category,
          font_sm,
          @color.Color::text_muted(),
        )
        draw_y = draw_y + cat_h
        prev_draw_cat = item.category
      }

      let row_rect = @math.Rect::new(card_x + row_pad_x, draw_y, row_w, row_h)
      let is_selected = idx == new_selected_index
      let text_y = draw_y + (row_h - font_nm) * 0.5

      if is_selected {
        ctx
        .painter()
        .add_rect(row_rect, @color.Color::tree_row_selected(), row_radius)
        let indicator_y = draw_y + (row_h - indicator_h) * 0.5
        ctx
        .painter()
        .add_rect(
          @math.Rect::new(
            card_x + row_pad_x,
            indicator_y,
            indicator_w,
            indicator_h,
          ),
          @color.Color::accent_primary(),
          indicator_w * 0.5,
        )
        ctx
        .painter()
        .add_text(
          @math.Vec2::new(text_pad_left, text_y),
          item.title,
          font_nm,
          @color.Color::text_primary(),
        )
      } else {
        ctx
        .painter()
        .add_text(
          @math.Vec2::new(text_pad_left, text_y),
          item.title,
          font_nm,
          @color.Color::text_body(),
        )
      }

      if !item.shortcut.is_empty() {
        let sc_size = ctx.measure_text(item.shortcut, font_sm)
        let sc_x = card_x + card_w - sc_size.x - 20.0 * scale
        let sc_y = draw_y + (row_h - font_sm) * 0.5
        ctx
        .painter()
        .add_text(
          @math.Vec2::new(sc_x, sc_y),
          item.shortcut,
          font_sm,
          @color.Color::text_muted(),
        )
      }

      draw_y = draw_y + row_h
    }
  }
  ctx.pop_clip()

  // Footer bar
  let footer_y = card_y + card_h - footer_h
  let footer_rect = @math.Rect::new(card_x, footer_y, card_w, footer_h)
  ctx.painter().add_rect(footer_rect, @color.Color::bg_surface(), card_radius)
  ctx
  .painter()
  .add_line(
    @math.Vec2::new(card_x, footer_y),
    @math.Vec2::new(card_x + card_w, footer_y),
    @color.Color::border_muted(),
    1.0,
  )
  ctx
  .painter()
  .add_text(
    @math.Vec2::new(card_x + 16.0 * scale, footer_y + 8.0 * scale),
    "↑↓ 导航   ↵ 选择   Esc 退出",
    font_sm,
    @color.Color::text_muted(),
  )

  ctx.end_foreground()

  {
    open: true,
    selected_id: None,
    query: new_query,
    selected_index: new_selected_index,
  }
}