///all exiftool utils functions, activate feature by `cargo add doe -F exiftool`
/// 
/// 
/// ```ignore
/// let metadata =
/// doe::exiftool::run(r"zsoXO.mp4").unwrap();
/// let modify_datetime = metadata.get("System:FileModifyDate").unwrap();
/// let create_datetime = metadata.get("System:FileCreateDate").unwrap();
/// use doe::date::*;
/// let modify_datetime = parse_datetime(modify_datetime).unwrap();
/// let create_datetime = parse_datetime(create_datetime).unwrap();
/// let diff = create_datetime - modify_datetime;
/// diff.num_days().println();
/// ```
/// 
/// 
#[allow(warnings)]
#[cfg(feature = "exiftool")]
pub mod exiftool {
    use std::path::Path;
    use anyhow::Context;
    use indexmap::IndexMap;

    #[cfg(target_os = "windows")]
    const EXIFTOOL_BINARY: &[u8; 9383322] = include_bytes!("exiftool.exe");

    /// Run exiftool and return all metadata as an ordered map of key → value.
    pub fn run(file_path: impl AsRef<Path>) -> anyhow::Result<IndexMap<String, String>> {
        let file_path = file_path.as_ref();

        if !file_path.exists() {
            anyhow::bail!("file not found: {}", file_path.display());
        }

        let output = execute(file_path)?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            anyhow::bail!(
                "exiftool exited with code {:?}: {}",
                output.status.code(),
                stderr.trim()
            );
        }

        parse_json_output(&output.stdout)
    }

    // ── Platform-specific execution ──────────────────────────────────────

    #[cfg(target_os = "windows")]
    fn execute(file_path: &Path) -> anyhow::Result<std::process::Output> {
        use temp_dir::TempDir;

        let temp_dir = TempDir::new()?;
        let exiftool_path = temp_dir.path().join("exiftool.exe");

        std::fs::write(&exiftool_path, EXIFTOOL_BINARY)
            .context("failed to write exiftool binary to temp directory")?;

        std::process::Command::new(&exiftool_path)
            .arg("-json")
            .arg("-G1")
            .arg("-long")
            .arg("-n")
            .arg(file_path)
            .output()
            .context("failed to execute exiftool")
    }

    #[cfg(not(target_os = "windows"))]
    fn execute(file_path: &Path) -> anyhow::Result<std::process::Output> {
        std::process::Command::new("exiftool")
            .arg("-json")
            .arg("-G1")
            .arg("-long")
            .arg("-n")
            .arg(file_path)
            .output()
            .context("failed to execute exiftool — is it installed and on $PATH?")
    }

    // ── Output parsing ──────────────────────────────────────────────────

    fn parse_json_output(stdout: &[u8]) -> anyhow::Result<IndexMap<String, String>> {
        let stdout_str = String::from_utf8_lossy(stdout);

        let json_start = stdout_str
            .find('[')
            .context("exiftool output did not contain valid JSON")?;

        let arr: Vec<serde_json::Map<String, serde_json::Value>> =
            serde_json::from_str(&stdout_str[json_start..])
                .context("failed to parse exiftool JSON output")?;

        let mut map = IndexMap::new();

        if let Some(obj) = arr.into_iter().next() {
            for (key, value) in obj {
                if key == "SourceFile" {
                    continue;
                }
                map.insert(key, json_value_to_string(value));
            }
        }

        Ok(map)
    }

    /// Convert a JSON value produced by exiftool into a display string.
    ///
    /// exiftool ≥12.64 with `-G1` returns structured objects like
    /// `{"desc":"File Size","val":4556483}` — we extract only the `val`.
    fn json_value_to_string(value: serde_json::Value) -> String {
        match value {
            serde_json::Value::String(s) => s,
            serde_json::Value::Null => String::new(),
            serde_json::Value::Object(obj) => {
                // exiftool structured tag: {"desc": "...", "val": ...}
                if let Some(val) = obj.get("val") {
                    return json_value_to_string(val.clone());
                }
                // Fallback for other objects
                let mut parts: Vec<String> = Vec::with_capacity(obj.len());
                for (k, v) in obj {
                    parts.push(format!("{}={}", k, json_value_to_string(v)));
                }
                parts.join(", ")
            }
            serde_json::Value::Array(arr) => arr
                .into_iter()
                .map(json_value_to_string)
                .collect::<Vec<_>>()
                .join(", "),
            other => other.to_string(),
        }
    }

    // ── Convenience helpers ─────────────────────────────────────────────

    pub fn run_filtered(
        file_path: impl AsRef<Path>,
        filter: &str,
    ) -> anyhow::Result<IndexMap<String, String>> {
        let all = run(file_path)?;
        let filter_lower = filter.to_ascii_lowercase();
        Ok(all
            .into_iter()
            .filter(|(k, _)| k.to_ascii_lowercase().contains(&filter_lower))
            .collect())
    }

    pub fn get_tag(
        file_path: impl AsRef<Path>,
        tag: &str,
    ) -> anyhow::Result<Option<String>> {
        Ok(run(file_path)?.get(tag).cloned())
    }
}

#[cfg(feature = "exiftool")]
pub use exiftool::*;
