/*
 * PyO3 wrapper for Flash Shell Interpreter
 * With REAL Flash Lexer and Parser!
 */

#![allow(dead_code)]

use crate::interpreter::{Interpreter, OutputSink};
use crate::lexer::Lexer;
use crate::parser::Node;
use crate::parser::Parser;
use crate::remote::{RemoteExecutor, RemoteOutput};
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::collections::{HashMap, VecDeque};
use std::io;
use std::io::{BufRead, BufReader, Read, Write};
use std::os::fd::FromRawFd;
use std::process::{Child, Command, Stdio};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, OnceLock};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};

/// Result of executing a shell command
#[pyclass]
#[derive(Debug)]
pub struct ExecResult {
    #[pyo3(get)]
    pub exit_code: i32,
    #[pyo3(get)]
    pub stdout: String,
    #[pyo3(get)]
    pub stderr: String,
}

impl ExecResult {
    pub fn new(exit_code: i32, stdout: String, stderr: String) -> Self {
        Self {
            exit_code,
            stdout,
            stderr,
        }
    }
}

fn _capture_stdio_run<F>(f: F) -> io::Result<(i32, String, String)>
where
    F: FnOnce() -> io::Result<i32>,
{
    let mut out_pipe = [0; 2];
    let mut err_pipe = [0; 2];
    let out_ok = unsafe { libc::pipe(out_pipe.as_mut_ptr()) } == 0;
    let err_ok = unsafe { libc::pipe(err_pipe.as_mut_ptr()) } == 0;
    if !out_ok || !err_ok {
        return f().map(|code| (code, String::new(), String::new()));
    }

    let old_out = unsafe { libc::dup(libc::STDOUT_FILENO) };
    let old_err = unsafe { libc::dup(libc::STDERR_FILENO) };
    if old_out < 0 || old_err < 0 {
        return f().map(|code| (code, String::new(), String::new()));
    }

    unsafe {
        libc::dup2(out_pipe[1], libc::STDOUT_FILENO);
        libc::dup2(err_pipe[1], libc::STDERR_FILENO);
    }

    let run_result = f();
    unsafe {
        libc::fflush(std::ptr::null_mut());
        libc::dup2(old_out, libc::STDOUT_FILENO);
        libc::dup2(old_err, libc::STDERR_FILENO);
        libc::close(old_out);
        libc::close(old_err);
        libc::close(out_pipe[1]);
        libc::close(err_pipe[1]);
    }

    let mut out_s = String::new();
    let mut err_s = String::new();
    unsafe {
        let mut out_f = std::fs::File::from_raw_fd(out_pipe[0]);
        let mut err_f = std::fs::File::from_raw_fd(err_pipe[0]);
        let _ = out_f.read_to_string(&mut out_s);
        let _ = err_f.read_to_string(&mut err_s);
    }

    run_result.map(|code| (code, out_s, err_s))
}

#[derive(Clone)]
struct PyRemoteExecutor {
    py_executor: Py<PyAny>,
}

impl PyRemoteExecutor {
    fn new(executor: &PyAny) -> Self {
        Self {
            py_executor: executor.into_py(executor.py()),
        }
    }
}

fn _sh_quote_arg(s: &str) -> String {
    if s.is_empty() {
        return "''".to_string();
    }
    if !s
        .chars()
        .any(|c| c.is_whitespace() || "'\"\\$`|&;()<>*?![]{}~".contains(c))
    {
        return s.to_string();
    }
    let escaped = s.replace('\'', r"'\''");
    format!("'{}'", escaped)
}

fn _execute_pipeline_sequential<F>(
    commands: &[(&str, &[String])],
    operators: &[&str],
    mut run_one: F,
) -> io::Result<RemoteOutput>
where
    F: FnMut(&str, &[String]) -> io::Result<RemoteOutput>,
{
    let _ = operators;
    let mut last = RemoteOutput {
        exit_code: 0,
        stdout: String::new(),
        stderr: String::new(),
        glob_matches: None,
    };
    for (cmd, args) in commands {
        let out = run_one(cmd, args)?;
        let code = out.exit_code;
        last = out;
        if code != 0 {
            break;
        }
    }
    Ok(last)
}

fn _try_execute_pipeline_via_python(
    exec: &PyAny,
    commands: &[(&str, &[String])],
    operators: &[&str],
    cwd: &str,
) -> io::Result<Option<RemoteOutput>> {
    if !exec.hasattr("execute_pipeline").unwrap_or(false) {
        return Ok(None);
    }
    let py_commands: Vec<(String, Vec<String>)> = commands
        .iter()
        .map(|(cmd, args)| ((*cmd).to_string(), args.to_vec()))
        .collect();
    let py_operators: Vec<String> = operators.iter().map(|op| (*op).to_string()).collect();
    let result = exec
        .call_method1("execute_pipeline", (py_commands, py_operators, cwd))
        .map_err(|e| io::Error::other(e.to_string()))?;
    let stdout: String = result
        .get_item(0)
        .and_then(|x| x.extract())
        .map_err(|e| io::Error::other(e.to_string()))?;
    let stderr: String = result
        .get_item(1)
        .and_then(|x| x.extract())
        .map_err(|e| io::Error::other(e.to_string()))?;
    let exit_code: i32 = result
        .get_item(2)
        .and_then(|x| x.extract())
        .map_err(|e| io::Error::other(e.to_string()))?;
    Ok(Some(RemoteOutput {
        exit_code,
        stdout,
        stderr,
        glob_matches: None,
    }))
}

impl RemoteExecutor for PyRemoteExecutor {
    fn execute(
        &self,
        command: &str,
        args: &[String],
        cwd: &str,
        _env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        Python::with_gil(|py| {
            let exec = self.py_executor.as_ref(py);
            let result = exec
                .call_method1("execute", (command, args.to_vec(), cwd))
                .map_err(|e| io::Error::other(e.to_string()))?;
            let stdout: String = result
                .get_item(0)
                .and_then(|x| x.extract())
                .map_err(|e| io::Error::other(e.to_string()))?;
            let stderr: String = result
                .get_item(1)
                .and_then(|x| x.extract())
                .map_err(|e| io::Error::other(e.to_string()))?;
            let exit_code: i32 = result
                .get_item(2)
                .and_then(|x| x.extract())
                .map_err(|e| io::Error::other(e.to_string()))?;
            Ok(RemoteOutput {
                exit_code,
                stdout,
                stderr,
                glob_matches: None,
            })
        })
    }

    fn execute_ast(
        &self,
        node: &crate::parser::Node,
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        match node {
            crate::parser::Node::Command { name, args, .. } => self.execute(name, args, cwd, env),
            crate::parser::Node::Pipeline { commands } => {
                let mut owned: Vec<(String, Vec<String>)> = Vec::new();
                for n in commands {
                    if let crate::parser::Node::Command { name, args, .. } = n {
                        owned.push((name.clone(), args.clone()));
                    } else {
                        return Err(io::Error::other(
                            "PyRemoteExecutor only supports command pipeline AST",
                        ));
                    }
                }
                let refs: Vec<(&str, &[String])> = owned
                    .iter()
                    .map(|(name, args)| (name.as_str(), args.as_slice()))
                    .collect();
                self.execute_pipeline(&refs, &vec!["|"; refs.len().saturating_sub(1)], cwd, env)
            }
            _ => Err(io::Error::other(
                "PyRemoteExecutor only supports command/pipeline AST",
            )),
        }
    }

    fn glob(&self, patterns: &[String], cwd: &str) -> io::Result<Vec<String>> {
        Python::with_gil(|py| {
            let exec = self.py_executor.as_ref(py);
            let result = exec
                .call_method1("glob", (patterns.to_vec(), cwd))
                .map_err(|e| io::Error::other(e.to_string()))?;
            result
                .extract::<Vec<String>>()
                .map_err(|e| io::Error::other(e.to_string()))
        })
    }

    fn read_file(&self, path: &str) -> io::Result<String> {
        Python::with_gil(|py| {
            let exec = self.py_executor.as_ref(py);
            if exec.hasattr("read_file").unwrap_or(false) {
                let result = exec
                    .call_method1("read_file", (path,))
                    .map_err(|e| io::Error::other(e.to_string()))?;
                result
                    .extract::<String>()
                    .map_err(|e| io::Error::other(e.to_string()))
            } else {
                std::fs::read_to_string(path)
            }
        })
    }

    fn process_subst(
        &self,
        direction: &str,
        command: &str,
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<String> {
        Python::with_gil(|py| {
            let exec = self.py_executor.as_ref(py);
            if exec.hasattr("process_subst").unwrap_or(false) {
                let result = exec
                    .call_method1("process_subst", (direction, command, cwd, env.clone()))
                    .map_err(|e| io::Error::other(e.to_string()))?;
                result
                    .extract::<String>()
                    .map_err(|e| io::Error::other(e.to_string()))
            } else {
                Err(io::Error::other("process_subst not supported"))
            }
        })
    }

    fn execute_pipeline(
        &self,
        commands: &[(&str, &[String])],
        operators: &[&str],
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        if let Some(out) = Python::with_gil(|py| {
            let exec = self.py_executor.as_ref(py);
            _try_execute_pipeline_via_python(exec, commands, operators, cwd)
        })? {
            return Ok(out);
        }
        _execute_pipeline_sequential(commands, operators, |cmd, args| {
            self.execute(cmd, args, cwd, env)
        })
    }
}

#[derive(Clone)]
struct PyStreamingRemoteExecutor {
    py_executor: Py<PyAny>,
    callback: Option<Py<PyAny>>,
    stdout_acc: Arc<Mutex<String>>,
    stderr_acc: Arc<Mutex<String>>,
    exit_emitted: Arc<AtomicBool>,
}

impl PyStreamingRemoteExecutor {
    fn new(executor: &PyAny, callback: Option<&PyAny>) -> Self {
        let py = executor.py();
        Self {
            py_executor: executor.into_py(py),
            callback: callback.map(|cb| cb.into_py(py)),
            stdout_acc: Arc::new(Mutex::new(String::new())),
            stderr_acc: Arc::new(Mutex::new(String::new())),
            exit_emitted: Arc::new(AtomicBool::new(false)),
        }
    }

    fn combined_output(&self) -> (String, String) {
        let out = self
            .stdout_acc
            .lock()
            .map(|s| s.clone())
            .unwrap_or_default();
        let err = self
            .stderr_acc
            .lock()
            .map(|s| s.clone())
            .unwrap_or_default();
        (out, err)
    }

    fn emit_callback(
        &self,
        py: Python<'_>,
        ev_type: &str,
        run_id: &str,
        data: Option<&str>,
        exit_code: Option<i32>,
    ) {
        let Some(cb) = &self.callback else {
            return;
        };
        if ev_type == "exit" {
            self.exit_emitted.store(true, Ordering::SeqCst);
        }
        let d = PyDict::new(py);
        let _ = d.set_item("type", ev_type);
        let _ = d.set_item("run_id", run_id);
        if let Some(s) = data {
            let _ = d.set_item("data", s);
            if ev_type == "stdout" {
                let _ = d.set_item("stdout", s);
            } else if ev_type == "stderr" {
                let _ = d.set_item("stderr", s);
            }
        }
        if let Some(code) = exit_code {
            let _ = d.set_item("exit_code", code);
        }
        let _ = cb.call1(py, (d,));
    }

    fn emit_started_callback(
        &self,
        py: Python<'_>,
        run_id: &str,
        backend_pid: Option<u64>,
        child_pid: Option<u64>,
        command: Option<&str>,
    ) {
        let Some(cb) = &self.callback else {
            return;
        };
        let d = PyDict::new(py);
        let _ = d.set_item("type", "started");
        let _ = d.set_item("run_id", run_id);
        if let Some(v) = backend_pid {
            let _ = d.set_item("backend_pid", v);
        }
        if let Some(v) = child_pid {
            let _ = d.set_item("child_pid", v);
        }
        if let Some(v) = command {
            let _ = d.set_item("command", v);
        }
        let _ = cb.call1(py, (d,));
    }

    fn has_exit_event(&self) -> bool {
        self.exit_emitted.load(Ordering::SeqCst)
    }

