//! Widget used to display timestamped error and warning reports in an overlay.

use iced::advanced::{layout, overlay};
use iced::{mouse, Alignment, Element, Event, Point, Rectangle, Size, Vector};
use iced_core::widget::{Operation, Tree};
use iced_core::{renderer, Clipboard, Layout, Shell};
use jiff::Timestamp;

#[derive(Clone, Copy, Debug)]
pub enum Level {
    Info,
    Warning,
    Error,
}

#[derive(Debug)]
pub struct Entry {
    pub level: Level,
    pub msg: String,
    pub time: Timestamp,
    pub is_read: bool,
}

#[derive(Debug, Default)]
pub struct Container {
    pub hidden: bool,
    /// Entries in insert order. For display use reverse iterator to start with
    /// most recent ones.
    pub entries: Vec<Entry>,
}

struct Widget<'a, Msg, Theme, Renderer> {
    pub overlay: Element<'a, Msg, Theme, Renderer>,
    pub overlaid: Element<'a, Msg, Theme, Renderer>,
}

pub fn view<'a, Msg, Theme, Renderer, FV>(
    container: &'a Container,
    overlaid: Element<'a, Msg, Theme, Renderer>,
    view_container: FV,
) -> Element<'a, Msg, Theme, Renderer>
where
    Msg: 'a,
    Theme: 'a,
    Renderer: 'a + iced::advanced::Renderer,
    FV: Fn(&'a Container) -> Element<'a, Msg, Theme, Renderer>,
{
    if container.hidden || container.entries.is_empty() {
        overlaid
    } else {
        let overlay = view_container(container);
        Element::new(Widget::<'a, Msg, _, _> { overlay, overlaid })
    }
}

impl<Msg, Theme, Renderer> iced::advanced::Widget<Msg, Theme, Renderer>
    for Widget<'_, Msg, Theme, Renderer>
where
    Renderer: iced::advanced::Renderer,
{
    fn size(&self) -> iced::Size<iced::Length> {
        self.overlaid.as_widget().size()
    }

    fn layout(
        &mut self,
        tree: &mut iced_core::widget::Tree,
        renderer: &Renderer,
        limits: &iced_core::layout::Limits,
    ) -> iced_core::layout::Node {
        self.overlaid.as_widget_mut().layout(
            &mut tree.children[0],
            renderer,
            limits,
        )
    }

    fn update(
        &mut self,
        state: &mut Tree,
        event: &Event,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        renderer: &Renderer,
        clipboard: &mut dyn Clipboard,
        shell: &mut Shell<'_, Msg>,
        viewport: &Rectangle,
    ) {
        self.overlaid.as_widget_mut().update(
            &mut state.children[0],
            event,
            layout,
            cursor,
            renderer,
            clipboard,
            shell,
            viewport,
        );
    }

    fn draw(
        &self,
        tree: &iced_core::widget::Tree,
        renderer: &mut Renderer,
        theme: &Theme,
        style: &iced_core::renderer::Style,
        layout: iced_core::Layout<'_>,
        cursor: iced_core::mouse::Cursor,
        viewport: &iced::Rectangle,
    ) {
        self.overlaid.as_widget().draw(
            &tree.children[0],
            renderer,
            theme,
            style,
            layout,
            cursor,
            viewport,
        )
    }

    fn children(&self) -> Vec<Tree> {
        vec![Tree::new(&self.overlaid), Tree::new(&self.overlay)]
    }

    fn diff(&self, tree: &mut Tree) {
        tree.diff_children(&[&self.overlaid, &self.overlay]);
    }

    fn operate(
        &mut self,
        state: &mut Tree,
        layout: Layout<'_>,
        renderer: &Renderer,
        operation: &mut dyn Operation,
    ) {
        operation.traverse(&mut |operation| {
            self.overlaid.as_widget_mut().operate(
                &mut state.children[0],
                layout,
                renderer,
                operation,
            );
        });
    }

    fn mouse_interaction(
        &self,
        state: &Tree,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        viewport: &Rectangle,
        renderer: &Renderer,
    ) -> mouse::Interaction {
        self.overlaid.as_widget().mouse_interaction(
            &state.children[0],
            layout,
            cursor,
            viewport,
            renderer,
        )
    }

    fn overlay<'a>(
        &'a mut self,
        tree: &'a mut Tree,
        layout: Layout<'a>,
        renderer: &Renderer,
        viewport: &Rectangle,
        translation: Vector,
    ) -> Option<overlay::Element<'a, Msg, Theme, Renderer>> {
        let (content_tree, toast_tree) = tree.children.split_at_mut(1);
        let content_overlay = self.overlaid.as_widget_mut().overlay(
            &mut content_tree[0],
            layout,
            renderer,
            viewport,
            translation,
        );

        let toast_overlay = Overlay {
            overlay: &mut self.overlay,
            tree: &mut toast_tree[0],
            position: layout.bounds().position() + translation,
            bounds: layout.bounds(),
        };
        let toast_overlay = overlay::Element::new(Box::new(toast_overlay));

        let overlays = content_overlay
            .into_iter()
            .chain(std::iter::once(toast_overlay))
            .collect::<Vec<_>>();

        Some(overlay::Group::with_children(overlays).overlay())
    }
}

