use dioxus::prelude::*;
use jacquard_common::xrpc::XrpcClient;
use polymodel_api::space_polymodel::library;

use crate::auth::ToastState;
use crate::client::PolymodelClient;
use crate::session::SessionIdentity;
use crate::shell::SessionControl;
use crate::thing_card::ThingCard;
use crate::thing_detail::thing_detail_href;
use crate::viewer::demo_models;
use crate::Route;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum FeedAlgorithm {
    Recent,
    Hot,
}

impl FeedAlgorithm {
    fn as_str(self) -> &'static str {
        match self {
            Self::Recent => "recent",
            Self::Hot => "hot",
        }
    }

    fn label(self) -> &'static str {
        match self {
            Self::Recent => "Recent",
            Self::Hot => "Hot",
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
enum BrowseFeedState {
    Loading,
    Error(String),
    Empty,
    Populated(Vec<library::ThingViewBasic>),
}

fn browse_feed_state(
    pending: bool,
    result: Option<&Result<Vec<library::ThingViewBasic>, String>>,
) -> BrowseFeedState {
    if pending && result.is_none() {
        return BrowseFeedState::Loading;
    }

    match result {
        Some(Ok(things)) if things.is_empty() => BrowseFeedState::Empty,
        Some(Ok(things)) => BrowseFeedState::Populated(things.clone()),
        Some(Err(error)) => BrowseFeedState::Error(error.clone()),
        None => BrowseFeedState::Loading,
    }
}

#[allow(clippy::useless_format)]
#[component]
pub(crate) fn Browse() -> Element {
    let viewer_model = demo_models()[0].id.to_string();
    let client = use_context::<PolymodelClient>();
    let session = use_context::<Signal<SessionIdentity>>();
    let toasts = use_context::<Signal<ToastState>>();
    let selected_algorithm = use_signal(|| FeedAlgorithm::Recent);

    let mut feed = use_resource(move || {
        let client = client.clone();
        let algorithm = *selected_algorithm.read();
        async move {
            let request = library::get_feed::GetFeed::new()
                .algorithm(Some(algorithm.as_str().into()))
                .limit(24)
                .build();
            let response = client
                .send(request)
                .await
                .map_err(|error| format!("Feed request failed: {error}"))?;
            let output = response
                .into_output()
                .map_err(|error| format!("Feed decode failed: {error}"))?;
            Ok(output
                .value
                .items
                .into_iter()
                .map(|item| item.thing)
                .collect::<Vec<_>>())
        }
    });

    let feed_result = feed.read();
    let feed_state = browse_feed_state(feed.pending(), feed_result.as_ref());

    rsx! {
        main { class: "browse-page product-shell",
            header { class: "product-header",
                div { class: "product-kicker", "ATProto model sharing" }
                div { class: "product-header-row",
                    div { h1 { "Polymodel" } }
                    nav { class: "product-nav", aria_label: "Primary",
                        a { href: "#browse-shell", "Browse" }
                        Link { to: Route::Foundation {}, "Foundation" }
                        Link { class: "button button-secondary", to: Route::Viewer { model_id: viewer_model }, "Open viewer" }
                        SessionControl { session, toasts }
                    }
                }
            }

            section { id: "browse-shell", class: "browse-controls blueprint-panel", aria_label: "Browse shell",
                label { class: "search-field",
                    span { "Search model projects" }
                    input { r#type: "search", placeholder: "Search by project, tag, or maker", aria_label: "Search model projects" }
                }
                div { class: "tab-row", role: "tablist", aria_label: "Feeds",
                    FeedTab { algorithm: FeedAlgorithm::Recent, selected_algorithm }
                    FeedTab { algorithm: FeedAlgorithm::Hot, selected_algorithm }
                    button { class: "tab", role: "tab", aria_selected: "false", disabled: true, "Following" }
                }
            }

            section { class: "browse-results-grid", aria_label: "Browse results",
                match feed_state {
                    BrowseFeedState::Loading => rsx! {
                        for index in 0..6 {
                            div { key: "skeleton-{index}", class: "state-card loading-state",
                                span { class: "status-pill", "Loading" }
                                div { class: "skeleton-line skeleton-wide" }
                                div { class: "skeleton-line" }
                                div { class: "skeleton-box" }
                            }
                        }
                    },
                    BrowseFeedState::Error(error) => rsx! {
                        div { class: "state-card error-state browse-state-card",
                            p { "{error}" }
                            button { class: "button button-secondary", onclick: move |_| feed.restart(), "Retry" }
                        }
                    },
                    BrowseFeedState::Empty => rsx! {
                        div { class: "state-card empty-state browse-state-card",
                            span { class: "status-pill status-muted", "Empty" }
                            h2 { "No projects yet" }
                            p { "Polymodel reached the appview feed, but this local index has no projects for the selected feed yet." }
                        }
                    },
                    BrowseFeedState::Populated(things) => rsx! {
                        for thing in things {
                            BrowseThingCard { key: "{thing.uri}", thing }
                        }
                    },
                }
            }
        }
    }
}

#[component]
fn BrowseThingCard(thing: library::ThingViewBasic) -> Element {
    let detail_href = thing_detail_href(&thing.uri);
    rsx! { ThingCard { thing, detail_href } }
}

#[component]
fn FeedTab(algorithm: FeedAlgorithm, selected_algorithm: Signal<FeedAlgorithm>) -> Element {
    let is_active = *selected_algorithm.read() == algorithm;
    let class = if is_active { "tab is-active" } else { "tab" };

    rsx! {
        button { class, role: "tab", aria_selected: "{is_active}", onclick: move |_| selected_algorithm.set(algorithm), "{algorithm.label()}" }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::thing_card::thing_card_fixtures;

    #[test]
    fn browse_state_distinguishes_loading_empty_error_and_populated() {
        assert_eq!(browse_feed_state(true, None), BrowseFeedState::Loading);
        assert_eq!(browse_feed_state(false, Some(&Ok(vec![]))), BrowseFeedState::Empty);
        assert_eq!(
            browse_feed_state(false, Some(&Err("boom".to_string()))),
            BrowseFeedState::Error("boom".to_string())
        );
        match browse_feed_state(false, Some(&Ok(thing_card_fixtures()))) {
            BrowseFeedState::Populated(things) => assert_eq!(things.len(), 3),
            state => panic!("expected populated state, got {state:?}"),
        }
    }
}
