use dioxus::prelude::ServerFnError;
use dioxus::prelude::*;
use jacquard_common::xrpc::XrpcClient;
use polymodel_api::space_polymodel::actor::bootstrap_profile::BootstrapProfile;
use polymodel_api::space_polymodel::actor::get_session::{GetSession, GetSessionOutput};

mod about;
mod auth;
mod browse;
mod client;
mod examples;
mod fonts;
mod foundation;
mod mesh;
mod profile;
mod publish;
mod search;
mod session;
mod shell;
mod thing_card;
mod thing_detail;
mod viewer;
mod viewer_route;

// Frontend compile-time configuration, generated by build.rs from POLYMODEL_*.
mod env;
// Server-only indexing infrastructure (Hydrant firehose + SQLite projection).
#[cfg(any(feature = "server", test))]
mod indexing;
// Server-only XRPC read endpoints (appview query layer).
#[cfg(any(feature = "server", test))]
mod appview;
#[cfg(any(feature = "server", test))]
mod oauth;

use about::About;
use browse::Browse;
use client::PolymodelClient;
use foundation::Foundation;
use profile::{Profile, ProfileSelf};
use publish::Publish;
use search::Search;
use session::SessionIdentity;
use shell::{AppShell, ToastShelf};
use thing_detail::ThingDetail;
use viewer_route::Viewer;

const FAVICON: Asset = asset!("/assets/favicon.jpg");
const THEME_CSS: Asset = asset!("/assets/styling/theme.css");
const BASE_CSS: Asset = asset!("/assets/styling/base.css");
const PRIMITIVES_CSS: Asset = asset!("/assets/styling/primitives.css");
const SHELL_CSS: Asset = asset!("/assets/styling/shell.css");
const HOME_CSS: Asset = asset!("/assets/styling/home.css");
const THING_DETAIL_CSS: Asset = asset!("/assets/styling/thing-detail.css");
const PROFILE_CSS: Asset = asset!("/assets/styling/profile.css");
const VIEWER_CSS: Asset = asset!("/assets/styling/viewer.css");
const PUBLISH_CSS: Asset = asset!("/assets/styling/publish.css");
const PLACEHOLDERS_CSS: Asset = asset!("/assets/styling/placeholders.css");
const ABOUT_CSS: Asset = asset!("/assets/styling/about.css");

#[cfg(feature = "server")]
fn main() {
    // Initialise tracing and env before dioxus::serve so our filter takes
    // precedence over any subscriber the Dioxus runtime installs.
    let _ = dotenvy::dotenv();
    let _ = tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info,hydrant=warn")),
        )
        .try_init();
    // The closure runs inside the server's Tokio runtime at startup, so the
    // indexing tasks spawned here live for the process lifetime alongside Axum.
    dioxus::serve(|| async {
        let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); // Hydrant selects AWS-LC while other deps enable ring; choose before websocket TLS setup.
        let cfg = indexing::config::ServerConfig::load();
        let db = indexing::db::init_db(&cfg)
            .await
            .expect("failed to initialize sqlite projection database");
        if cfg.sample_data.should_seed(&cfg.database_url) {
            indexing::sample_data::seed_if_empty(&db)
                .await
                .expect("failed to seed local/demo sample data");
        }
        // Indexing is a soft dependency; serve read-only from the projection if
        // the firehose can't start (e.g. the hydrant DB is locked by another dev server).
        if let Err(e) = indexing::start_indexing(db.clone()).await {
            tracing::error!("indexing pipeline disabled, serving from existing projection: {e:#}");
        }
        let oauth = oauth::bootstrap_oauth(db.clone(), cfg.base_url.as_deref())
            .expect("failed to bootstrap OAuth client");
        // The same SQLite pool backs both indexing and the appview read handlers.
        let app_state = appview::state::AppState::new(db, oauth);
        Ok(dioxus::server::router(App)
            .layer(axum::Extension(app_state.clone()))
            .merge(
                appview::router()
                    .merge(jacquard_axum::oauth::routes::<
                        jacquard::identity::PublicResolver,
                        oauth::SqliteAuthStore,
                        appview::state::AppState,
                    >(&app_state.oauth_config.clone()))
                    .with_state(app_state),
            ))
    });
}

