use boltffi_bindgen::render::python::{PythonEmitter, PythonLowerError, PythonLowerer};

use crate::cli::{CliError, Result};
use crate::commands::generate::generator::{GenerateRequest, LanguageGenerator, ScanPointerWidth};
use crate::config::Target;

pub struct PythonGenerator;

impl PythonGenerator {
    pub(crate) fn generate_from_source_directory(
        config: &crate::config::Config,
        output_override: Option<std::path::PathBuf>,
        source_directory: &std::path::Path,
        crate_name: &str,
    ) -> Result<()> {
        let request = GenerateRequest::new(
            config,
            output_override,
            crate::commands::generate::generator::SourceCrate::new(source_directory, crate_name),
        );

        Self::generate(&request)
    }
}

impl LanguageGenerator for PythonGenerator {
    const TARGET: Target = Target::Python;

    fn generate(request: &GenerateRequest<'_>) -> Result<()> {
        if !request.config().is_python_enabled() {
            return Err(CliError::CommandFailed {
                command: "targets.python.enabled = false".to_string(),
                status: None,
            });
        }

        let output_directory = request
            .output_override()
            .map(ToOwned::to_owned)
            .unwrap_or_else(|| request.config().python_output());

        request.ensure_output_directory(&output_directory)?;

        let lowered_crate = request.lowered_crate(ScanPointerWidth::Host)?;
        let module_name = request.config().python_module_name();
        let python_module = PythonLowerer::new(
            &lowered_crate.ffi_contract,
            &lowered_crate.abi_contract,
            &module_name,
            &request.config().package.name,
            request.config().package_version(),
            &request.config().crate_artifact_name(),
        )
        .lower()
        .map_err(|error| match error {
            PythonLowerError::TopLevelNameCollision { .. }
            | PythonLowerError::ParameterNameCollision { .. }
            | PythonLowerError::RecordFieldNameCollision { .. }
            | PythonLowerError::RecordCallableNameCollision { .. }
            | PythonLowerError::EnumMemberNameCollision { .. }
            | PythonLowerError::EnumCallableNameCollision { .. }
            | PythonLowerError::NativeModuleNameCollision { .. } => CliError::CommandFailed {
                command: format!("generate python: {error}"),
                status: None,
            },
        })?;
        let python_sources = PythonEmitter::emit(&python_module);

        python_sources.files.iter().try_for_each(|output_file| {
            let output_path = output_directory.join(&output_file.relative_path);

            if let Some(parent_directory) = output_path.parent() {
                request.ensure_output_directory(parent_directory)?;
            }

            request.write_output(&output_path, &output_file.contents)
        })
    }
}
