use axum::{
    Router, extract::Path, response::Html, routing::get
};
use tower_http::services::ServeDir;
use askama::Template;
use std::{collections::HashMap, fs, io::{Error, Write}, process::{Command, Stdio}};
use regex::Regex;



fn load_posts() -> Result<HashMap<String, Post>, Error> {

    let dirs = fs::read_dir("static/posts")?;

    
    let re_slug = Regex::new(r#"#let\s+post_slug\s*=\s*"([^"]*)""#).unwrap();
    let re_summary = Regex::new(r#"#let\s+post_summary\s*=\s*"([^"]*)""#).unwrap();
    let re_img = Regex::new(r#"#let\s+post_preview_image\s*=\s*"([^"]*)""#).unwrap();
    let re_title = Regex::new("(?m)^=+ (.*)").unwrap();
    
    let mut posts: HashMap<String, Post> = HashMap::new();
    for dir in dirs {
        let dir_path = dir.as_ref().unwrap().path();
        let typst_path = dir_path.join("post.typ");
        let typst = fs::read_to_string(typst_path).expect("Failed reading post");
        let title = re_title.captures(&typst)
            .expect(format!("Post title not found in {}", dir_path.to_str().unwrap()).as_str())
            .get(1).unwrap().as_str().trim().to_string();
        let slug = re_slug.captures(&typst)
            .expect("Post slug not found")
            .get(1).unwrap().as_str().trim().to_string();
        let summary = re_summary.captures(&typst)
            .expect("Post summary not found")
            .get(1).unwrap().as_str().trim().to_string();
        let img_path = "/".to_string() + dir?.path().join(
                re_img.captures(&typst)
                .expect("Post preview image not found")
                .get(1).unwrap().as_str().trim().to_string()
            ).to_str().expect("Failed converting path to string");
        posts.insert(
            slug.clone(),
            Post {
                slug: slug,
                title: title,
                preview_image: img_path.clone(),
                summary: summary,
                render: Html(PostTemplate {
                    content: typst_to_html(typst),
                    image: img_path
                }.render().expect("Failed rendering post")),
            }
        );
    }
    Ok(posts)
}

#[derive(Template)]
#[template(path = "post.html")]
struct PostTemplate {
    image: String,
    content: String,
}

#[derive(Clone)]
#[derive(Debug)]
struct Post {
    title: String,
    preview_image: String,
    summary: String,
    render: Html<String>,
    slug: String
}


#[derive(Debug)]
#[derive(Template)]
#[template(path = "posts.html")]
struct PostsTemplate {
    posts: Vec<Post>
}

// Template for home 
#[derive(Template)]
#[template(path = "home.html", ext = "html")]
struct HomeTemplate {
    content: String
}

fn typst_to_html(typst: String) -> String {
    let mut child = Command::new("typst")
        .args(["compile", "-", "-", "--format", "html", "--features", "html" ])
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .expect("Failed running typst");
    let mut stdin = child.stdin.take().expect("Failed opening STDIN");
    stdin.write_all(typst.as_bytes()).expect("Failed writing to STDIN");
    drop(stdin);

    let output = child.wait_with_output().expect("Failed reading output");
    String::from_utf8_lossy(&output.stdout).to_string()
}
// , posts: &Vec<Post>
async fn get_post(
        Path(slug): Path<String>,
        posts: HashMap<String, Post>
    ) -> Html<String> {
    if posts.contains_key(&slug) {
        return posts[&slug].render.clone()
    }
    else {
        return Html("404".to_string());
    }
}

#[tokio::main]
async fn main() {
    // unsafe {
    // std::env::set_var("TYPST_FEATURES", "html");
    // }

    let home_html = Html(
        HomeTemplate {
            content: typst_to_html(fs::read_to_string(
                "static/home.typ"
                ).expect("Couldnt read file"))
        }.render().expect("Failed rendering home template")
    );

    let posts: HashMap<String, Post> = load_posts().expect("Failed loading posts");
    let posts_html = Html(
        PostsTemplate{
            posts: posts.values().cloned().collect::<Vec<Post>>()
        }.render().expect("Failed rendering posts")
    );

    let site: Router = Router::new()
        .route("/", get(home_html))
        .route("/posts", get(posts_html))
        .route("/posts/{slug}", get(|slug| get_post(slug, posts)))
        .nest_service("/static", ServeDir::new("static"));
    
    let listener = tokio::net::TcpListener::bind("0.0.0.0:42069").await.unwrap();
    println!("Running on http://127.0.0.1:42069");
    axum::serve(listener, site).await.unwrap();
}
