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

///|
/// Cardinal placement direction for floating tooltips.
pub(all) enum TooltipPlacement {
  Top
  Bottom
  Left
  Right
} derive(Eq, Debug)

///|
/// Component-level style configuration for tooltips, derived from foundational design tokens.
pub struct TooltipStyle {
  padding_x : Double
  padding_y : Double
  corner_radius : Double
  font_size : Double
  offset : Double
  delay_frames : Int
} derive(Eq, Debug)

///|
/// Derives the standard tooltip style from the active UIContext widget style tokens.
pub fn TooltipStyle::default_for(style : @core.WidgetStyle) -> TooltipStyle {
  let scale = style.scale
  {
    padding_x: style.spacing_sm * scale,
    padding_y: style.spacing_xs * scale,
    corner_radius: style.radius_sm * scale,
    font_size: style.font_small * scale,
    offset: (style.spacing_xs + 2.0) * scale,
    delay_frames: 6,
  }
}

///|
/// Tooltip widget providing floating contextual hints on hover with delayed revelation.
pub struct Tooltip {
  text : String
  mut placement_val : TooltipPlacement
  mut custom_style : TooltipStyle?
  mut delay_frames_val : Int
} derive(Eq, Debug)

///|
pub fn Tooltip::new(text : String) -> Tooltip {
  { text, placement_val: Top, custom_style: None, delay_frames_val: 6, }
}

///|
pub fn Tooltip::placement(self : Tooltip, p : TooltipPlacement) -> Tooltip {
  self.placement_val = p
  self
}

///|
pub fn Tooltip::delay(self : Tooltip, frames : Int) -> Tooltip {
  self.delay_frames_val = frames
  self
}

///|
pub fn Tooltip::style(self : Tooltip, s : TooltipStyle) -> Tooltip {
  self.custom_style = Some(s)
  self
}

///|
/// Helper to draw a tooltip box in the foreground layer at a given position.
fn render_tooltip_box(
  ctx : UIContext,
  rect : @math.Rect,
  text : String,
  style : TooltipStyle,
) -> Unit {
  ctx.begin_foreground()
  let p = ctx.painter()

  let radius = style.corner_radius
  let scale = ctx.style.scale

  // Drop shadow
  let theme = ctx.theme
  let shadow_rect = @math.Rect::new(
    rect.x,
    rect.y + 2.0 * scale,
    rect.w,
    rect.h,
  )
  p.add_rect(shadow_rect, theme.shadow, radius)

  // Surface fill & boundary
  p.add_rect(rect, theme.bg_inverse, radius)
  p.add_rect_stroke(rect, theme.border_strong, 1.0 * scale, radius)

  // Label text
  let text_x = rect.x + style.padding_x
  let text_y = rect.y + style.padding_y
  p.add_text(
    @math.Vec2::new(text_x, text_y),
    text,
    style.font_size,
    theme.text_inverse,
  )

  ctx.end_foreground()
}

///|
/// Show the tooltip bubble at current cursor position (immediate or delayed).
pub fn Tooltip::show(self : Tooltip, ctx : UIContext) -> Unit {
  let style = match self.custom_style {
    Some(s) => s
    None => TooltipStyle::default_for(ctx.style)
  }

  let text_sz = ctx.measure_text(self.text, style.font_size)
  let tip_w = text_sz.x + style.padding_x * 2.0
  let tip_h = style.font_size + style.padding_y * 2.0

  let mouse = ctx.input.mouse_pos
  let scale = ctx.style.scale
  let mut tip_x = mouse.x + 12.0 * scale
  let mut tip_y = mouse.y + 12.0 * scale

  let clip_opt = ctx.current_clip()
  let vp_w = if ctx.available_width() > 0.0 {
    ctx.available_width()
  } else {
    match clip_opt {
      Some(c) => if c.w > 0.0 { c.x + c.w } else { 800.0 * scale }
      None => 800.0 * scale
    }
  }
  let vp_h = match clip_opt {
    Some(c) => if c.h > 0.0 { c.y + c.h } else { 600.0 * scale }
    None => 600.0 * scale
  }

  if tip_x + tip_w > vp_w {
    tip_x = mouse.x - tip_w - 4.0 * scale
    if tip_x < 4.0 * scale {
      tip_x = 4.0 * scale
    }
  }
  if tip_y + tip_h > vp_h {
    tip_y = mouse.y - tip_h - 4.0 * scale
    if tip_y < 4.0 * scale {
      tip_y = 4.0 * scale
    }
  }
  let tip_rect = @math.Rect::new(tip_x, tip_y, tip_w, tip_h)
  render_tooltip_box(ctx, tip_rect, self.text, style)
}

