use std::ffi::OsStr;
use std::path::PathBuf;

use super::stylesheet::Stylesheet;
use crate::cli::parameters::BuildArgs;
use crate::command::build::render::SiteRenderer;
use crate::config::Config;
use crate::error::CustomError;
use crate::intermediate::BlogPost;
use include_dir::include_dir;
use include_dir::Dir;

static DEFAULT_STYLESHEETS: Dir =
    include_dir!("$CARGO_MANIFEST_DIR/default_environment/stylesheet");

pub(crate) async fn build_site(args: BuildArgs) -> Result<(), CustomError> {
    let config = Config::load_from_file(args.config).await?;
    let blog_posts = load_blog_posts(&config).await?;
    let stylesheets = load_stylesheets().await?;
    let renderer = SiteRenderer::new(
        get_output_directory(&config).await?,
        blog_posts,
        stylesheets,
    );
    renderer.render_blog_posts(&config).await?;
    renderer.render_stylesheets().await?;

    Ok(())
}

/// Delete everything inside the output directory and return the path to that directory.
async fn get_output_directory(config: &Config) -> Result<PathBuf, CustomError> {
    let output_directory = config.get_output_directory();
    if !output_directory.exists() {
        tokio::fs::create_dir(&output_directory).await?;
    } else {
        let mut existing_entries = tokio::fs::read_dir(&output_directory).await?;
        while let Some(entry) = existing_entries.next_entry().await? {
            let file_type = entry.file_type().await?;
            if file_type.is_dir() {
                tokio::fs::remove_dir_all(entry.path()).await?;
            } else {
                tokio::fs::remove_file(entry.path()).await?;
            }
        }
    }
    Ok(output_directory)
}

async fn get_post_directories(config: &Config) -> Result<Vec<PathBuf>, CustomError> {
    let mut ret = Vec::new();
    let mut entries = tokio::fs::read_dir(config.get_posts_directory()).await?;
    while let Some(entry) = entries.next_entry().await? {
        let file_type = entry.file_type().await?;
        if file_type.is_dir() {
            ret.push(entry.path());
        }
    }
    Ok(ret)
}

async fn load_blog_posts(config: &Config) -> Result<Vec<BlogPost>, CustomError> {
    let root_directory = config.get_root_directory().to_owned();
    let post_directories = get_post_directories(&config).await?;
    let load_jobs = post_directories
        .into_iter()
        .map(|path| tokio::spawn(BlogPost::load_blog_post(root_directory.clone(), path)));
    let mut blog_posts = Vec::new();
    for job in load_jobs {
        blog_posts.push(job.await??);
    }
    Ok(blog_posts)
}

async fn load_stylesheets() -> Result<Vec<Stylesheet>, CustomError> {
    let sources: Vec<_> = DEFAULT_STYLESHEETS
        .files()
        .filter(|f| f.path().extension() == Some(OsStr::new("css")))
        .collect();
    let mut ret = Vec::with_capacity(sources.len());
    for entry in sources {
        let path = entry.path().to_path_buf();
        let contents = String::from_utf8(entry.contents().to_vec())?;
        let stylesheet = Stylesheet::new(path, contents).await?;
        ret.push(stylesheet);
    }
    Ok(ret)
}