//! Penpot-led editor design-system primitives.
//!
//! The first consumer is the Material Slot panel. These semantic tokens and controls are kept
//! independent from material/world state so later editor panels can migrate without copying style.

pub(crate) mod color_picker;
pub(crate) mod controls;
pub(crate) mod property_grid;
pub(crate) mod typography;

use bevy_egui::egui::{self, Color32, CornerRadius, Stroke};

use crate::ui::theme::EditorVisualPalette;

#[cfg(test)]
pub(crate) const REFERENCE_WIDTH: f32 = 620.0;
pub(crate) const INSPECTOR_MIN_WIDTH: f32 = 420.0;
pub(crate) const OUTER_PADDING: f32 = 12.0;
pub(crate) const SECTION_GAP: f32 = 8.0;
pub(crate) const MATERIALS_HEADING_HEIGHT: f32 = 32.0;
pub(crate) const HEADER_HEIGHT: f32 = 82.0;
pub(crate) const COLLAPSED_HEADER_HEIGHT: f32 = 52.0;
pub(crate) const MATERIAL_PREVIEW_SIZE: f32 = 64.0;
pub(crate) const MATERIAL_IDENTITY_WIDTH: f32 = 229.5;
pub(crate) const SURFACE_SECTION_HEIGHT: f32 = 46.0;
pub(crate) const COMPACT_SURFACE_SECTION_HEIGHT: f32 = 46.0;
pub(crate) const INPUT_SECTION_HEADER_HEIGHT: f32 = 24.0;
pub(crate) const PARAMETER_ROW_HEIGHT: f32 = 32.0;
pub(crate) const COMPACT_PARAMETER_ROW_HEIGHT: f32 = 58.0;
pub(crate) const UV_SECTION_HEIGHT: f32 = 54.0;
pub(crate) const COMPACT_UV_SECTION_HEIGHT: f32 = 94.0;
pub(crate) const ADVANCED_COLLAPSED_HEIGHT: f32 = 30.0;
pub(crate) const ADVANCED_EXPANDED_HEIGHT: f32 = 178.0;
pub(crate) const CONTROL_HEIGHT: f32 = 24.0;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct DesignPalette {
    pub accent: Color32,
    pub accent_dark: Color32,
    pub panel: Color32,
    pub section: Color32,
    pub recessed: Color32,
    pub control: Color32,
    pub elevated: Color32,
    pub row_even: Color32,
    pub row_odd: Color32,
    pub checker: Color32,
    pub border: Color32,
    pub border_strong: Color32,
    pub text_primary: Color32,
    pub text_secondary: Color32,
    pub text_muted: Color32,
    pub healthy: Color32,
    pub warning: Color32,
    pub error: Color32,
}

impl From<EditorVisualPalette> for DesignPalette {
    fn from(value: EditorVisualPalette) -> Self {
        Self {
            accent: value.selection,
            accent_dark: value.selection_bg,
            panel: value.panel,
            section: value.widget,
            recessed: value.panel_dark,
            control: value.widget,
            elevated: value.elevated,
            row_even: value.panel,
            row_odd: value.widget,
            checker: value.elevated,
            border: value.border,
            border_strong: value.border_strong,
            text_primary: value.text,
            text_secondary: value.text_dim,
            text_muted: value.text_muted,
            healthy: value.success,
            warning: value.warning,
            error: value.error,
        }
    }
}

pub(crate) fn palette(ui: &egui::Ui) -> DesignPalette {
    crate::ui::theme::editor_palette(ui.ctx()).into()
}