    fn emit_synthetic_exit(&self, code: i32) {
        Python::with_gil(|py| self.emit_callback(py, "exit", "builtin-0", None, Some(code)));
    }
}

impl RemoteExecutor for PyStreamingRemoteExecutor {
    fn execute(
        &self,
        command: &str,
        args: &[String],
        cwd: &str,
        _env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        let run_id = Python::with_gil(|py| {
            let exec = self.py_executor.as_ref(py);
            let v = exec
                .call_method1("start_run_argv", (command, args.to_vec(), cwd))
                .map_err(|e| io::Error::other(e.to_string()))?;
            v.extract::<String>()
                .map_err(|e| io::Error::other(e.to_string()))
        })?;

        let mut out = String::new();
        let mut err = String::new();
        let mut exit_code = 1;
        let mut saw_exit = false;
        let mut legacy_retried = false;

        loop {
            let (ev_opt, running) = Python::with_gil(|py| {
                let exec = self.py_executor.as_ref(py);
                let ev = exec
                    .call_method1("poll_event", (0u64,))
                    .ok()
                    .and_then(|v| v.extract::<Option<String>>().ok())
                    .flatten();
                let running = exec
                    .call_method0("is_running")
                    .ok()
                    .and_then(|v| v.extract::<bool>().ok())
                    .unwrap_or(false);
                (ev, running)
            });

            if let Some(ev) = ev_opt {
                if let Ok(v) = serde_json::from_str::<serde_json::Value>(&ev) {
                    let t = v.get("type").and_then(|x| x.as_str()).unwrap_or("");
                    match t {
                        "started" => {
                            let backend_pid = v.get("backend_pid").and_then(|x| x.as_u64());
                            let child_pid = v.get("child_pid").and_then(|x| x.as_u64());
                            let cmd = v.get("command").and_then(|x| x.as_str());
                            Python::with_gil(|py| {
                                self.emit_started_callback(py, &run_id, backend_pid, child_pid, cmd)
                            });
                        }
                        "recv" => {
                            Python::with_gil(|py| {
                                self.emit_callback(py, "recv", &run_id, None, None)
                            });
                        }
                        "dispatch" => {
                            Python::with_gil(|py| {
                                self.emit_callback(py, "dispatch", &run_id, None, None)
                            });
                        }
                        "stdout" => {
                            let s = v.get("data").and_then(|x| x.as_str()).unwrap_or("");
                            out.push_str(s);
                            if let Ok(mut a) = self.stdout_acc.lock() {
                                a.push_str(s);
                            }
                            Python::with_gil(|py| {
                                self.emit_callback(py, "stdout", &run_id, Some(s), None)
                            });
                        }
                        "stderr" => {
                            let s = v.get("data").and_then(|x| x.as_str()).unwrap_or("");
                            err.push_str(s);
                            if let Ok(mut a) = self.stderr_acc.lock() {
                                a.push_str(s);
                            }
                            Python::with_gil(|py| {
                                self.emit_callback(py, "stderr", &run_id, Some(s), None)
                            });
                        }
                        "exit" => {
                            exit_code =
                                v.get("exit_code").and_then(|x| x.as_i64()).unwrap_or(1) as i32;
                            saw_exit = true;
                            Python::with_gil(|py| {
                                self.emit_callback(py, "exit", &run_id, None, Some(exit_code))
                            });
                        }
                        _ => {
                            let err_text = v
                                .get("error")
                                .and_then(|x| x.as_str())
                                .unwrap_or("")
                                .to_ascii_lowercase();
                            if !legacy_retried
                                && err_text.contains("unknown")
                                && err_text.contains("run.start")
                            {
                                let retried = Python::with_gil(|py| {
                                    let exec = self.py_executor.as_ref(py);
                                    exec.call_method1(
                                        "retry_execute_legacy",
                                        (&run_id, command, args.to_vec(), cwd),
                                    )
                                    .ok()
                                    .and_then(|v| v.extract::<bool>().ok())
                                    .unwrap_or(false)
                                });
                                if retried {
                                    legacy_retried = true;
                                    saw_exit = false;
                                    continue;
                                }
                            }
                            if let Some(s) = v.get("stdout").and_then(|x| x.as_str()) {
                                out.push_str(s);
                                if let Ok(mut a) = self.stdout_acc.lock() {
                                    a.push_str(s);
                                }
                                Python::with_gil(|py| {
                                    self.emit_callback(py, "stdout", &run_id, Some(s), None)
                                });
                            }
                            if let Some(s) = v.get("stderr").and_then(|x| x.as_str()) {
                                err.push_str(s);
                                if let Ok(mut a) = self.stderr_acc.lock() {
                                    a.push_str(s);
                                }
                                Python::with_gil(|py| {
                                    self.emit_callback(py, "stderr", &run_id, Some(s), None)
                                });
                            }
                            if let Some(c) = v.get("exit_code").and_then(|x| x.as_i64()) {
                                exit_code = c as i32;
                                saw_exit = true;
                                Python::with_gil(|py| {
                                    self.emit_callback(py, "exit", &run_id, None, Some(exit_code))
                                });
                            }
                        }
                    }
                }
            } else if !running {
                break;
            } else {
                // Avoid tight GIL contention when command is idle but still running (e.g. waiting for stdin).
                std::thread::sleep(Duration::from_millis(20));
            }

            if saw_exit && !running {
                break;
            }
        }

        Python::with_gil(|py| {
            let exec = self.py_executor.as_ref(py);
            let _ = exec.call_method1("flush", (&run_id, 50u64));
            let _ = exec.call_method0("close_run");
        });

        Ok(RemoteOutput {
            exit_code,
            stdout: out,
            stderr: err,
            glob_matches: None,
        })
    }

    fn execute_ast(
        &self,
        node: &crate::parser::Node,
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        match node {
            crate::parser::Node::Command { name, args, .. } => self.execute(name, args, cwd, env),
            crate::parser::Node::Pipeline { commands } => {
                let mut owned: Vec<(String, Vec<String>)> = Vec::new();
                for n in commands {
                    if let crate::parser::Node::Command { name, args, .. } = n {
                        owned.push((name.clone(), args.clone()));
                    } else {
                        return Err(io::Error::other(
                            "PyStreamingRemoteExecutor only supports command pipeline AST",
                        ));
                    }
                }
                let refs: Vec<(&str, &[String])> = owned
                    .iter()
                    .map(|(name, args)| (name.as_str(), args.as_slice()))
                    .collect();
                self.execute_pipeline(&refs, &vec!["|"; refs.len().saturating_sub(1)], cwd, env)
            }
            _ => Err(io::Error::other(
                "PyStreamingRemoteExecutor only supports command/pipeline AST",
            )),
        }
    }

    fn glob(&self, patterns: &[String], cwd: &str) -> io::Result<Vec<String>> {
        Python::with_gil(|py| {
            let exec = self.py_executor.as_ref(py);
            let result = exec
                .call_method1("glob", (patterns.to_vec(), cwd))
                .map_err(|e| io::Error::other(e.to_string()))?;
            result
                .extract::<Vec<String>>()
                .map_err(|e| io::Error::other(e.to_string()))
        })
    }

    fn read_file(&self, path: &str) -> io::Result<String> {
        Python::with_gil(|py| {
            let exec = self.py_executor.as_ref(py);
            if exec.hasattr("read_file").unwrap_or(false) {
                let result = exec
                    .call_method1("read_file", (path,))
                    .map_err(|e| io::Error::other(e.to_string()))?;
                result
                    .extract::<String>()
                    .map_err(|e| io::Error::other(e.to_string()))
            } else {
                std::fs::read_to_string(path)
            }
        })
    }

    fn process_subst(
        &self,
        direction: &str,
        command: &str,
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<String> {
        Python::with_gil(|py| {
            let exec = self.py_executor.as_ref(py);
            if exec.hasattr("process_subst").unwrap_or(false) {
                let result = exec
                    .call_method1("process_subst", (direction, command, cwd, env.clone()))
                    .map_err(|e| io::Error::other(e.to_string()))?;
                result
                    .extract::<String>()
                    .map_err(|e| io::Error::other(e.to_string()))
            } else {
                Err(io::Error::other("process_subst not supported"))
            }
        })
    }

    fn execute_pipeline(
        &self,
        commands: &[(&str, &[String])],
        operators: &[&str],
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        if let Some(out) = Python::with_gil(|py| {
            let exec = self.py_executor.as_ref(py);
            _try_execute_pipeline_via_python(exec, commands, operators, cwd)
        })? {
            let run_id = "builtin-0";
            if !out.stdout.is_empty() {
                if let Ok(mut a) = self.stdout_acc.lock() {
                    a.push_str(&out.stdout);
                }
                Python::with_gil(|py| {
                    self.emit_callback(py, "stdout", run_id, Some(&out.stdout), None)
                });
            }
            if !out.stderr.is_empty() {
                if let Ok(mut a) = self.stderr_acc.lock() {
                    a.push_str(&out.stderr);
                }
                Python::with_gil(|py| {
                    self.emit_callback(py, "stderr", run_id, Some(&out.stderr), None)
                });
            }
            Python::with_gil(|py| {
                self.emit_callback(py, "exit", run_id, None, Some(out.exit_code))
            });
            return Ok(out);
        }
        _execute_pipeline_sequential(commands, operators, |cmd, args| {
            self.execute(cmd, args, cwd, env)
        })
    }
}

#[derive(Clone)]
struct NativeStreamingRemoteExecutor {
    state: Arc<Mutex<ExecutorState>>,
    callback: Option<Py<PyAny>>,
    stdout_acc: Arc<Mutex<String>>,
    stderr_acc: Arc<Mutex<String>>,
    exit_emitted: Arc<AtomicBool>,
}

impl NativeStreamingRemoteExecutor {
    fn new(state: Arc<Mutex<ExecutorState>>, callback: Option<&PyAny>) -> Self {
        let py = callback.map(|c| c.py());
        Self {
            state,
            callback: callback.map(|cb| cb.into_py(cb.py())),
            stdout_acc: Arc::new(Mutex::new(String::new())),
            stderr_acc: Arc::new(Mutex::new(String::new())),
            exit_emitted: Arc::new(AtomicBool::new(false)),
        }
    }

    fn combined_output(&self) -> (String, String) {
        let out = self
            .stdout_acc
            .lock()
            .map(|s| s.clone())
            .unwrap_or_default();
        let err = self
            .stderr_acc
            .lock()
            .map(|s| s.clone())
            .unwrap_or_default();
        (out, err)
    }

    fn emit_callback(
        &self,
        py: Python<'_>,
        ev_type: &str,
        run_id: &str,
        data: Option<&str>,
        exit_code: Option<i32>,
    ) {
        let Some(cb) = &self.callback else {
            return;
        };
        if ev_type == "exit" {
            self.exit_emitted.store(true, Ordering::SeqCst);
        }
        let d = PyDict::new(py);
        let _ = d.set_item("type", ev_type);
        let _ = d.set_item("run_id", run_id);
        if let Some(s) = data {
            let _ = d.set_item("data", s);
            if ev_type == "stdout" {
                let _ = d.set_item("stdout", s);
            } else if ev_type == "stderr" {
                let _ = d.set_item("stderr", s);
            }
        }
        if let Some(code) = exit_code {
            let _ = d.set_item("exit_code", code);
        }
        let _ = cb.call1(py, (d,));
    }

    fn emit_started_callback(
        &self,
        py: Python<'_>,
        run_id: &str,
        backend_pid: Option<u64>,
        child_pid: Option<u64>,
        command: Option<&str>,
    ) {
        let Some(cb) = &self.callback else {
            return;
        };
        let d = PyDict::new(py);
        let _ = d.set_item("type", "started");
        let _ = d.set_item("run_id", run_id);
        if let Some(v) = backend_pid {
            let _ = d.set_item("backend_pid", v);
        }
        if let Some(v) = child_pid {
            let _ = d.set_item("child_pid", v);
        }
        if let Some(v) = command {
            let _ = d.set_item("command", v);
        }
        let _ = cb.call1(py, (d,));
    }

    fn has_exit_event(&self) -> bool {
        self.exit_emitted.load(Ordering::SeqCst)
    }

    fn emit_synthetic_exit(&self, code: i32) {
        Python::with_gil(|py| self.emit_callback(py, "exit", "builtin-0", None, Some(code)));
    }
}

impl RemoteExecutor for NativeStreamingRemoteExecutor {
    fn execute(
        &self,
        command: &str,
        args: &[String],
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        // Prefer run.ast path for stdio backend to avoid run.start compatibility fallback latency.
        let ast = serde_json::to_value(crate::parser::Node::Command {
            name: command.to_string(),
            args: args.to_vec(),
            redirects: vec![],
        })
        .map_err(|e| io::Error::other(e.to_string()))?;
        let run_id = _start_backend_ast(&self.state, ast, cwd.to_string(), env.clone())
            .map_err(|e| io::Error::other(e.to_string()))?;

        let mut out = String::new();
        let mut err = String::new();
        let mut exit_code = 1;
        let mut saw_exit = false;

        loop {
            let ev_opt = _poll_event_impl(&self.state, 0);
            let running = if let Ok(st) = self.state.lock() {
                if let Some(ar) = &st.active {
                    ar.running.load(Ordering::SeqCst)
                } else {
                    false
                }
            } else {
                false
            };

            if let Some(ev) = ev_opt {
                if let Ok(v) = serde_json::from_str::<serde_json::Value>(&ev) {
                    let t = v.get("type").and_then(|x| x.as_str()).unwrap_or("");
                    match t {
                        "started" => {
                            let backend_pid = v.get("backend_pid").and_then(|x| x.as_u64());
                            let child_pid = v.get("child_pid").and_then(|x| x.as_u64());
                            let cmd = v.get("command").and_then(|x| x.as_str());
                            Python::with_gil(|py| {
                                self.emit_started_callback(py, &run_id, backend_pid, child_pid, cmd)
                            });
                        }
                        "recv" => {
                            Python::with_gil(|py| {
                                self.emit_callback(py, "recv", &run_id, None, None)
                            });
                        }
                        "dispatch" => {
                            Python::with_gil(|py| {
                                self.emit_callback(py, "dispatch", &run_id, None, None)
                            });
                        }
                        "stdout" => {
                            let s = v.get("data").and_then(|x| x.as_str()).unwrap_or("");
                            out.push_str(s);
                            if let Ok(mut a) = self.stdout_acc.lock() {
                                a.push_str(s);
                            }
                            Python::with_gil(|py| {
                                self.emit_callback(py, "stdout", &run_id, Some(s), None)
                            });
                        }
                        "stderr" => {
                            let s = v.get("data").and_then(|x| x.as_str()).unwrap_or("");
                            err.push_str(s);
                            if let Ok(mut a) = self.stderr_acc.lock() {
                                a.push_str(s);
                            }
                            Python::with_gil(|py| {
                                self.emit_callback(py, "stderr", &run_id, Some(s), None)
                            });
                        }
                        "exit" => {
                            exit_code =
                                v.get("exit_code").and_then(|x| x.as_i64()).unwrap_or(1) as i32;
                            saw_exit = true;
                            Python::with_gil(|py| {
                                self.emit_callback(py, "exit", &run_id, None, Some(exit_code))
                            });
                        }
                        _ => {
                            if let Some(s) = v.get("stdout").and_then(|x| x.as_str()) {
                                out.push_str(s);
                                if let Ok(mut a) = self.stdout_acc.lock() {
                                    a.push_str(s);
                                }
                                Python::with_gil(|py| {
                                    self.emit_callback(py, "stdout", &run_id, Some(s), None)
                                });
                            }
                            if let Some(s) = v.get("stderr").and_then(|x| x.as_str()) {
                                err.push_str(s);
                                if let Ok(mut a) = self.stderr_acc.lock() {
                                    a.push_str(s);
                                }
                                Python::with_gil(|py| {
                                    self.emit_callback(py, "stderr", &run_id, Some(s), None)
                                });
                            }
                            if let Some(c) = v.get("exit_code").and_then(|x| x.as_i64()) {
                                exit_code = c as i32;
                                saw_exit = true;
                                Python::with_gil(|py| {
                                    self.emit_callback(py, "exit", &run_id, None, Some(exit_code))
                                });
                            }
                        }
                    }
                }
            } else if !running {
                break;
            } else {
                std::thread::sleep(Duration::from_millis(20));
            }

            if saw_exit && !running {
                break;
            }
        }

        {
            let mut st = self
                .state
                .lock()
                .map_err(|_| io::Error::other("state lock poisoned"))?;
            if let Some(ar) = &st.active {
                ar.running.store(false, Ordering::SeqCst);
            }
        }

        Ok(RemoteOutput {
            exit_code,
            stdout: out,
            stderr: err,
            glob_matches: None,
        })
    }

