use anyhow::{Context, Result};
use std::fs;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
use crate::consts::{
    EXT_CONTENT,
    EXT_SNIPPET
};
/// Ensure parent directory exists.
pub fn ensure_parent(p: &Path) -> Result<()> {
    if let Some(parent) = p.parent() {
        fs::create_dir_all(parent)?;
    }
    Ok(())
}

/// Join content/<key>.toml (without adding extension here).
pub fn join_content_file(content_dir: &Path, key: &str) -> PathBuf {
    let mut p = content_dir.to_path_buf();
    for seg in key.split('/') {
        if !seg.is_empty() {
            p.push(seg);
        }
    }
    p.set_extension(EXT_CONTENT);
    p
}

/// Join snippets/<name>.htmls
pub fn join_snippet_file(snippets_dir: &Path, name: &str) -> PathBuf {
    let mut p = snippets_dir.to_path_buf();
    for seg in name.split('/') {
        if !seg.is_empty() {
            p.push(seg);
        }
    }
    p.set_extension(EXT_SNIPPET);
    p
}

/// Return a relative path (to `base`) without extension as a string, if possible.
pub fn path_no_ext(abs: &Path, base: &Path) -> Option<String> {
    let rel = abs.strip_prefix(base).ok()?;
    let mut s = rel.to_string_lossy().replace('\\', "/");
    if let Some(dot) = s.rfind('.') {
        s.truncate(dot);
    }
    Some(s)
}

/// Copy the entire `static_dir` tree into `out_dir`, but only copy files that are
/// missing or **different** (by size or modification time). If `static_dir`
/// does not exist, do nothing (quietly).
pub fn copy_tree(static_dir: &Path, out_dir: &Path, verbose: bool) -> Result<()> {
    if !static_dir.exists() {
        // Quietly skip if there's no static directory.
        return Ok(());
    }
    for entry in WalkDir::new(static_dir).into_iter().filter_map(|e| e.ok()) {
        let src = entry.path();
        if src.is_dir() {
            continue;
        }
        let rel = src.strip_prefix(static_dir).unwrap();
        let dst = out_dir.join(rel);
        ensure_parent(&dst)?;

        let need_copy = match (fs::metadata(src), fs::metadata(&dst)) {
            (Ok(src_md), Ok(dst_md)) => {
                // copy if size differs or src is newer than dst
                let size_diff = src_md.len() != dst_md.len();
                let newer = src_md.modified().ok()
                    .and_then(|s| dst_md.modified().ok().map(|d| s > d))
                    .unwrap_or(true);
                size_diff || newer
            }
            (Ok(_), Err(_)) => true,  // destination missing
            _ => true,                // if we can't stat, err on copying
        };

        if need_copy {
            fs::copy(src, &dst)
                .with_context(|| format!("copy static {} -> {}", src.display(), dst.display()))?;
            if verbose { log::info!("→ [static] {}", dst.display()); }
        }
    }
    Ok(())
}