struct Overlay<'a, 'b, Msg, Theme, Renderer> {
    overlay: &'b mut Element<'a, Msg, Theme, Renderer>,
    tree: &'b mut Tree,
    position: Point,
    bounds: Rectangle,
}

impl<'a, Msg, Theme, Renderer> overlay::Overlay<Msg, Theme, Renderer>
    for Overlay<'a, '_, Msg, Theme, Renderer>
where
    Renderer: iced::advanced::Renderer,
{
    fn layout(&mut self, renderer: &Renderer, _bounds: Size) -> layout::Node {
        let bounds = self.bounds.size();
        let limits = layout::Limits::new(Size::ZERO, bounds);
        layout::Node::with_children(
            bounds,
            vec![self
                .overlay
                .as_widget_mut()
                .layout(self.tree, renderer, &limits)
                .align(
                    // TODO parameterize
                    Alignment::End,
                    Alignment::End,
                    bounds,
                )],
        )
        .translate([self.position.x, self.position.y])
    }

    fn draw(
        &self,
        renderer: &mut Renderer,
        theme: &Theme,
        style: &renderer::Style,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
    ) {
        let viewport = layout.bounds();

        self.overlay.as_widget().draw(
            self.tree,
            renderer,
            theme,
            style,
            layout.children().next().unwrap(),
            cursor,
            &viewport,
        )
    }

    fn update(
        &mut self,
        event: &Event,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        renderer: &Renderer,
        clipboard: &mut dyn Clipboard,
        shell: &mut Shell<'_, Msg>,
    ) {
        let viewport = layout.bounds();

        self.overlay.as_widget_mut().update(
            self.tree,
            event,
            layout.children().next().unwrap(),
            cursor,
            renderer,
            clipboard,
            shell,
            &viewport,
        );
    }

    fn operate(
        &mut self,
        layout: Layout<'_>,
        renderer: &Renderer,
        operation: &mut dyn Operation,
    ) {
        operation.traverse(&mut |operation| {
            self.overlay.as_widget_mut().operate(
                self.tree,
                layout.children().next().unwrap(),
                renderer,
                operation,
            );
        });
    }

    fn mouse_interaction(
        &self,
        layout: Layout<'_>,
        cursor: mouse::Cursor,
        renderer: &Renderer,
    ) -> mouse::Interaction {
        let viewport = layout.bounds();

        self.overlay.as_widget().mouse_interaction(
            self.tree,
            layout.children().next().unwrap(),
            cursor,
            &viewport,
            renderer,
        )
    }
}

pub fn entries_to_string(report: &Container) -> String {
    use std::fmt::Write;

    let mut output = "".to_string();
    report.entries.iter().rev().for_each(
        |Entry {
             level,
             msg,
             time,
             is_read: _,
         }| {
            let level = match level {
                Level::Info => "INFO",
                Level::Warning => "WARNING",
                Level::Error => "ERROR",
            };
            writeln!(&mut output, "{time} - {level}: {msg}").unwrap();
        },
    );
    output
}

pub fn show_info(report: &mut Container, msg: String) {
    show_log(report, msg, Level::Info)
}

pub fn show_err(report: &mut Container, msg: String) {
    show_log(report, msg, Level::Error)
}

pub fn show_log(report: &mut Container, msg: String, level: Level) {
    report.hidden = false;
    report.entries.push(Entry {
        level,
        msg,
        time: Timestamp::now(),
        is_read: false,
    });
}