use maud::{Markup, Render, html};

/// The navigation: the ways in for a visitor, and the way back to oneself
/// and out again for someone signed in.
pub(super) struct Nav<'a> {
    pub(super) viewer: Option<&'a str>,
}

impl Render for Nav<'_> {
    fn render(&self) -> Markup {
        let links = match self.viewer {
            Some(name) => html! {
                (FrogueUsername(name))
                form class="session" method="post" action="/logout" {
                    button type="submit" { "Log out" }
                }
            },
            None => html! {
                a href="/login" { "Log in" }
                a href="/register" { "Register" }
            },
        };

        html! { nav { (links) } }
    }
}

/// An error message, rendered only when there is one.
pub(super) struct Complaint<'a>(pub(super) Option<&'a str>);

impl Render for Complaint<'_> {
    fn render(&self) -> Markup {
        html! {
            @if let Some(message) = self.0 {
                p class="error" { (message) }
            }
        }
    }
}

/// A repository heading: the owner, a slash, the slug, and the visibility indicator.
pub(super) struct RepositoryHeading<'a> {
    pub(super) owner: &'a str,
    pub(super) slug: &'a str,
    pub(super) visibility: &'a str,
}

impl Render for RepositoryHeading<'_> {
    fn render(&self) -> Markup {
        html! {
            h2 {
                (FrogueUsername(self.owner)) "/" (self.slug)
                " "
                (FrogueRepositoryVisibility(self.visibility))
            }
        }
    }
}

/// A text input field within a label.
pub(super) struct TextField<'a> {
    pub(super) label: Option<&'a str>,
    pub(super) name: &'a str,
    pub(super) input_type: &'a str,
    pub(super) value: Option<&'a str>,
    pub(super) required: bool,
    pub(super) maxlength: u32,
}

impl Render for TextField<'_> {
    fn render(&self) -> Markup {
        html! {
            label {
                @if let Some(label_text) = self.label {
                    (label_text)
                }
                input type=(self.input_type) name=(self.name) required[self.required]
                    value=(self.value.unwrap_or("")) maxlength=(self.maxlength);
            }
        }
    }
}

/// A password input field within a label.
///
/// Password fields have no value attribute, so the browser never pre-fills
/// them from prior submissions.
pub(super) struct PasswordField<'a> {
    pub(super) label: &'a str,
    pub(super) name: &'a str,
    pub(super) required: bool,
}

impl Render for PasswordField<'_> {
    fn render(&self) -> Markup {
        html! {
            label {
                (self.label)
                input type="password" name=(self.name) required[self.required];
            }
        }
    }
}

/// A textarea field within a label.
pub(super) struct TextArea<'a> {
    pub(super) label: Option<&'a str>,
    pub(super) name: &'a str,
    pub(super) rows: u32,
    pub(super) value: Option<&'a str>,
    pub(super) required: bool,
}

impl Render for TextArea<'_> {
    fn render(&self) -> Markup {
        html! {
            label {
                @if let Some(label_text) = self.label {
                    (label_text)
                }
                textarea name=(self.name) rows=(self.rows) required[self.required] {
                    (self.value.unwrap_or(""))
                }
            }
        }
    }
}

/// A frogue username, with a link to the user and prefixed by '~'
pub(super) struct FrogueUsername<'a>(pub(super) &'a str);

impl Render for FrogueUsername<'_> {
    fn render(&self) -> Markup {
        let username = self.0;
        html! {
            a class="frogue-username" href={"/~" (username)} {"~" (username)}
        }
    }
}

/// A comment author, local or remote: local gets a `~username` link, remote gets
/// a link to their actor URL plus a badge saying so.
pub(super) struct AuthorName<'a>(pub(super) &'a crate::comments::CommentAuthor);

impl Render for AuthorName<'_> {
    fn render(&self) -> Markup {
        let author = self.0;
        if author.is_remote {
            html! {
                @if let Some(url) = &author.actor_url {
                    a href=(url) { (author.name) }
                } @else {
                    (author.name)
                }
                " "
                span class="remote-badge" { "remote" }
            }
        } else {
            html! { (FrogueUsername(&author.name)) }
        }
    }
}

/// A frogue repository visibility indicator
pub(super) struct FrogueRepositoryVisibility<'a>(pub(super) &'a str);

impl Render for FrogueRepositoryVisibility<'_> {
    fn render(&self) -> Markup {
        html! {
            @if self.0 == "private" {
                dfn title="private" class="visibility" {
                    "🔒"
                }
            }
        }
    }
}