#[cfg(not(feature = "server"))]
fn main() {
    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
    {
        console_error_panic_hook::set_once();
        let mut wasm_log_config = tracing_wasm::WASMLayerConfigBuilder::new();
        wasm_log_config.set_max_level(tracing::Level::INFO);
        tracing_wasm::set_as_global_default_with_config(wasm_log_config.build());
    }

    dioxus::launch(App);
}

#[derive(Clone, Debug, PartialEq, Routable)]
pub(crate) enum Route {
    #[layout(AppShell)]
    #[route("/")]
    Browse {},
    #[route("/foundation")]
    Foundation {},
    #[route("/about")]
    About {},
    #[route("/search?:q")]
    Search { q: String },
    #[route("/publish?:draft")]
    Publish { draft: String },
    #[route("/profile")]
    ProfileSelf {},
    #[route("/profile/:actor")]
    Profile { actor: String },
    #[route("/viewer/:model_id")]
    Viewer { model_id: String },
    #[route("/:repo/thing/:rkey")]
    ThingDetail { repo: String, rkey: String },
}

#[component]
fn App() -> Element {
    let client = use_context_provider(PolymodelClient::default);
    let mut session = use_context_provider(|| Signal::new(SessionIdentity::Unknown));
    let toasts = use_context_provider(|| Signal::new(auth::ToastState::default()));

    let ssr_session_seed = use_server_future(server_session_seed)?;
    if let Some(Ok(output)) = ssr_session_seed() {
        session.set(SessionIdentity::from_get_session(output));
    }

    let _session_seed = use_resource(move || {
        let client = client.clone();
        let mut session = session;
        async move {
            #[cfg(feature = "server")]
            if dioxus::prelude::dioxus_fullstack::FullstackContext::current().is_some() {
                return;
            }

            match session.read().clone() {
                SessionIdentity::Authenticated(_) => {
                    if let Err(error) = client.send(BootstrapProfile).await {
                        tracing::warn!(%error, "failed to bootstrap Polymodel profile");
                    }
                    return;
                }
                SessionIdentity::Anonymous => return,
                SessionIdentity::Unknown => {}
            }

            match client.send(GetSession).await {
                Ok(response) => match response.into_output() {
                    Ok(output) => {
                        let identity = SessionIdentity::from_get_session(output);
                        let authenticated = matches!(identity, SessionIdentity::Authenticated(_));
                        session.set(identity);
                        // First-auth profile bootstrap: create a polymodel profile from
                        // Bluesky basics if the signed-in actor has none yet. Idempotent
                        // server-side (returns the existing profile when present).
                        if authenticated && let Err(error) = client.send(BootstrapProfile).await {
                            tracing::warn!(%error, "failed to bootstrap Polymodel profile");
                        }
                    }
                    Err(error) => {
                        tracing::warn!(%error, "failed to decode Polymodel session");
                        session.set(SessionIdentity::Anonymous);
                    }
                },
                Err(error) => {
                    tracing::warn!(%error, "failed to fetch Polymodel session");
                    session.set(SessionIdentity::Anonymous);
                }
            }
        }
    });

    rsx! {
        document::Meta { name: "viewport", content: "width=device-width, initial-scale=1" }
        document::Title { "Polymodel" }
        document::Link { rel: "icon", href: FAVICON }
        Stylesheet { href: THEME_CSS }
        Stylesheet { href: BASE_CSS }
        Stylesheet { href: PRIMITIVES_CSS }
        Stylesheet { href: SHELL_CSS }
        Stylesheet { href: HOME_CSS }
        Stylesheet { href: THING_DETAIL_CSS }
        Stylesheet { href: PROFILE_CSS }
        Stylesheet { href: VIEWER_CSS }
        Stylesheet { href: PUBLISH_CSS }
        Stylesheet { href: PLACEHOLDERS_CSS }
        Stylesheet { href: ABOUT_CSS }

        Router::<Route> {}
        ToastShelf { toasts }
    }
}

#[server]
async fn server_session_seed() -> Result<GetSessionOutput, ServerFnError> {
    appview::ssr::get_session_output_from_fullstack_context().await
}
