// DCR — Cargo-like C/C++ project manager.
//
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use crate::config::flags;
use crate::core::config::Config;
use crate::core::workspace::parse_workspace;
use crate::utils::build::parse_version_info;
use crate::utils::fs::{check_dir, find_project_root, with_dir};
use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_GREEN, colored};
use glob::glob;
use std::fs;
use std::path::Path;

pub fn clean(args: &[String]) -> i32 {
    let start_dir = match std::env::current_dir() {
        Ok(dir) => dir,
        Err(_) => {
            error("Failed to determine current directory");
            return 1;
        }
    };
    let root = match find_project_root(&start_dir) {
        Ok(Some(dir)) => dir,
        Ok(None) => {
            error("dcr.toml file not found");
            return 1;
        }
        Err(_) => {
            error("Failed to find project root");
            return 1;
        }
    };
    let flags = match parse_clean_flags(args) {
        Ok(v) => v,
        Err(msg) => {
            error(&msg);
            return 1;
        }
    };

    match with_dir(&root, || clean_from_root(&root, &flags)) {
        Ok(()) => 0,
        Err(msg) => {
            error(&msg);
            1
        }
    }
}

struct CleanFlags {
    profile: Option<String>,
    target: Option<String>,
    all: bool,
}

fn parse_clean_flags(args: &[String]) -> Result<CleanFlags, String> {
    let mut profile: Option<String> = None;
    let mut target: Option<String> = None;
    let mut all = false;
    let mut iter = args.iter();
    while let Some(arg) = iter.next() {
        if arg == "--all" {
            all = true;
            continue;
        }
        if arg == "--target" {
            if let Some(t) = iter.next() {
                target = Some(t.clone());
            } else {
                return Err("--target requires a value".to_string());
            }
            continue;
        }
        if arg.starts_with("--") {
            let candidate = arg.trim_start_matches("--").to_string();
            if flags(&candidate).is_some() {
                if profile.is_some() {
                    return Err("Duplicate profile flag".to_string());
                }
                profile = Some(candidate);
                continue;
            }
        }
        return Err("Unknown argument".to_string());
    }
    Ok(CleanFlags {
        profile,
        target,
        all,
    })
}

fn clean_from_root(root: &Path, flags: &CleanFlags) -> Result<(), String> {
    let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?;

    let target = flags
        .target
        .clone()
        .or_else(|| {
            config
                .get("build.target")
                .and_then(|v| v.as_str())
                .map(|s| s.trim().to_string())
                .filter(|s| !s.is_empty())
        })
        .or_else(|| {
            Some(if cfg!(target_os = "linux") {
                "x86_64-unknown-linux-gnu".to_string()
            } else if cfg!(target_os = "macos") {
                "x86_64-apple-darwin".to_string()
            } else if cfg!(target_os = "windows") {
                "x86_64-pc-windows-msvc".to_string()
            } else {
                "unknown".to_string()
            })
        });

    if flags.all
        && let Some(workspace) = parse_workspace(
            &config,
            flags.profile.as_deref().unwrap_or("debug"),
            target.as_deref(),
            root,
        )?
    {
        for member in &workspace.members {
            clean_project_at(&member.path, flags.profile.as_deref(), target.as_deref())?;
        }
    }
    clean_project_at(root, flags.profile.as_deref(), target.as_deref())
}

fn clean_project_at(
    project_root: &Path,
    profile: Option<&str>,
    target: Option<&str>,
) -> Result<(), String> {
    with_dir(project_root, || {
        let config = Config::open("./dcr.toml").map_err(|err| err.to_string())?;
        let project_name = std::env::current_dir()
            .ok()
            .and_then(|p| p.file_name().map(|v| v.to_string_lossy().to_string()))
            .unwrap_or_else(|| "project".to_string());
        let items = check_dir(None).map_err(|_| "Failed to read project directory".to_string())?;
        if !items.contains(&"dcr.toml".to_string()) {
            return Err("dcr.toml file not found".to_string());
        }
        println!(
            "    Cleaning project `{}`",
            colored(&project_name, BOLD_GREEN)
        );
        if let Some(profile) = profile {
            let target_dir = if let Some(t) = target {
                format!("target/{t}/{profile}")
            } else {
                format!("target/{profile}")
            };
            let target_items = check_dir(Some("target")).unwrap_or_default();
            let parent_dir = target.unwrap_or("");
            let dir_exists = if target.is_some() {
                target_items.contains(&parent_dir.to_string())
            } else {
                target_items.contains(&profile.to_string())
            };
            if !dir_exists {
                warn(&format!("Directory target/{} not found", target_dir));
            } else {
                println!("    Profile: {}", colored(profile, BOLD_GREEN));
                if let Some(t) = target {
                    println!("    Target: {}", colored(t, BOLD_GREEN));
                }
                let _ = fs::remove_dir_all(&target_dir);
                println!(
                    "{} Removed directory {}",
                    colored("\n    ✔", BOLD_GREEN),
                    target_dir
                );
            }
            clean_custom_paths(&config, profile)?;
            return Ok(());
        }

        if items.contains(&"target".to_string()) {
            let _ = fs::remove_dir_all("target");
            println!(
                "{} Removed directory target",
                colored("\n    ✔", BOLD_GREEN)
            );
        } else {
            warn("Directory target not found");
        }
        clean_custom_paths(&config, "debug")?;
        clean_custom_paths(&config, "release")?;
        Ok(())
    })
}

fn clean_custom_paths(config: &Config, profile: &str) -> Result<(), String> {
    let patterns = match config.get("build.clean") {
        Some(v) => v
            .as_array()
            .ok_or_else(|| "build.clean must be an array of strings".to_string())?
            .iter()
            .filter_map(|item| item.as_str())
            .map(|s| {
                let value = s.replace("{profile}", profile);
                substitute_version_vars(&value, config)
            })
            .collect::<Vec<String>>(),
        None => Vec::new(),
    };
    if patterns.is_empty() {
        return Ok(());
    }
    for pattern in patterns {
        for entry in glob(&pattern).map_err(|err| format!("glob error: {err}"))? {
            let path = entry.map_err(|err| format!("glob error: {err}"))?;
            if path.is_dir() {
                let _ = fs::remove_dir_all(&path);
            } else if path.is_file() {
                let _ = fs::remove_file(&path);
            }
        }
    }
    Ok(())
}

fn substitute_version_vars(value: &str, config: &Config) -> String {
    let version = config
        .get("package.version")
        .and_then(|v| v.as_str())
        .unwrap_or("");
    let info = parse_version_info(version);
    value
        .replace("{version}", &info.full)
        .replace("{version_major}", &info.major)
        .replace("{version_minor}", &info.minor)
        .replace("{version_patch}", &info.patch)
        .replace("{version_suffix}", &info.suffix)
        .replace("{version_suffix_dash}", &info.suffix_dash)
}