    fn execute_ast(
        &self,
        node: &crate::parser::Node,
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        let ast_json = serde_json::to_value(node).map_err(|e| io::Error::other(e.to_string()))?;
        let run_id = _start_backend_ast(&self.state, ast_json, cwd.to_string(), env.clone())
            .map_err(|e| io::Error::other(e.to_string()))?;

        let mut out = String::new();
        let mut err = String::new();
        let mut exit_code = 1;
        let mut saw_exit = false;

        loop {
            let ev_opt = _poll_event_impl(&self.state, 0);
            let running = if let Ok(st) = self.state.lock() {
                if let Some(ar) = &st.active {
                    ar.running.load(Ordering::SeqCst)
                } else {
                    false
                }
            } else {
                false
            };

            if let Some(ev) = ev_opt {
                if let Ok(v) = serde_json::from_str::<serde_json::Value>(&ev) {
                    let t = v.get("type").and_then(|x| x.as_str()).unwrap_or("");
                    match t {
                        "started" => {
                            let backend_pid = v.get("backend_pid").and_then(|x| x.as_u64());
                            let child_pid = v.get("child_pid").and_then(|x| x.as_u64());
                            let cmd = v.get("command").and_then(|x| x.as_str());
                            Python::with_gil(|py| {
                                self.emit_started_callback(py, &run_id, backend_pid, child_pid, cmd)
                            });
                        }
                        "recv" => {
                            Python::with_gil(|py| {
                                self.emit_callback(py, "recv", &run_id, None, None)
                            });
                        }
                        "dispatch" => {
                            Python::with_gil(|py| {
                                self.emit_callback(py, "dispatch", &run_id, None, None)
                            });
                        }
                        "stdout" => {
                            let s = v.get("data").and_then(|x| x.as_str()).unwrap_or("");
                            out.push_str(s);
                            if let Ok(mut a) = self.stdout_acc.lock() {
                                a.push_str(s);
                            }
                            Python::with_gil(|py| {
                                self.emit_callback(py, "stdout", &run_id, Some(s), None)
                            });
                        }
                        "stderr" => {
                            let s = v.get("data").and_then(|x| x.as_str()).unwrap_or("");
                            err.push_str(s);
                            if let Ok(mut a) = self.stderr_acc.lock() {
                                a.push_str(s);
                            }
                            Python::with_gil(|py| {
                                self.emit_callback(py, "stderr", &run_id, Some(s), None)
                            });
                        }
                        "exit" => {
                            exit_code =
                                v.get("exit_code").and_then(|x| x.as_i64()).unwrap_or(1) as i32;
                            saw_exit = true;
                            Python::with_gil(|py| {
                                self.emit_callback(py, "exit", &run_id, None, Some(exit_code))
                            });
                        }
                        _ => {
                            if let Some(s) = v.get("stdout").and_then(|x| x.as_str()) {
                                out.push_str(s);
                                if let Ok(mut a) = self.stdout_acc.lock() {
                                    a.push_str(s);
                                }
                                Python::with_gil(|py| {
                                    self.emit_callback(py, "stdout", &run_id, Some(s), None)
                                });
                            }
                            if let Some(s) = v.get("stderr").and_then(|x| x.as_str()) {
                                err.push_str(s);
                                if let Ok(mut a) = self.stderr_acc.lock() {
                                    a.push_str(s);
                                }
                                Python::with_gil(|py| {
                                    self.emit_callback(py, "stderr", &run_id, Some(s), None)
                                });
                            }
                            if let Some(c) = v.get("exit_code").and_then(|x| x.as_i64()) {
                                exit_code = c as i32;
                                saw_exit = true;
                                Python::with_gil(|py| {
                                    self.emit_callback(py, "exit", &run_id, None, Some(exit_code))
                                });
                            }
                        }
                    }
                }
            } else if !running {
                break;
            } else {
                std::thread::sleep(Duration::from_millis(20));
            }

            if saw_exit && !running {
                break;
            }
        }

        {
            let mut st = self
                .state
                .lock()
                .map_err(|_| io::Error::other("state lock poisoned"))?;
            if let Some(ar) = &st.active {
                ar.running.store(false, Ordering::SeqCst);
            }
        }

        Ok(RemoteOutput {
            exit_code,
            stdout: out,
            stderr: err,
            glob_matches: None,
        })
    }

    fn glob(&self, patterns: &[String], cwd: &str) -> io::Result<Vec<String>> {
        use glob::glob;
        use std::path::Path;
        let mut matches = Vec::new();
        for pattern in patterns {
            let full_pattern = Path::new(cwd).join(pattern);
            if let Ok(entries) = glob(&full_pattern.to_string_lossy()) {
                for entry in entries.flatten() {
                    matches.push(entry.to_string_lossy().to_string());
                }
            }
        }
        Ok(matches)
    }

    fn read_file(&self, path: &str) -> io::Result<String> {
        std::fs::read_to_string(path)
    }

    fn process_subst(
        &self,
        _direction: &str,
        _command: &str,
        _cwd: &str,
        _env: &HashMap<String, String>,
    ) -> io::Result<String> {
        Err(io::Error::other("process_subst not supported"))
    }

    fn execute_pipeline(
        &self,
        commands: &[(&str, &[String])],
        operators: &[&str],
        cwd: &str,
        env: &HashMap<String, String>,
    ) -> io::Result<RemoteOutput> {
        _execute_pipeline_sequential(commands, operators, |cmd, args| {
            self.execute(cmd, args, cwd, env)
        })
    }
}

#[derive(Clone)]
struct AccumOutputSink {
    stdout_acc: Arc<Mutex<String>>,
    stderr_acc: Arc<Mutex<String>>,
    callback: Option<Py<PyAny>>,
    run_id: String,
}

impl AccumOutputSink {
    fn new(
        stdout_acc: Arc<Mutex<String>>,
        stderr_acc: Arc<Mutex<String>>,
        callback: Option<Py<PyAny>>,
    ) -> Self {
        Self {
            stdout_acc,
            stderr_acc,
            callback,
            run_id: "builtin-0".to_string(),
        }
    }

    fn emit_callback(&self, ev_type: &str, data: &str) {
        let Some(cb) = &self.callback else {
            return;
        };
        Python::with_gil(|py| {
            let d = PyDict::new(py);
            let _ = d.set_item("type", ev_type);
            let _ = d.set_item("run_id", &self.run_id);
            let _ = d.set_item("data", data);
            if ev_type == "stdout" {
                let _ = d.set_item("stdout", data);
            } else if ev_type == "stderr" {
                let _ = d.set_item("stderr", data);
            }
            let _ = cb.call1(py, (d,));
        });
    }
}

impl OutputSink for AccumOutputSink {
    fn stdout(&self, data: &str) {
        if let Ok(mut s) = self.stdout_acc.lock() {
            s.push_str(data);
        }
        self.emit_callback("stdout", data);
    }

    fn stderr(&self, data: &str) {
        if let Ok(mut s) = self.stderr_acc.lock() {
            s.push_str(data);
        }
        self.emit_callback("stderr", data);
    }
}

/// Convert Flash AST Node to JSON string
fn node_to_json(node: &Node) -> String {
    use Node::*;

    fn node_to_value(node: &Node) -> serde_json::Value {
        match node {
            Command {
                name,
                args,
                redirects,
            } => json!({
                "type": "Command",
                "name": name,
                "args": args,
                "redirects": redirects.len()
            }),
            Pipeline { commands } => json!({
                "type": "Pipeline",
                "commands": commands.iter().map(node_to_value).collect::<Vec<_>>()
            }),
            List {
                statements,
                operators,
            } => json!({
                "type": "List",
                "statements": statements.iter().map(node_to_value).collect::<Vec<_>>(),
                "operators": operators
            }),
            Assignment { name, value } => json!({
                "type": "Assignment",
                "name": name,
                "value": node_to_value(value)
            }),
            StringLiteral(s) => json!(s),
            SingleQuotedString(s) => json!(s),
            IfStatement {
                condition,
                consequence,
                alternative,
            } => json!({
                "type": "IfStatement",
                "condition": node_to_value(condition),
                "consequence": node_to_value(consequence),
                "alternative": alternative.as_ref().map(|n| node_to_value(n))
            }),
            ForLoop {
                variable,
                iterable,
                body,
            } => json!({
                "type": "ForLoop",
                "variable": variable,
                "iterable": node_to_value(iterable),
                "body": node_to_value(body)
            }),
            WhileLoop { condition, body } => json!({
                "type": "WhileLoop",
                "condition": node_to_value(condition),
                "body": node_to_value(body)
            }),
            CaseStatement {
                expression,
                patterns,
            } => json!({
                "type": "CaseStatement",
                "expression": node_to_value(expression),
                "patterns": patterns.len()
            }),
            Function { name, body } => json!({
                "type": "Function",
                "name": name,
                "body": node_to_value(body)
            }),
            Subshell { list } => json!({
                "type": "Subshell",
                "list": node_to_value(list)
            }),
            CommandSubstitution { command } => json!({
                "type": "CommandSubstitution",
                "command": node_to_value(command)
            }),
            ArithmeticExpansion { expression } => json!({
                "type": "ArithmeticExpansion",
                "expression": expression
            }),
            ArithmeticCommand { expression } => json!({
                "type": "ArithmeticCommand",
                "expression": expression
            }),
            Comment(s) => json!({
                "type": "Comment",
                "text": s
            }),
            _ => json!({
                "type": format!("{:?}", node).split('{').next().unwrap_or("Unknown")
            }),
        }
    }

    serde_json::to_string(&node_to_value(node))
        .unwrap_or_else(|_| "{\"type\":\"SerializationError\"}".to_string())
}

/// Flash Interpreter wrapper for Python
#[pyclass]
pub struct FlashInterpreter {
    #[pyo3(get, set)]
    pub cwd: String,
    #[pyo3(get, set)]
    pub debug: bool,
    state_synced: bool,
}

#[pymethods]
impl FlashInterpreter {
    #[new]
    fn new(_py: Python) -> PyResult<Self> {
        Ok(Self {
            cwd: std::env::current_dir()
                .map(|p| p.to_string_lossy().to_string())
                .unwrap_or_else(|_| "/tmp".to_string()),
            debug: false,
            state_synced: false,
        })
    }

    /// Parse input using Flash's REAL parser!
    /// Returns AST as JSON string
    fn parse(&self, input: &str) -> String {
        let lexer = Lexer::new(input);
        let mut parser = Parser::new(lexer);

        // Use parse_script to handle multiple statements
        let node = parser.parse_script();

        node_to_json(&node)
    }

    /// Execute a single command via executor (internal helper)
    fn exec_cmd(
        &self,
        executor: &PyAny,
        name: &str,
        args: Vec<String>,
    ) -> PyResult<(String, String, i32)> {
        let result = executor.call_method1("execute", (name, args, self.cwd.as_str()))?;

        let stdout: String = result.get_item(0)?.extract()?;
        let stderr: String = result.get_item(1)?.extract()?;
        let exit_code: i32 = result.get_item(2)?.extract()?;

        Ok((stdout, stderr, exit_code))
    }
    /// Run shell with streaming events. Executor is expected to implement:
    /// start_run_argv(command, args, cwd) -> run_id
    /// poll_event(timeout_ms) -> Optional[str(json)]
    /// is_running() -> bool
    /// flush(run_id, timeout_ms) -> bool
    fn run_shell_stream(
        &mut self,
        py: Python,
        input: &str,
        executor: &PyAny,
        callback: Option<&PyAny>,
    ) -> PyResult<ExecResult> {
        if !self.state_synced {
            self._sync_initial_state_from_executor(executor);
            self.state_synced = true;
        }

        if let Some(state) = self._extract_native_executor_state(executor) {
            if let Ok(env_any) = executor.call_method0("get_env") {
                if let Ok(executor_env) = env_any.extract::<HashMap<String, String>>() {
                    let mut interp = Interpreter::new();
                    interp.variables = executor_env;
                    interp.variables.insert("PWD".to_string(), self.cwd.clone());
                    interp.last_exit_code = 0;
                    interp.set_suppress_remote_output(true);
                    let streaming_exec =
                        Arc::new(NativeStreamingRemoteExecutor::new(state, callback));
                    interp.set_remote_executor(streaming_exec.clone());
                    let callback_py = callback.map(|cb| cb.into_py(py));
                    interp.set_output_sink(Arc::new(AccumOutputSink::new(
                        streaming_exec.stdout_acc.clone(),
                        streaming_exec.stderr_acc.clone(),
                        callback_py,
                    )));

                    let code = py.allow_threads(|| match interp.execute(input) {
                        Ok(c) => c,
                        Err(_) => 1,
                    });
                    if callback.is_some() && !streaming_exec.has_exit_event() {
                        streaming_exec.emit_synthetic_exit(code);
                    }
                    interp.clear_output_sink();

                    self.cwd = interp
                        .variables
                        .get("PWD")
                        .cloned()
                        .unwrap_or_else(|| self.cwd.clone());
                    let (out, err) = streaming_exec.combined_output();
                    return Ok(ExecResult::new(code, out, err));
                }
            }
        }

        // Preferred path: interpreter.rs + streaming executor bridge.
        if let Ok(env_any) = executor.call_method0("get_env") {
            if let Ok(executor_env) = env_any.extract::<HashMap<String, String>>() {
                let mut interp = Interpreter::new();
                interp.variables = executor_env;
                interp.variables.insert("PWD".to_string(), self.cwd.clone());
                interp.last_exit_code = 0;
                interp.set_suppress_remote_output(true);
                let streaming_exec = Arc::new(PyStreamingRemoteExecutor::new(executor, callback));
                interp.set_remote_executor(streaming_exec.clone());
                let callback_py = callback.map(|cb| cb.into_py(py));
                interp.set_output_sink(Arc::new(AccumOutputSink::new(
                    streaming_exec.stdout_acc.clone(),
                    streaming_exec.stderr_acc.clone(),
                    callback_py,
                )));

                let code = py.allow_threads(|| match interp.execute(input) {
                    Ok(c) => c,
                    Err(_) => 1,
                });
                if callback.is_some() && !streaming_exec.has_exit_event() {
                    streaming_exec.emit_synthetic_exit(code);
                }
                interp.clear_output_sink();

                self.cwd = interp
                    .variables
                    .get("PWD")
                    .cloned()
                    .unwrap_or_else(|| self.cwd.clone());
                let (out, err) = streaming_exec.combined_output();
                return Ok(ExecResult::new(code, out, err));
            }
        }

        // Fallback: legacy AST streaming path.
        let lexer = Lexer::new(input);
        let mut parser = Parser::new(lexer);
        let ast = parser.parse_script();
        self._run_stream_node(py, executor, &ast, callback)
    }