/// Which of a repository's pages is on show, for the tab bar that says so.
///
/// A tab only exists once the page behind it does: there is no value in a
/// tab that leads nowhere.
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum RepositoryTab {
    Summary,
    Tickets,
    Patches,
    Refs,
    Commits,
    Compare,
    Browse,
    Settings,
}

/// The navigation across a repository's own pages, with the current one
/// marked so a visitor can tell where they are in the repository.
pub(super) struct RepoNav<'a> {
    pub(super) owner: &'a str,
    pub(super) slug: &'a str,
    /// The branch the browse tab addresses: the tree page needs a default,
    /// and the repository opens on it.
    pub(super) default_branch: &'a str,
    pub(super) current: RepositoryTab,
    pub(super) is_owner: bool,
}

impl Render for RepoNav<'_> {
    fn render(&self) -> Markup {
        let base = format!("/~{}/{}", self.owner, self.slug);
        html! {
            nav class="repository-tabs" {
                (repository_tab(&base, "Summary", self.current == RepositoryTab::Summary))
                (repository_tab(
                    &format!("{base}/tickets"),
                    "Tickets",
                    self.current == RepositoryTab::Tickets,
                ))
                (repository_tab(
                    &format!("{base}/patches"),
                    "Patches",
                    self.current == RepositoryTab::Patches,
                ))
                (repository_tab(&format!("{base}/refs"), "Refs", self.current == RepositoryTab::Refs))
                (repository_tab(
                    &format!("{base}/commits/{}", self.default_branch),
                    "Commits",
                    self.current == RepositoryTab::Commits,
                ))
                (repository_tab(
                    &format!("{base}/compare"),
                    "Compare",
                    self.current == RepositoryTab::Compare,
                ))
                (repository_tab(
                    &format!("{base}/tree/{}", self.default_branch),
                    "Browse",
                    self.current == RepositoryTab::Browse,
                ))
                @if self.is_owner {
                    (repository_tab(
                        &format!("{base}/settings"),
                        "Settings",
                        self.current == RepositoryTab::Settings,
                    ))
                }
            }
        }
    }
}

/// A repository's full header: the heading with owner/slug/visibility,
/// followed by the navigation tabs.
///
/// This combines RepositoryHeading and RepoNav for pages that need both,
/// which is most repository pages.
pub(super) struct RepositoryChrome<'a> {
    pub(super) owner: &'a str,
    pub(super) slug: &'a str,
    pub(super) visibility: &'a str,
    pub(super) default_branch: &'a str,
    pub(super) current_tab: RepositoryTab,
    pub(super) is_owner: bool,
}

impl Render for RepositoryChrome<'_> {
    fn render(&self) -> Markup {
        html! {
            (RepositoryHeading {
                owner: self.owner,
                slug: self.slug,
                visibility: self.visibility,
            })
            (RepoNav {
                owner: self.owner,
                slug: self.slug,
                default_branch: self.default_branch,
                current: self.current_tab,
                is_owner: self.is_owner,
            })
        }
    }
}

/// A delete form: a POST form with a submit button.
///
/// Used for deleting tickets, SSH keys, and labels. The form and button may
/// carry optional classes for styling. An optional `confirm` message triggers
/// a browser confirmation dialog before submission.
pub(super) struct DeleteForm<'a> {
    pub(super) action: String,
    pub(super) form_class: Option<&'a str>,
    pub(super) button_class: Option<&'a str>,
    pub(super) confirm: Option<&'a str>,
}

impl Render for DeleteForm<'_> {
    fn render(&self) -> Markup {
        let button = if let Some(button_class) = self.button_class {
            html! { button type="submit" class=(button_class) { "Delete" } }
        } else {
            html! { button type="submit" { "Delete" } }
        };

        match (&self.form_class, self.confirm) {
            (Some(fc), Some(msg)) => {
                let onsubmit = format!("return confirm('{}');", msg.replace('\'', "\\'"));
                html! { form method="post" action=(&self.action) class=(fc) onsubmit=(onsubmit) { (button) } }
            }
            (Some(fc), None) => {
                html! { form method="post" action=(&self.action) class=(fc) { (button) } }
            }
            (None, Some(msg)) => {
                let onsubmit = format!("return confirm('{}');", msg.replace('\'', "\\'"));
                html! { form method="post" action=(&self.action) onsubmit=(onsubmit) { (button) } }
            }
            (None, None) => {
                html! { form method="post" action=(&self.action) { (button) } }
            }
        }
    }
}

