use chrono::{DateTime, Local, Utc};
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, Context};
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),
    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)) => {
            Some(Event::OpenChannel(server, channel))
        }
        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,
) -> 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,
            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.buffer.nickname.click,
        );

        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| match link {
                message::Link::User(_, 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),
                    )
                }
                message::Link::Url(_) => context_menu::Entry::url_list(
                    false, None, None, false, false, false
                ),
                _ => vec![],
            },
            move |link, entry, length| {
                let link_context = if let Some(user) = link.user() {
                    let current_user =
                        users.and_then(|users| users.resolve(user));

                    Some(Context::User {
                        server,
                        prefix,
                        channel: Some(channel),
                        registry,
                        avatar: context_menu::user_avatar(
                            user, registry, previews,
                        ),
                        user,
                        current_user,
                    })
                } else {
                    link.url().map(|url| Context::Url {
                        url,
                        message: None,
                        selected_reactions: vec![],
                    })
                };

                entry
                    .view(link_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]
}
