//! Reusable Penpot-styled editor controls with no world or asset-store access.

use egui;
use egui_phosphor_icons::{icons, Icon};
use std::ops::RangeInclusive;

use super::palette;
use super::typography::TypeRole;

// Penpot's scalar component keeps a six-pixel leading inset inside its value-group frame. The
// exported child geometry is authoritative here; the group's transparent parent frame is six
// pixels narrower than its visible children and must not be mistaken for the control width.
const SCALAR_TRACK_X: f32 = 6.0;
const SCALAR_NUMBER_GAP: f32 = 6.0;
const SCALAR_TRACK_INTERACTION_HEIGHT: f32 = 10.0;
const SCALAR_NUMBER_WIDTH: f32 = 33.0;
const SCALAR_THUMB_RADIUS: f32 = 4.0;

pub fn section_frame(ui: &egui::Ui) -> egui::Frame {
    let palette = palette(ui);
    egui::Frame::new()
        .fill(palette.section)
        .stroke(egui::Stroke::new(1.0_f32, palette.border))
        .corner_radius(egui::CornerRadius::same(5))
}

pub fn section_header(ui: &mut egui::Ui, title: &str, open: &mut bool) -> egui::Response {
    section_header_with_summary(ui, title, None, open)
}

pub fn section_header_with_summary(
    ui: &mut egui::Ui,
    title: &str,
    summary: Option<&str>,
    open: &mut bool,
) -> egui::Response {
    let palette = palette(ui);
    let (rect, response) = ui.allocate_exact_size(
        egui::vec2(
            ui.available_width().max(1.0),
            super::INPUT_SECTION_HEADER_HEIGHT,
        ),
        egui::Sense::click(),
    );
    ui.painter().rect_filled(rect, 4.0, palette.elevated);
    ui.painter().text(
        rect.left_center() + egui::vec2(12.0, 0.0),
        egui::Align2::CENTER_CENTER,
        if *open {
            icons::CARET_DOWN.as_str()
        } else {
            icons::CARET_RIGHT.as_str()
        },
        egui::FontId::new(10.0, egui::FontFamily::Name("phosphor-bold".into())),
        palette.text_secondary,
    );
    ui.painter().text(
        rect.left_center() + egui::vec2(26.0, 0.0),
        egui::Align2::LEFT_CENTER,
        title,
        TypeRole::Section.font(),
        palette.text_primary,
    );
    if let Some(summary) = summary {
        ui.painter().text(
            rect.right_center() - egui::vec2(12.0, 0.0),
            egui::Align2::RIGHT_CENTER,
            summary,
            TypeRole::Small.font(),
            palette.text_muted,
        );
    }
    if response.clicked() {
        *open = !*open;
    }
    response
}

/// Compose a Phosphor glyph and Source Sans label without asking either font to render the
/// other's codepoints. This is the canonical path for icon-plus-text controls in a design-system
/// scope.
pub fn icon_label(
    icon: Icon,
    label: &str,
    role: TypeRole,
    icon_size: f32,
    color: egui::Color32,
) -> egui::WidgetText {
    let mut job = egui::text::LayoutJob::default();
    job.append(
        icon.as_str(),
        0.0,
        egui::TextFormat {
            font_id: egui::FontId::new(icon_size, egui::FontFamily::Name("phosphor-bold".into())),
            color,
            ..Default::default()
        },
    );
    if !label.is_empty() {
        job.append(
            label,
            6.0,
            egui::TextFormat {
                font_id: role.font(),
                color,
                ..Default::default()
            },
        );
    }
    job.into()
}

pub fn icon_button(ui: &mut egui::Ui, icon: Icon, tooltip: &str, enabled: bool) -> egui::Response {
    let palette = palette(ui);
    let response = ui.add_enabled(
        enabled,
        egui::Button::new(
            egui::RichText::new(icon.as_str())
                .font(egui::FontId::new(
                    14.0,
                    egui::FontFamily::Name("phosphor-bold".into()),
                ))
                .color(palette.text_secondary),
        )
        .min_size(egui::vec2(24.0, 24.0))
        .fill(palette.control)
        .stroke(egui::Stroke::new(1.0_f32, palette.border))
        .corner_radius(egui::CornerRadius::same(4)),
    );
    response.on_hover_text(tooltip)
}

