//! Testable geometry for dense Penpot material property rows.

use super::{COMPACT_PARAMETER_ROW_HEIGHT, PARAMETER_ROW_HEIGHT};
use bevy_egui::egui;

/// Minimum live section width that can safely retain the exported wide-row geometry.
///
/// The Penpot reference section itself is 569 px wide. In the native Inspector, dock chrome,
/// component-card borders, and the floating scrollbar consume up to ten pixels before the
/// material parameter body is allocated. Treat that 559 px contained canvas as wide so a 640 px
/// Inspector renders the authoritative wide reference instead of incorrectly switching to the
/// 420 px two-line rows.
pub(crate) const WIDE_SECTION_WIDTH: f32 = 559.0;
pub(crate) const RESPONSIVE_BREAKPOINT: f32 = WIDE_SECTION_WIDTH;
/// The actor component chrome can consume up to twelve pixels of the exported 372 px compact
/// canvas. At 360 px the complete texture row still fits: 12 px inset + 336 px group + 12 px inset.
const COMPACT_INLINE_ACTION_FIT_WIDTH: f32 = 357.0;

#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct PropertyGridGeometry {
    pub stacked: bool,
    pub row_height: f32,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct PropertyRowRects {
    pub row: egui::Rect,
    pub label: egui::Rect,
    pub value: egui::Rect,
    pub color_value: egui::Rect,
    pub channel: egui::Rect,
    pub texture_group: egui::Rect,
    pub texture_field: egui::Rect,
    pub locate: egui::Rect,
    pub clear: egui::Rect,
    pub inline_texture_actions: bool,
}

impl PropertyGridGeometry {
    pub(crate) fn for_width(section_width: f32) -> Self {
        if section_width + f32::EPSILON >= RESPONSIVE_BREAKPOINT {
            Self {
                stacked: false,
                row_height: PARAMETER_ROW_HEIGHT,
            }
        } else {
            Self {
                stacked: true,
                row_height: COMPACT_PARAMETER_ROW_HEIGHT,
            }
        }
    }

