use dioxus::prelude::*;

mod fonts;
mod mesh;
mod viewer;

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

use viewer::{ViewerPage, demo_model_for_route, demo_models};

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 CARDS_CSS: Asset = asset!("/assets/styling/cards.css");
const BROWSE_CSS: Asset = asset!("/assets/styling/browse.css");
const POLYMODEL_CSS: Asset = asset!("/assets/styling/polymodel.css");

#[cfg(feature = "server")]
fn main() {
    // 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 {
        // Server runtime config: load .env, then read DATABASE_URL (and any
        // future server env) in one place. Hydrant reads its own HYDRANT_*.
        let _ = dotenvy::dotenv();
        let _ = tracing_subscriber::fmt()
            .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
            .try_init();
        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");
        indexing::start_indexing(db)
            .await
            .expect("failed to start indexing pipeline");

        Ok(dioxus::server::router(App))
    });
}

#[cfg(not(feature = "server"))]
fn main() {
    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
    {
        console_error_panic_hook::set_once();
        tracing_wasm::set_as_global_default();
    }

    dioxus::launch(App);
}

#[derive(Clone, Debug, PartialEq, Routable)]
enum Route {
    #[route("/")]
    Browse {},
    #[route("/foundation")]
    Foundation {},
    #[route("/viewer/:model_id")]
    Viewer { model_id: String },
}

#[component]
fn App() -> Element {
    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: CARDS_CSS }
        Stylesheet { href: BROWSE_CSS }
        Stylesheet { href: POLYMODEL_CSS }

        Router::<Route> {}
    }
}

#[component]
fn Browse() -> Element {
    let viewer_model = demo_models()[0].id.to_string();

    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"
                        }
                    }
                }
            }

            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: "Feed placeholders",
                    button { class: "tab is-active", role: "tab", aria_selected: "true", "Recent" }
                    button { class: "tab", role: "tab", aria_selected: "false", "Hot" }
                    button { class: "tab", role: "tab", aria_selected: "false", "Following" }
                }
            }

            section { class: "state-card empty-state", aria_label: "Browse results placeholder",
                span { class: "status-pill status-muted", "Empty" }
            }
        }
    }
}

#[component]
fn Foundation() -> Element {
    rsx! {
        main { class: "browse-page product-shell",
            header { class: "product-header",
                div { class: "product-kicker", "PM-38 foundation" }
                div { class: "product-header-row",
                    div {
                        h1 { "Foundation" }
                        p { class: "product-lede",
                            "Self-hosted typography, blueprint tokens, and reusable front-end states for upcoming product slices."
                        }
                    }
                    nav { class: "product-nav", aria_label: "Foundation navigation",
                        Link { to: Route::Browse {}, "Back to home" }
                        a { href: "#ui-states", "UI states" }
                    }
                }
            }

            section { id: "browse-foundation", class: "foundation-hero blueprint-panel",
                div { class: "foundation-hero-copy",
                    p { class: "eyebrow", "Token and asset check" }
                    h2 { "Blueprint styling foundation" }
                    p {
                        "This route keeps PM-38 primitives visible without making implementation scaffolding the product home."
                    }
                    div { class: "button-row",
                        button { class: "button button-primary", "Primary action" }
                        button { class: "button button-ghost", "Ghost action" }
                    }
                }
                div { class: "blueprint-preview", aria_label: "Blueprint placeholder preview",
                    div { class: "blueprint-preview-grid" }
                    span { class: "blueprint-axis blueprint-axis-x", "X" }
                    span { class: "blueprint-axis blueprint-axis-y", "Y" }
                    span { class: "blueprint-dimension", "120 × 86 × 42 mm" }
                }
            }

            section { class: "foundation-grid", aria_label: "Foundation primitives",
                for item in foundation_items() {
                    FoundationCard { item }
                }
            }

            section { id: "ui-states", class: "state-grid", aria_label: "Reusable states",
                div { class: "state-card loading-state",
                    span { class: "status-pill", "Loading" }
                    div { class: "skeleton-line skeleton-wide" }
                    div { class: "skeleton-line" }
                    div { class: "skeleton-box" }
                }
                div { class: "state-card empty-state",
                    span { class: "status-pill status-muted", "Empty" }
                    h2 { "No projects yet" }
                    p { "Empty browse states can use this panel before live feeds are connected." }
                }
                div { class: "state-card error-state",
                    span { class: "status-pill status-error", "Error" }
                    h2 { "Feed unavailable" }
                    p { "Error states keep the browse surface useful without inventing data plumbing in this slice." }
                }
            }
        }
    }
}

#[component]
fn Viewer(model_id: String) -> Element {
    let model = demo_model_for_route(&model_id);

    rsx! {
        main { class: "viewer-route-page",
            nav { class: "viewer-route-nav", aria_label: "Viewer navigation",
                Link { to: Route::Browse {}, "← Back to browse" }
            }
            ViewerPage { key: "{model.id}", initial_model: model }
        }
    }
}

#[derive(Clone, Copy, PartialEq)]
struct FoundationItem {
    label: &'static str,
    title: &'static str,
    body: &'static str,
    meta: &'static str,
}

fn foundation_items() -> [FoundationItem; 3] {
    [
        FoundationItem {
            label: "Panel",
            title: "Project card surface",
            body: "A crisp bordered card for future model summaries, previews, and maker metadata.",
            meta: "primitive/card",
        },
        FoundationItem {
            label: "Input",
            title: "Search and filter shell",
            body: "Reusable input styling for browse, profile, and publish flows without binding to a data source yet.",
            meta: "primitive/input",
        },
        FoundationItem {
            label: "Status",
            title: "Metadata and chips",
            body: "Compact mono labels for license, dimensions, file state, and feed status.",
            meta: "primitive/status",
        },
    ]
}

#[component]
fn FoundationCard(item: FoundationItem) -> Element {
    rsx! {
        article { class: "foundation-card card-surface",
            div { class: "card-media blueprint-media",
                span { class: "status-pill", "{item.label}" }
            }
            div { class: "card-body",
                h2 { class: "card-title", "{item.title}" }
                p { "{item.body}" }
                span { class: "card-meta", "{item.meta}" }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::foundation_items;

    #[test]
    fn foundation_items_cover_base_primitives() {
        let items = foundation_items();
        assert!(items.iter().any(|item| item.label == "Panel"));
        assert!(items.iter().any(|item| item.label == "Input"));
        assert!(items.iter().any(|item| item.label == "Status"));
    }

    #[test]
    fn foundation_copy_avoids_workflow_marketing() {
        let combined = foundation_items()
            .iter()
            .map(|item| item.body)
            .collect::<Vec<_>>()
            .join(" ");
        assert!(!combined.contains("Jira"));
        assert!(!combined.contains("Polytoken"));
    }
}
