use crate::border::BorderBuilder;
use crate::decorator::Decorator;
use crate::draw::Draw;
use crate::scroll_viewer::ScrollViewer;
use crate::{
    brush::Brush,
    core::{color::Color, pool::Handle},
    draw::{CommandTexture, DrawingContext},
    message::{
        DecoratorMessage, ListViewMessage, MessageDirection, UiMessage, UiMessageData,
        WidgetMessage,
    },
    scroll_viewer::ScrollViewerBuilder,
    stack_panel::StackPanelBuilder,
    widget::{Widget, WidgetBuilder},
    BuildContext, Control, NodeHandleMapping, Thickness, UiNode, UserInterface, BRUSH_DARK,
    BRUSH_LIGHT,
};
use std::any::Any;
use std::ops::{Deref, DerefMut};

#[derive(Clone)]
pub struct ListView {
    widget: Widget,
    selected_index: Option<usize>,
    item_containers: Vec<Handle<UiNode>>,
    panel: Handle<UiNode>,
    items: Vec<Handle<UiNode>>,
}

crate::define_widget_deref!(ListView);

impl ListView {
    pub fn new(widget: Widget, items: Vec<Handle<UiNode>>) -> Self {
        Self {
            widget,
            selected_index: None,
            item_containers: items,
            panel: Default::default(),
            items: Default::default(),
        }
    }

    pub fn selected(&self) -> Option<usize> {
        self.selected_index
    }

    pub fn item_containers(&self) -> &[Handle<UiNode>] {
        &self.item_containers
    }

    pub fn items(&self) -> &[Handle<UiNode>] {
        &self.items
    }

    fn fix_selection(&self, ui: &UserInterface) {
        // Check if current selection is out-of-bounds.
        if let Some(selected_index) = self.selected_index {
            if selected_index >= self.items.len() {
                let new_selection = if self.items.is_empty() {
                    None
                } else {
                    Some(self.items.len() - 1)
                };

                ui.send_message(ListViewMessage::selection(
                    self.handle,
                    MessageDirection::ToWidget,
                    new_selection,
                ));
            }
        }
    }

    fn sync_decorators(&self, ui: &UserInterface) {
        for (i, &container) in self.item_containers.iter().enumerate() {
            let select = match self.selected_index {
                None => false,
                Some(selected_index) => i == selected_index,
            };
            if let Some(container) = ui.node(container).cast::<ListViewItem>() {
                let mut stack = container.children().to_vec();
                while let Some(handle) = stack.pop() {
                    let node = ui.node(handle);

                    if node.cast::<ListView>().is_some() {
                        // Do nothing.
                    } else if node.cast::<Decorator>().is_some() {
                        ui.send_message(DecoratorMessage::select(
                            handle,
                            MessageDirection::ToWidget,
                            select,
                        ));
                    } else {
                        stack.extend_from_slice(node.children())
                    }
                }
            }
        }
    }
}

#[derive(Clone)]
pub struct ListViewItem {
    widget: Widget,
}

crate::define_widget_deref!(ListViewItem);

impl Control for ListViewItem {
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }

    fn clone_boxed(&self) -> Box<dyn Control> {
        Box::new(self.clone())
    }

    fn draw(&self, drawing_context: &mut DrawingContext) {
        // Emit transparent geometry so item container can be picked by hit test.
        drawing_context.push_rect_filled(&self.widget.screen_bounds(), None);
        drawing_context.commit(
            self.clip_bounds(),
            Brush::Solid(Color::TRANSPARENT),
            CommandTexture::None,
            None,
        );
    }

    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
        self.widget.handle_routed_message(ui, message);

        let parent_list_view =
            self.find_by_criteria_up(ui, |node| node.cast::<ListView>().is_some());

        if let UiMessageData::Widget(WidgetMessage::MouseUp { .. }) = &message.data() {
            if !message.handled() {
                let self_index = ui
                    .node(parent_list_view)
                    .cast::<ListView>()
                    .expect("Parent of ListViewItem must be ListView!")
                    .item_containers
                    .iter()
                    .position(|c| *c == self.handle)
                    .expect("ListViewItem must be used as a child of ListView");

                // Explicitly set selection on parent items control. This will send
                // SelectionChanged message and all items will react.
                ui.send_message(ListViewMessage::selection(
                    parent_list_view,
                    MessageDirection::ToWidget,
                    Some(self_index),
                ));
                message.set_handled(true);
            }
        }
    }
}

