//! LLVM/clang link path for the **embedded-bytecode + VM** native binary.
//!
//! Emits a C file that only holds the serialized chunk as bytes; clang produces `chunk.o`,
//! then links with **`tishlang_cranelift_runtime`** (same `tish_run_chunk` + `tishlang_vm`
//! entry as `--native-backend cranelift`). This is **not** LLVM IR lowering of Tish opcodes.

use std::fs;
use std::path::Path;
use std::process::Command;

use tishlang_bytecode::{serialize, Chunk};
use tishlang_compile::{detect_cycles, merge_modules, resolve_project};

/// Compile a Tish program to a native binary via clang/LLVM.
pub fn compile_to_native(
    entry_path: &Path,
    project_root: Option<&Path>,
    output_path: &Path,
) -> Result<(), LlvmError> {
    let modules = resolve_project(entry_path, project_root).map_err(|e| LlvmError {
        message: e.to_string(),
    })?;
    detect_cycles(&modules).map_err(|e| LlvmError {
        message: e.to_string(),
    })?;
    let program = merge_modules(modules)
        .map(|m| m.program)
        .map_err(|e| LlvmError {
            message: e.to_string(),
        })?;
    let chunk = tishlang_bytecode::compile(&program).map_err(|e| LlvmError {
        message: e.to_string(),
    })?;

    let features = tishlang_compile::extract_native_import_features(&program);
    compile_chunk_to_native(&chunk, output_path, &features)
}

/// Compile a bytecode chunk to a native binary.
/// `features` are passed to tishlang_cranelift_runtime (e.g. fs, process, http for built-in modules).
pub fn compile_chunk_to_native(
    chunk: &Chunk,
    output_path: &Path,
    features: &[String],
) -> Result<(), LlvmError> {
    let chunk_bytes = serialize(chunk);
    let out_name = output_path
        .file_stem()
        .and_then(|s| s.to_str())
        .unwrap_or("tish_out");
    let build_dir = tishlang_build_utils::create_build_dir("tishlang_llvm_build", out_name)
        .map_err(|e| LlvmError { message: e })?;

    // Generate C source with embedded bytecode (data only; main comes from Rust link step)
    let bytes_str: Vec<String> = chunk_bytes.iter().map(|b| b.to_string()).collect();
    let chunk_c = format!(
        r#"/* Generated by tishlang_llvm - do not edit */
#include <stddef.h>
#include <stdint.h>

const uint8_t tish_chunk_data[] = {{ {} }};
const uint64_t tish_chunk_len = sizeof(tish_chunk_data);
"#,
        bytes_str.join(",")
    );

    let chunk_c_path = build_dir.join("chunk.c");
    fs::write(&chunk_c_path, chunk_c).map_err(|e| LlvmError {
        message: format!("Cannot write chunk.c: {}", e),
    })?;

    // Compile C to object file using clang (LLVM)
    let object_path = build_dir.join("chunk.o");
    let clang = std::env::var("CLANG").unwrap_or_else(|_| "clang".to_string());
    let status = Command::new(&clang)
        .args(["-c", "-o"])
        .arg(&object_path)
        .arg(&chunk_c_path)
        .status()
        .map_err(|e| LlvmError {
            message: format!(
                "Failed to run clang: {}. Install clang (LLVM) or set CLANG env var.",
                e
            ),
        })?;

    if !status.success() {
        return Err(LlvmError {
            message: "clang compilation failed. Ensure clang (LLVM) is installed.".to_string(),
        });
    }

    // Link using the same infrastructure as Cranelift (links .o with tishlang_cranelift_runtime)
    let object_path_canonical = object_path.canonicalize().map_err(|e| LlvmError {
        message: format!("Cannot canonicalize object path: {}", e),
    })?;
    tishlang_cranelift::link_to_binary(&object_path_canonical, output_path, features)
        .map_err(|e| LlvmError { message: e.message })?;

    Ok(())
}

/// Error from LLVM compilation.
#[derive(Debug)]
pub struct LlvmError {
    pub message: String,
}

impl std::fmt::Display for LlvmError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl std::error::Error for LlvmError {}
