use dioxus::prelude::*;

use crate::Route;
use crate::auth::{ToastState, broadcast_sign_out, logout_url};
use crate::examples::{random_sample_handle, random_sample_search_query};
use crate::session::SessionIdentity;

/// Global product shell. Renders the persistent header (wordmark, primary nav,
/// global directed-search field, Publish CTA, and session/account control) once
/// and hosts every route through the router `Outlet`. Pages render their own
/// `<main>`/`<h1>` inside the outlet, so the shell deliberately uses a `<div>`
/// content region rather than a competing `<main>`.
#[component]
pub(crate) fn AppShell() -> Element {
    let session = use_context::<Signal<SessionIdentity>>();
    let toasts = use_context::<Signal<ToastState>>();
    let route = use_route::<Route>();
    let show_browse_link = !matches!(route, Route::Browse {});

    // One orange (`--color-action`) per view: while logged out the primary
    // action is Sign in (in `SessionControl`), so Publish drops to secondary
    // until the visitor is authenticated and can actually publish. `Unknown`
    // (pre-resolution) stays secondary to avoid an orange flash for visitors
    // who turn out anonymous.
    let publish_class = if matches!(&*session.read(), SessionIdentity::Authenticated(_)) {
        "button button-primary app-publish-cta"
    } else {
        "button button-secondary app-publish-cta"
    };

    rsx! {
        div { class: "app-shell",
            header { class: "app-header",
                div { class: "app-header-inner",
                    Link {
                        class: "app-wordmark",
                        to: Route::Browse {},
                        aria_label: "Polymodel home",
                        "Polymodel"
                    }
                    nav { class: "app-nav", aria_label: "Primary",
                        if show_browse_link {
                            Link { class: "app-nav-link", active_class: "is-active", to: Route::Browse {}, "Browse" }
                        }
                    }
                    ShellSearch {}
                    Link { class: publish_class, to: Route::Publish { draft: String::new() }, "Publish" }
                    SessionControl { session, toasts }
                }
            }
            div { class: "app-content",
                Outlet::<Route> {}
            }
        }
    }
}

/// Global directed-search field. Uses a plain GET form so SSR/pre-hydration
/// submissions still resolve to `/search?q=...`; Dioxus keeps the value synced
/// with the current route once hydrated.
#[component]
fn ShellSearch() -> Element {
    let route = use_route::<Route>();
    let route_query = match route {
        Route::Search { q } => Some(q),
        _ => None,
    };
    let mut query = use_signal(|| route_query.clone().unwrap_or_default());
    let mut synced_route_query = use_signal(|| route_query.clone());
    let search_placeholder = use_server_cached(random_sample_search_query);

    if *synced_route_query.read() != route_query {
        synced_route_query.set(route_query.clone());
        if let Some(route_query) = route_query {
            query.set(route_query);
        }
    }

    rsx! {
        form { class: "app-search", role: "search", action: "/search", method: "get",
            input {
                class: "app-search-input",
                r#type: "search",
                name: "q",
                placeholder: "{search_placeholder}",
                aria_label: "Search Polymodel",
                value: "{query}",
                oninput: move |event| query.set(event.value()),
            }
            button {
                class: "button button-secondary app-search-button",
                r#type: "submit",
                "Search"
            }
        }
    }
}

/// Returns the current route as a path string for use as an OAuth `return_to`
/// value, so signing in returns the user to the page they started on.
fn current_return_to() -> String {
    use_route::<Route>().to_string()
}

#[allow(clippy::useless_format)]
#[component]
pub(crate) fn SessionControl(
    session: Signal<SessionIdentity>,
    toasts: Signal<ToastState>,
) -> Element {
    let identity = session.read().clone();
    let return_to = current_return_to();
    let handle_placeholder = use_server_cached(random_sample_handle);
    let mut identifier = use_signal(String::new);

    match identity {
        // Session still resolving: render nothing rather than flashing a sign-in
        // form at users who turn out to be authenticated.
        SessionIdentity::Unknown => rsx! {},
        SessionIdentity::Anonymous => rsx! {
           form {
               class: "session-control",
               action: "/oauth/start",
               method: "get",
               input { r#type: "hidden", name: "return_to", value: "{return_to}" },
               input {
                    r#type: "text",
                    name: "identifier",
                    placeholder: "{handle_placeholder}",
                    aria_label: "ATProto handle",
                    value: "{identifier}",
                    oninput: move |event| identifier.set(event.value()),
                }
                button { class: "button button-primary", r#type: "submit", "Sign in" }
            }
        },
        SessionIdentity::Authenticated(identity) => {
            let label = identity.label().to_string();
            let handle = identity.handle.as_str().to_string();
            let avatar_url = identity.avatar.as_ref().map(|u| u.as_ref().to_string());
            let initial = label.chars().next().unwrap_or('?').to_ascii_uppercase();
            rsx! {
                details { class: "account-menu",
                    summary { class: "account-trigger", aria_label: "Account menu for {label}",
                        if let Some(url) = &avatar_url {
                            img { class: "session-avatar", src: "{url}", alt: "{label}", loading: "lazy" }
                        } else {
                            span { class: "session-avatar session-avatar-fallback", aria_hidden: "true", "{initial}" }
                        }
                    }
                    div { class: "account-menu-panel blueprint-panel",
                        div { class: "account-menu-identity",
                            strong { "{label}" }
                            span { class: "account-menu-handle", "@{handle}" }
                        }
                        Link { class: "account-menu-item", to: Route::ProfileSelf {}, "Profile" }
                        form { class: "account-menu-form", method: "post", action: "{logout_url()}",
                            button {
                                class: "account-menu-item account-menu-signout",
                                r#type: "submit",
                                onclick: move |_| {
                                    broadcast_sign_out();
                                    session.set(SessionIdentity::Anonymous);
                                    toasts.write().push("Signing out…");
                                },
                                "Sign out"
                            }
                        }
                    }
                }
            }
        }
    }
}

#[allow(clippy::useless_format)]
#[component]
pub(crate) fn ToastShelf(toasts: Signal<ToastState>) -> Element {
    let items = toasts.read().items().to_vec();

    rsx! {
        div { class: "toast-shelf", aria_live: "polite",
            for toast in items {
                div { key: "toast-{toast.id}", class: "toast blueprint-panel",
                    span { "{toast.message}" }
                    button {
                        class: "toast-dismiss",
                        aria_label: "Dismiss notification",
                        onclick: move |_| toasts.write().dismiss(toast.id),
                        "×"
                    }
                }
            }
        }
    }
}
