// 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 vertical scrolling container supporting fluent configuration.
pub struct ScrollArea {
  id_salt : String
  size : @math.Vec2
} derive(Eq, Debug)

///|
/// Create a new scroll area with an identifier and viewport dimension.
pub fn ScrollArea::new(id_salt : String, size : @math.Vec2) -> ScrollArea {
  { id_salt, size, }
}

///|
/// Show the scroll area and execute the nested content closure.
pub fn ScrollArea::show(
  self : ScrollArea,
  ctx : UIContext,
  content : (UIContext) -> Unit,
) -> Unit {
  scroll_area(ctx, self.id_salt, self.size, content)
}

///|
/// Render a vertical scrolling container with wheel scrolling, nested scissor clipping,
/// and smooth interactive scrollbar indicator in Studio Light porcelain style.
pub fn scroll_area(
  ctx : UIContext,
  id_salt : String,
  size : @math.Vec2,
  content : (UIContext) -> Unit,
) -> Unit {
  let container_id = ctx.id_stack.derive_id(id_salt)
  let (_id, container_rect, _resp) = ctx.allocate_space(size)

  // The viewport is Tab-reachable: keyboard scrolling applies only while the
  // container itself holds focus, so focused children keep their arrow keys.
  ctx.register_focusable(container_id)

  if ctx.is_hovered(container_rect) {
    ctx.set_wants_capture_mouse(true)
  }

  let mut scroll_y = ctx.get_scroll_offset(container_id)
  let prev_content_h = ctx.get_scroll_content_height(container_id)
  let max_scroll = if prev_content_h > size.y {
    prev_content_h - size.y
  } else {
    0.0
  }

  // Handle pointer wheel scrolling inside container
  if ctx.is_hovered(container_rect) && ctx.input.scroll_delta.y != 0.0 {
    scroll_y = scroll_y - ctx.input.scroll_delta.y
  }

  // Clamp scroll range against previous frame estimate
  if scroll_y < 0.0 {
    scroll_y = 0.0
  }
  if max_scroll > 0.0 && scroll_y > max_scroll {
    scroll_y = max_scroll
  }
  ctx.set_scroll_offset(container_id, scroll_y)

  // Push nested scissor clipping
  ctx.push_clip(container_rect)

  // Save current context layout state
  let prev_cursor = ctx.cursor()
  let prev_avail_w = ctx.available_width()

  // Adjust available width if scrollbar is present
  let scale = ctx.style.scale
  let scrollbar_w = 6.0 * scale
  let inner_w = if max_scroll > 0.0 {
    size.x - scrollbar_w - 4.0 * scale
  } else {
    size.x
  }

  ctx.set_cursor(@math.Vec2::new(container_rect.x, container_rect.y - scroll_y))
  ctx.set_available_width(inner_w)

  content(ctx)

  let measured_content_h = ctx.cursor().y + scroll_y - container_rect.y
  ctx.set_scroll_content_height(container_id, measured_content_h)

  // Post-closure height clamping: immediately clamp scroll_y if content shrank
  let real_max_scroll = if measured_content_h > size.y {
    measured_content_h - size.y
  } else {
    0.0
  }
  if scroll_y > real_max_scroll {
    scroll_y = real_max_scroll
    ctx.set_scroll_offset(container_id, scroll_y)
  }

  // Keyboard scrolling while the container holds focus. Focus is exclusive,
  // so a focused child (text_edit, slider, ...) keeps its arrow keys.
  if ctx.has_focus(container_id) && real_max_scroll > 0.0 {
    let kb_step = 36.0 * ctx.style.scale
    let page = size.y * 0.85
    let mut delta = 0.0
    if ctx.input.key_pressed(Key::ArrowDown) {
      delta = kb_step
      ctx.input.consume_key(Key::ArrowDown)
    }
    if ctx.input.key_pressed(Key::ArrowUp) {
      delta = -kb_step
      ctx.input.consume_key(Key::ArrowUp)
    }
    if ctx.input.key_pressed(Key::PageDown) {
      delta = page
      ctx.input.consume_key(Key::PageDown)
    }
    if ctx.input.key_pressed(Key::PageUp) {
      delta = -page
      ctx.input.consume_key(Key::PageUp)
    }
    if ctx.input.key_pressed(Key::End) {
      delta = real_max_scroll - scroll_y
      ctx.input.consume_key(Key::End)
    }
    if ctx.input.key_pressed(Key::Home) {
      delta = -scroll_y
      ctx.input.consume_key(Key::Home)
    }
    if delta != 0.0 {
      scroll_y = if scroll_y + delta < 0.0 {
        0.0
      } else if scroll_y + delta > real_max_scroll {
        real_max_scroll
      } else {
        scroll_y + delta
      }
      ctx.set_scroll_offset(container_id, scroll_y)
    }
  }

  // Restore cursor and pop scissor clip
  ctx.set_cursor(prev_cursor)
  ctx.set_available_width(prev_avail_w)
  ctx.pop_clip()

  // Focus ring so Tab users can see the viewport is keyboard-scrollable
  let theme = ctx.theme
  if ctx.has_focus(container_id) {
    ctx
    .painter()
    .add_rect_stroke(
      container_rect,
      theme.border_focus,
      1.0 * scale,
      ctx.style.radius_md * scale,
    )
  }

  // Draw scrollbar thumb if content exceeds container height
  if real_max_scroll > 0.0 && measured_content_h > 0.0 {
    let bar_x = container_rect.x + size.x - scrollbar_w - 2.0 * scale
    let bar_track_y = container_rect.y
    let bar_track_h = size.y

    let ratio = size.y / measured_content_h
    let raw_thumb_h = size.y * ratio
    let thumb_h = if raw_thumb_h < 24.0 * scale {
      24.0 * scale
    } else if raw_thumb_h > bar_track_h {
      bar_track_h
    } else {
      raw_thumb_h
    }

    let usable_h = bar_track_h - thumb_h
    let thumb_id = ctx.id_stack.derive_id(id_salt + "::thumb")
    let track_rect = @math.Rect::new(
      bar_x - 2.0 * scale,
      bar_track_y,
      scrollbar_w + 4.0 * scale,
      bar_track_h,
    )

    let mut current_pct = if real_max_scroll > 0.0001 {
      scroll_y / real_max_scroll
    } else {
      0.0
    }
    let thumb_y = bar_track_y + current_pct * usable_h
    let thumb_rect = @math.Rect::new(bar_x, thumb_y, scrollbar_w, thumb_h)

    let is_thumb_hovered = ctx.is_hovered(thumb_rect)
    let is_track_hovered = ctx.is_hovered(track_rect)

    if is_thumb_hovered || is_track_hovered || ctx.active_id() == thumb_id {
      ctx.set_cursor_icon("pointer")
    }

    let grab_id = ctx.id_stack.derive_id(id_salt + "::grab_offset")
    if (is_thumb_hovered || is_track_hovered) && ctx.input.mouse_pressed {
      ctx.set_active_id(thumb_id)
      let initial_grab = if is_thumb_hovered {
        ctx.input.mouse_pos.y - thumb_y
      } else {
        thumb_h * 0.5
      }
      ctx.set_scroll_offset(grab_id, initial_grab)
    }

    if ctx.active_id() == thumb_id {
      ctx.set_wants_capture_mouse(true)
      if ctx.input.mouse_down && usable_h > 0.0 {
        let grab_offset = ctx.get_scroll_offset(grab_id)
        let rel_mouse_y = ctx.input.mouse_pos.y - bar_track_y - grab_offset
        let mut new_pct = rel_mouse_y / usable_h
        if new_pct < 0.0 {
          new_pct = 0.0
        }
        if new_pct > 1.0 {
          new_pct = 1.0
        }
        scroll_y = new_pct * real_max_scroll
        ctx.set_scroll_offset(container_id, scroll_y)
        current_pct = new_pct
      }
      if ctx.input.mouse_released {
        ctx.set_active_id(@core.Id::zero())
      }
    }

    let active_thumb_y = bar_track_y + current_pct * usable_h
    let active_thumb_rect = @math.Rect::new(
      bar_x, active_thumb_y, scrollbar_w, thumb_h,
    )

    let thumb_color = if ctx.active_id() == thumb_id || is_thumb_hovered {
      theme.accent_primary
    } else {
      theme.border_muted
    }
    ctx
    .painter()
    .add_rect(active_thumb_rect, thumb_color, ctx.style.radius_sm * scale)
  }
}