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

///|
/// Representation of an item within a collapsed pagination bar.
pub(all) enum PaginationItem {
  Page(Int)
  Ellipsis
} derive(Eq, Debug)

///|
/// Generates list of displayed page numbers with intelligent ellipsis folding.
fn generate_pagination_items(
  current : Int,
  total : Int,
) -> Array[PaginationItem] {
  if total <= 7 {
    let res = []
    for p = 1; p <= total; p = p + 1 {
      res.push(PaginationItem::Page(p))
    }
    res
  } else if current <= 4 {
    [
      PaginationItem::Page(1),
      PaginationItem::Page(2),
      PaginationItem::Page(3),
      PaginationItem::Page(4),
      PaginationItem::Page(5),
      PaginationItem::Ellipsis,
      PaginationItem::Page(total),
    ]
  } else if current >= total - 3 {
    [
      PaginationItem::Page(1),
      PaginationItem::Ellipsis,
      PaginationItem::Page(total - 4),
      PaginationItem::Page(total - 3),
      PaginationItem::Page(total - 2),
      PaginationItem::Page(total - 1),
      PaginationItem::Page(total),
    ]
  } else {
    [
      PaginationItem::Page(1),
      PaginationItem::Ellipsis,
      PaginationItem::Page(current - 1),
      PaginationItem::Page(current),
      PaginationItem::Page(current + 1),
      PaginationItem::Ellipsis,
      PaginationItem::Page(total),
    ]
  }
}

///|
/// Standalone data pagination bar widget supporting fluent configuration.
pub struct Pagination {
  id_salt : String
  current_page : Int
  total_pages : Int
  item_size : Double?
  spacing : Double?
  show_prev_next : Bool
} derive(Eq, Debug)

///|
/// Creates a new Pagination widget with initial page (1-indexed) and total pages count.
pub fn Pagination::new(
  id_salt : String,
  current_page : Int,
  total_pages : Int,
) -> Pagination {
  let total = if total_pages > 0 { total_pages } else { 1 }
  let current = if current_page < 1 {
    1
  } else if current_page > total {
    total
  } else {
    current_page
  }
  {
    id_salt,
    current_page: current,
    total_pages: total,
    item_size: None,
    spacing: None,
    show_prev_next: true,
  }
}

///|
/// Configures individual button square size.
pub fn Pagination::item_size(
  self : Pagination,
  item_size : Double,
) -> Pagination {
  { ..self, item_size: Some(item_size), }
}

///|
/// Sets spacing between pagination buttons.
pub fn Pagination::spacing(self : Pagination, spacing : Double) -> Pagination {
  { ..self, spacing: Some(spacing), }
}

///|
/// Configures whether Previous and Next navigation buttons are rendered.
pub fn Pagination::show_prev_next(
  self : Pagination,
  show_prev_next : Bool,
) -> Pagination {
  { ..self, show_prev_next, }
}

///|
/// Reference-bound Pagination widget for uniform `ui.add(...)` dispatch.
pub struct PaginationRef {
  pagination : Pagination
  target : Ref[Int]
}

///|
/// Creates a Pagination widget bound directly to a `Ref[Int]`.
pub fn Pagination::from_ref(
  id_salt : String,
  target : Ref[Int],
  total_pages : Int,
) -> PaginationRef {
  { pagination: Pagination::new(id_salt, target.val, total_pages), target, }
}

///|
pub fn PaginationRef::item_size(
  self : PaginationRef,
  item_size : Double,
) -> PaginationRef {
  { ..self, pagination: self.pagination.item_size(item_size), }
}

///|
pub fn PaginationRef::spacing(
  self : PaginationRef,
  spacing : Double,
) -> PaginationRef {
  { ..self, pagination: self.pagination.spacing(spacing), }
}

///|
pub fn PaginationRef::show_prev_next(
  self : PaginationRef,
  show_prev_next : Bool,
) -> PaginationRef {
  { ..self, pagination: self.pagination.show_prev_next(show_prev_next), }
}