pub fn status(ui: &mut egui::Ui, icon: Icon, color: egui::Color32, label: &str, tooltip: &str) {
    let palette = palette(ui);
    let mut job = egui::text::LayoutJob::default();
    job.append(
        icon.as_str(),
        0.0,
        egui::TextFormat {
            font_id: egui::FontId::new(12.0, egui::FontFamily::Name("phosphor-bold".into())),
            color,
            ..Default::default()
        },
    );
    job.append(
        label,
        5.0,
        egui::TextFormat {
            font_id: TypeRole::Small.font(),
            color: palette.text_secondary,
            ..Default::default()
        },
    );
    let response = ui.add(egui::Label::new(job).sense(egui::Sense::hover()));
    response.on_hover_text(tooltip);
}

pub fn switch(ui: &mut egui::Ui, value: &mut bool) -> egui::Response {
    let palette = palette(ui);
    let (rect, mut response) = ui.allocate_exact_size(egui::vec2(31.0, 16.0), egui::Sense::click());
    if response.clicked() {
        *value = !*value;
        response.mark_changed();
    }
    let fill = if *value {
        palette.accent_dark
    } else {
        palette.control
    };
    let stroke = if *value {
        palette.accent
    } else {
        palette.border_strong
    };
    ui.painter().rect(
        rect,
        8.0,
        fill,
        egui::Stroke::new(1.0_f32, stroke),
        egui::StrokeKind::Inside,
    );
    let x = if *value {
        rect.right() - 8.0
    } else {
        rect.left() + 8.0
    };
    ui.painter()
        .circle_filled(egui::pos2(x, rect.center().y), 5.0, palette.text_primary);
    response
}

/// Fixed 128×24 two-option control used by the Penpot material Surface section.
///
/// Returning the selected side keeps the control independent from material state and prevents
/// stock `selectable_label` padding from changing its geometry.
pub fn two_option_segmented(
    ui: &mut egui::Ui,
    left_label: &str,
    right_label: &str,
    right_selected: bool,
) -> Option<bool> {
    const WIDTH: f32 = 128.0;
    const HEIGHT: f32 = 24.0;
    const OPTION_WIDTH: f32 = WIDTH * 0.5;

    let palette = palette(ui);
    let (rect, _) = ui.allocate_exact_size(egui::vec2(WIDTH, HEIGHT), egui::Sense::hover());
    ui.painter().rect(
        rect,
        4.0,
        palette.control,
        egui::Stroke::new(1.0_f32, palette.border),
        egui::StrokeKind::Inside,
    );
    let left = egui::Rect::from_min_size(rect.min, egui::vec2(OPTION_WIDTH, HEIGHT));
    let right = egui::Rect::from_min_size(
        rect.min + egui::vec2(OPTION_WIDTH, 0.0),
        egui::vec2(OPTION_WIDTH, HEIGHT),
    );
    let mut selection = None;
    for (option_rect, label, selected, value) in [
        (left, left_label, !right_selected, false),
        (right, right_label, right_selected, true),
    ] {
        let response = ui.interact(
            option_rect,
            ui.make_persistent_id(("two_option_segment", label)),
            egui::Sense::click(),
        );
        let fill = if selected {
            palette.accent_dark
        } else if response.hovered() {
            palette.elevated
        } else {
            palette.control
        };
        ui.painter().rect_filled(option_rect.shrink(1.0), 3.0, fill);
        if selected {
            ui.painter().rect_stroke(
                option_rect,
                3.0,
                egui::Stroke::new(1.0_f32, palette.accent),
                egui::StrokeKind::Inside,
            );
        }
        ui.painter().text(
            option_rect.center(),
            egui::Align2::CENTER_CENTER,
            label,
            TypeRole::Control.font(),
            if selected {
                palette.text_primary
            } else {
                palette.text_secondary
            },
        );
        if response.clicked() && right_selected != value {
            selection = Some(value);
        }
    }
    selection
}

