use std::fmt;
use std::path::{Path, PathBuf};

use serde::Serialize;

/// Pre-read directory child entry. Built once per directory during the walk
/// and shared with plugins to avoid redundant readdir syscalls.
pub struct DirChild {
    pub name: String,
    pub path: PathBuf,
    pub is_file: bool,
}

/// Whether a deletion can be recovered from.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Safety {
    /// Can be regenerated by rebuilding, reinstalling, etc.
    Recoverable,
    /// Gone forever once deleted.
    Unsafe,
}

impl fmt::Display for Safety {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Safety::Recoverable => write!(f, "safe"),
            Safety::Unsafe => write!(f, "UNSAFE"),
        }
    }
}

#[derive(Debug, Serialize)]
pub struct CleanTarget {
    pub label: String,
    pub size_bytes: u64,
    pub action: CleanAction,
    pub plugin_name: String,
    pub safety: Safety,
    pub explanation: String,
}

#[derive(Debug, Clone)]
pub enum CleanAction {
    DeletePath(PathBuf),
    /// Delete the contents of a directory but keep the directory itself.
    DeleteContents(PathBuf),
    RunCommand {
        command: String,
        args: Vec<String>,
        description: String,
    },
}

impl Serialize for CleanAction {
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        use serde::ser::SerializeMap;
        match self {
            CleanAction::DeletePath(p) => {
                let mut map = serializer.serialize_map(Some(2))?;
                map.serialize_entry("type", "delete_path")?;
                map.serialize_entry("path", p)?;
                map.end()
            }
            CleanAction::DeleteContents(p) => {
                let mut map = serializer.serialize_map(Some(2))?;
                map.serialize_entry("type", "delete_contents")?;
                map.serialize_entry("path", p)?;
                map.end()
            }
            CleanAction::RunCommand {
                command,
                args,
                description,
            } => {
                let mut map = serializer.serialize_map(Some(4))?;
                map.serialize_entry("type", "run_command")?;
                map.serialize_entry("command", command)?;
                map.serialize_entry("args", args)?;
                map.serialize_entry("description", description)?;
                map.end()
            }
        }
    }
}

pub trait Plugin: Send + Sync {
    fn name(&self) -> &str;
    fn description(&self) -> &str;

    /// Whether this plugin can run in the current environment.
    /// Return false to silently skip (e.g. docker not installed).
    fn is_available(&self) -> bool {
        true
    }

    /// Whether this plugin needs to scan every directory during the walk,
    /// even without marker matches. Only plugins that scan for file patterns
    /// (e.g. by extension) need this. Default: false.
    fn scans_all_directories(&self) -> bool {
        false
    }

    /// Called per directory during filesystem walk. Return targets found here.
    fn scan_directory(&self, _dir: &Path) -> Vec<CleanTarget> {
        Vec::new()
    }

    /// Called per directory with pre-read directory entries.
    /// Override this instead of scan_directory to reuse the directory listing
    /// that the scanner already read for marker matching.
    fn scan_with_entries(&self, dir: &Path, _entries: &[DirChild]) -> Vec<CleanTarget> {
        self.scan_directory(dir)
    }

    /// Called once after walk. Return global targets (caches, system resources).
    fn global_targets(&self) -> Vec<CleanTarget> {
        Vec::new()
    }

    /// Directory names the scanner should skip descending into.
    fn skip_subdirectories(&self) -> &'static [&'static str] {
        &[]
    }

    /// Marker files/directories whose presence triggers this plugin's scan_directory.
    /// When non-empty, scan_directory is only called for directories containing
    /// at least one of these markers — avoiding unnecessary work for the vast
    /// majority of directories. Return empty (default) to be called for every directory.
    fn markers(&self) -> &'static [&'static str] {
        &[]
    }
}
