use egui::{Frame, Layout, Margin, Stroke, UiBuilder};
use egui_extras::{Size, StripBuilder};

/// Direction a [`paint_chevron`] chevron points.
#[derive(Clone, Copy)]
pub enum ChevronDir {
    /// Apex on the left (‹).
    Left,
    /// Apex on the right (›).
    Right,
    /// Apex at the bottom (⌄), e.g. an expanded disclosure.
    Down,
}

/// Paint a two-stroke chevron inside `rect`, inset by `pad`, pointing `dir`.
///
/// Shared by the clickable [`chevron`] widget and other callers (e.g. icon
/// badges) that already own a painter and just want the glyph.
pub fn paint_chevron(
    painter: &egui::Painter,
    rect: egui::Rect,
    pad: f32,
    dir: ChevronDir,
    stroke: impl Into<Stroke>,
) {
    let min = rect.min;
    let max = rect.max;
    let mid_x = (min.x + max.x) / 2.0;
    let mid_y = (min.y + max.y) / 2.0;

    let (apex, top, bottom) = match dir {
        ChevronDir::Left => (
            egui::Pos2::new(min.x + pad, mid_y),
            egui::Pos2::new(max.x - pad, min.y + pad),
            egui::Pos2::new(max.x - pad, max.y - pad),
        ),
        ChevronDir::Right => (
            egui::Pos2::new(max.x - pad, mid_y),
            egui::Pos2::new(min.x + pad, min.y + pad),
            egui::Pos2::new(min.x + pad, max.y - pad),
        ),
        ChevronDir::Down => (
            egui::Pos2::new(mid_x, max.y - pad),
            egui::Pos2::new(min.x + pad, min.y + pad),
            egui::Pos2::new(max.x - pad, min.y + pad),
        ),
    };

    let stroke = stroke.into();
    painter.line_segment([apex, top], stroke);
    painter.line_segment([apex, bottom], stroke);
}

pub fn chevron(
    ui: &mut egui::Ui,
    pad: f32,
    size: egui::Vec2,
    stroke: impl Into<Stroke>,
) -> egui::Response {
    let (r, painter) = ui.allocate_painter(size, egui::Sense::click());
    paint_chevron(&painter, r.rect, pad, ChevronDir::Left, stroke);
    r
}

/// Paint a small disclosure chevron in a fixed slot: pointing right when
/// collapsed, down when expanded. Purely visual — the caller is expected to
/// sense clicks on the surrounding row/header to drive the toggle.
pub fn disclosure_chevron(ui: &mut egui::Ui, expanded: bool, stroke: impl Into<Stroke>) {
    let (rect, _) = ui.allocate_exact_size(egui::Vec2::new(10.0, 12.0), egui::Sense::hover());
    if expanded {
        // Draw the down caret into a short, vertically-centred band so it stays
        // wide-and-shallow (⌄). Using the full slot height would make it taller
        // than wide and read as the letter "V".
        let caret = egui::Rect::from_center_size(rect.center(), egui::vec2(rect.width(), 6.0));
        paint_chevron(ui.painter(), caret, 1.0, ChevronDir::Down, stroke);
    } else {
        paint_chevron(ui.painter(), rect, 2.0, ChevronDir::Right, stroke);
    }
}

/// Generic UI Widget to render widgets horizontally where each is aligned vertically
pub struct HorizontalHeader {
    height: f32,
    margin: Margin,
    layout: Layout,
}

impl HorizontalHeader {
    pub fn new(height: f32) -> Self {
        Self {
            height,
            margin: Margin::same(notedeck::tokens::SPACING_SM as i8),
            layout: Layout::left_to_right(egui::Align::Center),
        }
    }

    pub fn with_margin(mut self, margin: Margin) -> Self {
        self.margin = margin;
        self
    }

