mod handlers;
mod helpers;
mod mutations;
mod views;

use std::path::Path;
use std::sync::Arc;

use anyhow::Result;
use axum::response::IntoResponse;
use axum::routing::{get, post};
use axum::Router;

use crate::db;

#[derive(Clone)]
struct AppState {
    data_root: Arc<std::path::PathBuf>,
}

async fn static_oat_css() -> impl IntoResponse {
    (
        [(axum::http::header::CONTENT_TYPE, "text/css; charset=utf-8")],
        include_bytes!("../../../static/oat.min.css").as_slice(),
    )
}

async fn static_td_css() -> impl IntoResponse {
    (
        [(axum::http::header::CONTENT_TYPE, "text/css; charset=utf-8")],
        include_bytes!("../../../static/td.css").as_slice(),
    )
}

async fn static_js() -> impl IntoResponse {
    (
        [(
            axum::http::header::CONTENT_TYPE,
            "application/javascript; charset=utf-8",
        )],
        include_bytes!("../../../static/oat.min.js").as_slice(),
    )
}

async fn static_td_js() -> impl IntoResponse {
    (
        [(
            axum::http::header::CONTENT_TYPE,
            "application/javascript; charset=utf-8",
        )],
        include_bytes!("../../../static/td.js").as_slice(),
    )
}

pub fn run(cwd: &Path, host: &str, port: u16, explicit_project: Option<&str>) -> Result<()> {
    let data_root = db::data_root()?;
    let state = AppState {
        data_root: Arc::new(data_root),
    };

    let app = Router::new()
        .route("/", get(handlers::index_handler))
        .route("/projects", post(mutations::create_project_handler))
        .route("/projects/{name}", get(handlers::project_handler))
        .route("/projects/{name}/tasks", post(mutations::create_handler))
        .route(
            "/projects/{name}/tasks/{id}",
            get(handlers::task_handler).post(mutations::update_handler),
        )
        .route(
            "/projects/{name}/tasks/{id}/log",
            post(mutations::log_handler),
        )
        .route(
            "/projects/{name}/tasks/{id}/done",
            post(mutations::done_handler),
        )
        .route(
            "/projects/{name}/tasks/{id}/reopen",
            post(mutations::reopen_handler),
        )
        .route(
            "/projects/{name}/tasks/{id}/labels",
            post(mutations::label_handler),
        )
        .route(
            "/projects/{name}/tasks/{id}/deps",
            post(mutations::dep_handler),
        )
        .route(
            "/projects/{name}/tasks/{id}/delete",
            post(mutations::delete_handler),
        )
        .route("/static/oat.min.css", get(static_oat_css))
        .route("/static/td.css", get(static_td_css))
        .route("/static/oat.min.js", get(static_js))
        .route("/static/td.js", get(static_td_js))
        .with_state(state);

    let addr = format!("{host}:{port}");
    let root_url = format!("http://{addr}");

    // Resolve current project for a convenience URL.
    let project_url = match explicit_project {
        Some(p) => Some(format!("{root_url}/projects/{p}")),
        None => db::try_open(cwd)
            .ok()
            .flatten()
            .map(|s| format!("{root_url}/projects/{}", s.project_name())),
    };

    eprintln!("listening on {root_url}");
    if let Some(ref url) = project_url {
        eprintln!("project: {url}");
    }

    tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()?
        .block_on(async {
            let listener = tokio::net::TcpListener::bind(&addr).await?;
            axum::serve(listener, app).await?;
            Ok(())
        })
}