    fn send_stdin(&self, executor: &PyAny, run_id: &str, data: &str) -> PyResult<bool> {
        let v = executor.call_method1("send_stdin", (run_id, data))?;
        Ok(v.extract::<bool>().unwrap_or(false))
    }

    /// Build prompt string using flash interpreter prompt logic.
    /// When executor is provided, it uses executor initial env/cwd context.
    fn build_prompt(&mut self, executor: Option<&PyAny>) -> PyResult<String> {
        let mut env_map = _build_prompt_init_env(false);

        if let Some(exec) = executor {
            if !self.state_synced {
                self._sync_initial_state_from_executor(exec);
                self.state_synced = true;
            }
            if let Ok(env_any) = exec.call_method0("get_env") {
                if let Ok(executor_env) = env_any.extract::<HashMap<String, String>>() {
                    env_map = executor_env;
                }
            }
        }

        let mut interp = Interpreter::new();
        interp.variables = env_map;
        interp.variables.insert("PWD".to_string(), self.cwd.clone());
        Ok(interp.get_prompt())
    }
}

impl FlashInterpreter {
    fn _extract_native_executor_state(
        &self,
        executor: &PyAny,
    ) -> Option<Arc<Mutex<ExecutorState>>> {
        if let Ok(stdio) = executor.extract::<pyo3::PyRef<StdioExecutor>>() {
            return Some(stdio.state.clone());
        }
        None
    }

    fn _sync_initial_state_from_executor(&mut self, executor: &PyAny) {
        if let Ok(state_any) = executor.call_method0("get_initial_state") {
            if let Ok((cwd, _env)) = state_any.extract::<(String, HashMap<String, String>)>() {
                self.cwd = cwd;
                return;
            }
        }
        if let Ok(cwd_any) = executor.call_method0("get_cwd") {
            if let Ok(cwd) = cwd_any.extract::<String>() {
                self.cwd = cwd;
            }
        }
    }

    fn _emit_stream_callback(&self, py: Python, callback: &PyAny, ev_json: &serde_json::Value) {
        let d = PyDict::new(py);
        if let Some(t) = ev_json.get("type").and_then(|v| v.as_str()) {
            let _ = d.set_item("type", t);
        }
        if let Some(v) = ev_json.get("run_id").and_then(|v| v.as_str()) {
            let _ = d.set_item("run_id", v);
        }
        if let Some(v) = ev_json.get("data").and_then(|v| v.as_str()) {
            let _ = d.set_item("data", v);
        }
        if let Some(v) = ev_json.get("stdout").and_then(|v| v.as_str()) {
            let _ = d.set_item("stdout", v);
        }
        if let Some(v) = ev_json.get("stderr").and_then(|v| v.as_str()) {
            let _ = d.set_item("stderr", v);
        }
        if let Some(v) = ev_json.get("exit_code").and_then(|v| v.as_i64()) {
            let _ = d.set_item("exit_code", v as i32);
        }
        if let Some(v) = ev_json.get("error").and_then(|v| v.as_str()) {
            let _ = d.set_item("error", v);
        }
        let _ = callback.call1((d,));
    }

    fn _stream_command_argv(
        &self,
        py: Python,
        executor: &PyAny,
        name: &str,
        args: Vec<String>,
        callback: Option<&PyAny>,
    ) -> PyResult<ExecResult> {
        let run_id: String = executor
            .call_method1("start_run_argv", (name, args, self.cwd.as_str()))?
            .extract()?;
        let mut out = String::new();
        let mut err = String::new();
        let mut exit_code = 0;
        let mut saw_exit = false;

        loop {
            // Keep Python-side calls non-blocking so we can release the GIL while waiting.
            let polled = executor.call_method1("poll_event", (0u64,))?;
            if polled.is_none() {
                let running = executor
                    .call_method0("is_running")
                    .ok()
                    .and_then(|v| v.extract::<bool>().ok())
                    .unwrap_or(false);
                if !running {
                    break;
                }
                // Reduce polling frequency to keep PTK responsive when command is idle.
                py.allow_threads(|| std::thread::sleep(Duration::from_millis(20)));
                continue;
            }

            let ev_line: String = polled.extract()?;
            let ev_json: serde_json::Value = serde_json::from_str(&ev_line).unwrap_or_else(|_| {
                json!({
                    "type": "raw",
                    "data": ev_line,
                })
            });

            if let Some(cb) = callback {
                self._emit_stream_callback(py, cb, &ev_json);
            }

            let ev_type = ev_json.get("type").and_then(|v| v.as_str()).unwrap_or("");
            if ev_type == "stdout" {
                if let Some(v) = ev_json
                    .get("data")
                    .and_then(|v| v.as_str())
                    .or_else(|| ev_json.get("stdout").and_then(|v| v.as_str()))
                {
                    out.push_str(v);
                }
            } else if ev_type == "stderr" {
                if let Some(v) = ev_json
                    .get("data")
                    .and_then(|v| v.as_str())
                    .or_else(|| ev_json.get("stderr").and_then(|v| v.as_str()))
                {
                    err.push_str(v);
                }
            } else if ev_type == "exit" {
                if let Some(v) = ev_json.get("exit_code").and_then(|v| v.as_i64()) {
                    exit_code = v as i32;
                }
                saw_exit = true;
                break;
            } else {
                if let Some(v) = ev_json.get("stdout").and_then(|v| v.as_str()) {
                    out.push_str(v);
                }
                if let Some(v) = ev_json.get("stderr").and_then(|v| v.as_str()) {
                    err.push_str(v);
                }
                if let Some(v) = ev_json.get("exit_code").and_then(|v| v.as_i64()) {
                    exit_code = v as i32;
                    saw_exit = true;
                    break;
                }
            }
        }

        let _ = executor.call_method1("flush", (run_id.clone(), 50u64));
        if !saw_exit {
            let _ = executor.call_method0("close_run");
        }
        Ok(ExecResult::new(exit_code, out, err))
    }

    fn _run_stream_node(
        &self,
        py: Python,
        executor: &PyAny,
        node: &Node,
        callback: Option<&PyAny>,
    ) -> PyResult<ExecResult> {
        match node {
            Node::Command { name, args, .. } => {
                self._stream_command_argv(py, executor, name, args.clone(), callback)
            }
            Node::FunctionCall { name, args, .. } => {
                self._stream_command_argv(py, executor, name, args.clone(), callback)
            }
            Node::Pipeline { commands } => {
                let mut last = ExecResult::new(0, String::new(), String::new());
                for cmd in commands {
                    let res = self._run_stream_node(py, executor, cmd, callback)?;
                    let ec = res.exit_code;
                    last = res;
                    if ec != 0 {
                        break;
                    }
                }
                Ok(last)
            }
            Node::List {
                statements,
                operators,
            } => {
                let mut all_out = String::new();
                let mut all_err = String::new();
                let mut last_exit_code = 0;
                for (i, stmt) in statements.iter().enumerate() {
                    let res = self._run_stream_node(py, executor, stmt, callback)?;
                    all_out.push_str(&res.stdout);
                    all_err.push_str(&res.stderr);
                    last_exit_code = res.exit_code;

                    if i < operators.len() {
                        let op = &operators[i];
                        if op == "&&" && last_exit_code != 0 {
                            break;
                        }
                        if op == "||" && last_exit_code == 0 {
                            break;
                        }
                    }
                }
                Ok(ExecResult::new(last_exit_code, all_out, all_err))
            }
            Node::ForLoop {
                variable,
                iterable,
                body,
            } => {
                let mut values: Vec<String> = Vec::new();
                match iterable.as_ref() {
                    Node::StringLiteral(s) | Node::SingleQuotedString(s) => values.push(s.clone()),
                    Node::Array { elements } => {
                        // Parser currently flattens `for i in $(seq 5)` / backticks into ["seq","5"].
                        // Keep a pragmatic compatibility path for common seq usage.
                        if !elements.is_empty() && elements[0] == "seq" {
                            let exec = executor.call_method1(
                                "execute",
                                ("seq", elements[1..].to_vec(), self.cwd.as_str()),
                            )?;
                            let out: String = exec.get_item(0)?.extract()?;
                            values.extend(
                                out.split_whitespace()
                                    .map(|s| s.trim().to_string())
                                    .filter(|s| !s.is_empty()),
                            );
                        } else {
                            values.extend(elements.iter().cloned());
                        }
                    }
                    _ => {}
                }

                let mut all_out = String::new();
                let mut all_err = String::new();
                let mut last_exit_code = 0;
                for v in values {
                    if let Node::List { statements, .. } = body.as_ref() {
                        for stmt in statements {
                            match stmt {
                                Node::Command { name, args, .. } => {
                                    let processed_args: Vec<String> = args
                                        .iter()
                                        .map(|arg| {
                                            arg.replace(&format!("${}", variable), &v)
                                                .replace(&format!("${{{}}}", variable), &v)
                                        })
                                        .collect();
                                    let res = self._stream_command_argv(
                                        py,
                                        executor,
                                        name,
                                        processed_args,
                                        callback,
                                    )?;
                                    all_out.push_str(&res.stdout);
                                    all_err.push_str(&res.stderr);
                                    last_exit_code = res.exit_code;
                                    if last_exit_code != 0 {
                                        break;
                                    }
                                }
                                _ => {
                                    let res =
                                        self._run_stream_node(py, executor, stmt, callback)?;
                                    all_out.push_str(&res.stdout);
                                    all_err.push_str(&res.stderr);
                                    last_exit_code = res.exit_code;
                                    if last_exit_code != 0 {
                                        break;
                                    }
                                }
                            }
                        }
                    }
                    if last_exit_code != 0 {
                        break;
                    }
                }
                Ok(ExecResult::new(last_exit_code, all_out, all_err))
            }
            Node::WhileLoop { body, .. } => {
                // Keep same behavior as non-stream path: bounded loop to avoid hangs.
                let mut all_out = String::new();
                let mut all_err = String::new();
                let mut last_exit_code = 0;
                for _ in 0..3 {
                    let res = self._run_stream_node(py, executor, body.as_ref(), callback)?;
                    all_out.push_str(&res.stdout);
                    all_err.push_str(&res.stderr);
                    last_exit_code = res.exit_code;
                    if last_exit_code != 0 {
                        break;
                    }
                }
                Ok(ExecResult::new(last_exit_code, all_out, all_err))
            }
            Node::IfStatement {
                condition,
                consequence,
                alternative,
            } => {
                let cond_ok = match condition.as_ref() {
                    Node::Command { name, args, .. } => {
                        let r =
                            self._stream_command_argv(py, executor, name, args.clone(), callback)?;
                        r.exit_code == 0
                    }
                    _ => false,
                };
                let branch = if cond_ok {
                    Some(consequence.as_ref())
                } else {
                    alternative.as_ref().map(|b| b.as_ref())
                };
                if let Some(b) = branch {
                    self._run_stream_node(py, executor, b, callback)
                } else {
                    Ok(ExecResult::new(0, String::new(), String::new()))
                }
            }
            _ => Err(pyo3::exceptions::PyRuntimeError::new_err(format!(
                "run_shell_stream does not support AST node: {:?}",
                node
            ))),
        }
    }
}

impl Default for FlashInterpreter {
    fn default() -> Self {
        Self {
            cwd: std::env::current_dir()
                .map(|p| p.to_string_lossy().to_string())
                .unwrap_or_else(|_| "/tmp".to_string()),
            debug: false,
            state_synced: false,
        }
    }
}

fn _gen_run_id() -> String {
    let ts = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis();
    format!("run-{ts}")
}

fn _parse_run_command(command_line: &str) -> Result<(String, Vec<String>), String> {
    let lexer = Lexer::new(command_line);
    let mut parser = Parser::new(lexer);
    let ast = parser.parse_script();
    match ast {
        Node::List { statements, .. } if statements.len() == 1 => match &statements[0] {
            Node::Command { name, args, .. } => Ok((name.clone(), args.clone())),
            Node::FunctionCall { name, args, .. } => Ok((name.clone(), args.clone())),
            _ => Err("start_run only supports a single executable command".to_string()),
        },
        _ => Err("start_run only supports a single executable command".to_string()),
    }
}

#[derive(Clone)]
struct ActiveRun {
    run_id: String,
    events: Arc<Mutex<VecDeque<String>>>,
    running: Arc<AtomicBool>,
    stdin: Arc<Mutex<Option<Box<dyn Write + Send>>>>,
    child: Arc<Mutex<Option<Child>>>,
    external_transport: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct ExecRedirect {
    fd: Option<i32>,
    kind: String,
    file: String,
}

fn _to_exec_redirects(redirects: &[crate::parser::Redirect]) -> Vec<ExecRedirect> {
    redirects
        .iter()
        .map(|r| {
            let kind = match r.kind {
                crate::parser::RedirectKind::Input => "Input",
                crate::parser::RedirectKind::Output => "Output",
                crate::parser::RedirectKind::Append => "Append",
                crate::parser::RedirectKind::HereDoc => "HereDoc",
                crate::parser::RedirectKind::HereDocDash => "HereDocDash",
                crate::parser::RedirectKind::HereString => "HereString",
                crate::parser::RedirectKind::InputDup => "InputDup",
                crate::parser::RedirectKind::OutputDup => "OutputDup",
            }
            .to_string();
            ExecRedirect {
                fd: r.fd,
                kind,
                file: r.file.clone(),
            }
        })
        .collect()
}

impl ActiveRun {
    fn push_event(&self, event: serde_json::Value) {
        if let Ok(mut q) = self.events.lock() {
            q.push_back(event.to_string());
        }
    }
}

fn _spawn_backend_stdout_reader<R>(out: R, events: Arc<Mutex<VecDeque<String>>>, running: Arc<AtomicBool>)
where
    R: Read + Send + 'static,
{
    std::thread::spawn(move || {
        let mut reader = BufReader::new(out);
        let mut line = String::new();
        loop {
            line.clear();
            match reader.read_line(&mut line) {
                Ok(0) => break,
                Ok(_) => {
                    if let Ok(mut q) = events.lock() {
                        q.push_back(line.trim_end_matches('\n').to_string());
                    }
                    if let Ok(v) = serde_json::from_str::<serde_json::Value>(&line) {
                        let t = v.get("type").and_then(|x| x.as_str()).unwrap_or("");
                        if t == "exit" || v.get("exit_code").is_some() {
                            running.store(false, Ordering::SeqCst);
                        }
                    }
                }
                Err(_) => break,
            }
        }
        running.store(false, Ordering::SeqCst);
    });
}

fn _spawn_backend_stderr_reader<R>(err: R, events: Arc<Mutex<VecDeque<String>>>, run_id: String)
where
    R: Read + Send + 'static,
{
    std::thread::spawn(move || {
        let mut reader = BufReader::new(err);
        let mut line = String::new();
        loop {
            line.clear();
            match reader.read_line(&mut line) {
                Ok(0) => break,
                Ok(_) => {
                    let s = line.trim_end_matches('\n').to_string();
                    let ev = json!({"type":"stderr","run_id":run_id,"data":s,"stderr":s});
                    if let Ok(mut q) = events.lock() {
                        q.push_back(ev.to_string());
                    }
                }
                Err(_) => break,
            }
        }
    });
}

fn _spawn_local_output_reader<R>(
    reader: R,
    events: Arc<Mutex<VecDeque<String>>>,
    run_id: String,
    stream_type: &'static str,
) where
    R: Read + Send + 'static,
{
    std::thread::spawn(move || {
        let mut reader = reader;
        let mut buf = [0u8; 4096];
        loop {
            match reader.read(&mut buf) {
                Ok(0) => break,
                Ok(n) => {
                    let data = String::from_utf8_lossy(&buf[..n]).to_string();
                    if data.is_empty() {
                        continue;
                    }
                    let ev = json!({
                        "type": stream_type,
                        "run_id": run_id,
                        "data": data,
                        stream_type: data,
                    });
                    if let Ok(mut q) = events.lock() {
                        q.push_back(ev.to_string());
                    }
                }
                Err(_) => break,
            }
        }
    });
}

fn _start_local_command(
    state: &Mutex<ExecutorState>,
    program: String,
    args: Vec<String>,
    cwd: String,
    env: HashMap<String, String>,
) -> PyResult<String> {
    let mut st = state
        .lock()
        .map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("state lock poisoned"))?;
    if let Some(ar) = st.active.clone() {
        if ar.running.load(Ordering::SeqCst) {
            return Err(pyo3::exceptions::PyRuntimeError::new_err(
                "run already active",
            ));
        }
        st.active = None;
    }