///|
/// Evaluates hover timing and renders the tooltip adjacent to target_rect if visible.
/// Returns true if the tooltip was displayed during the current frame.
pub fn Tooltip::show_for(
  self : Tooltip,
  ctx : UIContext,
  id : Id,
  target_rect : @math.Rect,
  is_hovered : Bool,
) -> Bool {
  let style = match self.custom_style {
    Some(s) => s
    None => TooltipStyle::default_for(ctx.style)
  }
  let delay = self.delay_frames_val

  // Retrieve persistent hover frame counter from Memory
  let prev_count = ctx.memory.get_int_or_default(id, 0, tag="tooltip_hover")
  let current_count = if is_hovered { prev_count + 1 } else { 0 }
  ctx.memory.set_int(id, current_count, tag="tooltip_hover")

  if current_count <= delay || !is_hovered {
    return false
  }

  // Measure content
  let font_sz = style.font_size
  let text_sz = ctx.measure_text(self.text, font_sz)
  let tip_w = text_sz.x + style.padding_x * 2.0
  let tip_h = font_sz + style.padding_y * 2.0

  // Viewport boundaries
  let scale = ctx.style.scale
  let clip_opt = ctx.current_clip()
  let vp_w = if ctx.available_width() > 0.0 {
    ctx.available_width()
  } else {
    match clip_opt {
      Some(c) => if c.w > 0.0 { c.x + c.w } else { 800.0 * scale }
      None => 800.0 * scale
    }
  }
  let vp_h = match clip_opt {
    Some(c) => if c.h > 0.0 { c.y + c.h } else { 600.0 * scale }
    None => 600.0 * scale
  }
  let edge_margin = 4.0 * scale

  // Compute position based on placement and boundary clamping
  let (raw_x, raw_y) = match self.placement_val {
    Top => {
      let x = target_rect.x + (target_rect.w - tip_w) * 0.5
      let y = target_rect.y - tip_h - style.offset
      if y < edge_margin {
        (x, target_rect.y + target_rect.h + style.offset)
      } else {
        (x, y)
      }
    }
    Bottom => {
      let x = target_rect.x + (target_rect.w - tip_w) * 0.5
      let y = target_rect.y + target_rect.h + style.offset
      if y + tip_h > vp_h - edge_margin {
        (x, target_rect.y - tip_h - style.offset)
      } else {
        (x, y)
      }
    }
    Left => {
      let x = target_rect.x - tip_w - style.offset
      let y = target_rect.y + (target_rect.h - tip_h) * 0.5
      if x < edge_margin {
        (target_rect.x + target_rect.w + style.offset, y)
      } else {
        (x, y)
      }
    }
    Right => {
      let x = target_rect.x + target_rect.w + style.offset
      let y = target_rect.y + (target_rect.h - tip_h) * 0.5
      if x + tip_w > vp_w - edge_margin {
        (target_rect.x - tip_w - style.offset, y)
      } else {
        (x, y)
      }
    }
  }

  // Clamp within screen boundaries
  let min_x = edge_margin
  let max_x = if vp_w > tip_w + edge_margin {
    vp_w - tip_w - edge_margin
  } else {
    edge_margin
  }
  let clamped_x = if raw_x < min_x {
    min_x
  } else if raw_x > max_x {
    max_x
  } else {
    raw_x
  }

  let min_y = edge_margin
  let max_y = if vp_h > tip_h + edge_margin {
    vp_h - tip_h - edge_margin
  } else {
    edge_margin
  }
  let clamped_y = if raw_y < min_y {
    min_y
  } else if raw_y > max_y {
    max_y
  } else {
    raw_y
  }

  let tip_rect = @math.Rect::new(clamped_x, clamped_y, tip_w, tip_h)
  render_tooltip_box(ctx, tip_rect, self.text, style)
  true
}

///|
/// Render an on-hover floating tooltip bubble at cursor position.
pub fn tooltip(ctx : UIContext, text : String) -> Unit {
  let tip = Tooltip::new(text).delay(0)
  tip.show(ctx)
}

///|
/// Fluent helper function to attach floating hover text to a Response.
pub fn on_hover_text(
  resp : Response,
  ctx : UIContext,
  text : String,
  placement? : TooltipPlacement = Top,
  delay_frames? : Int = 6,
) -> Response {
  let tip = Tooltip::new(text).placement(placement).delay(delay_frames)
  let _ = tip.show_for(ctx, resp.id, resp.rect, resp.hovered)
  resp
}

///|
/// Convenience helper function to display a tooltip for a specified target rect.
pub fn tooltip_for(
  ctx : UIContext,
  id_salt : String,
  target_rect : @math.Rect,
  is_hovered : Bool,
  text : String,
  placement? : TooltipPlacement = Top,
  delay_frames? : Int = 6,
) -> Bool {
  let id = ctx.id_stack.derive_id(id_salt)
  let tip = Tooltip::new(text).placement(placement).delay(delay_frames)
  tip.show_for(ctx, id, target_rect, is_hovered)
}