//! Wizard I/O — the ONLY place in `service/wizard/` that may perform side effects.
//!
//! Side effects here: reading/writing `wizard_state.json`, listing directory
//! entries, detecting build tool manifests, and writing step output files.
//! All business logic and branching belong in `mod.rs` or `state_machine.rs`.

use super::Error;
use super::state_machine::ProjectTools;
use crate::io::wizard::WizardState;
use std::path::Path;

/// Load wizard state from disk. Returns `None` if the state file is absent.
pub(super) fn load(root: &Path) -> Option<WizardState> {
    WizardState::load(root)
}

/// Save wizard state to disk.
pub(super) fn save(state: &WizardState, root: &Path) -> Result<(), Error> {
    state.save(root).map_err(Error::PersistenceFailure)
}

/// Return the names of all top-level entries in `root`.
///
/// Used by `state_machine::is_bare_project` to decide whether the project has
/// existing source code. Returns an empty vec if the directory cannot be read.
/// `pub(crate)` so HTTP adapters can read dir names without duplicating logic.
pub(crate) fn list_dir_names(root: &Path) -> Vec<String> {
    std::fs::read_dir(root)
        .ok()
        .map(|entries| {
            entries
                .filter_map(|e| e.ok())
                .map(|e| e.file_name().to_string_lossy().to_string())
                .collect()
        })
        .unwrap_or_default()
}

/// Probe `root` for build tool manifests and return detection flags.
/// `pub(crate)` so HTTP adapters can call this without duplicating detection.
pub(crate) fn detect_project_tools(root: &Path) -> ProjectTools {
    ProjectTools {
        has_cargo: root.join("Cargo.toml").exists(),
        has_pkg: root.join("package.json").exists(),
        has_pnpm: root.join("pnpm-lock.yaml").exists(),
        has_frontend_subdir: root.join("frontend").join("package.json").exists()
            || root.join("client").join("package.json").exists(),
        has_go: root.join("go.mod").exists(),
        has_python: root.join("pyproject.toml").exists() || root.join("requirements.txt").exists(),
    }
}

/// Write `content` to `path`, skipping if the file already has real (non-template) content.
///
/// Scaffold template placeholders (files containing `TEMPLATE_SENTINEL`) are
/// treated as empty and will be overwritten. Returns `Ok(true)` when the file
/// was written, `Ok(false)` when an existing real file was preserved.
/// For script steps, the file is also made executable after writing.
pub(super) fn write_step_file(path: &Path, content: &str, executable: bool) -> Result<bool, Error> {
    use crate::io::onboarding::TEMPLATE_SENTINEL;
    if path.exists() {
        let is_template = std::fs::read_to_string(path)
            .map(|s| s.contains(TEMPLATE_SENTINEL))
            .unwrap_or(false);
        if !is_template {
            return Ok(false);
        }
    }
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent).map_err(|e| {
            Error::PersistenceFailure(format!(
                "Failed to create directory {}: {e}",
                parent.display()
            ))
        })?;
    }
    std::fs::write(path, content).map_err(|e| {
        Error::PersistenceFailure(format!("Failed to write {}: {e}", path.display()))
    })?;

    if executable {
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            let mut perms = std::fs::metadata(path)
                .map_err(|e| Error::PersistenceFailure(format!("Failed to read permissions: {e}")))?
                .permissions();
            perms.set_mode(0o755);
            std::fs::set_permissions(path, perms).map_err(|e| {
                Error::PersistenceFailure(format!("Failed to set permissions: {e}"))
            })?;
        }
    }

    Ok(true)
}
