#![allow(dead_code)]

use std::{
    cmp::Ordering,
    collections::VecDeque,
    hash::{Hash, Hasher},
    ops::Add,
    ops::Div,
    ops::DivAssign,
    ops::Mul,
};
use wgpu::util::DeviceExt;

#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn monotonic_now_seconds() -> f64 {
    use std::sync::OnceLock;
    use std::time::Instant;

    static START: OnceLock<Instant> = OnceLock::new();
    START.get_or_init(Instant::now).elapsed().as_secs_f64()
}

#[cfg(target_arch = "wasm32")]
pub(crate) fn monotonic_now_seconds() -> f64 {
    web_sys::window()
        .and_then(|w| w.performance())
        .map(|p| p.now() / 1000.0)
        .unwrap_or(0.0)
}

pub(crate) struct DrawingContext<'a> {
    pub(crate) rpass: &'a mut wgpu::RenderPass<'a>,
    pub(crate) pipeline: &'a wgpu::RenderPipeline,
}

#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Debug)]
#[allow(dead_code)]
pub(crate) struct UVTransform {
    pub(crate) uv_offset: [f32; 2],
    pub(crate) uv_scale: [f32; 2],
    pub(crate) tint: [f32; 4],
}

#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Debug)]
#[allow(dead_code)]
pub(crate) struct Vertex {
    pub(crate) position: [f32; 2],
    pub(crate) tex_coords: [f32; 2], // u, v texture coordinates
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
#[allow(dead_code)]
pub(crate) struct TransformUniform {
    pub(crate) transform: [[f32; 4]; 4], // 4x4 transformation matrix
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
#[allow(dead_code)]
pub(crate) struct InstanceRaw {
    pub(crate) model: [[f32; 4]; 4],
    pub(crate) uv_offset: [f32; 2],
    pub(crate) uv_scale: [f32; 2],
    pub(crate) msdf_px_range: f32,
    pub(crate) _msdf_pad: [f32; 3],
    pub(crate) tint: [f32; 4],
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
#[allow(dead_code)]
pub(crate) struct RectInstanceRaw {
    pub(crate) model: [[f32; 4]; 4],
    pub(crate) color: [f32; 4],
    pub(crate) corner_radius_px: f32,
    pub(crate) border_thickness_px: f32,
    pub(crate) _pad0: [f32; 2],
    pub(crate) border_color: [f32; 4],
    pub(crate) rect_size_px: [f32; 2],
    pub(crate) _pad1: [f32; 2],
    pub(crate) _pad2: [f32; 4],
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
#[allow(dead_code)]
pub(crate) struct GlowInstanceRaw {
    pub(crate) model: [[f32; 4]; 4],
    pub(crate) color: [f32; 4],
    pub(crate) rect_size_px: [f32; 2],
    pub(crate) corner_radius_px: f32,
    pub(crate) glow_radius_px: f32,
    pub(crate) sigma: f32,
    pub(crate) max_alpha: f32,
    pub(crate) mode: f32,
    pub(crate) border_width: f32,
    pub(crate) _pad: [f32; 4],
}

#[derive(Debug, Clone, Copy)]
/// Size data.
pub struct Size {
    /// Width in logical pixels.
    pub width: f32,
    /// Height in logical pixels.
    pub height: f32,
}

impl Size {
    /// Creates a new value.
    pub fn new(width: f32, height: f32) -> Self {
        Self { width, height }
    }
}

/// Documents create unit quad buffers.
pub fn create_unit_quad_buffers(device: &wgpu::Device) -> (wgpu::Buffer, wgpu::Buffer) {
    // A simple unit quad (0,0) to (1,1) with UVs matching positions
    let vertices: [Vertex; 4] = [
        Vertex {
            position: [0.0, 0.0],
            tex_coords: [0.0, 0.0],
        },
        Vertex {
            position: [1.0, 0.0],
            tex_coords: [1.0, 0.0],
        },
        Vertex {
            position: [1.0, 1.0],
            tex_coords: [1.0, 1.0],
        },
        Vertex {
            position: [0.0, 1.0],
            tex_coords: [0.0, 1.0],
        },
    ];
    let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];

    let vbuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
        label: Some("unit-quad-vertices"),
        contents: bytemuck::cast_slice(&vertices),
        usage: wgpu::BufferUsages::VERTEX,
    });
    let ibuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
        label: Some("unit-quad-indices"),
        contents: bytemuck::cast_slice(&indices),
        usage: wgpu::BufferUsages::INDEX,
    });
    (vbuf, ibuf)
}

