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

use anyhow::{Context, Result};

use crate::overlay::{self, Layer};
use crate::registry::{Dep, Registry};

pub const REGION_START: &str =
    "# >>> rustelo-sync:workspace-deps (managed by `cargo xtask sync-deps` — DO NOT EDIT)";
pub const REGION_END: &str = "# <<< rustelo-sync:workspace-deps";

/// Translates path-deps (stored as rustelo-root-relative strings on `Dep`)
/// into the form needed by a target Cargo.toml.
///
/// - `is_self` (target == rustelo root): emit the stored path as-is.
/// - `rebase = false`: emit the stored path as-is on every target. Used for
///   external paths whose location relative to siblings is identical from
///   each workspace (e.g. `../stratumiops/...`).
/// - Otherwise: resolve to absolute via the rustelo root, then diff against
///   the target's Cargo.toml location.
pub struct Rebase {
    pub rustelo_root: PathBuf,
    pub target_dir: PathBuf,
    pub is_self: bool,
}

impl Rebase {
    pub fn new(rustelo_root: &Path, target_dir: &Path) -> Result<Self> {
        let rustelo_root = rustelo_root
            .canonicalize()
            .with_context(|| format!("canonicalize rustelo_root {}", rustelo_root.display()))?;
        let target_dir = target_dir
            .canonicalize()
            .with_context(|| format!("canonicalize target {}", target_dir.display()))?;
        let is_self = rustelo_root == target_dir;
        Ok(Self {
            rustelo_root,
            target_dir,
            is_self,
        })
    }

    fn translate_path(&self, path: &str, rebase: bool) -> String {
        if self.is_self || !rebase {
            return path.to_string();
        }
        let absolute = self.rustelo_root.join(path);
        match pathdiff::diff_paths(&absolute, &self.target_dir) {
            Some(rel) => rel.to_string_lossy().into_owned(),
            None => path.to_string(),
        }
    }
}

/// Renders the entire managed region (markers included) for a target from an
/// ordered overlay chain (parent→child). Registry-dep features are folded across
/// every layer; extras accumulate down the chain and receive feature additions
/// from any layer that targets them. `validate_chain` has already guaranteed no
/// extra redefinition and no overlay of an unknown name, so accumulation here is
/// insert-once. A one-link chain (or empty chain) reproduces the historical
/// single-overlay (or no-overlay) rendering byte-for-byte.
pub fn render_region(registry: &Registry, layers: &[Layer], rebase: &Rebase) -> String {
    let mut out = String::new();
    out.push_str(REGION_START);
    out.push('\n');

    for (name, dep) in &registry.deps {
        let mut merged = dep.clone();
        for layer in layers {
            if let Some(fo) = layer.overlay.overlay.get(name) {
                merged = overlay::apply_features(&merged, Some(fo));
            }
        }
        out.push_str(&render_entry(name, &merged, rebase));
        out.push('\n');
    }

    let mut extras: indexmap::IndexMap<String, Dep> = indexmap::IndexMap::new();
    for layer in layers {
        for (name, dep) in &layer.overlay.extra {
            extras.insert(name.clone(), dep.clone());
        }
    }
    // Feature additions onto extras (a later layer featuring an upstream extra).
    // Registry-targeting overlays are skipped here — they are not in `extras`.
    for layer in layers {
        for (name, fo) in &layer.overlay.overlay {
            if let Some(dep) = extras.get_mut(name) {
                *dep = overlay::apply_features(dep, Some(fo));
            }
        }
    }
    for (name, dep) in &extras {
        out.push_str(&render_entry(name, dep, rebase));
        out.push('\n');
    }

    out.push_str(REGION_END);
    out
}

fn render_entry(name: &str, dep: &Dep, rebase: &Rebase) -> String {
    if dep.is_simple_inline() {
        let v = dep.version.as_deref().unwrap_or_default();
        return format!("{name} = \"{v}\"");
    }

    let mut parts: Vec<String> = Vec::new();
    if let Some(v) = &dep.version {
        parts.push(format!("version = \"{v}\""));
    }
    if let Some(p) = &dep.path {
        let translated = rebase.translate_path(p, dep.rebase);
        parts.push(format!("path = \"{translated}\""));
    }
    if let Some(df) = dep.default_features {
        parts.push(format!("default-features = {df}"));
    }
    if !dep.features.is_empty() {
        let feats = dep
            .features
            .iter()
            .map(|f| format!("\"{f}\""))
            .collect::<Vec<_>>()
            .join(", ");
        parts.push(format!("features = [{feats}]"));
    }
    format!("{name} = {{ {} }}", parts.join(", "))
}
