use std::{path::Path, sync::LazyLock};

use crate::{Result, git_util, step::ShellType};
use itertools::Itertools;
use serde::Serialize;
use tera::Tera;

pub fn render(input: &str, ctx: &Context) -> Result<String> {
    let mut tera = Tera::default();
    let output = tera.render_str(input, &ctx.ctx)?;
    Ok(output)
}

static BASE_CONTEXT: LazyLock<tera::Context> = LazyLock::new(|| {
    let mut ctx = tera::Context::new();
    let root = git_util::find_work_tree_root();
    ctx.insert("color", &console::colors_enabled_stderr());
    ctx.insert("root", &root.display().to_string());
    ctx
});

#[derive(Clone)]
pub struct Context {
    ctx: tera::Context,
}

impl Default for Context {
    fn default() -> Self {
        Self {
            ctx: BASE_CONTEXT.clone(),
        }
    }
}

impl Context {
    pub fn insert<T: Serialize + ?Sized, S: Into<String>>(&mut self, key: S, val: &T) {
        self.ctx.insert(key, val);
    }

    pub fn with_globs<P: AsRef<Path>>(&mut self, globs: &[P]) -> &mut Self {
        let globs = globs.iter().map(|m| m.as_ref().to_str().unwrap()).join(" ");
        self.insert("globs", &globs);
        self
    }

    pub fn with_files<P: AsRef<Path>>(&mut self, shell_type: ShellType, files: &[P]) -> &mut Self {
        let files_list: Vec<String> = files
            .iter()
            .map(|f| f.as_ref().to_str().unwrap().to_string())
            .collect();
        self.insert("files_list", &files_list);
        let quoted_files = files
            .iter()
            .map(|m| shell_type.quote(m.as_ref().to_str().unwrap()))
            .join(" ");
        self.insert("files", &quoted_files);
        self
    }

    pub fn with_workspace_indicator<P: AsRef<Path>>(
        &mut self,
        workspace_indicator: &P,
    ) -> &mut Self {
        let workspace_indicator = workspace_indicator.as_ref();
        let workspace_dir = workspace_indicator
            .parent()
            .filter(|p| !p.as_os_str().is_empty())
            .unwrap_or(Path::new("."));
        self.insert("workspace", &workspace_dir.display().to_string());
        self.insert(
            "workspace_indicator",
            &workspace_indicator.display().to_string(),
        );
        self
    }

    pub fn with_workspace_files<P: AsRef<Path>>(
        &mut self,
        shell_type: ShellType,
        workspace_dir: &Path,
        files: &[P],
    ) -> &mut Self {
        let files = files
            .iter()
            .map(|m| {
                let p = m.as_ref();
                let rel = p.strip_prefix(workspace_dir).unwrap_or(p);
                shell_type.quote(rel.to_str().unwrap())
            })
            .join(" ");
        self.insert("workspace_files", &files);
        self
    }
}
