#![doc = include_str!("../README.md")]

use clap::Parser;
use download_cef::{CefFile, CefIndex, OsAndArch, DEFAULT_TARGET};
use std::{
    fs,
    path::{Path, PathBuf},
    sync::OnceLock,
    time::Duration,
};

fn default_version() -> &'static str {
    static DEFAULT_VERSION: OnceLock<String> = OnceLock::new();
    DEFAULT_VERSION
        .get_or_init(|| download_cef::default_version(env!("CARGO_PKG_VERSION")))
        .as_str()
}

fn default_download_url() -> &'static str {
    static DEFAULT_DOWNLOAD_URL: OnceLock<String> = OnceLock::new();
    DEFAULT_DOWNLOAD_URL
        .get_or_init(download_cef::default_download_url)
        .as_str()
}

#[derive(Parser, Debug)]
#[command(about, long_about = None)]
struct Args {
    #[arg(short, long)]
    force: bool,
    #[arg(short, long)]
    save_archive: bool,
    #[arg(short, long, default_value = DEFAULT_TARGET)]
    target: String,
    #[arg(short, long, default_value = default_version())]
    version: String,
    #[arg(short, long, default_value = default_download_url())]
    mirror_url: String,
    #[arg(short, long)]
    archive: Option<String>,
    output: String,
}

fn main() -> anyhow::Result<()> {
    let args = Args::parse();
    let output = PathBuf::from(args.output);
    let url = args.mirror_url.as_str();

    let parent = PathBuf::from(
        output
            .parent()
            .ok_or_else(|| anyhow::anyhow!("invalid target directory: {}", output.display()))?,
    );

    if fs::exists(&output)? {
        if !args.force {
            return Err(anyhow::anyhow!(
                "target directory already exists: {}",
                output.display()
            ));
        }

        let dir = output
            .file_name()
            .and_then(|dir| dir.to_str())
            .ok_or_else(|| anyhow::anyhow!("invalid target directory: {}", output.display()))?;
        let old_output = parent.join(format!("old_{dir}"));
        fs::rename(&output, &old_output)?;
        println!("Cleaning up: {}", old_output.display());
        fs::remove_dir_all(old_output)?
    }

    let target = args.target.as_str();
    let os_arch = OsAndArch::try_from(target)?;
    let cef_dir = os_arch.to_string();
    let cef_dir = parent.join(&cef_dir);

    if fs::exists(&cef_dir)? {
        let dir = cef_dir
            .file_name()
            .and_then(|dir| dir.to_str())
            .ok_or_else(|| anyhow::anyhow!("invalid target directory: {}", output.display()))?;
        let old_cef_dir = parent.join(format!("old_{dir}"));
        fs::rename(&cef_dir, &old_cef_dir)?;
        println!("Cleaning up: {}", old_cef_dir.display());
        fs::remove_dir_all(old_cef_dir)?
    }

    let (archive, extracted_dir) = match args.archive {
        Some(archive) => {
            let extracted_dir =
                download_cef::extract_target_archive(target, &archive, &parent, true)?;
            let archive = CefFile::try_from(Path::new(&archive))?;
            (archive, extracted_dir)
        }
        None => {
            let cef_version = args.version.as_str();
            let index = CefIndex::download_from(url)?;
            let platform = index.platform(target)?;
            let version = platform.version(cef_version)?;

            let archive = version.download_archive_with_retry_from(
                url,
                &parent,
                true,
                Duration::from_secs(15),
                3,
            )?;
            let extracted_dir =
                download_cef::extract_target_archive(target, &archive, &parent, true)?;

            if !args.save_archive {
                println!("Cleaning up: {}", archive.display());
                fs::remove_file(archive)?;
            }

            let archive = version.minimal()?.clone();
            (archive, extracted_dir)
        }
    };

    if extracted_dir != cef_dir {
        return Err(anyhow::anyhow!(
            "extracted dir {extracted_dir:?} does not match cef_dir {cef_dir:?}",
        ));
    }

    archive.write_archive_json(extracted_dir)?;

    if output != cef_dir {
        println!("Renaming: {}", output.display());
        fs::rename(cef_dir, output)?;
    }

    Ok(())
}
