//! An expandable stack of cards
use std::time::Duration;

use crate::anim;
use crate::theme::THEME;
use crate::widget::icon::{self, Handle};
use crate::widget::{button, column, row, text};
use float_cmp::approx_eq;
use iced::widget;
use iced_core::border::Radius;
use iced_core::id::Id;
use iced_core::layout::Node;
use iced_core::renderer::Quad;
use iced_core::widget::{Tree, tree};
use iced_core::{Border, Element, Event, Length, Shadow, Size, Vector, Widget, window};

const ICON_SIZE: u16 = 16;
const TOP_SPACING: u16 = 4;
const VERTICAL_SPACING: f32 = 8.0;
const PADDING: u16 = 16;
const BG_CARD_VISIBLE_HEIGHT: f32 = 4.0;
const BG_CARD_MARGIN_STEP: f32 = 8.0;

/// get an expandable stack of cards
#[allow(clippy::too_many_arguments)]
pub fn cards<'a, Message, F, G>(
    id: widget::Id,
    card_inner_elements: Vec<Element<'a, Message, crate::Theme, crate::Renderer>>,
    on_clear_all: Message,
    on_show_more: Option<F>,
    on_activate: Option<G>,
    show_more_label: &'a str,
    show_less_label: &'a str,
    clear_all_label: &'a str,
    show_less_icon: Option<Handle>,
    expanded: bool,
) -> Cards<'a, Message, crate::Renderer>
where
    Message: 'static + Clone,
    F: 'a + Fn(bool) -> Message,
    G: 'a + Fn(usize) -> Message,
{
    Cards::new(
        id,
        card_inner_elements,
        on_clear_all,
        on_show_more,
        on_activate,
        show_more_label,
        show_less_label,
        clear_all_label,
        show_less_icon,
        expanded,
    )
}

impl<'a, Message, Renderer> Cards<'a, Message, Renderer>
where
    Renderer: iced_core::text::Renderer,
{
    fn fully_expanded(&self, t: f32) -> bool {
        self.expanded && self.elements.len() > 1 && self.can_show_more && approx_eq!(f32, t, 1.0)
    }

    fn fully_unexpanded(&self, t: f32) -> bool {
        self.elements.len() == 1
            || (!self.expanded && (!self.can_show_more || approx_eq!(f32, t, 0.0)))
    }
}

/// An expandable stack of cards.
#[allow(missing_debug_implementations)]
pub struct Cards<'a, Message, Renderer = crate::Renderer>
where
    Renderer: iced_core::text::Renderer,
{
    id: Id,
    show_less_button: Element<'a, Message, crate::Theme, Renderer>,
    clear_all_button: Element<'a, Message, crate::Theme, Renderer>,
    elements: Vec<Element<'a, Message, crate::Theme, Renderer>>,
    expanded: bool,
    can_show_more: bool,
    width: Length,
    anim_multiplier: f32,
    duration: Duration,
}