/// Documents create centered quad buffers.
pub fn create_centered_quad_buffers(device: &wgpu::Device) -> (wgpu::Buffer, wgpu::Buffer) {
    // Quad from (-1,-1) to (1,1) for SDF-based rects; tex_coords unused
    let vertices: [Vertex; 4] = [
        Vertex {
            position: [-1.0, -1.0],
            tex_coords: [0.0, 0.0],
        },
        Vertex {
            position: [1.0, -1.0],
            tex_coords: [1.0, 0.0],
        },
        Vertex {
            position: [1.0, 1.0],
            tex_coords: [1.0, 1.0],
        },
        Vertex {
            position: [-1.0, 1.0],
            tex_coords: [0.0, 1.0],
        },
    ];
    let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];

    let vbuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
        label: Some("centered-quad-vertices"),
        contents: bytemuck::cast_slice(&vertices),
        usage: wgpu::BufferUsages::VERTEX,
    });
    let ibuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
        label: Some("centered-quad-indices"),
        contents: bytemuck::cast_slice(&indices),
        usage: wgpu::BufferUsages::INDEX,
    });
    (vbuf, ibuf)
}
impl Add<f32> for Size {
    type Output = Size;
    fn add(self, rhs: f32) -> Self::Output {
        Size {
            width: self.width + rhs,
            height: self.height + rhs,
        }
    }
}
impl Mul<f32> for Size {
    type Output = Size;

    fn mul(self, rhs: f32) -> Self::Output {
        Size {
            width: self.width * rhs,
            height: self.height * rhs,
        }
    }
}

#[derive(Debug, Clone, Copy)]
/// Position data.
pub struct Position {
    /// Horizontal position in logical pixels.
    pub x: f32,
    /// Vertical position in logical pixels.
    pub y: f32,
}

impl Default for Position {
    fn default() -> Self {
        Position { x: 0.0, y: 0.0 }
    }
}

impl PartialEq for Position {
    fn eq(&self, other: &Self) -> bool {
        self.x == other.x && self.y == other.y
    }
}

impl DivAssign<f32> for Position {
    fn div_assign(&mut self, rhs: f32) {
        self.x /= rhs;
        self.y /= rhs;
    }
}

impl Div<f32> for Position {
    type Output = Position;
    fn div(self, factor: f32) -> Self::Output {
        Position {
            x: self.x / factor,
            y: self.y / factor,
        }
    }
}
impl Eq for Position {}

impl Hash for Position {
    fn hash<H: Hasher>(&self, state: &mut H) {
        // Convert the floating-point numbers to a fixed precision before hashing
        // This example rounds the numbers to a precision of two decimal places
        let precision = 100.0; // Adjust the precision as needed
        let x = (self.x * precision).round() as i32;
        let y = (self.y * precision).round() as i32;

        x.hash(state);
        y.hash(state);
    }
}

impl Mul<f32> for Position {
    type Output = Position;
    fn mul(self, factor: f32) -> Self::Output {
        Position {
            x: self.x * factor,
            y: self.y * factor,
        }
    }
}

impl Add<f32> for Position {
    type Output = Position;
    fn add(self, other: f32) -> Self::Output {
        Position {
            x: self.x + other,
            y: self.y + other,
        }
    }
}

impl Add<Position> for Position {
    type Output = Position;
    fn add(self, rhs: Position) -> Self::Output {
        Position {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
        }
    }
}

impl std::ops::Sub<Position> for Position {
    type Output = Position;
    fn sub(self, rhs: Position) -> Self::Output {
        Position {
            x: self.x - rhs.x,
            y: self.y - rhs.y,
        }
    }
}

#[derive(Debug, Clone, Copy)]
/// Rectangle data.
pub struct Rectangle {
    /// Horizontal position in logical pixels.
    pub x: f32,
    /// Vertical position in logical pixels.
    pub y: f32,
    /// Width in logical pixels.
    pub width: f32,
    /// Height in logical pixels.
    pub height: f32,
}

impl Rectangle {
    /// Padded contains.
    pub fn padded_contains(&self, position: Position, padding: f32) -> bool {
        position.x >= self.x + padding
            && position.x <= self.x + self.width - padding
            && position.y >= self.y + padding
            && position.y <= self.y + self.height - padding
    }

    /// Returns whether the point lies inside this value.
    pub fn contains(&self, position: Position) -> bool {
        position.x >= self.x
            && position.x <= self.x + self.width
            && position.y >= self.y
            && position.y <= self.y + self.height
    }

    /// Returns the current position.
    pub fn pos(&self) -> Position {
        Position {
            x: self.x,
            y: self.y,
        }
    }

    /// Size.
    pub fn size(&self) -> Size {
        Size {
            width: self.width,
            height: self.height,
        }
    }

    /// Sets the pos.
    pub fn set_pos(&mut self, pos: Position) {
        self.x = pos.x;
        self.y = pos.y;
    }

    /// Returns `rec` expanded outward by `padding` logical pixels on every side.
    ///
    /// Positive padding grows the rectangle symmetrically; negative padding
    /// shrinks it symmetrically around the same center.
    pub fn pad(rec: &Rectangle, padding: f32) -> Rectangle {
        Rectangle::new(
            rec.x - padding,
            rec.y - padding,
            rec.width + (padding * 2.0),
            rec.height + (padding * 2.0),
        )
    }

    /// Creates a new value.
    pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
        Self {
            x,
            y,
            width,
            height,
        }
    }

    /// New square.
    pub fn new_square(x: f32, y: f32, side_length: f32) -> Self {
        Self {
            x,
            y,
            width: side_length,
            height: side_length,
        }
    }
}

