gecol 0.1.0

A perception-aware accent color extractor and dynamic theme generator.
use std::{
    fs::create_dir_all,
    io::ErrorKind,
    path::PathBuf,
    process::{Command, ExitCode},
    time::Duration,
};

use clap::Parser;
use gecol_core::{
    Cache,
    template::build_templates,
    theme::{Color, Theme},
};
use indicatif::{ProgressBar, ProgressStyle};
use termal::{eprintcln, formatc};

use crate::{
    args::{
        action::{Action, Run, parse_hex_col},
        args_struct::Args,
    },
    config::Config,
    error::Error,
    extract::extract_color,
};

pub mod args;
pub mod config;
pub mod error;
pub mod extract;

fn main() -> ExitCode {
    match run() {
        Ok(_) => ExitCode::SUCCESS,
        Err(e) => {
            eprintcln!("{'r}Error:{'_} {}", e);
            ExitCode::FAILURE
        }
    }
}

fn run() -> Result<(), Error> {
    let args = Args::parse();

    if args.version {
        Args::version();
        return Ok(());
    }
    if args.help || args.action.is_none() {
        Args::help();
        return Ok(());
    }

    match args.action.as_ref().unwrap() {
        Action::Run(run_args) => handle_run(&args, run_args),
        Action::List => handle_list(&args),
        Action::Config => handle_config(&args),
        Action::ClearCache => clear_cache(&args),
    }
}

fn handle_run(args: &Args, run: &Run) -> Result<(), Error> {
    let mut config = load_config(&args.config)?;
    config.theme_type = run.theme.unwrap_or(config.theme_type);

    let (color, color_input) = if let Ok(color) = parse_hex_col(&run.target) {
        (color, true)
    } else {
        let path = PathBuf::from(&run.target);
        (extract_color(args, &mut config, &path)?, false)
    };

    if run.extract_only {
        if color_input {
            return Err(formatc!(
                "'{'dy}--extract-only{'_}' expects image as input, color given."
            )
            .into());
        }
        extract_only(args, color);
        return Ok(());
    }

    let theme = Theme::generate(config.theme_type, color);
    let theme_str = theme.to_string();
    if !run.skip_build {
        build(args, config, &run.templates, theme)?;
    }

    if run.skip_build || !args.quiet {
        println!("{theme_str}");
    }

    Ok(())
}

fn extract_only(args: &Args, color: (u8, u8, u8)) {
    let color: Color = color.into();
    if !args.quiet {
        print!("{}  \x1b[0m ", color);
    }
    println!("{}", color.hex());
}

fn handle_list(args: &Args) -> Result<(), Error> {
    let config = load_config(&args.config)?;
    if config.templates.is_empty() {
        println!("No templates found in you configuration file.");
        return Ok(());
    }

    let mut keys: Vec<&String> = config.templates.keys().collect();
    keys.sort();

    println!("Available templates:");
    for template in keys {
        println!(" - {template}");
    }
    Ok(())
}

fn handle_config(args: &Args) -> Result<(), Error> {
    let editor = std::env::var("EDITOR").unwrap_or("vi".to_string());
    let file = args.config.to_owned().unwrap_or_else(Config::file);

    if let Some(parent) = file.parent() {
        create_dir_all(parent)?;
    }
    if !file.exists() {
        Config::default().save(&file)?;
    }

    Command::new(editor).arg(file).spawn()?.wait()?;
    Ok(())
}

fn clear_cache(args: &Args) -> Result<(), Error> {
    let config = load_config(&args.config)?;

    let cache_path = config.cache_dir.unwrap_or_else(Cache::file);
    let msg = match std::fs::remove_file(&cache_path) {
        Ok(_) => "Cache cleared successfully!",
        Err(e) if e.kind() == ErrorKind::NotFound => {
            "Cache was already empty."
        }
        Err(e) => return Err(e.into()),
    };

    if !args.quiet {
        println!("{msg}");
    }
    Ok(())
}

fn build(
    args: &Args,
    mut conf: Config,
    templates: &[String],
    theme: Theme,
) -> Result<(), Error> {
    let spinner = get_spinner(args.quiet);

    spinner.set_message("Building templates...");
    if !templates.is_empty() {
        conf.templates.retain(|name, _| templates.contains(name));
        if conf.templates.is_empty() {
            spinner
                .finish_with_message("No matching templates found in config!");
            return Ok(());
        }
    }
    build_templates(&conf.templates, theme)?;

    spinner.finish_with_message("Templates build!");
    if !args.quiet {
        println!();
    }
    Ok(())
}

fn load_config(path: &Option<PathBuf>) -> Result<Config, Error> {
    match path {
        Some(path) => Config::load(path),
        None => Ok(Config::load_default()),
    }
}

pub fn get_spinner(quiet: bool) -> ProgressBar {
    if quiet {
        return ProgressBar::hidden();
    }

    let spinner = ProgressBar::new_spinner();
    spinner.enable_steady_tick(Duration::from_millis(100));
    spinner.set_style(
        ProgressStyle::with_template("{spinner:.cyan} {msg}")
            .unwrap()
            .tick_strings(&[
                "", "", "", "", "", "", "", "", "", "", "",
            ]),
    );
    spinner
}