    let run_id = _gen_run_id();
    let mut cmd_str = _sh_quote_arg(&program);
    for arg in &args {
        cmd_str.push(' ');
        cmd_str.push_str(&_sh_quote_arg(arg));
    }

    let mut child = {
        let mut cmd = Command::new(&program);
        cmd.args(&args);
        cmd.current_dir(&cwd);
        cmd.env_clear();
        cmd.envs(&env);
        cmd.stdin(Stdio::piped());
        cmd.stdout(Stdio::piped());
        cmd.stderr(Stdio::piped());
        cmd.spawn().map_err(|e| {
            pyo3::exceptions::PyRuntimeError::new_err(format!(
                "Failed to spawn local command ({program}): {e}"
            ))
        })
    }?;

    let child_id = child.id();
    let stdin = child.stdin.take().map(|s| Box::new(s) as Box<dyn Write + Send>);
    let stdout = child.stdout.take();
    let stderr = child.stderr.take();
    let child_arc = Arc::new(Mutex::new(Some(child)));
    let events = Arc::new(Mutex::new(VecDeque::<String>::new()));
    let running = Arc::new(AtomicBool::new(true));
    let stdin_arc = Arc::new(Mutex::new(stdin));
    let ar = ActiveRun {
        run_id: run_id.clone(),
        events: events.clone(),
        running: running.clone(),
        stdin: stdin_arc.clone(),
        child: child_arc.clone(),
        external_transport: false,
    };

    let started = json!({
        "type": "started",
        "run_id": run_id,
        "backend_pid": child_id,
        "child_pid": child_id,
        "command": cmd_str,
    });
    ar.push_event(started);

    if let Some(out) = stdout {
        _spawn_local_output_reader(out, events.clone(), run_id.clone(), "stdout");
    }
    if let Some(err) = stderr {
        _spawn_local_output_reader(err, events.clone(), run_id.clone(), "stderr");
    }

    let wait_events = events.clone();
    let wait_run_id = run_id.clone();
    std::thread::spawn(move || {
        loop {
            let exit_code = {
                let mut guard = match child_arc.lock() {
                    Ok(v) => v,
                    Err(_) => break,
                };
                let Some(child) = guard.as_mut() else {
                    break;
                };
                match child.try_wait() {
                    Ok(Some(status)) => Some(status.code().unwrap_or(1)),
                    Ok(None) => None,
                    Err(_) => Some(1),
                }
            };
            if let Some(code) = exit_code {
                let ev = json!({
                    "type": "exit",
                    "run_id": wait_run_id,
                    "exit_code": code,
                });
                if let Ok(mut q) = wait_events.lock() {
                    q.push_back(ev.to_string());
                }
                running.store(false, Ordering::SeqCst);
                break;
            }
            std::thread::sleep(Duration::from_millis(20));
        }
    });

    st.active = Some(ar);
    Ok(run_id)
}

fn _execute_local_pipeline_collect(
    commands: &[(&str, &[String])],
    operators: &[&str],
    cwd: &str,
    env: &HashMap<String, String>,
) -> io::Result<RemoteOutput> {
    if commands.is_empty() {
        return Ok(RemoteOutput {
            exit_code: 0,
            stdout: String::new(),
            stderr: String::new(),
            glob_matches: None,
        });
    }

    let _ = operators;
    let mut children: Vec<Child> = Vec::with_capacity(commands.len());
    let mut prev_stdout: Option<std::process::ChildStdout> = None;
    let mut final_stdout: Option<std::process::ChildStdout> = None;
    let mut stderr_readers = Vec::with_capacity(commands.len());

    for (idx, (program, args)) in commands.iter().enumerate() {
        let is_last = idx + 1 == commands.len();
        let mut cmd = Command::new(program);
        cmd.args(*args);
        cmd.current_dir(cwd);
        cmd.env_clear();
        cmd.envs(env);
        if idx == 0 {
            cmd.stdin(Stdio::null());
        } else {
            let stdin_src = prev_stdout
                .take()
                .ok_or_else(|| io::Error::other("pipeline stdin missing"))?;
            cmd.stdin(Stdio::from(stdin_src));
        }
        cmd.stdout(Stdio::piped());
        cmd.stderr(Stdio::piped());

        let mut child = cmd.spawn()?;
        if is_last {
            final_stdout = child.stdout.take();
        } else {
            prev_stdout = child.stdout.take();
        }
        if let Some(err) = child.stderr.take() {
            stderr_readers.push(std::thread::spawn(move || {
                let mut s = String::new();
                let mut reader = BufReader::new(err);
                let _ = reader.read_to_string(&mut s);
                s
            }));
        }
        children.push(child);
    }

    let mut out = String::new();
    if let Some(mut stdout) = final_stdout {
        stdout.read_to_string(&mut out)?;
    }

    let mut exit_code = 1;
    for (idx, child) in children.iter_mut().enumerate() {
        let status = child.wait()?;
        if idx + 1 == commands.len() {
            exit_code = status.code().unwrap_or(1);
        }
    }

    let mut err = String::new();
    for h in stderr_readers {
        if let Ok(chunk) = h.join() {
            err.push_str(&chunk);
        }
    }

    Ok(RemoteOutput {
        exit_code,
        stdout: out,
        stderr: err,
        glob_matches: None,
    })
}

/// Mock executor - prints what would be executed
#[pyclass]
pub struct MockExecutor;

#[pymethods]
impl MockExecutor {
    #[new]
    fn new() -> Self {
        MockExecutor
    }

    fn execute(&self, command: String, args: Vec<String>, cwd: String) -> (String, String, i32) {
        let cmd_str = if args.is_empty() {
            command.clone()
        } else {
            format!("{} {}", command, args.join(" "))
        };

        println!("[MockExecutor] execute: {} (cwd: {})", cmd_str, cwd);

        // Simulate true/false commands
        let exit_code = match command.as_str() {
            "true" => 0,
            "false" => 1,
            _ => 0,
        };

        (
            format!("Would execute: {}\n", cmd_str),
            String::new(),
            exit_code,
        )
    }

    fn glob(&self, patterns: Vec<String>, cwd: String) -> Vec<String> {
        println!("[MockExecutor] glob: {:?} (cwd: {})", patterns, cwd);
        vec![]
    }

    /// 获取初始环境变量
    fn get_env(&self) -> std::collections::HashMap<String, String> {
        _build_prompt_init_env(false)
    }

    /// 获取当前工作目录
    fn get_cwd(&self) -> String {
        std::env::current_dir()
            .map(|p| p.to_string_lossy().to_string())
            .unwrap_or_else(|_| "/tmp".to_string())
    }

    fn get_initial_state(&self) -> (String, HashMap<String, String>) {
        (self.get_cwd(), self.get_env())
    }
}

#[derive(Default)]
struct ExecutorState {
    active: Option<ActiveRun>,
}

fn _active_run_child_alive(ar: &ActiveRun) -> bool {
    if ar.external_transport {
        return ar.stdin.lock().ok().and_then(|g| g.as_ref().map(|_| true)).unwrap_or(false);
    }
    let mut c = match ar.child.lock() {
        Ok(v) => v,
        Err(_) => return false,
    };
    let ch = match c.as_mut() {
        Some(v) => v,
        None => return false,
    };
    match ch.try_wait() {
        Ok(None) => true,
        Ok(Some(_)) => false,
        Err(_) => false,
    }
}

fn _clear_active_run_events(ar: &ActiveRun) {
    if let Ok(mut q) = ar.events.lock() {
        q.clear();
    }
}

fn _send_request(ar: &ActiveRun, req: &serde_json::Value) -> bool {
    if let Ok(mut si) = ar.stdin.lock() {
        if let Some(stdin) = si.as_mut() {
            if writeln!(stdin, "{}", req).is_ok() {
                return stdin.flush().is_ok();
            }
        }
    }
    false
}

fn _emit_dispatch_event(ar: &ActiveRun, msg_type: &str) {
    let ev = json!({
        "type":"dispatch",
        "run_id": ar.run_id,
        "msg_type": msg_type,
    });
    if let Ok(mut q) = ar.events.lock() {
        q.push_back(ev.to_string());
    }
}

fn _shutdown_backend_state(state: &Mutex<ExecutorState>) {
    let mut st = match state.lock() {
        Ok(v) => v,
        Err(_) => return,
    };
    if let Some(ar) = &st.active {
        if let Ok(mut c) = ar.child.lock() {
            if let Some(ch) = c.as_mut() {
                let _ = ch.kill();
                let _ = ch.wait();
            }
            *c = None;
        }
        if let Ok(mut si) = ar.stdin.lock() {
            *si = None;
        }
        ar.running.store(false, Ordering::SeqCst);
    }
    st.active = None;
}

fn _poll_event_impl(state: &Mutex<ExecutorState>, timeout_ms: u64) -> Option<String> {
    let deadline = Instant::now() + Duration::from_millis(timeout_ms.max(1));
    loop {
        if let Ok(st) = state.lock() {
            if let Some(ar) = &st.active {
                if let Ok(mut q) = ar.events.lock() {
                    if let Some(ev) = q.pop_front() {
                        return Some(ev);
                    }
                }
                if !ar.running.load(Ordering::SeqCst) {
                    return None;
                }
            } else {
                return None;
            }
        }
        if Instant::now() >= deadline {
            return None;
        }
        std::thread::sleep(Duration::from_millis(5));
    }
}

fn _run_start_compat_wait_ms() -> u64 {
    std::env::var("FLASH_RUN_START_COMPAT_WAIT_MS")
        .ok()
        .and_then(|v| v.trim().parse::<u64>().ok())
        .map(|v| v.min(500))
        .unwrap_or(60)
}

fn _retry_execute_legacy_impl(
    state: &Mutex<ExecutorState>,
    run_id: &str,
    command: &str,
    args: Vec<String>,
    cwd: &str,
) -> bool {
    let st = match state.lock() {
        Ok(v) => v,
        Err(_) => return false,
    };
    let ar = match &st.active {
        Some(v) if v.run_id == run_id => v,
        _ => return false,
    };
    ar.running.store(true, Ordering::SeqCst);
    let req = json!({
        "type":"execute",
        "run_id": run_id,
        "command": command,
        "args": args,
        "cwd": cwd,
        "env": {},
    });
    if let Ok(mut si) = ar.stdin.lock() {
        if let Some(stdin) = si.as_mut() {
            if writeln!(stdin, "{}", req).is_ok() {
                return stdin.flush().is_ok();
            }
        }
    }
    false
}

fn _spawn_stdio_backend_child() -> Result<Child, pyo3::PyErr> {
    if let Ok(custom_bin) = std::env::var("FLASH_STDIO_EXECUTOR_BIN") {
        let bin = custom_bin.trim();
        if !bin.is_empty() {
            let mut cmd = Command::new(bin);
            if let Ok(args_json) = std::env::var("FLASH_STDIO_EXECUTOR_ARGS_JSON") {
                if !args_json.trim().is_empty() {
                    let parsed: Vec<String> = serde_json::from_str(&args_json).map_err(|e| {
                        pyo3::exceptions::PyRuntimeError::new_err(format!(
                            "invalid FLASH_STDIO_EXECUTOR_ARGS_JSON: {e}"
                        ))
                    })?;
                    cmd.args(parsed);
                }
            }
            return cmd
                .stdin(Stdio::piped())
                .stdout(Stdio::piped())
                .stderr(Stdio::piped())
                .spawn()
                .map_err(|e| {
                    pyo3::exceptions::PyRuntimeError::new_err(format!(
                        "Failed to spawn custom stdio executor ({bin}): {e}"
                    ))
                });
        }
    }

    let executor_paths = ["flash-stdio-executor"];
    for path in &executor_paths {
        if let Ok(c) = Command::new(path)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
        {
            return Ok(c);
        }
    }
    Err(pyo3::exceptions::PyRuntimeError::new_err(
        "Failed to spawn flash-stdio-executor. Set FLASH_STDIO_EXECUTOR_BIN or install nimbie_flash with a bundled executor.",
    ))
}

fn _install_backend_from_fds(
    state: &Mutex<ExecutorState>,
    stdin_fd: i32,
    stdout_fd: i32,
    stderr_fd: Option<i32>,
) -> PyResult<()> {
    let mut st = state
        .lock()
        .map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("state lock poisoned"))?;
    if let Some(ar) = &st.active {
        if ar.running.load(Ordering::SeqCst) {
            return Err(pyo3::exceptions::PyRuntimeError::new_err(
                "run already active",
            ));
        }
    }
    st.active = None;