impl Add<f32> for Rectangle {
    type Output = Rectangle;
    fn add(self, other: f32) -> Self::Output {
        Rectangle::new(self.x, self.y, self.width + other, self.height + other)
    }
}

impl Mul<f32> for Rectangle {
    type Output = Rectangle;
    fn mul(self, factor: f32) -> Self::Output {
        Rectangle::new(self.x, self.y, self.width * factor, self.height * factor)
    }
}

impl Div<f32> for Rectangle {
    type Output = Rectangle;
    fn div(self, factor: f32) -> Self::Output {
        Rectangle::new(self.x, self.y, self.width / factor, self.height / factor)
    }
}

impl PartialEq for Rectangle {
    fn eq(&self, other: &Self) -> bool {
        const EPSILON: f32 = 1e-6;

        (self.x - other.x).abs() < EPSILON
            && (self.y - other.y).abs() < EPSILON
            && (self.width - other.width).abs() < EPSILON
            && (self.height - other.height).abs() < EPSILON
    }
}

#[derive(Copy, Clone, Debug, Default)]
/// MouseInfo data.
pub struct MouseInfo {
    /// Whether the right mouse button was clicked this frame.
    pub is_rmb_clicked: bool,
    /// Whether the left mouse button was clicked this frame.
    pub is_lmb_clicked: bool,
    /// Whether the middle mouse button was clicked this frame.
    pub is_mmb_clicked: bool,
    /// Current mouse position in logical pixels.
    pub mouse_pos: Position,
    /// Horizontal scroll delta for the current frame.
    pub scroll_dx: f32,
    /// Vertical scroll delta for the current frame.
    pub scroll_dy: f32,
}

// Simple sliding-window frame time metrics for real-time reporting
#[derive(Debug)]
/// FrameTimeMetrics data.
pub struct FrameTimeMetrics {
    buffer: VecDeque<f32>, // seconds
    capacity: usize,
    last_report_secs: f64,
    report_period_secs: f32,
}

impl FrameTimeMetrics {
    /// Creates a new value.
    pub fn new(capacity: usize, report_period_secs: f32) -> Self {
        Self {
            buffer: VecDeque::with_capacity(capacity),
            capacity,
            last_report_secs: monotonic_now_seconds(),
            report_period_secs,
        }
    }

    /// Record.
    pub fn record(&mut self, delta_seconds: f32) {
        if self.buffer.len() == self.capacity {
            self.buffer.pop_front();
        }
        self.buffer.push_back(delta_seconds);
    }

    fn percentile(sorted_ms: &[f32], p: f32) -> f32 {
        if sorted_ms.is_empty() {
            return 0.0;
        }
        let clamped = p.clamp(0.0, 100.0);
        let rank = (clamped / 100.0) * ((sorted_ms.len() - 1) as f32);
        let lower = rank.floor() as usize;
        let upper = rank.ceil() as usize;
        match lower.cmp(&upper) {
            Ordering::Equal => sorted_ms[lower],
            _ => {
                let w = rank - (lower as f32);
                sorted_ms[lower] * (1.0 - w) + sorted_ms[upper] * w
            }
        }
    }

    /// Stats.
    pub fn stats(&self) -> Option<(f32, f32, f32, f32)> {
        // returns (p50_ms, p95_ms, p99_ms, avg_fps)
        if self.buffer.is_empty() {
            return None;
        }
        let mut ms: Vec<f32> = self.buffer.iter().map(|s| s * 1000.0).collect();
        ms.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
        let p50 = Self::percentile(&ms, 50.0);
        let p95 = Self::percentile(&ms, 95.0);
        let p99 = Self::percentile(&ms, 99.0);
        let avg_dt = self.buffer.iter().copied().sum::<f32>() / (self.buffer.len() as f32);
        let avg_fps = if avg_dt > 0.0 { 1.0 / avg_dt } else { 0.0 };
        Some((p50, p95, p99, avg_fps))
    }

    /// Maybe report.
    pub fn maybe_report(&mut self) -> Option<String> {
        let now = monotonic_now_seconds();
        let elapsed = (now - self.last_report_secs).max(0.0) as f32;
        if elapsed < self.report_period_secs {
            return None;
        }
        self.last_report_secs = now;
        self.stats().map(|(p50, p95, p99, fps)| {
            format!(
				"frame_metrics p50_ms={:.2} p95_ms={:.2} p99_ms={:.2} avg_fps={:.1} window_frames={}",
				p50, p95, p99, fps, self.buffer.len()
			)
        })
    }
}

#[cfg(test)]
mod tests {
    use super::{Position, Rectangle};

    #[test]
    fn padded_contains_shrinks_hit_region_uniformly() {
        let rect = Rectangle::new(10.0, 20.0, 100.0, 60.0);

        assert!(!rect.padded_contains(Position { x: 10.0, y: 50.0 }, 8.0));
        assert!(!rect.padded_contains(Position { x: 50.0, y: 20.0 }, 8.0));
        assert!(rect.padded_contains(Position { x: 60.0, y: 50.0 }, 8.0));
    }
}