impl Control for ListView {
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }

    fn clone_boxed(&self) -> Box<dyn Control> {
        Box::new(self.clone())
    }

    fn resolve(&mut self, node_map: &NodeHandleMapping) {
        node_map.resolve(&mut self.panel);
        node_map.resolve_slice(&mut self.items);
        node_map.resolve_slice(&mut self.item_containers);
    }

    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
        self.widget.handle_routed_message(ui, message);

        if let UiMessageData::ListView(msg) = &message.data() {
            if message.destination() == self.handle()
                && message.direction() == MessageDirection::ToWidget
            {
                match msg {
                    ListViewMessage::Items(items) => {
                        // Remove previous items.
                        for child in ui.node(self.panel).children().to_vec() {
                            ui.send_message(WidgetMessage::remove(
                                child,
                                MessageDirection::ToWidget,
                            ));
                        }

                        // Generate new items.
                        let item_containers = generate_item_containers(&mut ui.build_ctx(), items);

                        for item_container in item_containers.iter() {
                            ui.send_message(WidgetMessage::link(
                                *item_container,
                                MessageDirection::ToWidget,
                                self.panel,
                            ));
                        }

                        self.item_containers = item_containers;
                        self.items = items.clone();

                        self.fix_selection(ui);
                        self.sync_decorators(ui);
                    }
                    &ListViewMessage::AddItem(item) => {
                        let item_container = generate_item_container(&mut ui.build_ctx(), item);

                        ui.send_message(WidgetMessage::link(
                            item_container,
                            MessageDirection::ToWidget,
                            self.panel,
                        ));

                        self.item_containers.push(item_container);
                        self.items.push(item);
                    }
                    &ListViewMessage::SelectionChanged(selection) => {
                        if self.selected_index != selection {
                            self.selected_index = selection;
                            self.sync_decorators(ui);
                            ui.send_message(message.reverse());
                        }
                    }
                    &ListViewMessage::RemoveItem(item) => {
                        if let Some(item_position) = self.items.iter().position(|i| *i == item) {
                            self.items.remove(item_position);
                            self.item_containers.remove(item_position);

                            let container = ui.node(item).parent();

                            ui.send_message(WidgetMessage::remove(
                                container,
                                MessageDirection::ToWidget,
                            ));

                            self.fix_selection(ui);
                            self.sync_decorators(ui);
                        }
                    }
                }
            }
        }
    }

    fn remove_ref(&mut self, handle: Handle<UiNode>) {
        self.item_containers.retain(|i| *i != handle);
    }
}

pub struct ListViewBuilder {
    widget_builder: WidgetBuilder,
    items: Vec<Handle<UiNode>>,
    panel: Option<Handle<UiNode>>,
    scroll_viewer: Option<Handle<UiNode>>,
}

impl ListViewBuilder {
    pub fn new(widget_builder: WidgetBuilder) -> Self {
        Self {
            widget_builder,
            items: Vec::new(),
            panel: None,
            scroll_viewer: None,
        }
    }

    pub fn with_items(mut self, items: Vec<Handle<UiNode>>) -> Self {
        self.items = items;
        self
    }

    pub fn with_items_panel(mut self, panel: Handle<UiNode>) -> Self {
        self.panel = Some(panel);
        self
    }

    pub fn with_scroll_viewer(mut self, sv: Handle<UiNode>) -> Self {
        self.scroll_viewer = Some(sv);
        self
    }

    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
        let item_containers = generate_item_containers(ctx, &self.items);

        let panel = self.panel.unwrap_or_else(|| {
            StackPanelBuilder::new(
                WidgetBuilder::new().with_children(item_containers.iter().cloned()),
            )
            .build(ctx)
        });

        let back = BorderBuilder::new(
            WidgetBuilder::new()
                .with_background(BRUSH_DARK)
                .with_foreground(BRUSH_LIGHT),
        )
        .with_stroke_thickness(Thickness::uniform(1.0))
        .build(ctx);

        let scroll_viewer = self.scroll_viewer.unwrap_or_else(|| {
            ScrollViewerBuilder::new(WidgetBuilder::new().with_margin(Thickness::uniform(3.0)))
                .build(ctx)
        });
        let scroll_viewer_ref = ctx[scroll_viewer]
            .cast_mut::<ScrollViewer>()
            .expect("ListView must have ScrollViewer");
        scroll_viewer_ref.set_content(panel);
        let content_presenter = scroll_viewer_ref.scroll_panel;
        ctx.link(panel, content_presenter);

        ctx.link(scroll_viewer, back);

        let list_box = ListView {
            widget: self.widget_builder.with_child(back).build(),
            selected_index: None,
            item_containers,
            items: self.items,
            panel,
        };

        ctx.add_node(UiNode::new(list_box))
    }
}

fn generate_item_container(ctx: &mut BuildContext, item: Handle<UiNode>) -> Handle<UiNode> {
    let item = ListViewItem {
        widget: WidgetBuilder::new().with_child(item).build(),
    };

    ctx.add_node(UiNode::new(item))
}

fn generate_item_containers(
    ctx: &mut BuildContext,
    items: &[Handle<UiNode>],
) -> Vec<Handle<UiNode>> {
    items
        .iter()
        .map(|&item| generate_item_container(ctx, item))
        .collect()
}
