use std::borrow::Cow;

use gpui::{
    elements::{
        ConstrainedBox, Container, ContainerStyle, Dimensions, Empty, Flex, KeystrokeLabel, Label,
        MouseEventHandler, ParentElement, Stack, Svg, SvgStyle,
    },
    fonts::TextStyle,
    geometry::vector::Vector2F,
    platform,
    platform::MouseButton,
    scene::MouseClick,
    Action, Element, EventContext, MouseState, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;

use crate::{ContainedText, Interactive};

#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct CheckboxStyle {
    pub icon: SvgStyle,
    pub label: ContainedText,
    pub default: ContainerStyle,
    pub checked: ContainerStyle,
    pub hovered: ContainerStyle,
    pub hovered_and_checked: ContainerStyle,
}

pub fn checkbox<Tag, V, F>(
    label: &'static str,
    style: &CheckboxStyle,
    checked: bool,
    id: usize,
    cx: &mut ViewContext<V>,
    change: F,
) -> MouseEventHandler<V>
where
    Tag: 'static,
    V: 'static,
    F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
{
    let label = Label::new(label, style.label.text.clone())
        .contained()
        .with_style(style.label.container);
    checkbox_with_label::<Tag, _, _, _>(label, style, checked, id, cx, change)
}

pub fn checkbox_with_label<Tag, D, V, F>(
    label: D,
    style: &CheckboxStyle,
    checked: bool,
    id: usize,
    cx: &mut ViewContext<V>,
    change: F,
) -> MouseEventHandler<V>
where
    Tag: 'static,
    D: Element<V>,
    V: 'static,
    F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
{
    MouseEventHandler::new::<Tag, _>(id, cx, |state, _| {
        let indicator = if checked {
            svg(&style.icon)
        } else {
            Empty::new()
                .constrained()
                .with_width(style.icon.dimensions.width)
                .with_height(style.icon.dimensions.height)
        };

        Flex::row()
            .with_child(indicator.contained().with_style(if checked {
                if state.hovered() {
                    style.hovered_and_checked
                } else {
                    style.checked
                }
            } else {
                if state.hovered() {
                    style.hovered
                } else {
                    style.default
                }
            }))
            .with_child(label)
            .align_children_center()
    })
    .on_click(platform::MouseButton::Left, move |_, view, cx| {
        change(view, !checked, cx)
    })
    .with_cursor_style(platform::CursorStyle::PointingHand)
}

pub fn svg<V: 'static>(style: &SvgStyle) -> ConstrainedBox<V> {
    Svg::new(style.asset.clone())
        .with_color(style.color)
        .constrained()
        .with_width(style.dimensions.width)
        .with_height(style.dimensions.height)
}

#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct IconStyle {
    pub icon: SvgStyle,
    pub container: ContainerStyle,
}

impl IconStyle {
    pub fn width(&self) -> f32 {
        self.icon.dimensions.width
            + self.container.padding.left
            + self.container.padding.right
            + self.container.margin.left
            + self.container.margin.right
    }
}

pub fn icon<V: 'static>(style: &IconStyle) -> Container<V> {
    svg(&style.icon).contained().with_style(style.container)
}

pub fn keystroke_label<V: 'static>(
    label_text: &'static str,
    label_style: &ContainedText,
    keystroke_style: &ContainedText,
    action: Box<dyn Action>,
    cx: &mut ViewContext<V>,
) -> Container<V> {
    // FIXME: Put the theme in it's own global so we can
    // query the keystroke style on our own
    Flex::row()
        .with_child(Label::new(label_text, label_style.text.clone()).contained())
        .with_child(
            KeystrokeLabel::new(
                cx.view_id(),
                action,
                keystroke_style.container,
                keystroke_style.text.clone(),
            )
            .flex_float(),
        )
        .contained()
        .with_style(label_style.container)
}

pub type CopilotCTAButton = Interactive<ContainedText>;

pub fn cta_button<Tag, L, V, F>(
    label: L,
    max_width: f32,
    style: &CopilotCTAButton,
    cx: &mut ViewContext<V>,
    f: F,
) -> MouseEventHandler<V>
where
    Tag: 'static,
    L: Into<Cow<'static, str>>,
    V: 'static,
    F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
{
    MouseEventHandler::new::<Tag, _>(0, cx, |state, _| {
        let style = style.style_for(state);
        Label::new(label, style.text.to_owned())
            .aligned()
            .contained()
            .with_style(style.container)
            .constrained()
            .with_max_width(max_width)
    })
    .on_click(MouseButton::Left, f)
    .with_cursor_style(platform::CursorStyle::PointingHand)
}

#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct ModalStyle {
    close_icon: Interactive<IconStyle>,
    container: ContainerStyle,
    titlebar: ContainerStyle,
    title_text: Interactive<TextStyle>,
    dimensions: Dimensions,
}

impl ModalStyle {
    pub fn dimensions(&self) -> Vector2F {
        self.dimensions.to_vec()
    }
}

pub fn modal<Tag, V, I, D, F>(
    title: I,
    style: &ModalStyle,
    cx: &mut ViewContext<V>,
    build_modal: F,
) -> impl Element<V>
where
    Tag: 'static,
    I: Into<Cow<'static, str>>,
    D: Element<V>,
    V: 'static,
    F: FnOnce(&mut gpui::ViewContext<V>) -> D,
{
    const TITLEBAR_HEIGHT: f32 = 28.;

    Flex::column()
        .with_child(
            Stack::new()
                .with_child(Label::new(
                    title,
                    style
                        .title_text
                        .style_for(&mut MouseState::default())
                        .clone(),
                ))
                .with_child(
                    // FIXME: Get a better tag type
                    MouseEventHandler::new::<Tag, _>(999999, cx, |state, _cx| {
                        let style = style.close_icon.style_for(state);
                        icon(style)
                    })
                    .on_click(platform::MouseButton::Left, move |_, _, cx| {
                        cx.remove_window();
                    })
                    .with_cursor_style(platform::CursorStyle::PointingHand)
                    .aligned()
                    .right(),
                )
                .contained()
                .with_style(style.titlebar)
                .constrained()
                .with_height(TITLEBAR_HEIGHT),
        )
        .with_child(
            build_modal(cx)
                .contained()
                .with_style(style.container)
                .constrained()
                .with_width(style.dimensions().x())
                .with_height(style.dimensions().y() - TITLEBAR_HEIGHT),
        )
        .constrained()
        .with_height(style.dimensions().y())
}