    if stdin_fd < 0 || stdout_fd < 0 {
        return Err(pyo3::exceptions::PyValueError::new_err(
            "stdin_fd/stdout_fd must be >= 0",
        ));
    }

    let run_id = _gen_run_id();
    let stdin_file = unsafe { std::fs::File::from_raw_fd(stdin_fd) };
    let stdout_file = unsafe { std::fs::File::from_raw_fd(stdout_fd) };
    let stderr_file = stderr_fd.map(|fd| unsafe { std::fs::File::from_raw_fd(fd) });

    let events = Arc::new(Mutex::new(VecDeque::<String>::new()));
    let running = Arc::new(AtomicBool::new(false));
    let stdin_arc = Arc::new(Mutex::new(Some(
        Box::new(stdin_file) as Box<dyn Write + Send>
    )));
    let child_arc = Arc::new(Mutex::new(None));

    _spawn_backend_stdout_reader(stdout_file, events.clone(), running.clone());
    if let Some(err) = stderr_file {
        _spawn_backend_stderr_reader(err, events.clone(), run_id.clone());
    }

    st.active = Some(ActiveRun {
        run_id,
        events,
        running,
        stdin: stdin_arc,
        child: child_arc,
        external_transport: true,
    });
    Ok(())
}

fn _has_custom_backend_env() -> bool {
    _custom_backend_signature().is_some()
}

fn _custom_backend_signature() -> Option<String> {
    let bin = std::env::var("FLASH_STDIO_EXECUTOR_BIN").ok()?;
    let b = bin.trim().to_string();
    if b.is_empty() {
        return None;
    }
    let args = std::env::var("FLASH_STDIO_EXECUTOR_ARGS_JSON").unwrap_or_default();
    Some(format!("{b}\n{args}"))
}

fn _backend_identity_cache() -> &'static Mutex<Option<(String, String, String)>> {
    static CACHE: OnceLock<Mutex<Option<(String, String, String)>>> = OnceLock::new();
    CACHE.get_or_init(|| Mutex::new(None))
}

fn _infer_local_username() -> Option<String> {
    std::env::var("USER")
        .ok()
        .filter(|v| !v.trim().is_empty())
        .or_else(|| {
            std::env::var("USERNAME")
                .ok()
                .filter(|v| !v.trim().is_empty())
        })
        .or_else(|| {
            std::env::var("LOGNAME")
                .ok()
                .filter(|v| !v.trim().is_empty())
        })
}

fn _infer_local_hostname() -> Option<String> {
    if let Ok(v) = std::env::var("HOSTNAME") {
        if !v.trim().is_empty() {
            return Some(v);
        }
    }
    if let Ok(v) = std::env::var("COMPUTERNAME") {
        if !v.trim().is_empty() {
            return Some(v);
        }
    }
    if let Ok(s) = std::fs::read_to_string("/etc/hostname") {
        let h = s.trim().to_string();
        if !h.is_empty() {
            return Some(h);
        }
    }
    None
}

fn _probe_backend_identity() -> Option<(String, String)> {
    let sig = _custom_backend_signature()?;
    if let Ok(cache) = _backend_identity_cache().lock() {
        if let Some((cached_sig, cached_user, cached_host)) = &*cache {
            if cached_sig == &sig {
                return Some((cached_user.clone(), cached_host.clone()));
            }
        }
    }

    let mut child = _spawn_stdio_backend_child().ok()?;
    let req = json!({
        "type":"execute",
        "command":"sh",
        "args": ["-lc", "printf '__FLASH_USER=%s\\n' \"$USER\"; printf '__FLASH_HOSTNAME=%s\\n' \"${HOSTNAME:-$(hostname 2>/dev/null)}\""],
        "cwd": ".",
        "env": {},
    });
    if let Some(stdin) = child.stdin.as_mut() {
        if writeln!(stdin, "{}", req).is_err() {
            let _ = child.kill();
            return None;
        }
        if stdin.flush().is_err() {
            let _ = child.kill();
            return None;
        }
    } else {
        let _ = child.kill();
        return None;
    }

    let mut user: Option<String> = None;
    let mut host: Option<String> = None;
    if let Some(stdout) = child.stdout.take() {
        let mut reader = BufReader::new(stdout);
        let mut line = String::new();
        loop {
            line.clear();
            match reader.read_line(&mut line) {
                Ok(0) => break,
                Ok(_) => {
                    if let Ok(v) = serde_json::from_str::<serde_json::Value>(&line) {
                        if let Some(s) = v.get("stdout").and_then(|x| x.as_str()) {
                            for part in s.lines().map(str::trim).filter(|p| !p.is_empty()) {
                                if let Some(val) = part.strip_prefix("__FLASH_USER=") {
                                    if !val.is_empty() {
                                        user = Some(val.to_string());
                                    }
                                } else if let Some(val) = part.strip_prefix("__FLASH_HOSTNAME=") {
                                    if !val.is_empty() {
                                        host = Some(val.to_string());
                                    }
                                }
                            }
                        }
                        if v.get("exit_code").is_some() {
                            break;
                        }
                    }
                }
                Err(_) => break,
            }
        }
    }
    let _ = child.kill();

    match (user, host) {
        (Some(u), Some(h)) => {
            if let Ok(mut cache) = _backend_identity_cache().lock() {
                *cache = Some((sig, u.clone(), h.clone()));
            }
            Some((u, h))
        }
        _ => None,
    }
}

fn _build_prompt_init_env(use_backend_identity: bool) -> HashMap<String, String> {
    let mut env: HashMap<String, String> = if let Ok(raw) = std::env::var("FLASH_REMOTE_ENV_JSON") {
        let trimmed = raw.trim();
        if trimmed.is_empty() {
            std::env::vars().collect()
        } else {
            serde_json::from_str::<HashMap<String, String>>(trimmed)
                .unwrap_or_else(|_| std::env::vars().collect())
        }
    } else {
        std::env::vars().collect()
    };
    let remote_user = std::env::var("FLASH_REMOTE_USER")
        .ok()
        .map(|s| s.trim().to_string())
        .filter(|s| !s.is_empty());
    let remote_host = std::env::var("FLASH_REMOTE_HOSTNAME")
        .ok()
        .map(|s| s.trim().to_string())
        .filter(|s| !s.is_empty());
    let remote_home = std::env::var("FLASH_REMOTE_HOME")
        .ok()
        .map(|s| s.trim().to_string())
        .filter(|s| !s.is_empty());
    let remote_path = std::env::var("FLASH_REMOTE_PATH")
        .ok()
        .map(|s| s.trim().to_string())
        .filter(|s| !s.is_empty());

    if let Some(user) = remote_user.clone() {
        env.insert("USER".to_string(), user.clone());
        env.entry("USERNAME".to_string()).or_insert(user);
    }
    if let Some(host) = remote_host.clone() {
        env.insert("HOSTNAME".to_string(), host);
    }
    if let Some(home) = remote_home {
        env.insert("HOME".to_string(), home);
    }
    if let Some(path) = remote_path {
        env.insert("PATH".to_string(), path);
    }

    let probe_disabled = std::env::var("FLASH_DISABLE_BACKEND_IDENTITY_PROBE")
        .ok()
        .map(|v| {
            let s = v.trim().to_ascii_lowercase();
            s == "1" || s == "true" || s == "yes" || s == "on"
        })
        .unwrap_or(false);

    if use_backend_identity && !probe_disabled && (remote_user.is_none() || remote_host.is_none()) {
        if let Some((user, host)) = _probe_backend_identity() {
            env.insert("USER".to_string(), user.clone());
            env.entry("USERNAME".to_string()).or_insert(user);
            env.insert("HOSTNAME".to_string(), host);
        }
    }

    if !env.contains_key("USER") {
        if let Some(user) = _infer_local_username() {
            env.insert("USER".to_string(), user.clone());
            env.entry("USERNAME".to_string()).or_insert(user);
        }
    }
    if !env.contains_key("HOSTNAME") {
        if let Some(host) = _infer_local_hostname() {
            env.insert("HOSTNAME".to_string(), host);
        }
    }

    env
}

fn _backend_cwd_cache() -> &'static Mutex<Option<(String, String)>> {
    static CACHE: OnceLock<Mutex<Option<(String, String)>>> = OnceLock::new();
    CACHE.get_or_init(|| Mutex::new(None))
}

fn _extract_stdout_text(line: &str) -> Option<String> {
    let v: serde_json::Value = serde_json::from_str(line).ok()?;
    let s = v.get("stdout").and_then(|x| x.as_str())?;
    let candidate = s
        .lines()
        .map(str::trim)
        .find(|part| !part.is_empty())
        .unwrap_or("")
        .to_string();
    if candidate.is_empty() {
        None
    } else {
        Some(candidate)
    }
}

fn _probe_backend_cwd() -> Option<String> {
    let sig = _custom_backend_signature()?;
    if let Ok(cache) = _backend_cwd_cache().lock() {
        if let Some((cached_sig, cached_cwd)) = &*cache {
            if cached_sig == &sig {
                return Some(cached_cwd.clone());
            }
        }
    }

    let mut child = _spawn_stdio_backend_child().ok()?;
    let req = json!({
        "type":"execute",
        "command":"sh",
        "args": ["-lc", "echo \"$HOME\"; pwd"],
        "cwd": ".",
        "env": {},
    });
    if let Some(stdin) = child.stdin.as_mut() {
        if writeln!(stdin, "{}", req).is_err() {
            let _ = child.kill();
            return None;
        }
        if stdin.flush().is_err() {
            let _ = child.kill();
            return None;
        }
    } else {
        let _ = child.kill();
        return None;
    }

    let mut home_candidate: Option<String> = None;
    let mut pwd_candidate: Option<String> = None;
    if let Some(stdout) = child.stdout.take() {
        let mut reader = BufReader::new(stdout);
        let mut line = String::new();
        loop {
            line.clear();
            match reader.read_line(&mut line) {
                Ok(0) => break,
                Ok(_) => {
                    if let Some(candidate) = _extract_stdout_text(&line) {
                        if home_candidate.is_none() {
                            home_candidate = Some(candidate);
                        } else {
                            pwd_candidate = Some(candidate);
                        }
                    }
                    if let Ok(v) = serde_json::from_str::<serde_json::Value>(&line) {
                        if v.get("exit_code").is_some() {
                            break;
                        }
                    }
                }
                Err(_) => break,
            }
        }
    }
    let _ = child.kill();
    let resolved = home_candidate.or(pwd_candidate);
    if let Some(cwd) = resolved.clone() {
        if let Ok(mut cache) = _backend_cwd_cache().lock() {
            *cache = Some((sig, cwd.clone()));
        }
    }
    resolved
}

fn _backend_glob_via_stdio(patterns: &[String], cwd: &str) -> Vec<String> {
    let mut child = match _spawn_stdio_backend_child() {
        Ok(c) => c,
        Err(_) => return vec![],
    };
    let req = json!({"type":"glob","patterns":patterns,"cwd":cwd});
    if let Some(stdin) = child.stdin.as_mut() {
        if writeln!(stdin, "{}", req).is_err() || stdin.flush().is_err() {
            let _ = child.kill();
            return vec![];
        }
    } else {
        let _ = child.kill();
        return vec![];
    }

    let mut line = String::new();
    if let Some(stdout) = child.stdout.take() {
        let mut reader = BufReader::new(stdout);
        let _ = reader.read_line(&mut line);
    }
    let _ = child.kill();
    let v = match serde_json::from_str::<serde_json::Value>(&line) {
        Ok(x) => x,
        Err(_) => return vec![],
    };
    v.get("glob_matches")
        .and_then(|x| x.as_array())
        .map(|arr| {
            arr.iter()
                .filter_map(|it| it.as_str().map(|s| s.to_string()))
                .collect()
        })
        .unwrap_or_default()
}