impl<'a, Message> Cards<'a, Message, crate::Renderer>
where
    Message: Clone + 'static,
{
    /// Get an expandable stack of cards
    #[allow(clippy::too_many_arguments)]
    pub fn new<F, G>(
        id: widget::Id,
        card_inner_elements: Vec<Element<'a, Message, crate::Theme, crate::Renderer>>,
        on_clear_all: Message,
        on_show_more: Option<F>,
        on_activate: Option<G>,
        show_more_label: &'a str,
        show_less_label: &'a str,
        clear_all_label: &'a str,
        show_less_icon: Option<Handle>,
        expanded: bool,
    ) -> Self
    where
        F: 'a + Fn(bool) -> Message,
        G: 'a + Fn(usize) -> Message,
    {
        let can_show_more = card_inner_elements.len() > 1 && on_show_more.is_some();

        Self {
            can_show_more,
            id,
            show_less_button: {
                let mut show_less_children = Vec::with_capacity(3);
                if let Some(source) = show_less_icon {
                    show_less_children.push(icon::icon(source).size(ICON_SIZE).into());
                }
                show_less_children.push(text::body(show_less_label).width(Length::Shrink).into());
                show_less_children.push(
                    icon::from_name("pan-up-symbolic")
                        .size(ICON_SIZE)
                        .icon()
                        .into(),
                );

                let button_content = row::with_children(show_less_children)
                    .align_y(iced_core::Alignment::Center)
                    .spacing(TOP_SPACING)
                    .width(Length::Shrink);

                Element::from(
                    button::custom(button_content)
                        .class(crate::theme::Button::Text)
                        .width(Length::Shrink)
                        .on_press_maybe(on_show_more.as_ref().map(|f| f(false)))
                        .padding([PADDING / 2, PADDING]),
                )
            },
            clear_all_button: Element::from(
                button::custom(text(clear_all_label))
                    .class(crate::theme::Button::Text)
                    .width(Length::Shrink)
                    .on_press(on_clear_all)
                    .padding([PADDING / 2, PADDING]),
            ),
            elements: card_inner_elements
                .into_iter()
                .enumerate()
                .map(|(i, w)| {
                    let custom_content = if i == 0 && !expanded && can_show_more {
                        column::with_capacity(2)
                            .push(w)
                            .push(text::caption(show_more_label))
                            .spacing(VERTICAL_SPACING)
                            .align_x(iced_core::Alignment::Center)
                            .into()
                    } else {
                        w
                    };

                    let b = crate::iced::widget::button(custom_content)
                        .class(crate::theme::iced::Button::Card)
                        .padding(PADDING);
                    if i == 0 && !expanded && can_show_more {
                        b.on_press_maybe(on_show_more.as_ref().map(|f| f(true)))
                    } else {
                        b.on_press_maybe(on_activate.as_ref().map(|f| f(i)))
                    }
                    .into()
                })
                // we will set the width of the container to shrink, then when laying out the top bar
                // we will set the fill limit to the max of the shrink top bar width and the max shrink width of the
                // cards
                .collect(),
            width: Length::Shrink,
            anim_multiplier: 1.0,
            expanded,
            duration: Duration::from_millis(200),
        }
    }

    ///  Set the width of the cards stack
    #[must_use]
    pub fn width(mut self, width: Length) -> Self {
        self.width = width;
        self
    }

    #[must_use]
    /// The default animation time is 100ms, to speed up the toggle
    /// animation use a value less than 1.0, and to slow down the
    /// animation use a value greater than 1.0.
    pub fn anim_multiplier(mut self, multiplier: f32) -> Self {
        self.anim_multiplier = multiplier;
        self
    }

    pub fn duration(mut self, dur: Duration) -> Self {
        self.duration = dur;
        self
    }

    pub fn id(mut self, id: Id) -> Self {
        self.id = id;
        self
    }
}

