use nalgebra_glm::Vec2;

use super::types::Rect;

#[derive(Clone, Debug)]
pub enum Layout {
    Free,
    Vertical {
        cursor: Vec2,
        spacing: f32,
        content_width: f32,
        alignment: LayoutAlignment,
    },
    Horizontal {
        cursor: Vec2,
        spacing: f32,
        max_height: f32,
        alignment: LayoutAlignment,
    },
    Panel {
        rect: Rect,
        cursor: Vec2,
        padding: f32,
        spacing: f32,
    },
}

#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum LayoutAlignment {
    #[default]
    Start,
    Center,
    End,
}

impl Layout {
    pub fn vertical(start: Vec2, spacing: f32, content_width: f32) -> Self {
        Self::Vertical {
            cursor: start,
            spacing,
            content_width,
            alignment: LayoutAlignment::Start,
        }
    }

    pub fn horizontal(start: Vec2, spacing: f32) -> Self {
        Self::Horizontal {
            cursor: start,
            spacing,
            max_height: 0.0,
            alignment: LayoutAlignment::Start,
        }
    }

    pub fn panel(rect: Rect, padding: f32, spacing: f32) -> Self {
        Self::Panel {
            rect,
            cursor: Vec2::new(rect.min.x + padding, rect.min.y + padding),
            padding,
            spacing,
        }
    }

    pub fn allocate(&mut self, size: Vec2) -> Rect {
        match self {
            Layout::Free => Rect::new(0.0, 0.0, size.x, size.y),

            Layout::Vertical {
                cursor,
                spacing,
                content_width,
                alignment,
            } => {
                let x = match alignment {
                    LayoutAlignment::Start => cursor.x,
                    LayoutAlignment::Center => cursor.x + (*content_width - size.x) * 0.5,
                    LayoutAlignment::End => cursor.x + *content_width - size.x,
                };
                let rect = Rect::new(x, cursor.y, size.x, size.y);
                cursor.y += size.y + *spacing;
                rect
            }

            Layout::Horizontal {
                cursor,
                spacing,
                max_height,
                ..
            } => {
                let rect = Rect::new(cursor.x, cursor.y, size.x, size.y);
                cursor.x += size.x + *spacing;
                if size.y > *max_height {
                    *max_height = size.y;
                }
                rect
            }

            Layout::Panel {
                rect: panel_rect,
                cursor,
                spacing,
                padding,
            } => {
                let available_width = panel_rect.width() - *padding * 2.0;
                let width = size.x.min(available_width);
                let allocated = Rect::new(cursor.x, cursor.y, width, size.y);
                cursor.y += size.y + *spacing;
                allocated
            }
        }
    }

    pub fn cursor(&self) -> Vec2 {
        match self {
            Layout::Free => Vec2::new(0.0, 0.0),
            Layout::Vertical { cursor, .. } => *cursor,
            Layout::Horizontal { cursor, .. } => *cursor,
            Layout::Panel { cursor, .. } => *cursor,
        }
    }

    pub fn set_cursor(&mut self, new_cursor: Vec2) {
        match self {
            Layout::Free => {}
            Layout::Vertical { cursor, .. } => *cursor = new_cursor,
            Layout::Horizontal { cursor, .. } => *cursor = new_cursor,
            Layout::Panel { cursor, .. } => *cursor = new_cursor,
        }
    }

    pub fn available_width(&self) -> f32 {
        match self {
            Layout::Free => f32::INFINITY,
            Layout::Vertical { content_width, .. } => *content_width,
            Layout::Horizontal { .. } => f32::INFINITY,
            Layout::Panel { rect, padding, .. } => rect.width() - *padding * 2.0,
        }
    }

    pub fn add_spacing(&mut self, amount: f32) {
        match self {
            Layout::Free => {}
            Layout::Vertical { cursor, .. } => cursor.y += amount,
            Layout::Horizontal { cursor, .. } => cursor.x += amount,
            Layout::Panel { cursor, .. } => cursor.y += amount,
        }
    }

    pub fn newline(&mut self) {
        if let Layout::Horizontal {
            cursor, max_height, ..
        } = self
        {
            cursor.y += *max_height;
            *max_height = 0.0;
        }
    }

    pub fn with_alignment(mut self, alignment: LayoutAlignment) -> Self {
        match &mut self {
            Layout::Vertical { alignment: a, .. } => *a = alignment,
            Layout::Horizontal { alignment: a, .. } => *a = alignment,
            _ => {}
        }
        self
    }
}

#[derive(Clone, Debug, Default)]
pub struct LayoutStack {
    layouts: Vec<Layout>,
}

impl LayoutStack {
    pub fn new() -> Self {
        Self { layouts: vec![] }
    }

    pub fn push(&mut self, layout: Layout) {
        self.layouts.push(layout);
    }

    pub fn pop(&mut self) -> Option<Layout> {
        self.layouts.pop()
    }

    pub fn current(&self) -> Option<&Layout> {
        self.layouts.last()
    }

    pub fn current_mut(&mut self) -> Option<&mut Layout> {
        self.layouts.last_mut()
    }

    pub fn allocate(&mut self, size: Vec2) -> Rect {
        if let Some(layout) = self.layouts.last_mut() {
            layout.allocate(size)
        } else {
            Rect::new(0.0, 0.0, size.x, size.y)
        }
    }

    pub fn cursor(&self) -> Vec2 {
        self.layouts.last().map(|l| l.cursor()).unwrap_or_default()
    }

    pub fn available_width(&self) -> f32 {
        self.layouts
            .last()
            .map(|l| l.available_width())
            .unwrap_or(f32::INFINITY)
    }

    pub fn is_empty(&self) -> bool {
        self.layouts.is_empty()
    }

    pub fn len(&self) -> usize {
        self.layouts.len()
    }

    pub fn get(&self, index: usize) -> Option<&Layout> {
        self.layouts.get(index)
    }
}