fn _start_backend_run(
    state: &Mutex<ExecutorState>,
    program: String,
    args: Vec<String>,
    cwd: String,
    redirects: Option<Vec<ExecRedirect>>,
) -> PyResult<String> {
    let redirects_payload = redirects.clone().unwrap_or_default();
    let mut st = state
        .lock()
        .map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("state lock poisoned"))?;
    if let Some(ar) = st.active.clone() {
        if ar.running.load(Ordering::SeqCst) {
            return Err(pyo3::exceptions::PyRuntimeError::new_err(
                "run already active",
            ));
        }
        if _active_run_child_alive(&ar) {
            _clear_active_run_events(&ar);
            ar.running.store(true, Ordering::SeqCst);
            let req = json!({
                "type":"run.start",
                "run_id": ar.run_id,
                "command": program,
                "args": args,
                "cwd":cwd,
                "redirects": redirects_payload.clone(),
                "env": {},
            });
            if !_send_request(&ar, &req) {
                ar.running.store(false, Ordering::SeqCst);
                st.active = None;
            } else {
                let mut fallback_to_execute = false;
                let compat_wait_ms = _run_start_compat_wait_ms();
                if compat_wait_ms > 0 {
                    let deadline = Instant::now() + Duration::from_millis(compat_wait_ms);
                    while Instant::now() < deadline {
                        let maybe_ev = if let Ok(mut q) = ar.events.lock() {
                            q.pop_front()
                        } else {
                            None
                        };
                        if let Some(ev) = maybe_ev {
                            if let Ok(v) = serde_json::from_str::<serde_json::Value>(&ev) {
                                let err = v
                                    .get("error")
                                    .and_then(|x| x.as_str())
                                    .unwrap_or("")
                                    .to_ascii_lowercase();
                                if err.contains("unknown") && err.contains("run.start") {
                                    fallback_to_execute = true;
                                    break;
                                }
                            }
                            if let Ok(mut q) = ar.events.lock() {
                                q.push_front(ev);
                            }
                            break;
                        }
                        std::thread::sleep(Duration::from_millis(5));
                    }
                }
                if fallback_to_execute {
                    let req2 = json!({
                        "type":"execute",
                        "run_id": ar.run_id,
                        "command": req.get("command").and_then(|x| x.as_str()).unwrap_or(""),
                        "args": req.get("args").cloned().unwrap_or(json!([])),
                        "cwd": req.get("cwd").and_then(|x| x.as_str()).unwrap_or("/tmp"),
                        "env": {},
                    });
                    if !_send_request(&ar, &req2) {
                        ar.running.store(false, Ordering::SeqCst);
                        st.active = None;
                    }
                }
                if st.active.is_some() {
                    return Ok(ar.run_id.clone());
                }
            }
        } else {
            st.active = None;
        }
    }

    let mut child = _spawn_stdio_backend_child()?;
    let run_id = _gen_run_id();
    let stdin = child.stdin.take().map(|s| Box::new(s) as Box<dyn Write + Send>);
    let stdout = child.stdout.take();
    let stderr = child.stderr.take();
    let child_arc = Arc::new(Mutex::new(Some(child)));
    let events = Arc::new(Mutex::new(VecDeque::<String>::new()));
    let running = Arc::new(AtomicBool::new(true));
    let stdin_arc = Arc::new(Mutex::new(stdin));
    let ar = ActiveRun {
        run_id: run_id.clone(),
        events: events.clone(),
        running: running.clone(),
        stdin: stdin_arc.clone(),
        child: child_arc.clone(),
        external_transport: false,
    };

    if let Some(out) = stdout {
        _spawn_backend_stdout_reader(out, events.clone(), running.clone());
    }

    if let Some(err) = stderr {
        _spawn_backend_stderr_reader(err, events.clone(), run_id.clone());
    }

    let req = json!({
        "type":"run.start",
        "run_id": run_id,
        "command": program,
        "args": args,
        "cwd":cwd,
        "redirects": redirects_payload,
        "env": {},
    });
    let _ = _send_request(&ar, &req);

    // Compatibility fallback: some old executors only support `execute`.
    // If we immediately see "unknown request type: run.start", resend as execute.
    let mut fallback_to_execute = false;
    let compat_wait_ms = _run_start_compat_wait_ms();
    if compat_wait_ms > 0 {
        let deadline = Instant::now() + Duration::from_millis(compat_wait_ms);
        while Instant::now() < deadline {
            let maybe_ev = if let Ok(mut q) = events.lock() {
                q.pop_front()
            } else {
                None
            };
            if let Some(ev) = maybe_ev {
                if let Ok(v) = serde_json::from_str::<serde_json::Value>(&ev) {
                    let err = v
                        .get("error")
                        .and_then(|x| x.as_str())
                        .unwrap_or("")
                        .to_ascii_lowercase();
                    if err.contains("unknown") && err.contains("run.start") {
                        fallback_to_execute = true;
                        break;
                    }
                }
                if let Ok(mut q) = events.lock() {
                    q.push_front(ev);
                }
                break;
            }
            std::thread::sleep(Duration::from_millis(5));
        }
    }

    if fallback_to_execute {
        running.store(true, Ordering::SeqCst);
        let req2 = json!({
            "type":"execute",
            "run_id": run_id,
            "command": req.get("command").and_then(|x| x.as_str()).unwrap_or(""),
            "args": req.get("args").cloned().unwrap_or(json!([])),
            "cwd": req.get("cwd").and_then(|x| x.as_str()).unwrap_or("/tmp"),
            "env": {},
        });
        let _ = _send_request(&ar, &req2);
    }

    st.active = Some(ar);
    Ok(run_id)
}

fn _start_backend_ast(
    state: &Mutex<ExecutorState>,
    ast: serde_json::Value,
    cwd: String,
    env: HashMap<String, String>,
) -> PyResult<String> {
    let mut st = state
        .lock()
        .map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("state lock poisoned"))?;
    if let Some(ar) = st.active.clone() {
        if ar.running.load(Ordering::SeqCst) {
            return Err(pyo3::exceptions::PyRuntimeError::new_err(
                "run already active",
            ));
        }
        if _active_run_child_alive(&ar) {
            _clear_active_run_events(&ar);
            ar.running.store(true, Ordering::SeqCst);
            let req = json!({
                "type":"run.ast",
                "run_id": ar.run_id,
                "ast": ast,
                "cwd": cwd,
                "env": env.clone(),
            });
            if _send_request(&ar, &req) {
                _emit_dispatch_event(&ar, "run.ast");
                return Ok(ar.run_id.clone());
            }
            ar.running.store(false, Ordering::SeqCst);
            st.active = None;
        } else {
            st.active = None;
        }
    }

    let mut child = _spawn_stdio_backend_child()?;
    let run_id = _gen_run_id();
    let stdin = child.stdin.take().map(|s| Box::new(s) as Box<dyn Write + Send>);
    let stdout = child.stdout.take();
    let stderr = child.stderr.take();
    let child_arc = Arc::new(Mutex::new(Some(child)));
    let events = Arc::new(Mutex::new(VecDeque::<String>::new()));
    let running = Arc::new(AtomicBool::new(true));
    let stdin_arc = Arc::new(Mutex::new(stdin));
    let ar = ActiveRun {
        run_id: run_id.clone(),
        events: events.clone(),
        running: running.clone(),
        stdin: stdin_arc.clone(),
        child: child_arc.clone(),
        external_transport: false,
    };

    if let Some(out) = stdout {
        _spawn_backend_stdout_reader(out, events.clone(), running.clone());
    }

    if let Some(err) = stderr {
        _spawn_backend_stderr_reader(err, events.clone(), run_id.clone());
    }

    let req = json!({
        "type":"run.ast",
        "run_id": run_id,
        "ast": ast,
        "cwd": cwd,
        "env": env,
    });
    if _send_request(&ar, &req) {
        _emit_dispatch_event(&ar, "run.ast");
    }

    st.active = Some(ar);
    Ok(run_id)
}

fn _start_backend_pipeline(
    state: &Mutex<ExecutorState>,
    commands: &[(&str, &[String])],
    operators: &[&str],
    cwd: String,
) -> PyResult<String> {
    let mut st = state
        .lock()
        .map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("state lock poisoned"))?;
    if let Some(ar) = st.active.clone() {
        if ar.running.load(Ordering::SeqCst) {
            return Err(pyo3::exceptions::PyRuntimeError::new_err(
                "run already active",
            ));
        }
        if _active_run_child_alive(&ar) {
            _clear_active_run_events(&ar);
            ar.running.store(true, Ordering::SeqCst);
            let cmds_json: Vec<serde_json::Value> = commands
                .iter()
                .map(|(c, a)| json!({"command": c, "args": a}))
                .collect();
            let req = json!({
                "type":"run.pipeline",
                "run_id": ar.run_id,
                "commands": cmds_json,
                "operators": operators,
                "cwd":cwd,
                "env": {},
            });
            if _send_request(&ar, &req) {
                return Ok(ar.run_id.clone());
            }
            ar.running.store(false, Ordering::SeqCst);
            st.active = None;
        } else {
            st.active = None;
        }
    }

    let mut child = _spawn_stdio_backend_child()?;
    let run_id = _gen_run_id();
    let stdout = child.stdout.take();
    let stderr = child.stderr.take();
    let stdin = child.stdin.take().map(|s| Box::new(s) as Box<dyn Write + Send>);

    let events = Arc::new(Mutex::new(VecDeque::<String>::new()));
    let running = Arc::new(AtomicBool::new(true));
    let child_arc = Arc::new(Mutex::new(Some(child)));
    let stdin_arc = Arc::new(Mutex::new(stdin));

    let ar = ActiveRun {
        run_id: run_id.clone(),
        events: events.clone(),
        running: running.clone(),
        stdin: stdin_arc.clone(),
        child: child_arc.clone(),
        external_transport: false,
    };

    if let Some(out) = stdout {
        _spawn_backend_stdout_reader(out, events.clone(), running.clone());
    }

    if let Some(err) = stderr {
        _spawn_backend_stderr_reader(err, events.clone(), run_id.clone());
    }

    let cmds_json: Vec<serde_json::Value> = commands
        .iter()
        .map(|(c, a)| json!({"command": c, "args": a}))
        .collect();
    let req = json!({
        "type":"run.pipeline",
        "run_id": run_id,
        "commands": cmds_json,
        "operators": operators,
        "cwd":cwd,
        "env": {},
    });
    let _ = _send_request(&ar, &req);

    st.active = Some(ar);
    Ok(run_id)
}

/// Real local executor (in-process).
#[pyclass]
pub struct LocalExecutor {
    state: Arc<Mutex<ExecutorState>>,
    cached_cwd: Mutex<Option<String>>,
}

impl LocalExecutor {
    fn start_run_inner(&self, program: String, args: Vec<String>, cwd: String) -> PyResult<String> {
        _start_local_command(&self.state, program, args, cwd, self.get_env())
    }
}

impl Drop for LocalExecutor {
    fn drop(&mut self) {
        _shutdown_backend_state(&self.state);
    }
}

#[pymethods]
impl LocalExecutor {
    #[new]
    fn new() -> Self {
        Self {
            state: Arc::new(Mutex::new(ExecutorState::default())),
            cached_cwd: Mutex::new(None),
        }
    }

    fn get_env(&self) -> HashMap<String, String> {
        std::env::vars().collect()
    }

    fn get_cwd(&self) -> String {
        std::env::current_dir()
            .map(|p| p.to_string_lossy().to_string())
            .unwrap_or_else(|_| "/tmp".to_string())
    }

    fn get_initial_state(&self) -> (String, HashMap<String, String>) {
        (self.get_cwd(), self.get_env())
    }

    fn start_run(&self, command: String, cwd: String) -> PyResult<String> {
        let (program, args) =
            _parse_run_command(&command).map_err(pyo3::exceptions::PyRuntimeError::new_err)?;
        self.start_run_inner(program, args, cwd)
    }

    fn start_run_argv(&self, command: String, args: Vec<String>, cwd: String) -> PyResult<String> {
        self.start_run_inner(command, args, cwd)
    }

    fn is_running(&self) -> bool {
        if let Ok(st) = self.state.lock() {
            if let Some(ar) = &st.active {
                return ar.running.load(Ordering::SeqCst);
            }
        }
        false
    }

    fn poll_event(&self, timeout_ms: u64) -> Option<String> {
        _poll_event_impl(&self.state, timeout_ms)
    }

    fn send_stdin(&self, run_id: &str, data: &str) -> bool {
        let st = match self.state.lock() {
            Ok(v) => v,
            Err(_) => return false,
        };
        let ar = match &st.active {
            Some(v) if v.run_id == run_id => v,
            _ => return false,
        };
        if let Ok(mut si) = ar.stdin.lock() {
            if let Some(stdin) = si.as_mut() {
                if stdin.write_all(data.as_bytes()).is_ok() {
                    return stdin.flush().is_ok();
                }
            }
        }
        false
    }

    fn retry_execute_legacy(
        &self,
        _run_id: &str,
        _command: String,
        _args: Vec<String>,
        _cwd: String,
    ) -> bool {
        false
    }

    fn signal(&self, run_id: &str, signal_name: &str) -> bool {
        let st = match self.state.lock() {
            Ok(v) => v,
            Err(_) => return false,
        };
        let ar = match &st.active {
            Some(v) if v.run_id == run_id => v,
            _ => return false,
        };
        let sig = match signal_name.to_ascii_uppercase().as_str() {
            "INT" | "SIGINT" => libc::SIGINT,
            "TERM" | "SIGTERM" => libc::SIGTERM,
            "KILL" | "SIGKILL" => libc::SIGKILL,
            _ => libc::SIGTERM,
        };
        if let Ok(mut child_guard) = ar.child.lock() {
            if let Some(child) = child_guard.as_mut() {
                #[cfg(unix)]
                {
                    return unsafe { libc::kill(child.id() as i32, sig) } == 0;
                }
            }
        }
        false
    }