impl<'a, Message, Renderer> Widget<Message, crate::Theme, Renderer> for Cards<'a, Message, Renderer>
where
    Message: 'a + Clone,
    Renderer: 'a + iced_core::Renderer + iced_core::text::Renderer,
{
    fn children(&self) -> Vec<Tree> {
        [&self.show_less_button, &self.clear_all_button]
            .iter()
            .map(|w| Tree::new(w.as_widget()))
            .chain(self.elements.iter().map(|w| Tree::new(w.as_widget())))
            .collect()
    }

    fn diff(&mut self, tree: &mut Tree) {
        let mut children: Vec<_> = vec![
            self.show_less_button.as_widget_mut(),
            self.clear_all_button.as_widget_mut(),
        ]
        .into_iter()
        .chain(
            self.elements
                .iter_mut()
                .map(iced_core::Element::as_widget_mut),
        )
        .collect();

        tree.diff_children(children.as_mut_slice());
    }

    #[allow(clippy::too_many_lines)]
    fn layout(
        &mut self,
        tree: &mut Tree,
        renderer: &Renderer,
        limits: &iced_core::layout::Limits,
    ) -> iced_core::layout::Node {
        let my_state = tree.state.downcast_ref::<State>();

        let mut children = Vec::with_capacity(1 + self.elements.len());
        let mut size = Size::new(0.0, 0.0);
        let tree_children = &mut tree.children;
        let count = self.elements.len();
        if self.elements.is_empty() {
            return Node::with_children(Size::new(1., 1.), children);
        }
        let s = anim::smootherstep(my_state.anim.t(self.duration, self.expanded));
        let fully_expanded: bool = self.fully_expanded(s);
        let fully_unexpanded: bool = self.fully_unexpanded(s);

        let show_less = &mut self.show_less_button;
        let clear_all = &mut self.clear_all_button;

        let show_less_node = if self.can_show_more {
            show_less
                .as_widget_mut()
                .layout(&mut tree_children[0], renderer, limits)
        } else {
            Node::new(Size::default())
        };
        let clear_all_node =
            clear_all
                .as_widget_mut()
                .layout(&mut tree_children[1], renderer, limits);
        size.width += show_less_node.size().width + clear_all_node.size().width;

        let custom_limits = limits.min_width(size.width);
        for (c, t) in self.elements.iter_mut().zip(tree_children[2..].iter_mut()) {
            let card_node = c.as_widget_mut().layout(t, renderer, &custom_limits);
            size.width = size.width.max(card_node.size().width);
        }

        if fully_expanded {
            let show_less = &mut self.show_less_button;
            let clear_all = &mut self.clear_all_button;

            let show_less_node = if self.can_show_more {
                show_less
                    .as_widget_mut()
                    .layout(&mut tree_children[0], renderer, limits)
            } else {
                Node::new(Size::default())
            };
            let clear_all_node = if self.can_show_more {
                let mut n =
                    clear_all
                        .as_widget_mut()
                        .layout(&mut tree_children[1], renderer, limits);
                let clear_all_node_size = n.size();
                n = clear_all_node
                    .translate(Vector::new(size.width - clear_all_node_size.width, 0.0));
                size.height += show_less_node.size().height.max(n.size().height) + VERTICAL_SPACING;
                n
            } else {
                Node::new(Size::default())
            };

            children.push(show_less_node);
            children.push(clear_all_node);
        }

        let custom_limits = limits
            .min_width(size.width)
            .max_width(size.width)
            .width(Length::Fixed(size.width));

        for (i, (c, t)) in self
            .elements
            .iter_mut()
            .zip(tree_children[2..].iter_mut())
            .enumerate()
        {
            let progress = s * size.height;
            let card_node = c
                .as_widget_mut()
                .layout(t, renderer, &custom_limits)
                .translate(Vector::new(0.0, progress));

            size.height = size.height.max(progress + card_node.size().height);

            children.push(card_node);

            if fully_unexpanded {
                let width = children.last().unwrap().bounds().width;

                // push the background card nodes
                for i in 1..self.elements.len().min(3) {
                    // height must be 16px for 8px padding
                    // but we only want 4px visible
                    let guard = THEME.lock().unwrap();
                    let radius_xs = guard.cosmic().radius_xs();

                    let margin = f32::from(u8::try_from(i).unwrap()) * BG_CARD_MARGIN_STEP;
                    let node = Node::new(Size::new(width - 2.0 * margin, radius_xs[0] * 2.0))
                        .translate(Vector::new(
                            margin,
                            size.height - radius_xs[0] * 2.0 + BG_CARD_VISIBLE_HEIGHT,
                        ));
                    size.height += BG_CARD_VISIBLE_HEIGHT;
                    children.push(node);
                }
                break;
            }

            if i + 1 < count {
                size.height += VERTICAL_SPACING;
            }
        }

        Node::with_children(size, children)
    }

    fn draw(
        &self,
        state: &iced_core::widget::Tree,
        renderer: &mut Renderer,
        theme: &crate::Theme,
        style: &iced_core::renderer::Style,
        layout: iced_core::Layout<'_>,
        cursor: iced_core::mouse::Cursor,
        viewport: &iced_core::Rectangle,
    ) {
        let my_state = state.state.downcast_ref::<State>();

        // there are 4 cases for drawing
        // 1. empty entries list
        //      Nothing to draw
        // 2. un-expanded
        //      go through the layout, draw the card, the inner card, and the bg cards
        // 3. expanding / unexpanding
        //      go through the layout. draw each card and its inner card
        // 4. expanded =>
        //      go through the layout. draw the top bar, and do all of 3
        // cards may be hovered
        // any buttons may have a hover state as well
        if self.elements.is_empty() {
            return;
        }

        let t = my_state.anim.t(self.duration, self.expanded);
        let fully_unexpanded = self.fully_unexpanded(t);
        let fully_expanded = self.fully_expanded(t);

        let mut layout = layout.children();
        let mut tree_children = state.children.iter();

        if fully_expanded {
            let show_less = &self.show_less_button;
            let clear_all = &self.clear_all_button;

            let show_less_layout = layout.next().unwrap();
            let clear_all_layout = layout.next().unwrap();

            show_less.as_widget().draw(
                tree_children.next().unwrap(),
                renderer,
                theme,
                style,
                show_less_layout,
                cursor,
                viewport,
            );

            clear_all.as_widget().draw(
                tree_children.next().unwrap(),
                renderer,
                theme,
                style,
                clear_all_layout,
                cursor,
                viewport,
            );
        } else {
            _ = tree_children.next();
            _ = tree_children.next();
        }

        let radius_xs = theme.cosmic().radius_xs();
        // Draw first to appear behind
        if fully_unexpanded {
            use crate::widget::card::style::Catalog as _;
            let card_layout = layout.next().unwrap();
            let appearance = theme.default();
            let bg_layout = layout.collect::<Vec<_>>();
            for (i, layout) in (0..2).zip(bg_layout).rev() {
                renderer.fill_quad(
                    Quad {
                        bounds: layout.bounds(),
                        border: Border {
                            radius: Radius::from([0.0, 0.0, radius_xs[2], radius_xs[3]]),
                            ..Default::default()
                        },
                        shadow: Shadow::default(),
                        snap: true,
                    },
                    if i == 0 {
                        appearance.card_1
                    } else {
                        appearance.card_2
                    },
                );
            }
            self.elements[0].as_widget().draw(
                tree_children.next().unwrap(),
                renderer,
                theme,
                style,
                card_layout,
                cursor,
                viewport,
            );
        } else {
            let layout = layout.collect::<Vec<_>>();
            // draw in reverse order so later cards appear behind earlier cards
            for ((inner, layout), c_state) in self
                .elements
                .iter()
                .rev()
                .zip(layout.into_iter().rev())
                .zip(tree_children.rev())
            {
                inner
                    .as_widget()
                    .draw(c_state, renderer, theme, style, layout, cursor, viewport);
            }
        }
    }

    fn update(
        &mut self,
        state: &mut Tree,
        event: &iced_core::Event,
        layout: iced_core::Layout<'_>,
        cursor: iced_core::mouse::Cursor,
        renderer: &Renderer,
        clipboard: &mut dyn iced_core::Clipboard,
        shell: &mut iced_core::Shell<'_, Message>,
        viewport: &iced_core::Rectangle,
    ) {
        if self.elements.is_empty() {
            return;
        }

        if let Event::Window(window::Event::RedrawRequested(_)) = event {
            let state = state.state.downcast_mut::<State>();

            state.anim.anim_done(self.duration);
            if state.anim.last_change.is_some() {
                shell.request_redraw();
                shell.invalidate_layout();
            }
        }

        let my_state = state.state.downcast_ref::<State>();

        let mut layout = layout.children();
        let mut tree_children = state.children.iter_mut();
        let t = my_state.anim.t(self.duration, self.expanded);
        let fully_expanded = self.fully_expanded(t);
        let fully_unexpanded = self.fully_unexpanded(t);
        let show_less_state = tree_children.next();
        let clear_all_state = tree_children.next();

        if fully_expanded {
            let c_layout = layout.next().unwrap();
            let state = show_less_state.unwrap();
            self.show_less_button.as_widget_mut().update(
                state, event, c_layout, cursor, renderer, clipboard, shell, viewport,
            );

            if shell.is_event_captured() {
                return;
            }

            let c_layout = layout.next().unwrap();
            let state = clear_all_state.unwrap();
            self.clear_all_button.as_widget_mut().update(
                state, event, c_layout, cursor, renderer, clipboard, shell, viewport,
            );
        }

        if shell.is_event_captured() {
            return;
        }

        for ((inner, layout), c_state) in self.elements.iter_mut().zip(layout).zip(tree_children) {
            inner.as_widget_mut().update(
                c_state, event, layout, cursor, renderer, clipboard, shell, viewport,
            );
            if shell.is_event_captured() || fully_unexpanded {
                break;
            }
        }
    }

    fn size(&self) -> Size<Length> {
        Size::new(self.width, Length::Shrink)
    }

    fn tag(&self) -> tree::Tag {
        tree::Tag::of::<State>()
    }

    fn state(&self) -> tree::State {
        tree::State::new(State::default())
    }

    fn id(&self) -> Option<Id> {
        Some(self.id.clone())
    }

    fn set_id(&mut self, id: Id) {
        self.id = id;
    }
}

impl<'a, Message> From<Cards<'a, Message>> for Element<'a, Message, crate::Theme, crate::Renderer>
where
    Message: Clone + 'a,
{
    fn from(cards: Cards<'a, Message>) -> Self {
        Self::new(cards)
    }
}

#[derive(Debug, Default)]
pub struct State {
    anim: anim::State,
}