/// One tab of a repository's navigation.
///
/// `aria-current` is written only when it is true: an attribute present but
/// empty says nothing, and a screen reader would pass over it. The
/// alternative - a conditional attribute in maud - can only carry the
/// attribute's own name as its value, which is not a value `aria-current`
/// understands.
pub(super) fn repository_tab(href: &str, label: &str, current: bool) -> Markup {
    if current {
        html! { a href=(href) aria-current="page" { (label) } }
    } else {
        html! { a href=(href) { (label) } }
    }
}

/// A reference selection dropdown with branches, tags, and patches.
pub(super) struct RefSelect<'a> {
    pub(super) name: &'a str,
    pub(super) label: &'a str,
    pub(super) branches: &'a [super::repositories::RefSummary],
    pub(super) tags: &'a [super::repositories::RefSummary],
    pub(super) patches: &'a [super::compare::PatchSummary],
    /// The value to mark as selected. If it matches no listed option, a
    /// synthetic option is added at the top so the dropdown reflects the
    /// current state rather than silently falling back to the first branch.
    pub(super) selected: &'a str,
}

impl Render for RefSelect<'_> {
    fn render(&self) -> Markup {
        let listed = self.branches.iter().any(|b| b.name == self.selected)
            || self.tags.iter().any(|t| t.name == self.selected)
            || self
                .patches
                .iter()
                .any(|p| format!("patches/{}", p.number) == self.selected);

        html! {
            label {
                (self.label)
                select name=(self.name) {
                    @if !listed && !self.selected.is_empty() {
                        option value=(self.selected) selected { (self.selected) }
                    }
                    @if !self.branches.is_empty() {
                        optgroup label="Branches" {
                            @for branch in self.branches {
                                option value=(branch.name) selected[branch.name == self.selected] {
                                    (branch.name)
                                }
                            }
                        }
                    }
                    @if !self.tags.is_empty() {
                        optgroup label="Tags" {
                            @for tag in self.tags {
                                option value=(tag.name) selected[tag.name == self.selected] {
                                    (tag.name)
                                }
                            }
                        }
                    }
                    @if !self.patches.is_empty() {
                        optgroup label="Patches" {
                            @for patch in self.patches {
                                option value=(format!("patches/{}", patch.number)) selected[format!("patches/{}", patch.number) == self.selected] {
                                    "#" (patch.number) " " (patch.summary)
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

/// A filter link that can be marked as selected, for ticket/patch filters.
///
/// When selected, adds a "selected" class and aria-current="true".
pub(super) struct FilterLink<'a> {
    pub(super) href: &'a str,
    pub(super) label: &'a str,
    pub(super) base_class: &'a str,
    pub(super) selected: bool,
}

impl Render for FilterLink<'_> {
    fn render(&self) -> Markup {
        if self.selected {
            html! {
                a class=(format!("{} selected", self.base_class)) href=(self.href) aria-current="true" { (self.label) }
            }
        } else {
            html! { a class=(self.base_class) href=(self.href) { (self.label) } }
        }
    }
}

/// A reusable pagination component.
///
/// Renders Previous/Next links based on the current page and whether more items exist.
/// The base_url should include the path but not the query string.
pub(super) struct Pagination {
    pub current_page: u64,
    pub has_next: bool,
    pub base_url: String,
}

impl Render for Pagination {
    fn render(&self) -> Markup {
        html! {
            nav class="pagination" {
                @if self.current_page > 0 {
                    a href=(format!("{}?page={}", self.base_url, self.current_page - 1)) class="pagination-prev" { "← Previous" }
                }
                @if self.has_next {
                    a href=(format!("{}?page={}", self.base_url, self.current_page + 1)) class="pagination-next" { "Next →" }
                }
            }
        }
    }
}

pub struct DateTime {
    dt: chrono::DateTime<chrono::FixedOffset>,
}

impl From<&chrono::DateTime<chrono::FixedOffset>> for DateTime {
    fn from(value: &chrono::DateTime<chrono::FixedOffset>) -> Self {
        Self { dt: value.clone() }
    }
}

impl From<&chrono::DateTime<chrono::Utc>> for DateTime {
    fn from(value: &chrono::DateTime<chrono::Utc>) -> Self {
        Self {
            dt: value.fixed_offset(),
        }
    }
}

impl Render for DateTime {
    fn render(&self) -> Markup {
        html! {
            time datetime=(self.dt.to_rfc3339()) { (self.dt.to_rfc3339()) }
        }
    }
}
