/*!
 * Stage-1 CLI bridge — delegate to Hako Stage1 stub when explicitly enabled.
 *
 * - Entry: NYASH_USE_STAGE1_CLI=1 (default OFF).
 * - Toggle guard for child recursion: NYASH_STAGE1_CLI_CHILD=1 (set by bridge).
 * - Entry path override: STAGE1_CLI_ENTRY or HAKORUNE_STAGE1_ENTRY (optional).
 * - Mode toggles:
 *     - STAGE1_EMIT_PROGRAM_JSON=1 : emit program-json <source.hako>
 *     - STAGE1_EMIT_MIR_JSON=1     : emit mir-json (<source.hako> or STAGE1_PROGRAM_JSON)
 *     - STAGE1_BACKEND={vm|llvm|pyvm} hint for run path (default: CLI backend)
 *
 * Notes
 * - This bridge aims to keep Rust Stage0 thin: it only invokes the Stage1 stub
 *   (lang/src/runner/stage1_cli.hako) with script args and exits with the stub's code.
 * - When toggles are unset or this is a child invocation, the bridge is a no-op.
 */

mod args;
mod env;
mod modules;

use super::NyashRunner;
use crate::cli::CliGroups;
use crate::config;
use crate::config::env::stage1;
use crate::mir::MirPrinter;
use crate::runner::stage1_bridge::args::Stage1Args;
use std::io::Write;
use std::path::Path;

impl NyashRunner {
    /// Emit Program(JSON v0) using Stage-1 stub and write to a file.
    pub(crate) fn emit_program_json_v0(
        &self,
        groups: &CliGroups,
        out_path: &str,
    ) -> Result<(), String> {
        // Resolve source path from CLI groups or env
        let source = stage1::input_path()
            .or_else(|| groups.input.file.as_ref().cloned())
            .ok_or_else(|| "emit-program-json requires an input file".to_string())?;

        // Build minimal args to force emit program-json
        let args_result = Stage1Args {
            args: vec!["emit".into(), "program-json".into(), source.clone()],
            env_script_args: None,
            source_env: Some(source.clone()),
            progjson_env: None,
            emit_mir: false,
        };

        // Collect modules list (same as bridge)
        let modules_list = modules::collect_modules_list();

        // Prepare command
        let exe = std::env::current_exe()
            .unwrap_or_else(|_| std::path::PathBuf::from("target/release/nyash"));
        let mut cmd = std::process::Command::new(exe);
        let entry_fn =
            std::env::var("NYASH_ENTRY").unwrap_or_else(|_| "Stage1CliMain.main/0".to_string());
        let entry = stage1::entry_override()
            .unwrap_or_else(|| "lang/src/runner/stage1_cli.hako".to_string());
        cmd.arg(&entry).arg("--");
        for a in &args_result.args {
            cmd.arg(a);
        }
        // Set environment variables for args
        if let Some(src) = args_result.source_env.as_ref() {
            cmd.env("STAGE1_SOURCE", src);
            cmd.env("NYASH_STAGE1_INPUT", src);
        }
        if let Ok(text) = std::fs::read_to_string(&source) {
            cmd.env("STAGE1_SOURCE_TEXT", text);
        }
        cmd.env("NYASH_USE_STAGE1_CLI", "1");
        // Configure environment (shared helper)
        env::configure_stage1_env(&mut cmd, &entry_fn, &args_result.args, modules_list);
        cmd.env("STAGE1_EMIT_PROGRAM_JSON", "1");

        let output = cmd
            .output()
            .map_err(|e| format!("stage1 emit program-json spawn failed: {}", e))?;
        if !output.stderr.is_empty()
            && std::env::var("STAGE1_CLI_DEBUG").ok().as_deref() == Some("1")
        {
            let _ = std::io::stderr().write_all(&output.stderr);
        }
        if !output.status.success() {
            if !output.stdout.is_empty() {
                let _ = std::io::stdout().write_all(&output.stdout);
            }
            if !output.stderr.is_empty() {
                let _ = std::io::stderr().write_all(&output.stderr);
            }
            return Err(format!(
                "stage1 emit program-json exited with code {:?}",
                output.status.code()
            ));
        }
        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let line = crate::runner::modes::common_util::selfhost::json::first_json_v0_line(&stdout)
            .ok_or_else(|| {
            "stage1 emit program-json did not produce Program(JSON v0)".to_string()
        })?;
        std::fs::write(out_path, line).map_err(|e| format!("write {} failed: {}", out_path, e))?;
        Ok(())
    }