/// Applies the Penpot material-panel visual language locally without restyling the whole editor.
pub(crate) fn scope<R>(ui: &mut egui::Ui, add_contents: impl FnOnce(&mut egui::Ui) -> R) -> R {
    let palette = palette(ui);
    ui.scope(|ui| {
        let style = ui.style_mut();
        style.spacing.item_spacing = egui::vec2(8.0, 4.0);
        style.spacing.button_padding = egui::vec2(8.0, 3.0);
        style.spacing.interact_size = egui::vec2(31.0, CONTROL_HEIGHT);
        style.visuals.widgets.noninteractive.bg_fill = palette.section;
        style.visuals.widgets.noninteractive.bg_stroke = Stroke::new(1.0_f32, palette.border);
        style.visuals.widgets.noninteractive.fg_stroke =
            Stroke::new(1.0_f32, palette.text_secondary);
        style.visuals.widgets.inactive.bg_fill = palette.control;
        style.visuals.widgets.inactive.weak_bg_fill = palette.control;
        style.visuals.widgets.inactive.bg_stroke = Stroke::new(1.0_f32, palette.border);
        style.visuals.widgets.inactive.fg_stroke = Stroke::new(1.0_f32, palette.text_primary);
        style.visuals.widgets.hovered.bg_fill = palette.elevated;
        style.visuals.widgets.hovered.weak_bg_fill = palette.elevated;
        style.visuals.widgets.hovered.bg_stroke = Stroke::new(1.0_f32, palette.border_strong);
        style.visuals.widgets.hovered.fg_stroke = Stroke::new(1.0_f32, palette.text_primary);
        style.visuals.widgets.active.bg_fill = palette.accent_dark;
        style.visuals.widgets.active.weak_bg_fill = palette.accent_dark;
        style.visuals.widgets.active.bg_stroke = Stroke::new(1.0_f32, palette.accent);
        style.visuals.widgets.active.fg_stroke = Stroke::new(1.0_f32, palette.text_primary);
        style.visuals.selection.bg_fill = palette.accent_dark;
        style.visuals.selection.stroke = Stroke::new(1.0_f32, palette.accent);
        style.visuals.window_fill = palette.panel;
        style.visuals.window_stroke = Stroke::new(1.0_f32, palette.border_strong);
        style.visuals.window_corner_radius = CornerRadius::same(7);
        style.visuals.menu_corner_radius = CornerRadius::same(5);
        style.visuals.override_text_color = Some(palette.text_primary);
        typography::install_local_text_styles(style);
        add_contents(ui)
    })
    .inner
}

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

    #[test]
    fn penpot_geometry_tokens_are_exact() {
        assert_eq!(REFERENCE_WIDTH, 620.0);
        assert_eq!(MATERIALS_HEADING_HEIGHT, 32.0);
        assert_eq!(HEADER_HEIGHT, 82.0);
        assert_eq!(COLLAPSED_HEADER_HEIGHT, 52.0);
        assert_eq!(MATERIAL_PREVIEW_SIZE, 64.0);
        assert_eq!(MATERIAL_IDENTITY_WIDTH, 229.5);
        assert_eq!(SURFACE_SECTION_HEIGHT, 46.0);
        assert_eq!(COMPACT_SURFACE_SECTION_HEIGHT, 46.0);
        assert_eq!(INPUT_SECTION_HEADER_HEIGHT, 24.0);
        assert_eq!(PARAMETER_ROW_HEIGHT, 32.0);
        assert_eq!(COMPACT_PARAMETER_ROW_HEIGHT, 58.0);
        assert_eq!(UV_SECTION_HEIGHT, 54.0);
        assert_eq!(COMPACT_UV_SECTION_HEIGHT, 94.0);
        assert_eq!(ADVANCED_COLLAPSED_HEIGHT, 30.0);
        assert_eq!(ADVANCED_EXPANDED_HEIGHT, 178.0);
    }

    #[test]
    fn material_design_palette_uses_blacksite_semantics() {
        let palette = DesignPalette::from(crate::ui::theme::BLACKSITE_PALETTE);
        assert_eq!(palette.accent, crate::ui::theme::SELECTION);
        assert_eq!(palette.panel, crate::ui::theme::PANEL_BG);
        assert_eq!(palette.text_primary, crate::ui::theme::TEXT);
    }
}
