//! release-plz.toml package entry generation.
//!
//! Generates [[package]] entries from workspace metadata and policy overrides.

use crate::autogen::replace_named_block_toml;
use crate::policy::Policy;
use anyhow::Context;
use anyhow::Result;
use cargo_metadata::Metadata;
use std::fmt::Write as _;
use std::fs;

/// Get the changelog path for a package based on its manifest directory.
fn get_changelog_path(pkg: &cargo_metadata::Package, metadata: &Metadata) -> String {
    let manifest_path = pkg.manifest_path.as_std_path();
    let dir = manifest_path.parent().unwrap_or(manifest_path);

    // Make path relative to workspace root
    let ws_root = metadata.workspace_root.as_std_path();
    let relative = dir.strip_prefix(ws_root).map_or_else(
        |_| dir.to_string_lossy().to_string(),
        |p| p.to_string_lossy().to_string(),
    );

    let relative = relative.trim_start_matches('/');
    if relative.is_empty() {
        "CHANGELOG.md".to_string()
    } else {
        format!("{relative}/CHANGELOG.md")
    }
}

/// Render [[package]] entries for all workspace crates.
pub fn render_packages(metadata: &Metadata, policy: &Policy) -> String {
    let mut out = String::new();

    for pkg in &metadata.packages {
        if !metadata.workspace_members.contains(&pkg.id) {
            continue;
        }

        let name = &pkg.name;
        let changelog_path = get_changelog_path(pkg, metadata);

        // Get overrides from policy
        let override_entry = policy.release_plz.overrides.get(name);

        // Determine publish status
        let publish = override_entry
            .and_then(|o| o.publish)
            .unwrap_or(policy.release_plz.publish_default);

        // Determine git tag status
        let git_tag_enable = override_entry
            .and_then(|o| o.git_tag_enable)
            .unwrap_or(true);

        // Build tag name from format (keep {{ version }} as template placeholder)
        let tag_name = policy
            .release_plz
            .git_tag_format
            .replace("{{ name }}", name);

        let git_tag_enable_text = if git_tag_enable { "true" } else { "false" };
        let release_text = if publish { "true" } else { "false" };

        out.push_str("[[package]]\n");
        let _ = writeln!(out, "name = \"{name}\"");
        let _ = writeln!(out, "changelog_path = \"{changelog_path}\"");
        let _ = writeln!(out, "git_tag_name = \"{tag_name}\"");
        let _ = writeln!(out, "git_tag_enable = {git_tag_enable_text}");
        let _ = writeln!(out, "release = {release_text}");
        // Emit publish = false to override workspace default for non-publishable packages
        if !publish {
            out.push_str("publish = false\n");
        }
        out.push('\n');
    }

    out.trim_end().to_string()
}

/// Sync release-plz.toml packages block.
///
/// Returns true if the file was changed.
pub fn sync_release_plz(
    path: &str,
    metadata: &Metadata,
    policy: &Policy,
    dry_run: bool,
    check: bool,
) -> Result<bool> {
    let original = fs::read_to_string(path).with_context(|| format!("Failed to read {path}"))?;

    let body = render_packages(metadata, policy);
    let (updated, changed) = replace_named_block_toml(&original, "release-plz:packages", &body)?;

    if changed {
        if check {
            anyhow::bail!(
                "release-plz.toml packages block is out of date; run `cargo run -p xtask -- sync`"
            );
        }
        if dry_run {
            eprintln!("[sync] Would update {path} (dry-run)");
        } else {
            fs::write(path, &updated).with_context(|| format!("Failed to write {path}"))?;
            eprintln!("[sync] Updated {path}");
        }
    }

    Ok(changed)
}