/// Exact Penpot scalar editor used by material inputs.
///
/// The track and numeric field are painted as independent controls so egui's stock slider
/// padding cannot move the thumb into the value field at the reference width.
pub fn scalar_control(
    ui: &mut egui::Ui,
    salt: impl std::hash::Hash,
    value: &mut f32,
    range: RangeInclusive<f32>,
    max_decimals: usize,
) -> egui::Response {
    let palette = palette(ui);
    let before = *value;
    // The final Penpot component deliberately stretches only the track at the compact
    // breakpoint: 93.8 px at the 569 px reference and 160.3 px at the 369 px reference.
    // The numeric field remains a stable 33 px in both modes, after a six-pixel leading inset
    // and a six-pixel track-to-number gap.
    let width = ui.available_width().max(1.0);
    let (rect, base_response) =
        ui.allocate_exact_size(egui::vec2(width, 22.0), egui::Sense::hover());
    let number_width = SCALAR_NUMBER_WIDTH.min(width);
    let number_x = (width - number_width).max(0.0);
    let track_width = (number_x - SCALAR_TRACK_X - SCALAR_NUMBER_GAP).max(1.0);
    let slider_rect = egui::Rect::from_min_size(
        rect.min + egui::vec2(SCALAR_TRACK_X, 6.0),
        egui::vec2(track_width, SCALAR_TRACK_INTERACTION_HEIGHT),
    );
    let track_rect =
        egui::Rect::from_center_size(slider_rect.center(), egui::vec2(track_width, 3.0));
    let slider_response = ui.interact(
        slider_rect,
        ui.make_persistent_id(("penpot_scalar", salt)),
        egui::Sense::click_and_drag(),
    );
    if slider_response.clicked() || slider_response.dragged() {
        if let Some(pointer) = slider_response.interact_pointer_pos() {
            let start = *range.start();
            let end = *range.end();
            let normalized = ((pointer.x - track_rect.left()) / track_rect.width()).clamp(0.0, 1.0);
            *value = egui::lerp(start..=end, normalized);
        }
    }

    let start = *range.start();
    let end = *range.end();
    let normalized = if end > start {
        ((*value - start) / (end - start)).clamp(0.0, 1.0)
    } else {
        0.0
    };
    ui.painter().rect_filled(track_rect, 2.0, palette.border);
    let fill = egui::Rect::from_min_max(
        track_rect.left_top(),
        egui::pos2(
            egui::lerp(track_rect.x_range(), normalized),
            track_rect.bottom(),
        ),
    );
    ui.painter().rect_filled(fill, 2.0, palette.accent);
    let thumb_center = egui::pos2(
        egui::lerp(track_rect.x_range(), normalized),
        track_rect.center().y,
    );
    ui.painter().circle(
        thumb_center,
        SCALAR_THUMB_RADIUS,
        palette.text_primary,
        egui::Stroke::new(1.0_f32, palette.accent),
    );

    let number_rect = egui::Rect::from_min_size(
        rect.min + egui::vec2(number_x, 0.0),
        egui::vec2(number_width, 22.0),
    );
    let number_response = ui.put(
        number_rect,
        egui::DragValue::new(value)
            .speed(0.01)
            .range(range)
            .max_decimals(max_decimals),
    );
    let mut response = base_response.union(slider_response).union(number_response);
    if *value != before {
        response.mark_changed();
    }
    response
}

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

    #[test]
    fn penpot_scalar_geometry_matches_the_current_export() {
        let wide_width = 138.8;
        let wide_number_x = wide_width - SCALAR_NUMBER_WIDTH;
        let wide_track = wide_number_x - SCALAR_TRACK_X - SCALAR_NUMBER_GAP;
        assert!((wide_track - 93.8).abs() < 0.001);

        let compact_width = 205.3;
        let compact_number_x = compact_width - SCALAR_NUMBER_WIDTH;
        let compact_track = compact_number_x - SCALAR_TRACK_X - SCALAR_NUMBER_GAP;
        assert!((compact_track - 160.3).abs() < 0.001);
        assert_eq!(SCALAR_TRACK_INTERACTION_HEIGHT, 10.0);
        assert_eq!(SCALAR_THUMB_RADIUS, 4.0);
    }
}

#[cfg(test)]
mod segmented_tests {
    #[test]
    fn penpot_segmented_control_has_two_fixed_halves() {
        const WIDTH: f32 = 128.0;
        const OPTION_WIDTH: f32 = WIDTH * 0.5;
        assert_eq!(OPTION_WIDTH, 64.0);
        assert_eq!(OPTION_WIDTH * 2.0, WIDTH);
    }
}
