use anyhow::Result;
use axum::{routing::get, Router};
use clap::Parser;
use std::sync::Arc;
use tokio::sync::broadcast;
use tracing::info;

use pumlv::{
    config::Config,
    routes::{
        events_handler, files_handler, htmx_js_handler, render_handler, style_css_handler,
        viewer_js_handler, AppState,
    },
    watcher::{start_watcher, FileEvent},
};

#[tokio::main]
async fn main() -> Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| "pumlv=info".parse().unwrap()),
        )
        .init();

    let config = Config::parse();
    // Canonicalize so the watcher (which receives absolute inotify paths) and
    // the file-list API always use the same prefix when computing relative paths.
    let resolved = config.resolve_root();
    let root = Arc::new(resolved.canonicalize().unwrap_or(resolved));

    info!("root: {}", root.display());
    info!("plantuml: {}", config.plantuml_jar.display());

    let (event_tx, _) = broadcast::channel::<FileEvent>(64);
    let _watcher = start_watcher((*root).clone(), event_tx.clone())?;

    let initial_file = config.initial_file(&root);

    let state = AppState {
        root,
        plantuml_jar: Arc::new(config.plantuml_jar),
        event_tx,
    };

    let app = build_router(state, initial_file.as_deref());

    let addr = format!("0.0.0.0:{}", config.port);
    let listener = tokio::net::TcpListener::bind(&addr).await?;
    info!("listening on http://{addr}");

    axum::serve(listener, app).await?;
    Ok(())
}

pub fn build_router(state: AppState, initial_file: Option<&str>) -> Router {
    // Embed the initial file path into the index route at startup so the
    // frontend can read it from a <meta> tag without a separate API call.
    let initial = initial_file
        .map(|f| f.to_owned())
        .unwrap_or_default();

    Router::new()
        .route("/", get(move || index_with_initial(initial.clone())))
        .route("/static/viewer.js", get(viewer_js_handler))
        .route("/static/style.css", get(style_css_handler))
        .route("/static/htmx.min.js", get(htmx_js_handler))
        .route("/api/files", get(files_handler))
        .route("/api/render", get(render_handler))
        .route("/api/events", get(events_handler))
        .with_state(state)
}

async fn index_with_initial(initial_file: String) -> axum::response::Html<String> {
    let html = include_str!("../static/index.html")
        .replace("__INITIAL_FILE__", &html_escape_attr(&initial_file));
    axum::response::Html(html)
}

fn html_escape_attr(s: &str) -> String {
    s.replace('&', "&amp;")
        .replace('"', "&quot;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
}