    /// If enabled, run the Stage-1 CLI stub as a child process and return its exit code.
    /// Returns None when the bridge is not engaged.
    pub(crate) fn maybe_run_stage1_cli_stub(&self, groups: &CliGroups) -> Option<i32> {
        // Temporary trace: confirm the bridge is evaluated
        if config::env::cli_verbose_level() == 2 {
            eprintln!("[stage1-bridge/trace] maybe_run_stage1_cli_stub invoked");
        }

        // Guard: skip if child invocation
        if stage1::child_invocation() {
            if config::env::cli_verbose_level() == 2 {
                eprintln!("[stage1-bridge/trace] skip: NYASH_STAGE1_CLI_CHILD=1");
            }
            return None;
        }

        // Guard: skip if not enabled
        if !stage1::enabled() {
            if config::env::cli_verbose_level() == 2 {
                eprintln!("[stage1-bridge/trace] skip: NYASH_USE_STAGE1_CLI!=1");
            }
            return None;
        }

        if config::env::cli_verbose() || config::env::cli_verbose_level() == 2 {
            eprintln!("[stage1-bridge/debug] NYASH_USE_STAGE1_CLI=1 detected");
        }

        // Locate Stage-1 CLI entry
        let entry = stage1::entry_override()
            .unwrap_or_else(|| "lang/src/runner/stage1_cli.hako".to_string());
        if !Path::new(&entry).exists() {
            eprintln!("[stage1-cli] entry not found: {}", entry);
            return Some(97);
        }

        // Build args
        let args_result = args::build_stage1_args(groups);

        // Collect modules list
        let modules_list = modules::collect_modules_list();

        // Prepare command
        let exe = std::env::current_exe().unwrap_or_else(|_| {
            // Fallback to release binary path when current_exe is unavailable
            std::path::PathBuf::from("target/release/nyash")
        });
        let mut cmd = std::process::Command::new(exe);
        let entry_fn =
            std::env::var("NYASH_ENTRY").unwrap_or_else(|_| "Stage1CliMain.main/0".to_string());
        cmd.arg(&entry).arg("--");
        for a in &args_result.args {
            cmd.arg(a);
        }

        // Set environment variables for args
        if let Some(json) = args_result.env_script_args.as_ref() {
            cmd.env("NYASH_SCRIPT_ARGS_JSON", json);
        }
        if let Some(src) = args_result.source_env.as_ref() {
            cmd.env("STAGE1_SOURCE", src);
        }
        if let Some(pjson) = args_result.progjson_env.as_ref() {
            cmd.env("STAGE1_PROGRAM_JSON", pjson);
        }

        // Pass source text inline to avoid FileBox dependency when possible.
        if args_result.source_env.is_none() {
            if let Some(src_path) = groups.input.file.as_ref() {
                if let Ok(text) = std::fs::read_to_string(&src_path) {
                    cmd.env("STAGE1_SOURCE_TEXT", text);
                }
            }
        } else if let Some(src_path) = args_result.source_env.as_ref() {
            if let Ok(text) = std::fs::read_to_string(src_path) {
                cmd.env("STAGE1_SOURCE_TEXT", text);
            }
        }

        // Configure environment
        env::configure_stage1_env(&mut cmd, &entry_fn, &args_result.args, modules_list);

        // Emit-mir mode: capture stdout for Program(JSON v0) and lower in Rust to engage maybe_dump_mir.
        if args_result.emit_mir {
            let output = match cmd.output() {
                Ok(o) => o,
                Err(e) => {
                    eprintln!("[stage1-cli] failed to spawn stub: {}", e);
                    return Some(97);
                }
            };
            let code = output.status.code().unwrap_or(1);
            if code != 0 {
                if !output.stderr.is_empty() {
                    let _ = std::io::stderr().write_all(&output.stderr);
                }
                return Some(code);
            }
            let stdout = String::from_utf8_lossy(&output.stdout).to_string();
            let line = match crate::runner::modes::common_util::selfhost::json::first_json_v0_line(
                &stdout,
            ) {
                Some(l) => l,
                None => {
                    eprintln!("[stage1-cli] emit-mir: no Program(JSON v0) found in stub output");
                    return Some(98);
                }
            };
            let module = match super::json_v0_bridge::parse_json_v0_to_module(&line) {
                Ok(m) => m,
                Err(e) => {
                    eprintln!("[stage1-cli] emit-mir: Program(JSON v0) parse error: {}", e);
                    return Some(98);
                }
            };
            super::json_v0_bridge::maybe_dump_mir(&module);

            let groups = self.config.as_groups();
            if groups.debug.dump_mir {
                let mut printer = if groups.debug.mir_verbose {
                    MirPrinter::verbose()
                } else {
                    MirPrinter::new()
                };
                if groups.debug.mir_verbose_effects {
                    printer.set_show_effects_inline(true);
                }
                println!("{}", printer.print_module(&module));
            }
            if let Some(path) = groups.emit.emit_mir_json.as_ref() {
                let p = std::path::Path::new(path);
                if let Err(e) =
                    crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(&module, p)
                {
                    eprintln!("❌ MIR JSON emit error: {}", e);
                    return Some(98);
                }
                println!("MIR JSON written: {}", p.display());
            }
            return Some(0);
        }

        crate::cli_v!(
            "[stage1-cli] delegating to stub: {} -- {}",
            entry,
            args_result.args.join(" ")
        );

        // Execute
        let status = match cmd.status() {
            Ok(s) => s,
            Err(e) => {
                eprintln!("[stage1-cli] failed to spawn stub: {}", e);
                return Some(97);
            }
        };
        Some(status.code().unwrap_or(1))
    }
}