///|
pub impl @core.Widget for PaginationRef with fn ui(self, ctx : UIContext) -> Response {
  let (new_page, resp) = { ..self.pagination, current_page: self.target.val, }.show(
    ctx,
  )
  self.target.val = new_page
  resp
}

///|
/// Renders the pagination bar, handles clicks, keyboard navigation,
/// and returns the updated current page and interaction response.
pub fn Pagination::show(self : Pagination, ctx : UIContext) -> (Int, Response) {
  let scale = ctx.style.scale
  let btn_sz = match self.item_size {
    Some(sz) => sz * scale
    None => (ctx.style.control_h_sm - ctx.style.radius_xs) * scale
  }
  let spacing = match self.spacing {
    Some(sp) => sp * scale
    None => ctx.style.spacing_xs * scale
  }
  let radius = ctx.style.radius_sm * scale
  let font_sz = ctx.style.font_small * scale

  let total = if self.total_pages > 0 { self.total_pages } else { 1 }
  let mut new_page = if self.current_page < 1 {
    1
  } else if self.current_page > total {
    total
  } else {
    self.current_page
  }

  let items = generate_pagination_items(new_page, total)
  let total_buttons = items.length() + (if self.show_prev_next { 2 } else { 0 })
  let total_w = total_buttons.to_double() * btn_sz +
    (total_buttons - 1).to_double() * spacing
  let h = btn_sz

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

  let is_focused = resp.has_focus() || ctx.has_focus(id)

  // 1. Keyboard Navigation when focused
  if is_focused {
    if ctx.input.key_pressed(Key::ArrowLeft) ||
      ctx.input.key_pressed(Key::ArrowDown) {
      if new_page > 1 {
        new_page = new_page - 1
      }
      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) {
      if new_page < total {
        new_page = new_page + 1
      }
      ctx.input.consume_key(Key::ArrowRight)
      ctx.input.consume_key(Key::ArrowUp)
    }
    if ctx.input.key_pressed(Key::PageDown) {
      new_page = if new_page + 5 <= total { new_page + 5 } else { total }
      ctx.input.consume_key(Key::PageDown)
    }
    if ctx.input.key_pressed(Key::PageUp) {
      new_page = if new_page > 5 { new_page - 5 } else { 1 }
      ctx.input.consume_key(Key::PageUp)
    }
    if ctx.input.key_pressed(Key::Home) {
      new_page = 1
      ctx.input.consume_key(Key::Home)
    }
    if ctx.input.key_pressed(Key::End) {
      new_page = total
      ctx.input.consume_key(Key::End)
    }
  }

  // 2. Focus Ring
  let theme = ctx.theme
  if is_focused {
    ctx
    .painter()
    .add_rect_stroke(
      rect.expand(2.0 * scale),
      theme.border_focus,
      1.5 * scale,
      radius + 2.0 * scale,
    )
  }

  let mut curr_x = rect.x

  // 3. Render Previous Button
  if self.show_prev_next {
    let prev_rect = @math.Rect::new(curr_x, rect.y, btn_sz, h)
    let is_prev_enabled = new_page > 1
    let is_prev_hovered = is_prev_enabled && ctx.is_hovered(prev_rect)

    if is_prev_hovered {
      ctx.set_cursor_icon("pointer")
      if resp.clicked {
        new_page = new_page - 1
      }
    }

    let prev_bg = if is_prev_hovered {
      theme.bg_hover
    } else {
      theme.bg_surface
    }
    ctx.painter().add_rect(prev_rect, prev_bg, radius)
    let prev_border = if is_prev_enabled {
      theme.border_default
    } else {
      theme.border_muted
    }
    ctx.painter().add_rect_stroke(prev_rect, prev_border, 1.0 * scale, radius)

    let sym = "‹"
    let sym_sz = ctx.measure_text(sym, font_sz)
    let sym_pos = @math.Vec2::new(
      curr_x + (btn_sz - sym_sz.x) * 0.5,
      rect.y + (h - font_sz) * 0.5,
    )
    let sym_color = if is_prev_enabled {
      theme.text_primary
    } else {
      theme.text_disabled
    }
    ctx.painter().add_text(sym_pos, sym, font_sz, sym_color)

    curr_x = curr_x + btn_sz + spacing
  }

  // 4. Render Page Number & Ellipsis Buttons
  for item in items {
    let item_rect = @math.Rect::new(curr_x, rect.y, btn_sz, h)

    match item {
      Page(p) => {
        let is_active = p == new_page
        let is_hovered = !is_active && ctx.is_hovered(item_rect)

        if is_hovered {
          ctx.set_cursor_icon("pointer")
          if resp.clicked {
            new_page = p
          }
        }

        if is_active {
          ctx.painter().add_rect(item_rect, theme.accent_primary, radius)
          let p_str = p.to_string()
          let p_sz = ctx.measure_text(p_str, font_sz)
          let p_pos = @math.Vec2::new(
            curr_x + (btn_sz - p_sz.x) * 0.5,
            rect.y + (h - font_sz) * 0.5,
          )
          ctx.painter().add_text(p_pos, p_str, font_sz, theme.text_inverse)
        } else {
          let bg = if is_hovered { theme.bg_hover } else { theme.bg_surface }
          ctx.painter().add_rect(item_rect, bg, radius)
          ctx
          .painter()
          .add_rect_stroke(item_rect, theme.border_default, 1.0 * scale, radius)
          let p_str = p.to_string()
          let p_sz = ctx.measure_text(p_str, font_sz)
          let p_pos = @math.Vec2::new(
            curr_x + (btn_sz - p_sz.x) * 0.5,
            rect.y + (h - font_sz) * 0.5,
          )
          ctx.painter().add_text(p_pos, p_str, font_sz, theme.text_primary)
        }
      }
      Ellipsis => {
        let sym = "..."
        let sym_sz = ctx.measure_text(sym, font_sz)
        let sym_pos = @math.Vec2::new(
          curr_x + (btn_sz - sym_sz.x) * 0.5,
          rect.y + (h - font_sz) * 0.5,
        )
        ctx.painter().add_text(sym_pos, sym, font_sz, theme.text_muted)
      }
    }

    curr_x = curr_x + btn_sz + spacing
  }

  // 5. Render Next Button
  if self.show_prev_next {
    let next_rect = @math.Rect::new(curr_x, rect.y, btn_sz, h)
    let is_next_enabled = new_page < total
    let is_next_hovered = is_next_enabled && ctx.is_hovered(next_rect)

    if is_next_hovered {
      ctx.set_cursor_icon("pointer")
      if resp.clicked {
        new_page = new_page + 1
      }
    }

    let next_bg = if is_next_hovered {
      theme.bg_hover
    } else {
      theme.bg_surface
    }
    ctx.painter().add_rect(next_rect, next_bg, radius)
    let next_border = if is_next_enabled {
      theme.border_default
    } else {
      theme.border_muted
    }
    ctx.painter().add_rect_stroke(next_rect, next_border, 1.0 * scale, radius)

    let sym = "›"
    let sym_sz = ctx.measure_text(sym, font_sz)
    let sym_pos = @math.Vec2::new(
      curr_x + (btn_sz - sym_sz.x) * 0.5,
      rect.y + (h - font_sz) * 0.5,
    )
    let sym_color = if is_next_enabled {
      theme.text_primary
    } else {
      theme.text_disabled
    }
    ctx.painter().add_text(sym_pos, sym, font_sz, sym_color)
  }

  (new_page, resp)
}

///|
/// Convenience functional helper to render a Pagination component.
pub fn pagination(
  ctx : UIContext,
  id_salt : String,
  current_page : Int,
  total_pages : Int,
  item_size? : Double,
  spacing? : Double,
  show_prev_next? : Bool = true,
) -> (Int, Response) {
  let mut p = Pagination::new(id_salt, current_page, total_pages).show_prev_next(
    show_prev_next,
  )
  if item_size is Some(sz) {
    p = p.item_size(sz)
  }
  if spacing is Some(sp) {
    p = p.spacing(sp)
  }
  p.show(ctx)
}