    #[allow(clippy::too_many_arguments)]
    pub fn ui(
        self,
        ui: &mut egui::Ui,
        left_priority: i8, // lower the value, higher the priority
        center_priority: i8,
        right_priority: i8,
        left_aligned: impl FnMut(&mut egui::Ui),
        centered: impl FnMut(&mut egui::Ui),
        right_aligned: impl FnMut(&mut egui::Ui),
    ) {
        let prev_spacing = ui.spacing().item_spacing.y;
        ui.spacing_mut().item_spacing.y = 0.0;
        Frame::new().inner_margin(self.margin).show(ui, |ui| {
            let mut rect = ui.available_rect_before_wrap();
            rect.set_height(self.height);

            let mut child_ui = ui.new_child(UiBuilder::new().max_rect(rect));

            horizontal_header_inner(
                &mut child_ui,
                self.layout,
                left_priority,
                center_priority,
                right_priority,
                left_aligned,
                centered,
                right_aligned,
            );
            ui.advance_cursor_after_rect(rect);
        });
        ui.spacing_mut().item_spacing.y = prev_spacing;
    }
}

#[allow(clippy::too_many_arguments)]
fn horizontal_header_inner(
    ui: &mut egui::Ui,
    layout: Layout,
    left_priority: i8, // lower the value, higher the priority
    center_priority: i8,
    right_priority: i8,
    left_aligned: impl FnMut(&mut egui::Ui),
    centered: impl FnMut(&mut egui::Ui),
    right_aligned: impl FnMut(&mut egui::Ui),
) {
    let item_spacing = 6.0 * ui.spacing().item_spacing.x;
    let max_width = ui.available_width() - item_spacing;

    let (left_width, left_aligned) = measure_width(ui, left_aligned);
    let (center_width, centered) = measure_width(ui, centered);
    let (right_width, right_aligned) = measure_width(ui, right_aligned);

    let half_max = max_width / 2.0;
    let half_center = center_width / 2.0;
    let left_spacing = half_max - left_width - half_center;
    let right_spacing = half_max - right_width - half_center;

    let mut left_center = half_center;
    let left_cell = if left_spacing > 0.0 || left_priority < center_priority {
        Size::exact(left_width)
    } else {
        Size::remainder()
    };
    let mut left_gap = Size::exact(left_spacing.max(0.0));

    if left_spacing <= 0.0 {
        left_gap = Size::exact(0.0);
        if left_priority < center_priority {
            left_center = (half_center + left_spacing).max(0.0);
        }
    }

    let mut center_cell = Size::exact((left_center + half_center).max(0.0));
    let mut right_gap = Size::exact(right_spacing.max(0.0));
    let mut right_cell = Size::exact(right_width);

    if right_spacing <= 0.0 {
        right_gap = Size::exact(0.0);
        if center_priority < right_priority {
            right_cell = Size::remainder();
        } else {
            center_cell = Size::remainder();
        }
    }

    let sizes = [left_cell, left_gap, center_cell, right_gap, right_cell];

    let mut builder = StripBuilder::new(ui);
    for size in sizes {
        builder = builder.size(size);
    }

    builder.cell_layout(layout).horizontal(|mut strip| {
        strip.cell(left_aligned);
        strip.empty();
        strip.cell(centered);
        strip.empty();
        strip.cell(right_aligned);
    });
}

/// Inspired by VirtualList::ui_custom_layout
fn measure_width(
    ui: &mut egui::Ui,
    mut render: impl FnMut(&mut egui::Ui),
) -> (f32, impl FnMut(&mut egui::Ui)) {
    let mut measure_ui = ui.new_child(
        UiBuilder::new()
            .max_rect(ui.max_rect())
            .layout(Layout::left_to_right(egui::Align::Min)),
    );
    measure_ui.set_invisible();

    let start_width = measure_ui.next_widget_position();
    let res = measure_ui.scope_builder(UiBuilder::new().id_salt(ui.id().with("measure")), |ui| {
        render(ui);
        render
    });
    let end_width = measure_ui.next_widget_position();

    (
        (end_width.x - start_width.x + ui.spacing().item_spacing.x).max(0.0),
        res.inner,
    )
}
