use std::process::Command;

use crate::build::{OutputCallback, run_command_streaming};
use crate::cli::{CliError, Result};
use crate::pack::{PackError, print_cargo_line};
use crate::reporter::Step;

use super::plan::PythonPackagingPlan;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BuiltPythonSharedLibrary {
    pub source_path: std::path::PathBuf,
}

pub struct PythonSharedLibraryBuilder<'a> {
    plan: &'a PythonPackagingPlan,
}

impl<'a> PythonSharedLibraryBuilder<'a> {
    pub fn new(plan: &'a PythonPackagingPlan) -> Self {
        Self { plan }
    }

    pub fn existing(&self) -> Result<BuiltPythonSharedLibrary> {
        let source_path = self.plan.built_shared_library_path();
        source_path
            .exists()
            .then_some(BuiltPythonSharedLibrary { source_path })
            .ok_or(CliError::FileNotFound(
                self.plan.built_shared_library_path(),
            ))
    }

    pub fn build(&self, step: &Step) -> Result<BuiltPythonSharedLibrary> {
        let verbose = step.is_verbose();
        let on_output: Option<OutputCallback> =
            verbose.then(|| Box::new(|line: &str| print_cargo_line(line)) as OutputCallback);
        let mut command = Command::new("cargo");

        if let Some(toolchain_selector) = self.plan.cargo_context.toolchain_selector.as_deref() {
            command.arg(toolchain_selector);
        }

        command.arg("build");
        command
            .arg("--manifest-path")
            .arg(&self.plan.cargo_context.cargo_manifest_path);

        if let Some(package_selector) = self.plan.cargo_context.package_selector.as_deref() {
            command.arg("-p").arg(package_selector);
        }

        if self.plan.cargo_context.release {
            command.arg("--release");
        }

        command.args(&self.plan.cargo_context.cargo_command_args);

        if !run_command_streaming(&mut command, on_output.as_ref()) {
            return Err(PackError::BuildFailed {
                targets: vec![self.plan.host_platform.canonical_name().to_string()],
            }
            .into());
        }

        self.existing()
    }
}