    pub(crate) fn row_rects(self, row: egui::Rect) -> PropertyRowRects {
        let inset = 12.0_f32.min(row.width() * 0.5);
        let inner_width = (row.width() - inset * 2.0).max(1.0);
        if !self.stacked {
            let texture_group_width = (inner_width - 286.5).max(60.0);
            let texture_field_width = (texture_group_width - 60.0).max(1.0);
            let texture_group = egui::Rect::from_min_size(
                row.min + egui::vec2(298.5, 5.0),
                egui::vec2(texture_group_width, 22.0),
            );
            return PropertyRowRects {
                row,
                label: egui::Rect::from_min_size(
                    row.min + egui::vec2(12.0, 5.0),
                    egui::vec2(82.0, 22.0),
                ),
                value: egui::Rect::from_min_size(
                    row.min + egui::vec2(103.35, 5.0),
                    egui::vec2(132.8, 22.0),
                ),
                color_value: egui::Rect::from_min_size(
                    row.min + egui::vec2(107.95, 5.0),
                    egui::vec2(182.55, 22.0),
                ),
                channel: egui::Rect::from_min_size(
                    row.min + egui::vec2(234.0, 5.0),
                    egui::vec2(48.0, 22.0),
                ),
                texture_group,
                texture_field: egui::Rect::from_min_size(
                    texture_group.min,
                    egui::vec2(texture_field_width, 22.0),
                ),
                locate: egui::Rect::from_min_size(
                    texture_group.min + egui::vec2(texture_field_width + 6.0, 0.0),
                    egui::vec2(24.0, 22.0),
                ),
                clear: egui::Rect::from_min_size(
                    texture_group.min + egui::vec2(texture_field_width + 36.0, 0.0),
                    egui::vec2(24.0, 22.0),
                ),
                inline_texture_actions: true,
            };
        }

        let channel_width = 48.0_f32.min(inner_width);
        let channel_x = inset + inner_width - channel_width;
        let value_x = (inset + 91.35).min(channel_x);
        let value_width = (channel_x - value_x - 6.35).max(1.0);
        let color_value_x = (inset + 95.95).min(channel_x);
        let color_value_width = (inset + inner_width - color_value_x).max(1.0);
        let texture_group = egui::Rect::from_min_size(
            row.min + egui::vec2(inset, 31.0),
            egui::vec2(inner_width, 22.0),
        );
        let inline_texture_actions = row.width() + f32::EPSILON >= COMPACT_INLINE_ACTION_FIT_WIDTH;
        let texture_field_width = if inline_texture_actions {
            (inner_width - 60.0).max(1.0)
        } else {
            (inner_width - 30.0).max(1.0)
        };
        let overflow_or_locate_x = texture_field_width + 6.0;
        PropertyRowRects {
            row,
            label: egui::Rect::from_min_size(
                row.min + egui::vec2(inset, 5.0),
                egui::vec2(78.0_f32.min(inner_width), 22.0),
            ),
            value: egui::Rect::from_min_size(
                row.min + egui::vec2(value_x, 5.0),
                egui::vec2(value_width, 22.0),
            ),
            color_value: egui::Rect::from_min_size(
                row.min + egui::vec2(color_value_x, 5.0),
                egui::vec2(color_value_width, 22.0),
            ),
            channel: egui::Rect::from_min_size(
                row.min + egui::vec2(channel_x, 5.0),
                egui::vec2(channel_width, 22.0),
            ),
            texture_group,
            texture_field: egui::Rect::from_min_size(
                texture_group.min,
                egui::vec2(texture_field_width, 22.0),
            ),
            locate: egui::Rect::from_min_size(
                texture_group.min + egui::vec2(overflow_or_locate_x, 0.0),
                egui::vec2(24.0, 22.0),
            ),
            clear: egui::Rect::from_min_size(
                texture_group.min
                    + egui::vec2(
                        if inline_texture_actions {
                            texture_field_width + 36.0
                        } else {
                            inner_width
                        },
                        0.0,
                    ),
                egui::vec2(if inline_texture_actions { 24.0 } else { 0.0 }, 22.0),
            ),
            inline_texture_actions,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn penpot_exact_reference_geometry_matches_columns() {
        let geometry = PropertyGridGeometry::for_width(569.0);
        assert!(!geometry.stacked);
        assert_eq!(geometry.row_height, 32.0);
    }

    #[test]
    fn native_wide_containment_threshold_keeps_reference_rows() {
        let geometry = PropertyGridGeometry::for_width(559.0);
        assert!(!geometry.stacked);
        assert_eq!(geometry.row_height, 32.0);
    }

    #[test]
    fn below_native_wide_containment_threshold_reflows_without_compression() {
        let geometry = PropertyGridGeometry::for_width(558.0);
        assert!(geometry.stacked);
        assert_eq!(geometry.row_height, 58.0);
    }

    #[test]
    fn row_rects_match_exported_wide_geometry() {
        let row = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(569.0, 32.0));
        let rects = PropertyGridGeometry::for_width(569.0).row_rects(row);
        assert_eq!(rects.label.min, egui::pos2(12.0, 5.0));
        assert_eq!(rects.value.min, egui::pos2(103.35, 5.0));
        assert!((rects.value.width() - 132.8).abs() < 0.001);
        assert_eq!(rects.value.height(), 22.0);
        assert_eq!(rects.color_value.min, egui::pos2(107.95, 5.0));
        assert!((rects.color_value.width() - 182.55).abs() < 0.001);
        assert_eq!(
            rects.channel,
            egui::Rect::from_min_size(egui::pos2(234.0, 5.0), egui::vec2(48.0, 22.0))
        );
        assert_eq!(
            rects.texture_group,
            egui::Rect::from_min_size(egui::pos2(298.5, 5.0), egui::vec2(258.5, 22.0))
        );
        assert_eq!(rects.texture_field.size(), egui::vec2(198.5, 22.0));
        assert_eq!(rects.locate.min, egui::pos2(503.0, 5.0));
        assert_eq!(rects.clear.min, egui::pos2(533.0, 5.0));
        assert!(rects.inline_texture_actions);
    }

    #[test]
    fn row_rects_match_exported_compact_geometry() {
        let row = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(369.0, 58.0));
        let rects = PropertyGridGeometry::for_width(369.0).row_rects(row);
        assert_eq!(rects.label.min, egui::pos2(12.0, 5.0));
        assert_eq!(rects.value.min, egui::pos2(103.35, 5.0));
        assert!((rects.value.width() - 199.3).abs() < 0.001);
        assert_eq!(rects.color_value.min, egui::pos2(107.95, 5.0));
        assert!((rects.color_value.width() - 249.05).abs() < 0.001);
        assert_eq!(
            rects.channel,
            egui::Rect::from_min_size(egui::pos2(309.0, 5.0), egui::vec2(48.0, 22.0))
        );
        assert_eq!(
            rects.texture_group,
            egui::Rect::from_min_size(egui::pos2(12.0, 31.0), egui::vec2(345.0, 22.0))
        );
        assert_eq!(rects.texture_field.size(), egui::vec2(285.0, 22.0));
        assert_eq!(rects.locate.min, egui::pos2(303.0, 31.0));
        assert_eq!(rects.clear.min, egui::pos2(333.0, 31.0));
        assert!(rects.inline_texture_actions);
    }

