//! Hierarchical HTTP routing.
//!
//! Top level module establishes the overall
//! [`axum::Router`], and submodules organize nested subrouters into manageable
//! chunks. Pragmatically, the submodule tree should be kept fairly flat, lest
//! file paths grow exceedingly long. Deeply nested routers may still be
//! implemented, by use of the `super` keyword.

use axum::{
    Router,
    extract::State,
    http::{HeaderValue, header::CACHE_CONTROL},
    response::Redirect,
    routing::get,
};
use tower::ServiceBuilder;
use tower_http::{
    services::{ServeDir, ServeFile},
    set_header::SetResponseHeaderLayer,
};

use crate::{app::App, auth, settings::Settings};

mod relations_single;
mod workspaces_multi;
mod workspaces_single;

/// Create the root [`Router`] for the application, including nesting according
/// to the `root_path` [`crate::settings::Settings`] value, setting cache
/// headers, setting up static file handling, and defining fallback handlers.
pub(crate) fn new_router(app: App) -> Router<()> {
    let root_path = app.settings.root_path.clone();
    let router = Router::new()
        .route(
            "/",
            get(
                |State(Settings { root_path, .. }): State<Settings>| async move {
                    Redirect::to(&format!("{root_path}/workspaces/list/"))
                },
            ),
        )
        .nest("/workspaces", workspaces_multi::new_router())
        .nest("/w", workspaces_single::new_router())
        .nest("/auth", auth::new_router())
        .route("/__dev-healthz", get(|| async move { "ok" }))
        .layer(SetResponseHeaderLayer::if_not_present(
            CACHE_CONTROL,
            HeaderValue::from_static("no-cache"),
        ))
        .nest_service(
            "/js_dist",
            ServiceBuilder::new()
                .layer(SetResponseHeaderLayer::if_not_present(
                    CACHE_CONTROL,
                    // JS entrypoints should always be served from the current
                    // version.
                    HeaderValue::from_static("no-cache"),
                ))
                .service(
                    ServeDir::new("js_dist").not_found_service(
                        ServiceBuilder::new()
                            .layer(SetResponseHeaderLayer::if_not_present(
                                CACHE_CONTROL,
                                // Do not allow caching of paths if they return
                                // a 404 error.
                                HeaderValue::from_static("no-cache"),
                            ))
                            .service(ServeFile::new("static/_404.html")),
                    ),
                ),
        )
        .fallback_service(
            ServiceBuilder::new()
                .layer(SetResponseHeaderLayer::if_not_present(
                    CACHE_CONTROL,
                    HeaderValue::from_static(if cfg!(debug_assertions) {
                        // Disable caching when developing locally.
                        "no-cache"
                    } else {
                        "max-age=120, stale-while-revalidate=86400"
                    }),
                ))
                .service(
                    ServeDir::new("static").not_found_service(
                        ServiceBuilder::new()
                            .layer(SetResponseHeaderLayer::if_not_present(
                                CACHE_CONTROL,
                                // Do not allow caching of paths if they return
                                // a 404 error.
                                HeaderValue::from_static("no-cache"),
                            ))
                            .service(ServeFile::new("static/_404.html")),
                    ),
                ),
        )
        .with_state(app);
    if root_path.is_empty() {
        router
    } else {
        Router::new()
            .nest(&root_path, router)
            .fallback(|| async move { Redirect::to(&root_path) })
    }
}
