use chrono::{DateTime, Local, Utc};
use data::dashboard::BufferAction;
use data::user::ChannelUsers;
use data::{Config, Server, User, isupport, message, target};
use iced::widget::{Scrollable, column, container, row, rule, scrollable};
use iced::{Color, Length, padding};

use super::context_menu::{
    self, ChannelContext, Context, UrlContext, UserContext,
};
use crate::widget::user_display::UserDisplay;
use crate::widget::{Element, double_pass, message_content, selectable_text};
use crate::{Theme, font, theme};

#[derive(Debug, Clone)]
pub enum Event {
    ContextMenu(context_menu::Event),
    OpenChannel(Server, target::Channel, BufferAction),
    OpenUrl(String),
}

#[derive(Debug, Clone)]
pub enum Message {
    ContextMenu(context_menu::Message),
    Link(message::Link),
}

pub fn update(message: Message) -> Option<Event> {
    match message {
        Message::ContextMenu(message) => {
            context_menu::update(message).map(Event::ContextMenu)
        }
        Message::Link(message::Link::Channel(
            server,
            channel,
            buffer_action,
        )) => Some(Event::OpenChannel(server, channel, buffer_action?)),
        Message::Link(message::Link::Url(url)) => Some(Event::OpenUrl(url)),
        Message::Link(message::Link::User(_, user)) => {
            Some(Event::ContextMenu(context_menu::Event::InsertNickname(
                user.nickname().to_owned(),
            )))
        }
        Message::Link(message::Link::GoToMessage(..))
        | Message::Link(message::Link::ExpandMessage(..))
        | Message::Link(message::Link::ContractMessage(..)) => None,
    }
}

pub fn view<'a>(
    server: &'a Server,
    chantypes: &'a [char],
    casemapping: isupport::CaseMap,
    prefix: &'a [isupport::PrefixMap],
    channel: &'a target::Channel,
    content: &'a message::Content,
    who: Option<&'a User>,
    time: Option<&'a DateTime<Utc>>,
    max_lines: u16,
    users: Option<&'a ChannelUsers>,
    our_user: Option<&'a User>,
    config: &'a Config,
    theme: &'a Theme,
    registry: &'a dyn data::metadata::Registry,
    previews: &'a data::preview::Collection,
    channel_is_focused: impl Fn(&Server, &target::Channel) -> bool + Copy + 'a,
    channel_is_open: impl Fn(&Server, &target::Channel) -> bool + Copy + 'a,
) -> Element<'a, Message> {
    let set_by = who.and_then(|user| {
        let user_in_channel = users.and_then(|users| users.resolve(user));

        let user_display = UserDisplay::new(
            user,
            config.buffer.nickname.show_access_levels,
            config.buffer.nickname.show_bot_icon,
            false,
            registry,
            &config.display.nickname,
            None,
            config.display.truncation_character,
            None,
            true,
        );

        // If user is in channel, we return user_context component.
        // Otherwise selectable_text component.
        let content = context_menu::user(
            user_display.into_element(
                user, false, false, None, None, false, true, theme, config,
            ),
            server,
            prefix,
            Some(channel),
            registry,
            previews,
            user,
            user_in_channel,
            our_user,
            config,
            theme,
            &config.actions.buffer.click_username,
        );

        Some(
            Element::new(row![
                selectable_text("set by ")
                    .font_maybe(theme::font_style::topic(theme).map(font::get))
                    .style(theme::selectable_text::topic),
                content,
                selectable_text(format!(
                    " at {}",
                    time?.with_timezone(&Local).to_rfc2822()
                ))
                .font_maybe(theme::font_style::topic(theme).map(font::get))
                .style(theme::selectable_text::topic),
            ])
            .map(Message::ContextMenu),
        )
    });

    let content = column![
        message_content::with_context(
            content,
            &[],
            server,
            registry,
            chantypes,
            casemapping,
            theme,
            Message::Link,
            None,
            theme::selectable_text::topic,
            theme::font_style::topic,
            Option::<fn(Color) -> Color>::None,
            move |link| {
                context_menu::Entry::link_list(
                    link,
                    Some(|user| {
                        let user_in_channel =
                            users.and_then(|users| users.resolve(user));

                        context_menu::Entry::user_list(
                            true,
                            user_in_channel,
                            our_user,
                            config.file_transfer.enabled,
                            context_menu::has_user_metadata(
                                user, registry, config,
                            ),
                            false,
                        )
                    }),
                    Some(|_| {
                        context_menu::Entry::url_list(
                            false, None, None, false, false, false,
                        )
                    }),
                    Some(|server, channel| {
                        context_menu::Entry::channel_list(
                            channel_is_open(server, channel),
                            channel_is_focused(server, channel),
                        )
                    }),
                )
            },
            move |link, entry, length| {
                let context = Context::link(
                    link,
                    Some(|user| {
                        let current_user =
                            users.and_then(|users| users.resolve(user));

                        UserContext {
                            server,
                            prefix,
                            channel: Some(channel),
                            registry,
                            avatar: context_menu::user_avatar(
                                user, registry, previews,
                            ),
                            user,
                            current_user,
                        }
                    }),
                    Some(|url| UrlContext {
                        url,
                        message: None,
                        selected_reactions: vec![],
                    }),
                    Some(|server, channel| ChannelContext {
                        server,
                        channel,
                        is_open: channel_is_open(server, channel),
                    }),
                );

                entry
                    .view(context, length, config, theme)
                    .map(Message::ContextMenu)
            },
            None,
            config,
        ),
        set_by
    ];

    let scrollable = Scrollable::new(
        container(content).width(Length::Fill).padding(padding()),
    )
    .direction(scrollable::Direction::Vertical(
        scrollable::Scrollbar::new().width(1).scroller_width(1),
    ))
    .style(theme::scrollable::hidden);

    // Use double pass to limit layout to `max_lines` of text
    column![
        double_pass(
            container(column((0..max_lines).map(|_| "".into())))
                .width(Length::Fill)
                .padding(padding()),
            column![container(scrollable)].width(Length::Fill),
        ),
        container(rule::horizontal(1))
            .width(Length::Fill)
            .padding([0, 11])
    ]
    .padding(padding::top(4))
    .spacing(8)
    .into()
}

fn padding() -> [u16; 2] {
    [0, 8]
}
