use std::env;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};

fn main() {
    let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("manifest dir"));
    let workspace_web_build_dir = manifest_dir.join("../../web/build");
    let packaged_web_build_dir = manifest_dir.join("embedded-web");
    println!(
        "cargo:rerun-if-changed={}",
        workspace_web_build_dir.display()
    );
    println!(
        "cargo:rerun-if-changed={}",
        workspace_web_build_dir.join("spa.html").display()
    );
    println!(
        "cargo:rerun-if-changed={}",
        packaged_web_build_dir.display()
    );
    println!(
        "cargo:rerun-if-changed={}",
        packaged_web_build_dir.join("spa.html").display()
    );

    let web_build_dir = if workspace_web_build_dir.join("spa.html").is_file() {
        workspace_web_build_dir
    } else {
        packaged_web_build_dir
    };
    let out_file =
        PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")).join("embedded_web_assets.rs");
    let out_assets_dir =
        PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")).join("embedded_web_assets");

    let mut output = File::create(&out_file).expect("create embedded asset registry");
    writeln!(
        output,
        "pub struct EmbeddedAsset {{ pub path: &'static str, pub bytes: &'static [u8], }}"
    )
    .expect("write header");

    let assets = collect_assets(&web_build_dir);
    writeln!(
        output,
        "pub static EMBEDDED_WEB_ASSETS: &[EmbeddedAsset] = &["
    )
    .expect("write open");
    for (relative, absolute) in assets {
        println!("cargo:rerun-if-changed={}", absolute.display());
        let copied = copy_asset_to_out_dir(&out_assets_dir, &relative, &absolute);
        writeln!(
            output,
            "    EmbeddedAsset {{ path: {:?}, bytes: include_bytes!(r#\"{}\"#) }},",
            relative,
            copied.display()
        )
        .expect("write asset row");
    }
    writeln!(output, "];").expect("write close");
}

fn collect_assets(root: &Path) -> Vec<(String, PathBuf)> {
    let mut files = Vec::new();
    if !root.exists() {
        return files;
    }
    visit_dir(root, root, &mut files);
    files.sort_by(|a, b| a.0.cmp(&b.0));
    files
}

fn visit_dir(root: &Path, current: &Path, files: &mut Vec<(String, PathBuf)>) {
    let Ok(entries) = std::fs::read_dir(current) else {
        return;
    };
    for entry in entries.flatten() {
        let path = entry.path();
        if path.is_dir() {
            visit_dir(root, &path, files);
            continue;
        }
        if !path.is_file() {
            continue;
        }
        let Ok(relative) = path.strip_prefix(root) else {
            continue;
        };
        files.push((relative.to_string_lossy().replace('\\', "/"), path));
    }
}

fn copy_asset_to_out_dir(out_assets_dir: &Path, relative: &str, source: &Path) -> PathBuf {
    let target = relative
        .split('/')
        .fold(out_assets_dir.to_path_buf(), |path, segment| {
            path.join(segment)
        });
    if let Some(parent) = target.parent() {
        std::fs::create_dir_all(parent).expect("create embedded asset dir");
    }
    std::fs::copy(source, &target).expect("copy embedded asset");
    target
}