    fn flush(&self, run_id: &str, timeout_ms: u64) -> bool {
        let _ = run_id;
        let _ = timeout_ms;
        true
    }

    fn close_run(&self) -> bool {
        let mut st = match self.state.lock() {
            Ok(v) => v,
            Err(_) => return false,
        };
        if let Some(ar) = &st.active {
            _clear_active_run_events(ar);
            ar.running.store(false, Ordering::SeqCst);
            if let Ok(mut child_guard) = ar.child.lock() {
                if let Some(child) = child_guard.as_mut() {
                    let _ = child.kill();
                    let _ = child.wait();
                }
                *child_guard = None;
            }
        }
        st.active = None;
        true
    }

    fn execute(
        &self,
        py: Python<'_>,
        command: String,
        args: Vec<String>,
        cwd: String,
    ) -> (String, String, i32) {
        let run_id = match self.start_run_inner(command, args, cwd) {
            Ok(v) => v,
            Err(e) => return (String::new(), e.to_string(), 1),
        };
        let mut out = String::new();
        let mut err = String::new();
        let mut ec = 1;
        loop {
            if let Some(ev) = py.allow_threads(|| self.poll_event(100)) {
                if let Ok(v) = serde_json::from_str::<serde_json::Value>(&ev) {
                    match v.get("type").and_then(|x| x.as_str()).unwrap_or("") {
                        "stdout" => {
                            out.push_str(v.get("data").and_then(|x| x.as_str()).unwrap_or(""))
                        }
                        "stderr" => {
                            err.push_str(v.get("data").and_then(|x| x.as_str()).unwrap_or(""))
                        }
                        "exit" => {
                            ec = v.get("exit_code").and_then(|x| x.as_i64()).unwrap_or(1) as i32
                        }
                        _ => {}
                    }
                }
            } else if !py.allow_threads(|| self.is_running()) {
                break;
            }
        }
        let _ = self.flush(&run_id, 50);
        let _ = self.close_run();
        (out, err, ec)
    }

    fn execute_pipeline(
        &self,
        commands: Vec<(String, Vec<String>)>,
        operators: Vec<String>,
        cwd: String,
    ) -> (String, String, i32) {
        let cmd_refs: Vec<(&str, &[String])> = commands
            .iter()
            .map(|(cmd, args)| (cmd.as_str(), args.as_slice()))
            .collect();
        let op_refs: Vec<&str> = operators.iter().map(|op| op.as_str()).collect();
        match _execute_local_pipeline_collect(&cmd_refs, &op_refs, &cwd, &self.get_env()) {
            Ok(out) => (out.stdout, out.stderr, out.exit_code),
            Err(e) => (String::new(), e.to_string(), 1),
        }
    }

    fn glob(&self, patterns: Vec<String>, cwd: String) -> Vec<String> {
        use glob::glob;
        use std::path::Path;

        let mut matches = Vec::new();
        for pattern in patterns {
            let full_pattern = Path::new(&cwd).join(pattern);
            if let Ok(entries) = glob(&full_pattern.to_string_lossy()) {
                for entry in entries.flatten() {
                    matches.push(entry.to_string_lossy().to_string());
                }
            }
        }
        matches
    }
}

/// StdioExecutor - communicates with `flash-stdio-executor`.
#[pyclass]
pub struct StdioExecutor {
    state: Arc<Mutex<ExecutorState>>,
    cached_cwd: Mutex<Option<String>>,
}

impl StdioExecutor {
    fn start_run_inner(&self, program: String, args: Vec<String>, cwd: String) -> PyResult<String> {
        let ast = serde_json::to_value(crate::parser::Node::Command {
            name: program,
            args,
            redirects: vec![],
        })
        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
        _start_backend_ast(&self.state, ast, cwd, self.get_env())
    }
}

impl Drop for StdioExecutor {
    fn drop(&mut self) {
        _shutdown_backend_state(&self.state);
    }
}

#[pymethods]
impl StdioExecutor {
    #[new]
    fn new() -> Self {
        Self {
            state: Arc::new(Mutex::new(ExecutorState::default())),
            cached_cwd: Mutex::new(None),
        }
    }

    #[classmethod]
    fn from_fds(
        _cls: &PyType,
        stdin_fd: i32,
        stdout_fd: i32,
        stderr_fd: Option<i32>,
        _pid: Option<i32>,
    ) -> PyResult<Self> {
        let s = Self {
            state: Arc::new(Mutex::new(ExecutorState::default())),
            cached_cwd: Mutex::new(None),
        };
        _install_backend_from_fds(&s.state, stdin_fd, stdout_fd, stderr_fd)?;
        Ok(s)
    }

    fn get_env(&self) -> HashMap<String, String> {
        _build_prompt_init_env(false)
    }

    fn get_cwd(&self) -> String {
        if let Ok(v) = std::env::var("FLASH_REMOTE_LOGIN_PWD") {
            let s = v.trim();
            if !s.is_empty() {
                return s.to_string();
            }
        }
        if _has_custom_backend_env() {
            if let Ok(cache) = self.cached_cwd.lock() {
                if let Some(cwd) = &*cache {
                    return cwd.clone();
                }
            }
            if let Some(cwd) = _probe_backend_cwd() {
                if let Ok(mut cache) = self.cached_cwd.lock() {
                    *cache = Some(cwd.clone());
                }
                return cwd;
            }
        }
        std::env::current_dir()
            .map(|p| p.to_string_lossy().to_string())
            .unwrap_or_else(|_| "/tmp".to_string())
    }

    fn get_initial_state(&self) -> (String, HashMap<String, String>) {
        (self.get_cwd(), self.get_env())
    }

    fn start_run(&self, command: String, cwd: String) -> PyResult<String> {
        let (program, args) =
            _parse_run_command(&command).map_err(pyo3::exceptions::PyRuntimeError::new_err)?;
        self.start_run_inner(program, args, cwd)
    }

    fn start_run_argv(&self, command: String, args: Vec<String>, cwd: String) -> PyResult<String> {
        self.start_run_inner(command, args, cwd)
    }

    fn is_running(&self) -> bool {
        if let Ok(st) = self.state.lock() {
            if let Some(ar) = &st.active {
                return ar.running.load(Ordering::SeqCst);
            }
        }
        false
    }

    fn poll_event(&self, timeout_ms: u64) -> Option<String> {
        _poll_event_impl(&self.state, timeout_ms)
    }

    fn send_stdin(&self, run_id: &str, data: &str) -> bool {
        let st = match self.state.lock() {
            Ok(v) => v,
            Err(_) => return false,
        };
        let ar = match &st.active {
            Some(v) if v.run_id == run_id => v,
            _ => return false,
        };
        let req = json!({"type":"stdin.write","run_id":run_id,"data":data});
        if let Ok(mut si) = ar.stdin.lock() {
            if let Some(stdin) = si.as_mut() {
                if writeln!(stdin, "{}", req).is_ok() {
                    return stdin.flush().is_ok();
                }
            }
        }
        false
    }

    fn retry_execute_legacy(
        &self,
        run_id: &str,
        command: String,
        args: Vec<String>,
        cwd: String,
    ) -> bool {
        _retry_execute_legacy_impl(&self.state, run_id, &command, args, &cwd)
    }

    fn signal(&self, run_id: &str, signal_name: &str) -> bool {
        let st = match self.state.lock() {
            Ok(v) => v,
            Err(_) => return false,
        };
        let ar = match &st.active {
            Some(v) if v.run_id == run_id => v,
            _ => return false,
        };
        let req = json!({"type":"run.signal","run_id":run_id,"signal":signal_name});
        if let Ok(mut si) = ar.stdin.lock() {
            if let Some(stdin) = si.as_mut() {
                if writeln!(stdin, "{}", req).is_ok() {
                    return stdin.flush().is_ok();
                }
            }
        }
        false
    }

    fn flush(&self, run_id: &str, timeout_ms: u64) -> bool {
        let st = match self.state.lock() {
            Ok(v) => v,
            Err(_) => return false,
        };
        let ar = match &st.active {
            Some(v) if v.run_id == run_id => v,
            _ => return true,
        };
        if !ar.running.load(Ordering::SeqCst) {
            return true;
        }
        let req = json!({"type":"stream.flush","run_id":run_id,"timeout_ms": timeout_ms});
        if let Ok(mut si) = ar.stdin.lock() {
            if let Some(stdin) = si.as_mut() {
                if writeln!(stdin, "{}", req).is_ok() {
                    return stdin.flush().is_ok();
                }
            }
        }
        true
    }

    fn close_run(&self) -> bool {
        let st = match self.state.lock() {
            Ok(v) => v,
            Err(_) => return false,
        };
        if let Some(ar) = &st.active {
            _clear_active_run_events(ar);
            ar.running.store(false, Ordering::SeqCst);
        }
        true
    }

    fn execute(
        &self,
        py: Python<'_>,
        command: String,
        args: Vec<String>,
        cwd: String,
    ) -> (String, String, i32) {
        let run_id = match self.start_run_inner(command, args, cwd) {
            Ok(v) => v,
            Err(e) => return (String::new(), e.to_string(), 1),
        };
        let mut out = String::new();
        let mut err = String::new();
        let mut ec = 1;
        loop {
            if let Some(ev) = py.allow_threads(|| self.poll_event(100)) {
                if let Ok(v) = serde_json::from_str::<serde_json::Value>(&ev) {
                    let t = v.get("type").and_then(|x| x.as_str()).unwrap_or("");
                    match t {
                        "stdout" => {
                            out.push_str(v.get("data").and_then(|x| x.as_str()).unwrap_or(""))
                        }
                        "stderr" => {
                            err.push_str(v.get("data").and_then(|x| x.as_str()).unwrap_or(""))
                        }
                        "exit" => {
                            ec = v.get("exit_code").and_then(|x| x.as_i64()).unwrap_or(1) as i32
                        }
                        _ => {
                            if let Some(s) = v.get("stdout").and_then(|x| x.as_str()) {
                                out.push_str(s);
                            }
                            if let Some(s) = v.get("stderr").and_then(|x| x.as_str()) {
                                err.push_str(s);
                            }
                            if let Some(c) = v.get("exit_code").and_then(|x| x.as_i64()) {
                                ec = c as i32;
                            }
                        }
                    }
                }
            } else if !py.allow_threads(|| self.is_running()) {
                break;
            }
        }
        let _ = self.flush(&run_id, 50);
        let _ = self.close_run();
        (out, err, ec)
    }

    fn execute_pipeline(
        &self,
        py: Python<'_>,
        commands: Vec<(String, Vec<String>)>,
        operators: Vec<String>,
        cwd: String,
    ) -> (String, String, i32) {
        let cmd_refs: Vec<(&str, &[String])> = commands
            .iter()
            .map(|(cmd, args)| (cmd.as_str(), args.as_slice()))
            .collect();
        let op_refs: Vec<&str> = operators.iter().map(|op| op.as_str()).collect();
        let run_id = match _start_backend_pipeline(&self.state, &cmd_refs, &op_refs, cwd) {
            Ok(v) => v,
            Err(e) => return (String::new(), e.to_string(), 1),
        };
        let mut out = String::new();
        let mut err = String::new();
        let mut ec = 1;
        loop {
            if let Some(ev) = py.allow_threads(|| self.poll_event(100)) {
                if let Ok(v) = serde_json::from_str::<serde_json::Value>(&ev) {
                    let t = v.get("type").and_then(|x| x.as_str()).unwrap_or("");
                    match t {
                        "stdout" => {
                            out.push_str(v.get("data").and_then(|x| x.as_str()).unwrap_or(""))
                        }
                        "stderr" => {
                            err.push_str(v.get("data").and_then(|x| x.as_str()).unwrap_or(""))
                        }
                        "exit" => {
                            ec = v.get("exit_code").and_then(|x| x.as_i64()).unwrap_or(1) as i32
                        }
                        _ => {
                            if let Some(s) = v.get("stdout").and_then(|x| x.as_str()) {
                                out.push_str(s);
                            }
                            if let Some(s) = v.get("stderr").and_then(|x| x.as_str()) {
                                err.push_str(s);
                            }
                            if let Some(c) = v.get("exit_code").and_then(|x| x.as_i64()) {
                                ec = c as i32;
                            }
                        }
                    }
                }
            } else if !py.allow_threads(|| self.is_running()) {
                break;
            }
        }
        let _ = self.flush(&run_id, 50);
        let _ = self.close_run();
        (out, err, ec)
    }

    fn glob(&self, patterns: Vec<String>, cwd: String) -> Vec<String> {
        _backend_glob_via_stdio(&patterns, &cwd)
    }
}

#[pyfunction]
fn build_prompt_from_env(cwd: String, env: HashMap<String, String>) -> PyResult<String> {
    let mut interp = Interpreter::new();
    interp.variables = env;
    interp.variables.insert("PWD".to_string(), cwd);
    Ok(interp.get_prompt())
}

/// Python module entry point
#[pymodule]
fn _native(m: &PyModule) -> PyResult<()> {
    m.add_class::<FlashInterpreter>()?;
    m.add_class::<ExecResult>()?;
    m.add_class::<MockExecutor>()?;
    m.add_class::<LocalExecutor>()?;
    m.add_class::<StdioExecutor>()?;
    m.add_function(pyo3::wrap_pyfunction!(build_prompt_from_env, m)?)?;
    Ok(())
}

#[cfg(test)]
mod python_bridge_tests {
    use super::_extract_stdout_text;

    #[test]
    fn extract_stdout_text_works() {
        assert_eq!(
            _extract_stdout_text("{\"stdout\":\"/home/draplater\\n\"}"),
            Some("/home/draplater".to_string())
        );
        assert_eq!(_extract_stdout_text("{\"stdout\":\"   \\n\"}"), None);
        assert_eq!(_extract_stdout_text("{\"exit_code\":0}"), None);
        assert_eq!(_extract_stdout_text("not-json"), None);
    }
}