    #[test]
    fn transient_row_rects_are_finite_and_contained() {
        for width in [356.0, 300.0, 240.0] {
            let row = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(width, 58.0));
            let rects = PropertyGridGeometry::for_width(width).row_rects(row);
            for rect in [
                rects.label,
                rects.value,
                rects.color_value,
                rects.channel,
                rects.texture_group,
                rects.texture_field,
                rects.locate,
                rects.clear,
            ] {
                assert!(rect.is_finite());
                assert!(row.contains_rect(rect));
            }
            assert!(!rects.inline_texture_actions);
        }
    }

    #[test]
    fn inspector_chrome_reduced_floor_keeps_compact_texture_actions() {
        let row = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(363.0, 58.0));
        let rects = PropertyGridGeometry::for_width(363.0).row_rects(row);
        assert!(rects.inline_texture_actions);
        assert_eq!(rects.texture_group.min, egui::pos2(12.0, 31.0));
        assert_eq!(rects.texture_group.max, egui::pos2(351.0, 53.0));
        assert!(row.contains_rect(rects.locate));
        assert!(row.contains_rect(rects.clear));
    }

    #[test]
    fn penpot_wide_growth_only_expands_texture_field() {
        let reference_row = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(569.0, 32.0));
        let wide_row = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(669.0, 32.0));
        let reference = PropertyGridGeometry::for_width(569.0).row_rects(reference_row);
        let wide = PropertyGridGeometry::for_width(669.0).row_rects(wide_row);
        assert_eq!(wide.label, reference.label);
        assert_eq!(wide.value, reference.value);
        assert_eq!(wide.color_value, reference.color_value);
        assert_eq!(wide.channel, reference.channel);
        assert_eq!(
            wide.texture_field.width(),
            reference.texture_field.width() + 100.0
        );
    }

    #[test]
    fn penpot_very_narrow_first_line_never_exceeds_inner_width() {
        let geometry = PropertyGridGeometry::for_width(300.0);
        assert!(geometry.stacked);
        let row = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(300.0, 58.0));
        let rects = geometry.row_rects(row);
        assert!(row.contains_rect(rects.label));
        assert!(row.contains_rect(rects.value));
        assert!(row.contains_rect(rects.channel));
        assert_eq!(rects.texture_group.width(), 276.0);
    }
}
