use super::*;

#[cfg(not(target_arch = "wasm32"))]
fn entrypoint_target_function(
    assembly: &runmat_hir::HirAssembly,
) -> Option<runmat_hir::FunctionId> {
    assembly
        .entrypoints
        .first()
        .map(|entrypoint| entrypoint.target)
}

#[cfg(not(target_arch = "wasm32"))]
fn mir_local_fact_count_for_entrypoint(
    analysis: &runmat_mir::analysis::AnalysisStore,
    assembly: &runmat_hir::HirAssembly,
) -> usize {
    let Some(entrypoint_target) = entrypoint_target_function(assembly) else {
        return analysis.mir_locals.len();
    };
    analysis
        .mir_locals
        .keys()
        .filter(|key| key.function == entrypoint_target)
        .count()
}

fn discover_known_project_symbols(source_name: Option<&str>) -> HashSet<String> {
    use runmat_config::project::discover_known_project_symbols_from_source_name;
    use std::path::{Path, PathBuf};

    let Ok(cwd) = runmat_filesystem::current_dir() else {
        let Some(source_name) = source_name else {
            return HashSet::new();
        };
        let source_path = PathBuf::from(source_name);
        if source_path.is_absolute() {
            let source_cwd = source_path
                .parent()
                .map(Path::to_path_buf)
                .unwrap_or_else(|| PathBuf::from("/"));
            return discover_known_project_symbols_from_source_name(Some(source_name), &source_cwd);
        }
        return HashSet::new();
    };
    discover_known_project_symbols_from_source_name(source_name, &cwd)
}

impl RunMatSession {
    async fn run(
        &mut self,
        input: &str,
    ) -> std::result::Result<crate::abi::ExecutionOutcome, RunError> {
        let source_lookup_name = self
            .current_source_fullpath_name()
            .unwrap_or_else(|| self.current_source_name());
        let companion = super::compile::discover_companion_source_statements_async(
            source_lookup_name,
            self.compat_mode,
        )
        .await;
        self.pending_companion_source_discovery = Some(companion);
        let previous_workspace_names = self
            .workspace_values
            .keys()
            .cloned()
            .collect::<HashSet<_>>();
        let mut execution = self.execute_internal(input, true).await?;
        let workspace_names = execution
            .workspace_snapshot
            .values
            .iter()
            .map(|entry| entry.name.clone())
            .collect::<Vec<_>>();
        let workspace_full = execution.workspace_snapshot.full;
        let outcome = &mut execution.outcome;
        outcome.workspace_delta.upserts = self.abi_workspace_upserts(workspace_names);
        if workspace_full {
            outcome.workspace_delta.removals =
                self.abi_workspace_removals(previous_workspace_names);
            if !outcome.workspace_delta.removals.is_empty() {
                outcome.effects.push(crate::abi::ObservedEffect::Workspace(
                    crate::abi::WorkspaceEffectKind::Clear,
                ));
            }
        }
        Ok(execution.outcome)
    }

    /// Execute a structured runtime/workspace ABI request.
    pub async fn execute_request(
        &mut self,
        request: crate::abi::ExecutionRequest,
    ) -> crate::abi::ExecutionResponse {
        let requested_outputs = request.requested_outputs.clone();
        let source_input = request.source.clone();
        let source_resolution = match source_input_text(request.source).await {
            Ok(resolved) => resolved,
            Err(err) => {
                return crate::abi::ExecutionResponse {
                    source_context: unresolved_source_context(&source_input),
                    result: Err(err),
                };
            }
        };
        let source_name = source_resolution.display_name;
        let source_fullpath_name = source_resolution.fullpath_name;
        let source_text = source_resolution.text;
        let source_identity = resolve_source_identity(&source_input, &source_text);
        let previous_compat = self.compat_mode;
        let previous_top_level_await_enabled = self.top_level_await_enabled;
        let previous_dynamic_eval_enabled = self.dynamic_eval_enabled;
        let previous_source_name = self.active_source_name.clone();
        let previous_source_fullpath_name = self.active_source_fullpath_name.clone();
        let previous_workspace_handle = self.abi_workspace_handle;
        let previous_source_identity = self.active_source_identity.clone();

        self.compat_mode = request.compatibility;
        self.top_level_await_enabled = request.host_policy.top_level_await;
        self.dynamic_eval_enabled = request.host_policy.dynamic_eval;
        self.active_source_name = source_name.clone();
        self.active_source_fullpath_name = source_fullpath_name.clone();
        self.abi_workspace_handle = request.workspace;
        self.active_source_identity = source_identity.clone();

        let result = self.run(&source_text).await;

        self.compat_mode = previous_compat;
        self.top_level_await_enabled = previous_top_level_await_enabled;
        self.dynamic_eval_enabled = previous_dynamic_eval_enabled;
        self.active_source_name = previous_source_name;
        self.active_source_fullpath_name = previous_source_fullpath_name;
        self.abi_workspace_handle = previous_workspace_handle;
        self.active_source_identity = previous_source_identity;
        self.pending_companion_source_discovery = None;

        crate::abi::ExecutionResponse {
            source_context: crate::abi::ExecutionSourceContext {
                name: source_name,
                text: Some(source_text),
                identity: source_identity,
            },
            result: result
                .map(|outcome| apply_requested_output_policy(outcome, &requested_outputs)),
        }
    }

    async fn execute_internal(
        &mut self,
        input: &str,
        preserve_layout_var_names: bool,
    ) -> std::result::Result<SessionExecution, RunError> {
        let _active = ActiveExecutionGuard::new(self).map_err(|err| {
            RunError::Runtime(
                build_runtime_error(err.to_string())
                    .with_identifier("RunMat:ExecutionAlreadyActive")
                    .build(),
            )
        })?;
        runmat_vm::set_call_stack_limit(self.callstack_limit);
        runmat_vm::set_error_namespace(&self.error_namespace);
        runmat_vm::set_dynamic_eval_options(
            self.compat_mode,
            self.compat_mode.allows_runmat_extensions(),
            self.top_level_await_enabled,
            self.dynamic_eval_enabled,
        );
        runmat_hir::set_error_namespace(&self.error_namespace);
        let exec_span = info_span!(
            "runtime.execute",
            input_len = input.len(),
            verbose = self.verbose
        );
        let _exec_guard = exec_span.enter();
        runmat_runtime::console::reset_thread_buffer();
        runmat_runtime::plotting_hooks::reset_recent_figures();
        runmat_runtime::warning_store::reset();
        runmat_builtins::set_display_format(self.format_mode);
        reset_provider_telemetry();
        self.interrupt_flag.store(false, Ordering::Relaxed);
        let _interrupt_guard =
            runmat_runtime::interrupt::replace_interrupt(Some(self.interrupt_flag.clone()));
        let start_time = Instant::now();
        self.stats.total_executions += 1;
        let debug_trace = std::env::var("RUNMAT_DEBUG_REPL").is_ok();
        let stdin_events: Arc<Mutex<Vec<StdinEvent>>> = Arc::new(Mutex::new(Vec::new()));
        let host_async_handler = self.async_input_handler.clone();
        let stdin_events_async = Arc::clone(&stdin_events);
        let runtime_async_handler: Arc<runmat_runtime::interaction::AsyncInteractionHandler> =
            Arc::new(
                move |prompt: runmat_runtime::interaction::InteractionPromptOwned| {
                    let request_kind = match prompt.kind {
                        runmat_runtime::interaction::InteractionKind::Line { echo } => {
                            InputRequestKind::Line { echo }
                        }
                        runmat_runtime::interaction::InteractionKind::KeyPress => {
                            InputRequestKind::KeyPress
                        }
                    };
                    let request = InputRequest {
                        prompt: prompt.prompt,
                        kind: request_kind,
                    };
                    let (event_kind, echo_flag) = match &request.kind {
                        InputRequestKind::Line { echo } => (StdinEventKind::Line, *echo),
                        InputRequestKind::KeyPress => (StdinEventKind::KeyPress, false),
                    };
                    let mut event = StdinEvent {
                        prompt: request.prompt.clone(),
                        kind: event_kind,
                        echo: echo_flag,
                        value: None,
                        error: None,
                    };

                    let stdin_events_async = Arc::clone(&stdin_events_async);
                    let host_async_handler = host_async_handler.clone();
                    Box::pin(async move {
                        let resp: Result<InputResponse, String> =
                            if let Some(handler) = host_async_handler {
                                handler(request).await
                            } else {
                                match &request.kind {
                                    InputRequestKind::Line { echo } => {
                                        runmat_runtime::interaction::default_read_line(
                                            &request.prompt,
                                            *echo,
                                        )
                                        .map(InputResponse::Line)
                                    }
                                    InputRequestKind::KeyPress => {
                                        runmat_runtime::interaction::default_wait_for_key(
                                            &request.prompt,
                                        )
                                        .map(|_| InputResponse::KeyPress)
                                    }
                                }
                            };

                        let resp = resp.inspect_err(|err| {
                            event.error = Some(err.clone());
                            if let Ok(mut guard) = stdin_events_async.lock() {
                                guard.push(event.clone());
                            }
                        })?;

                        let interaction_resp = match resp {
                            InputResponse::Line(value) => {
                                event.value = Some(value.clone());
                                if let Ok(mut guard) = stdin_events_async.lock() {
                                    guard.push(event);
                                }
                                runmat_runtime::interaction::InteractionResponse::Line(value)
                            }
                            InputResponse::KeyPress => {
                                if let Ok(mut guard) = stdin_events_async.lock() {
                                    guard.push(event);
                                }
                                runmat_runtime::interaction::InteractionResponse::KeyPress
                            }
                        };
                        Ok(interaction_resp)
                    })
                },
            );
        let _async_input_guard =
            runmat_runtime::interaction::replace_async_handler(Some(runtime_async_handler));

        // Install a stateless expression evaluator for `input()` numeric parsing.
        //
        // The hook runs the full parse → lower → compile → interpret pipeline so
        // that users can type arbitrary MATLAB expressions at an input() prompt:
        // `sqrt(2)`, `pi/2`, `ones(3)`, `[1 2; 3 4]`, etc.
        //
        // Stack-overflow hazard: the hook calls runmat_vm::interpret() while
        // the outer interpret() is already on the call stack. On WASM the JS event
        // loop drives both as async state-machines and the WASM linear stack is
        // large, so nesting is safe. On native the default thread stack is too
        // small for two nested interpret() invocations. We cannot move the inner
        // evaluation to another thread because `Value` can carry thread-confined
        // GC handles, so the native path grows the stack around each poll of the
        // inner eval future. To avoid re-entering async-yielding prompt code
        // recursively, native prompt eval deliberately disables top-level await.
        let compat = self.compat_mode;
        #[cfg(not(target_arch = "wasm32"))]
        let dynamic_eval_enabled = self.dynamic_eval_enabled;
        #[cfg(target_arch = "wasm32")]
        let top_level_await_enabled = self.top_level_await_enabled;
        let source_name_for_eval_hook = self.current_source_name().to_string();
        let known_project_symbols_for_eval_hook = Arc::new(discover_known_project_symbols(Some(
            source_name_for_eval_hook.as_str(),
        )));
        let _eval_hook_guard =
            runmat_runtime::interaction::replace_eval_hook(Some(std::sync::Arc::new(
                move |expr: String| -> runmat_runtime::interaction::EvalHookFuture {
                    // Shared eval logic, used by both the WASM async path and the
                    // native thread path below.
                    async fn eval_expr(
                        expr: String,
                        compat: runmat_parser::CompatMode,
                        top_level_await_enabled: bool,
                        known_project_symbols: Arc<HashSet<String>>,
                    ) -> Result<Value, RuntimeError> {
                        let wrapped = format!("__runmat_input_result__ = ({expr});");
                        let ast = parse_with_options(&wrapped, ParserOptions::new(compat))
                            .map_err(|e| {
                                build_runtime_error(format!("input: parse error: {e}"))
                                    .with_identifier("RunMat:input:ParseError")
                                    .build()
                            })?;
                        let lowering = runmat_hir::lower(
                            &ast,
                            &LoweringContext::new(&HashMap::new())
                                .with_known_project_symbols(&known_project_symbols)
                                .with_runmat_extensions_enabled(compat.allows_runmat_extensions())
                                .with_top_level_await_enabled(top_level_await_enabled),
                        )
                        .map_err(|e| {
                            build_runtime_error(format!("input: lowering error: {e}"))
                                .with_identifier("RunMat:input:LowerError")
                                .build()
                        })?;
                        let bc =
                            compile_eval_hook_bytecode(&lowering).map_err(RuntimeError::from)?;
                        let result_idx = bc.var_names.iter().find_map(|(idx, name)| {
                            (name == "__runmat_input_result__").then_some(*idx)
                        });
                        let vars = runmat_vm::interpret(&bc).await?;
                        result_idx
                            .and_then(|idx| vars.get(idx).cloned())
                            .ok_or_else(|| {
                                build_runtime_error("input: expression produced no value")
                                    .with_identifier("RunMat:input:NoValue")
                                    .build()
                            })
                    }

                    let known_project_symbols = Arc::clone(&known_project_symbols_for_eval_hook);
                    #[cfg(target_arch = "wasm32")]
                    {
                        Box::pin(eval_expr(
                            expr,
                            compat,
                            top_level_await_enabled,
                            known_project_symbols,
                        ))
                    }
                    #[cfg(not(target_arch = "wasm32"))]
                    {
                        const INPUT_EVAL_STACK_BYTES: usize = 16 * 1024 * 1024;
                        let mut eval_future =
                            Box::pin(eval_expr(expr, compat, false, known_project_symbols));
                        Box::pin(futures::future::poll_fn(move |cx| {
                            stacker::grow(INPUT_EVAL_STACK_BYTES, || {
                                let _dynamic_eval_guard = runmat_vm::push_dynamic_eval_options(
                                    compat,
                                    compat.allows_runmat_extensions(),
                                    false,
                                    dynamic_eval_enabled,
                                );
                                eval_future.as_mut().poll(cx)
                            })
                        }))
                    }
                },
            )));

        if self.verbose {
            debug!("Executing: {}", input.trim());
        }

        let source_name_for_context = self.current_source_name().to_string();
        let _fallback_source_guard = runmat_runtime::source_context::replace_current_source_context(
            Some(&source_name_for_context),
            Some(input),
        );

        let PreparedExecution {
            ast,
            lowering,
            analysis,
            mut bytecode,
            function_registry_after_success,
            next_semantic_function_id_after_success,
        } = self.compile_input(input)?;
        let source_catalog_entries = self
            .source_pool
            .entries()
            .map(|(source_id, source)| {
                (
                    source_id,
                    source.name.to_string(),
                    source.fullpath_name.as_ref().map(ToString::to_string),
                    source.text.to_string(),
                )
            })
            .collect::<Vec<_>>();
        let _source_catalog_guard =
            runmat_runtime::source_context::replace_source_catalog_with_fullpaths(
                source_catalog_entries,
            );
        let _source_id_guard =
            runmat_runtime::source_context::replace_current_source_id(bytecode.source_id);
        #[cfg(target_arch = "wasm32")]
        let _ = &analysis;
        if self.verbose {
            debug!("AST: {ast:?}");
        }
        let display = execution_display_context(&lowering.assembly, bytecode.layout.as_ref());
        let display_context = display.context;
        let display_var_ids = display.display_var_ids;
        let stmt_count = entry_statement_count(&lowering.assembly);
        let execution_vars = execution_workspace_mapping(&bytecode);
        let max_var_id = execution_vars.values().copied().max().unwrap_or(0);
        if debug_trace {
            debug!(?execution_vars, "[repl] execution vars");
        }
        if debug_trace {
            debug!(workspace_values_before = ?self.workspace_values, "[repl] workspace snapshot before execution");
        }
        let id_to_name: HashMap<usize, String> = execution_vars
            .iter()
            .map(|(name, var_id)| (*var_id, name.clone()))
            .collect();
        let mut assigned_this_execution: HashSet<String> = HashSet::new();
        let assigned_snapshot: HashSet<String> = execution_vars
            .keys()
            .filter(|name| self.workspace_values.contains_key(name.as_str()))
            .cloned()
            .collect();
        let prev_assigned_snapshot = assigned_snapshot.clone();
        if debug_trace {
            debug!(?assigned_snapshot, "[repl] assigned snapshot");
        }
        let _pending_workspace_guard =
            runmat_vm::push_pending_workspace(execution_vars.clone(), assigned_snapshot.clone());
        if self.verbose {
            debug!("HIR generated successfully");
        }

        if preserve_layout_var_names && bytecode.layout.is_some() {
            for (slot, name) in &id_to_name {
                bytecode.var_names.insert(*slot, name.clone());
            }
        } else {
            bytecode.var_names = id_to_name.clone();
        }
        if self.verbose {
            debug!(
                "Bytecode compiled: {} instructions",
                bytecode.instructions.len()
            );
        }

        #[cfg(not(target_arch = "wasm32"))]
        let fusion_snapshot = if self.emit_fusion_plan {
            let runtime_groups = bytecode.runtime_fusion_groups();
            let (runtime_graph, runtime_graph_source) =
                bytecode.runtime_accel_graph_for_fusion_with_source(&runtime_groups);
            build_fusion_snapshot(
                &runtime_groups,
                &bytecode.fusion_metadata.mir_fusion_candidate_groups,
                &bytecode.fusion_metadata.instruction_windows,
                Some(crate::fusion::FusionPlannerMetadata {
                    source: "semantic-mir-analysis-runtime".to_string(),
                    accel_graph_state: if runtime_graph.is_some() {
                        "present".to_string()
                    } else {
                        "missing".to_string()
                    },
                    accel_graph_source: runtime_graph_source.as_str().to_string(),
                    mir_local_fact_count: mir_local_fact_count_for_entrypoint(
                        &analysis,
                        &lowering.assembly,
                    ),
                    mir_diagnostic_count: analysis.diagnostics.len(),
                    mir_fusion_signal_count: bytecode.fusion_metadata.mir_fusion_signal_count,
                    mir_fusion_candidate_group_count: bytecode
                        .fusion_metadata
                        .mir_fusion_candidate_group_count,
                    mir_semantic_instruction_window_count: bytecode
                        .fusion_metadata
                        .instruction_window_count,
                }),
            )
        } else {
            None
        };
        #[cfg(target_arch = "wasm32")]
        let fusion_snapshot: Option<FusionPlanSnapshot> = None;

        // Prepare variable array with existing values before execution
        self.prepare_variable_array_for_execution(&bytecode, &execution_vars, debug_trace);

        if self.verbose {
            debug!(
                "Variable array after preparation: {:?}",
                self.variable_array
            );
            debug!("Bytecode instructions: {:?}", bytecode.instructions);
        }

        #[cfg(feature = "jit")]
        let mut used_jit = false;
        #[cfg(not(feature = "jit"))]
        let used_jit = false;
        #[cfg(feature = "jit")]
        let mut execution_completed = false;
        #[cfg(not(feature = "jit"))]
        let execution_completed = false;
        let mut result_value: Option<Value> = None; // Always start fresh for each execution
        let mut suppressed_value: Option<Value> = None; // Track value for type info when suppressed
        let mut error = None;
        let mut workspace_updates: Vec<WorkspaceEntry> = Vec::new();
        let mut workspace_snapshot_force_full = false;
        let mut ans_update: Option<(usize, Value)> = None;

        // Check if this is an expression statement (ends with Pop)
        let is_expression_stmt = bytecode
            .instructions
            .last()
            .map(|instr| matches!(instr, runmat_vm::Instr::Pop))
            .unwrap_or(false);

        // Determine whether the final statement ended with a semicolon by inspecting the raw input.
        let is_semicolon_suppressed = {
            let toks = tokenize_detailed(input);
            toks.into_iter()
                .rev()
                .map(|t| t.token)
                .find(|token| {
                    !matches!(
                        token,
                        LexToken::Newline
                            | LexToken::LineComment
                            | LexToken::BlockComment
                            | LexToken::Section
                    )
                })
                .map(|t| matches!(t, LexToken::Semicolon))
                .unwrap_or(false)
        };
        let final_stmt_emit = display_context.final_stmt_emit;

        if self.verbose {
            debug!("Semantic entry body len: {stmt_count}");
            if let Some(stmt) = first_entry_statement(&lowering.assembly) {
                debug!("Semantic HIR statement: {stmt:?}");
            }
            debug!("is_semicolon_suppressed: {is_semicolon_suppressed}");
        }

        // Use JIT for assignments, interpreter for expressions (to capture results properly)
        #[cfg(feature = "jit")]
        {
            if let Some(ref mut jit_engine) = &mut self.jit_engine {
                if !is_expression_stmt {
                    // Ensure variable array is large enough
                    if self.variable_array.len() < bytecode.var_count {
                        self.variable_array
                            .resize(bytecode.var_count, Value::Num(0.0));
                    }

                    if self.verbose {
                        debug!(
                            "JIT path for assignment: variable_array size: {}, bytecode.var_count: {}",
                            self.variable_array.len(),
                            bytecode.var_count
                        );
                    }

                    // Use JIT for assignments
                    match jit_engine.execute_or_compile(&bytecode, &mut self.variable_array) {
                        Ok((_, actual_used_jit)) => {
                            used_jit = actual_used_jit;
                            execution_completed = true;
                            if actual_used_jit {
                                self.stats.jit_compiled += 1;
                            } else {
                                self.stats.interpreter_fallback += 1;
                            }
                            if !display_context.single_stmt_non_assign {
                                if let Some(var_id) = display_context.first_assign_var {
                                    if let Some(name) = id_to_name.get(&var_id) {
                                        assigned_this_execution.insert(name.clone());
                                    }
                                    if var_id < self.variable_array.len() {
                                        let assignment_value = self.variable_array[var_id].clone();
                                        if !is_semicolon_suppressed {
                                            result_value = Some(assignment_value);
                                            if self.verbose {
                                                debug!("JIT assignment result: {result_value:?}");
                                            }
                                        } else {
                                            suppressed_value = Some(assignment_value);
                                            if self.verbose {
                                                debug!(
                                                    "JIT assignment suppressed due to semicolon, captured for type info"
                                                );
                                            }
                                        }
                                    }
                                }
                            }

                            if self.verbose {
                                debug!(
                                    "{} assignment successful, variable_array: {:?}",
                                    if actual_used_jit {
                                        "JIT"
                                    } else {
                                        "Interpreter"
                                    },
                                    self.variable_array
                                );
                            }
                        }
                        Err(e) => {
                            if self.verbose {
                                debug!("JIT execution failed: {e}, using interpreter");
                            }
                            // Fall back to interpreter
                        }
                    }
                }
            }
        }

        // Use interpreter if JIT failed or is disabled
        if !execution_completed {
            if self.verbose {
                debug!(
                    "Interpreter path: variable_array size: {}, bytecode.var_count: {}",
                    self.variable_array.len(),
                    bytecode.var_count
                );
            }

            // For expressions, modify bytecode to store result in a temp variable instead of using stack
            let mut execution_bytecode = bytecode.clone();
            if is_expression_stmt
                && matches!(final_stmt_emit, FinalStmtEmitDisposition::Inline)
                && !execution_bytecode.instructions.is_empty()
            {
                execution_bytecode.instructions.pop(); // Remove the Pop instruction

                // Add StoreVar instruction to store the result in a temporary variable
                let temp_var_id = std::cmp::max(execution_bytecode.var_count, max_var_id + 1);
                execution_bytecode
                    .instructions
                    .push(runmat_vm::Instr::StoreVar(temp_var_id));
                execution_bytecode.var_count = temp_var_id + 1; // Expand variable count for temp variable

                // Ensure our variable array can hold the temporary variable
                if self.variable_array.len() <= temp_var_id {
                    self.variable_array.resize(temp_var_id + 1, Value::Num(0.0));
                }

                if self.verbose {
                    debug!(
                        "Modified expression bytecode, new instructions: {:?}",
                        execution_bytecode.instructions
                    );
                }
            }

            match self.interpret_with_context(&execution_bytecode).await {
                Ok(runmat_vm::InterpreterOutcome::Completed(results)) => {
                    // Only increment interpreter_fallback if JIT wasn't attempted
                    if !self.has_jit() || is_expression_stmt {
                        self.stats.interpreter_fallback += 1;
                    }
                    if self.verbose {
                        debug!("Interpreter results: {results:?}");
                    }

                    // Handle assignment statements (x = 42 should show the assigned value unless suppressed)
                    if stmt_count == 1 {
                        if !display_context.single_stmt_non_assign {
                            if let Some(var_id) = display_context.first_assign_var {
                                if let Some(name) = id_to_name.get(&var_id) {
                                    assigned_this_execution.insert(name.clone());
                                }
                                // For assignments, capture the assigned value for both display and type info
                                if var_id < self.variable_array.len() {
                                    let assignment_value = self.variable_array[var_id].clone();
                                    if !is_semicolon_suppressed {
                                        result_value = Some(assignment_value);
                                        if self.verbose {
                                            debug!(
                                                "Interpreter assignment result: {result_value:?}"
                                            );
                                        }
                                    } else {
                                        suppressed_value = Some(assignment_value);
                                        if self.verbose {
                                            debug!(
                                                "Interpreter assignment suppressed due to semicolon, captured for type info"
                                            );
                                        }
                                    }
                                }
                            }
                        } else if !is_expression_stmt
                            && !results.is_empty()
                            && !is_semicolon_suppressed
                            && !display_context.single_stmt_non_assign
                            && matches!(final_stmt_emit, FinalStmtEmitDisposition::NeedsFallback)
                        {
                            result_value = Some(results[0].clone());
                        }
                    }

                    // For expressions, get the result from the temporary variable (capture for both display and type info)
                    if is_expression_stmt
                        && matches!(final_stmt_emit, FinalStmtEmitDisposition::Inline)
                        && !execution_bytecode.instructions.is_empty()
                        && result_value.is_none()
                        && suppressed_value.is_none()
                    {
                        let temp_var_id = execution_bytecode.var_count - 1; // The temp variable we added
                        if temp_var_id < self.variable_array.len() {
                            let expression_value = self.variable_array[temp_var_id].clone();
                            if !is_semicolon_suppressed {
                                // Capture for 'ans' update when output is not suppressed
                                ans_update = Some((temp_var_id, expression_value.clone()));
                                result_value = Some(expression_value);
                                if self.verbose {
                                    debug!(
                                        "Expression result from temp var {temp_var_id}: {result_value:?}"
                                    );
                                }
                            } else {
                                suppressed_value = Some(expression_value);
                                if self.verbose {
                                    debug!(
                                        "Expression suppressed, captured for type info from temp var {temp_var_id}: {suppressed_value:?}"
                                    );
                                }
                            }
                        }
                    } else if !is_semicolon_suppressed
                        && matches!(final_stmt_emit, FinalStmtEmitDisposition::NeedsFallback)
                        && result_value.is_none()
                    {
                        result_value = results.into_iter().last();
                        if self.verbose {
                            debug!("Fallback result from interpreter: {result_value:?}");
                        }
                    }

                    if self.verbose {
                        debug!("Final result_value: {result_value:?}");
                    }
                    debug!("Interpreter execution successful");
                }

                Err(e) => {
                    debug!("Interpreter execution failed: {e}");
                    error = Some(e);
                }
            }
        }

        let last_assign_var = display_context.last_assign_var;
        let last_expr_emits = display_context.last_expr_emits;
        if !is_semicolon_suppressed && result_value.is_none() {
            let can_emit_from_context = !display_var_ids.is_empty() || last_expr_emits;
            if can_emit_from_context {
                if let Some(value) = runmat_runtime::console::take_last_value_output() {
                    result_value = Some(value);
                }
                if result_value.is_none() {
                    if let Some(var_id) = last_store_var_index(&bytecode) {
                        if var_id < self.variable_array.len() {
                            result_value = Some(self.variable_array[var_id].clone());
                        }
                    }
                    if result_value.is_none() {
                        if let Some(var_id) = last_assign_var {
                            if var_id < self.variable_array.len() {
                                result_value = Some(self.variable_array[var_id].clone());
                            }
                        }
                    }
                    if result_value.is_none() {
                        if let Some(var_id) = last_emit_var_index(&bytecode) {
                            if var_id < self.variable_array.len() {
                                result_value = Some(self.variable_array[var_id].clone());
                            }
                        }
                    }
                }
            }
        }

        let execution_time = start_time.elapsed();
        let execution_time_ms = execution_time.as_millis() as u64;

        self.stats.total_execution_time_ms += execution_time_ms;
        self.stats.average_execution_time_ms =
            self.stats.total_execution_time_ms as f64 / self.stats.total_executions as f64;

        // Update variable names mapping and function definitions if execution was successful
        if error.is_none() {
            if let Some(workspace_state) = runmat_vm::take_updated_workspace_state() {
                let mutated_names = workspace_state.names;
                let assigned = workspace_state.assigned;
                if debug_trace {
                    debug!(
                        ?mutated_names,
                        ?assigned,
                        "[repl] mutated names and assigned return values"
                    );
                }
                self.workspace_bindings.clear();
                for (name, slot) in &mutated_names {
                    self.bind_workspace_slot(name.clone(), *slot);
                }
                let previous_workspace = self.workspace_values.clone();
                let current_names: HashSet<String> = assigned
                    .iter()
                    .filter(|name| {
                        mutated_names
                            .get(*name)
                            .map(|var_id| *var_id < self.variable_array.len())
                            .unwrap_or(false)
                    })
                    .cloned()
                    .collect();
                let removed_names: HashSet<String> = previous_workspace
                    .keys()
                    .filter(|name| !current_names.contains(*name))
                    .cloned()
                    .collect();
                let mut rebuilt_workspace = HashMap::new();
                let mut changed_names: HashSet<String> = assigned
                    .difference(&prev_assigned_snapshot)
                    .cloned()
                    .collect();

                for name in &current_names {
                    let Some(var_id) = mutated_names.get(name).copied() else {
                        continue;
                    };
                    if var_id >= self.variable_array.len() {
                        continue;
                    }
                    let value_clone = self.variable_array[var_id].clone();
                    if previous_workspace.get(name) != Some(&value_clone) {
                        changed_names.insert(name.clone());
                    }
                    rebuilt_workspace.insert(name.clone(), value_clone);
                }

                if debug_trace {
                    debug!(?changed_names, ?removed_names, "[repl] workspace changes");
                }

                self.workspace_values = rebuilt_workspace;
                if !removed_names.is_empty() {
                    workspace_snapshot_force_full = true;
                } else {
                    for name in changed_names {
                        if let Some(value_clone) = self.workspace_values.get(&name).cloned() {
                            workspace_updates.push(workspace_entry(&name, &value_clone));
                            if debug_trace {
                                debug!(name, ?value_clone, "[repl] workspace update");
                            }
                        }
                    }
                }
            } else {
                let previous_workspace = self.workspace_values.clone();
                let mut rebuilt_workspace = HashMap::new();
                let mut changed_names: HashSet<String> = HashSet::new();

                for (name, var_id) in &execution_vars {
                    if *var_id >= self.variable_array.len() {
                        continue;
                    }
                    let value_clone = self.variable_array[*var_id].clone();
                    if previous_workspace.get(name) != Some(&value_clone) {
                        changed_names.insert(name.clone());
                    }
                    self.bind_workspace_slot(name.clone(), *var_id);
                    rebuilt_workspace.insert(name.clone(), value_clone);
                }

                let removed_names: HashSet<String> = previous_workspace
                    .keys()
                    .filter(|name| !rebuilt_workspace.contains_key(*name))
                    .cloned()
                    .collect();

                self.workspace_values = rebuilt_workspace;
                if !removed_names.is_empty() {
                    workspace_snapshot_force_full = true;
                } else {
                    for name in changed_names {
                        if let Some(value_clone) = self.workspace_values.get(&name).cloned() {
                            workspace_updates.push(workspace_entry(&name, &value_clone));
                        }
                    }
                }
            }
            self.function_registry = function_registry_after_success;
            self.next_semantic_function_id = next_semantic_function_id_after_success;
            // Apply 'ans' update if applicable (persisting expression result)
            if let Some((var_id, value)) = ans_update {
                self.bind_workspace_slot("ans".to_string(), var_id);
                self.workspace_values.insert("ans".to_string(), value);
                if debug_trace {
                    println!("Updated 'ans' to var_id {}", var_id);
                }
            }
        }

        if self.verbose {
            debug!("Execution completed in {execution_time_ms}ms (JIT: {used_jit})");
        }

        if !is_expression_stmt
            && !is_semicolon_suppressed
            && last_assign_var.is_some()
            && !display_context.single_stmt_non_assign
            && !display_var_ids.is_empty()
        {
            if let Some(var_id) = last_store_var_index(&bytecode) {
                if var_id < self.variable_array.len() {
                    result_value = Some(self.variable_array[var_id].clone());
                }
            } else if matches!(final_stmt_emit, FinalStmtEmitDisposition::NeedsFallback)
                && result_value.is_none()
            {
                if let Some(v) = self
                    .variable_array
                    .iter()
                    .rev()
                    .find(|v| !matches!(v, Value::Num(0.0)))
                    .cloned()
                {
                    result_value = Some(v);
                }
            }
        }

        if !is_semicolon_suppressed
            && (!display_var_ids.is_empty()
                || matches!(final_stmt_emit, FinalStmtEmitDisposition::NeedsFallback)
                || display_context.single_assign_var.is_some()
                || (is_expression_stmt
                    && matches!(final_stmt_emit, FinalStmtEmitDisposition::Inline)))
            && runmat_runtime::console::take_last_value_output().is_none()
        {
            if display_var_ids.is_empty() {
                if let Some(value) = result_value.as_ref() {
                    let label = last_emit_var_index(&bytecode)
                        .and_then(|var_id| id_to_name.get(&var_id).cloned())
                        .or_else(|| {
                            determine_display_label_from_context(
                                display_context.single_assign_var,
                                &id_to_name,
                                is_expression_stmt,
                                display_context.single_stmt_non_assign,
                            )
                        });
                    runmat_runtime::console::record_value_output(label.as_deref(), value);
                }
            } else {
                for var_id in display_var_ids {
                    if let (Some(label), Some(display_value)) =
                        (id_to_name.get(&var_id), self.variable_array.get(var_id))
                    {
                        runmat_runtime::console::record_value_output(
                            Some(label.as_str()),
                            display_value,
                        );
                    }
                }
            }
        }

        // Generate type info if we have a suppressed value
        let type_info = suppressed_value.as_ref().map(format_type_info);

        let streams = runmat_runtime::console::take_thread_buffer()
            .into_iter()
            .map(|entry| ExecutionStreamEntry {
                stream: match entry.stream {
                    runmat_runtime::console::ConsoleStream::Stdout => ExecutionStreamKind::Stdout,
                    runmat_runtime::console::ConsoleStream::Stderr => ExecutionStreamKind::Stderr,
                    runmat_runtime::console::ConsoleStream::ClearScreen => {
                        ExecutionStreamKind::ClearScreen
                    }
                },
                text: entry.text,
                timestamp_ms: entry.timestamp_ms,
            })
            .collect();
        let (workspace_entries, snapshot_full) = if workspace_snapshot_force_full {
            let mut entries: Vec<WorkspaceEntry> = self
                .workspace_values
                .iter()
                .map(|(name, value)| workspace_entry(name, value))
                .collect();
            entries.sort_by(|a, b| a.name.cmp(&b.name));
            (entries, true)
        } else if workspace_updates.is_empty() {
            if self.workspace_values.is_empty() {
                (workspace_updates, false)
            } else {
                let mut entries: Vec<WorkspaceEntry> = self
                    .workspace_values
                    .iter()
                    .map(|(name, value)| workspace_entry(name, value))
                    .collect();
                entries.sort_by(|a, b| a.name.cmp(&b.name));
                (entries, true)
            }
        } else {
            (workspace_updates, false)
        };
        let workspace_snapshot = self.build_workspace_snapshot(workspace_entries, snapshot_full);
        let figures_touched = runmat_runtime::plotting_hooks::take_recent_figures();
        let stdin_events = stdin_events
            .lock()
            .map(|guard| guard.clone())
            .unwrap_or_default();

        let warnings = runmat_runtime::warning_store::take_all();

        if let Some(runtime_error) = &mut error {
            self.normalize_error_namespace(runtime_error);
            self.populate_callstack(runtime_error);
        }

        let suppress_public_value =
            is_expression_stmt && matches!(final_stmt_emit, FinalStmtEmitDisposition::Suppressed);
        let public_value = if is_semicolon_suppressed || suppress_public_value {
            None
        } else {
            result_value
        };

        let mut diagnostics = Vec::new();
        if let Some(error) = &error {
            diagnostics.push(crate::abi::RuntimeDiagnostic {
                code: error
                    .identifier()
                    .unwrap_or("RunMat:RuntimeError")
                    .to_string(),
                severity: crate::abi::DiagnosticSeverity::Error,
                message: error.message().to_string(),
                span: runtime_error_span(error),
                callstack: if !error.context.call_stack.is_empty() {
                    error.context.call_stack.clone()
                } else {
                    error
                        .context
                        .call_frames
                        .iter()
                        .map(|frame| frame.function.clone())
                        .collect()
                },
                callstack_elided: error.context.call_frames_elided,
            });
        }
        diagnostics.extend(
            warnings
                .iter()
                .map(|warning| crate::abi::RuntimeDiagnostic {
                    code: warning.identifier.clone(),
                    severity: crate::abi::DiagnosticSeverity::Warning,
                    message: warning.message.clone(),
                    span: None,
                    callstack: Vec::new(),
                    callstack_elided: 0,
                }),
        );

        let display_events = public_value
            .as_ref()
            .map(|value| crate::abi::DisplayEvent {
                label: crate::abi::DisplayLabel::Anonymous,
                value: value.clone(),
                span: runmat_hir::Span::default(),
            })
            .into_iter()
            .collect();

        let profiling = gather_profiling(execution_time_ms);
        let outcome = crate::abi::ExecutionOutcome {
            flow: public_value
                .clone()
                .map(crate::abi::RuntimeFlow::Single)
                .unwrap_or(crate::abi::RuntimeFlow::NoValue),
            workspace_delta: crate::abi::WorkspaceDelta {
                version: workspace_snapshot.version,
                full_snapshot_required: workspace_snapshot.full,
                ..crate::abi::WorkspaceDelta::default()
            },
            display_events,
            streams,
            diagnostics,
            effects: Vec::new(),
            suspension: None,
            execution_time_ms,
            used_jit,
            type_info,
            figures_touched,
            stdin_events,
            fusion_plan: fusion_snapshot,
            profiling,
        };

        self.format_mode = runmat_builtins::get_display_format();
        Ok(SessionExecution {
            outcome,
            workspace_snapshot,
        })
    }

    /// Interpret bytecode with persistent variable context
    async fn interpret_with_context(
        &mut self,
        bytecode: &runmat_vm::Bytecode,
    ) -> Result<runmat_vm::InterpreterOutcome, RuntimeError> {
        let source_name = self.current_source_name().to_string();
        runmat_vm::interpret_with_vars(
            bytecode,
            &mut self.variable_array,
            Some(source_name.as_str()),
        )
        .await
    }

    fn abi_workspace_upserts(
        &self,
        workspace_names: Vec<String>,
    ) -> Vec<crate::abi::WorkspaceBindingValue> {
        let mut workspace_names = workspace_names;
        workspace_names.sort();
        workspace_names.dedup();
        workspace_names
            .into_iter()
            .filter_map(|name| {
                let value = self.workspace_values.get(&name)?.clone();
                let binding = runmat_hir::BindingName(name);
                let key = self
                    .workspace_bindings
                    .get(&binding.0)
                    .map(|binding| binding.key.clone())
                    .unwrap_or_else(|| self.workspace_binding_key(&binding.0));
                Some(crate::abi::WorkspaceBindingValue { key, value })
            })
            .collect()
    }

    fn abi_workspace_removals(
        &self,
        previous_workspace_names: HashSet<String>,
    ) -> Vec<crate::abi::WorkspaceBindingKey> {
        let mut removed_names = previous_workspace_names
            .into_iter()
            .filter(|name| !self.workspace_values.contains_key(name))
            .collect::<Vec<_>>();
        removed_names.sort();
        removed_names
            .into_iter()
            .map(|name| self.workspace_binding_key(&name))
            .collect()
    }
}

fn apply_requested_output_policy(
    mut outcome: crate::abi::ExecutionOutcome,
    requested_outputs: &runmat_hir::RequestedOutputCount,
) -> crate::abi::ExecutionOutcome {
    use crate::abi::RuntimeFlow;
    use runmat_hir::RequestedOutputCount;

    outcome.flow = match requested_outputs {
        RequestedOutputCount::Zero => RuntimeFlow::NoValue,
        RequestedOutputCount::One => match outcome.flow {
            RuntimeFlow::OutputList(mut values) | RuntimeFlow::CommaList(mut values) => {
                if values.is_empty() {
                    RuntimeFlow::NoValue
                } else {
                    RuntimeFlow::Single(values.remove(0))
                }
            }
            flow => flow,
        },
        RequestedOutputCount::Exactly(count) => {
            if *count == 0 {
                RuntimeFlow::NoValue
            } else if *count == 1 {
                match outcome.flow {
                    RuntimeFlow::OutputList(mut values) | RuntimeFlow::CommaList(mut values) => {
                        if values.is_empty() {
                            RuntimeFlow::NoValue
                        } else {
                            RuntimeFlow::Single(values.remove(0))
                        }
                    }
                    flow => flow,
                }
            } else {
                match outcome.flow {
                    RuntimeFlow::NoValue => RuntimeFlow::OutputList(Vec::new()),
                    RuntimeFlow::Single(value) => RuntimeFlow::OutputList(vec![value]),
                    RuntimeFlow::OutputList(mut values) | RuntimeFlow::CommaList(mut values) => {
                        values.truncate(*count);
                        RuntimeFlow::OutputList(values)
                    }
                    RuntimeFlow::DynamicList(handle) => RuntimeFlow::DynamicList(handle),
                }
            }
        }
        RequestedOutputCount::CurrentFunctionNargout => outcome.flow,
    };
    outcome
}

fn resolve_source_identity(
    source: &crate::abi::SourceInput,
    source_text: &str,
) -> Option<crate::abi::SourceIdentity> {
    match source {
        crate::abi::SourceInput::Path(path) => {
            Some(crate::abi::SourceIdentity::PathAndContentHash {
                path: path.clone(),
                hash: source_text_hash(source_text),
            })
        }
        crate::abi::SourceInput::Text { name, .. } => {
            if name.starts_with('<') {
                None
            } else {
                Some(crate::abi::SourceIdentity::PathAndContentHash {
                    path: name.clone(),
                    hash: source_text_hash(source_text),
                })
            }
        }
    }
}

fn source_text_hash(source_text: &str) -> String {
    use std::hash::{Hash, Hasher};
    let mut hasher = std::collections::hash_map::DefaultHasher::new();
    source_text.hash(&mut hasher);
    format!("{:016x}", hasher.finish())
}

fn unresolved_source_context(
    source: &crate::abi::SourceInput,
) -> crate::abi::ExecutionSourceContext {
    let name = match source {
        crate::abi::SourceInput::Path(path) => path.clone(),
        crate::abi::SourceInput::Text { name, .. } => name.clone(),
    };
    crate::abi::ExecutionSourceContext {
        name,
        text: match source {
            crate::abi::SourceInput::Text { text, .. } => Some(text.clone()),
            crate::abi::SourceInput::Path(_) => None,
        },
        identity: None,
    }
}

fn runtime_error_span(error: &runmat_runtime::RuntimeError) -> Option<runmat_hir::Span> {
    error.span.as_ref().map(|span| {
        let start = span.offset();
        runmat_hir::Span {
            start,
            end: start + span.len().max(1),
        }
    })
}

fn compile_eval_hook_bytecode(
    lowering: &runmat_hir::LoweringResult,
) -> Result<runmat_vm::Bytecode, runmat_vm::CompileError> {
    let entrypoint = lowering.assembly.entrypoints.first().ok_or_else(|| {
        runmat_vm::CompileError::new("semantic eval hook compile requires an entrypoint")
    })?;
    let mir = runmat_mir::lowering::lower_assembly(&lowering.assembly)
        .map_err(runmat_vm::CompileError::from)?;
    let _analysis = runmat_mir::analysis::analyze_assembly(&mir);
    runmat_vm::compile(&lowering.assembly, &mir, entrypoint.id)
}

fn execution_workspace_mapping(bytecode: &runmat_vm::Bytecode) -> HashMap<String, usize> {
    let Some(layout) = &bytecode.layout else {
        return HashMap::new();
    };
    let mut mapping = HashMap::new();
    for entrypoint in layout.entrypoints.values() {
        for export in &entrypoint.exports {
            mapping.insert(export.name.clone(), export.slot.0);
        }
    }
    mapping
}

fn entry_function(assembly: &runmat_hir::HirAssembly) -> Option<&runmat_hir::HirFunction> {
    let entrypoint = assembly.entrypoints.first()?;
    assembly
        .functions
        .iter()
        .find(|function| function.id == entrypoint.target)
}

fn entry_statement_count(assembly: &runmat_hir::HirAssembly) -> usize {
    entry_function(assembly)
        .map(|function| function.body.statements.len())
        .unwrap_or(0)
}

fn first_entry_statement(assembly: &runmat_hir::HirAssembly) -> Option<&runmat_hir::HirStmt> {
    entry_function(assembly)?.body.statements.first()
}

struct SessionExecution {
    outcome: crate::abi::ExecutionOutcome,
    workspace_snapshot: WorkspaceSnapshot,
}

#[derive(Debug)]
struct ResolvedSourceInput {
    display_name: String,
    fullpath_name: Option<String>,
    text: String,
}

async fn source_input_text(
    source: crate::abi::SourceInput,
) -> std::result::Result<ResolvedSourceInput, RunError> {
    match source {
        crate::abi::SourceInput::Text { name, text } => Ok(ResolvedSourceInput {
            display_name: name,
            fullpath_name: None,
            text,
        }),
        crate::abi::SourceInput::Path(path) => {
            let source_path = resolve_path_source_input(&path).await?;
            let source_name = crate::diagnostic_path::display_path_for_current_cwd(&source_path);
            let source_fullpath_name = source_path.to_string_lossy().to_string();

            let text = runmat_filesystem::read_to_string_async(&source_path)
                .await
                .map_err(|err| {
                    RunError::Runtime(
                        build_runtime_error(format!(
                            "failed to read source path '{}': {err}",
                            source_path.display()
                        ))
                        .with_identifier("RunMat:SourceReadFailed")
                        .build(),
                    )
                })?;
            Ok(ResolvedSourceInput {
                display_name: source_name,
                fullpath_name: Some(source_fullpath_name),
                text,
            })
        }
    }
}

async fn resolve_path_source_input(
    path: &str,
) -> std::result::Result<std::path::PathBuf, RunError> {
    #[cfg(not(target_arch = "wasm32"))]
    {
        use runmat_config::project::resolve_project_source_input_from;
        use std::path::Path;

        let cwd = runmat_filesystem::current_dir().map_err(|err| {
            RunError::Runtime(
                build_runtime_error(format!(
                    "failed to resolve current working directory while resolving source path '{path}': {err}"
                ))
                .with_identifier("RunMat:SourceResolveFailed")
                .build(),
            )
        })?;

        let source_path = std::path::PathBuf::from(path);
        let candidate = crate::diagnostic_path::resolve_against_base(path, &cwd);

        if let Ok(metadata) = runmat_filesystem::metadata_async(&candidate).await {
            if metadata.is_file() {
                return Ok(candidate);
            }
        }

        if source_path.extension().is_none() {
            let with_ext = candidate.with_extension("m");
            if let Ok(metadata) = runmat_filesystem::metadata_async(&with_ext).await {
                if metadata.is_file() {
                    return Ok(with_ext);
                }
            }
        }

        let resolved = resolve_project_source_input_from(&cwd, Path::new(path)).map_err(|err| {
            RunError::Runtime(
                build_runtime_error(format!(
                    "failed to resolve source input '{}' from working directory {}: {}",
                    path,
                    cwd.display(),
                    err
                ))
                .with_identifier("RunMat:EntrypointResolveFailed")
                .build(),
            )
        })?;
        Ok(if resolved.is_absolute() {
            resolved
        } else {
            cwd.join(resolved)
        })
    }

    #[cfg(target_arch = "wasm32")]
    {
        use std::path::PathBuf;

        let cwd = runmat_filesystem::current_dir().map_err(|err| {
            RunError::Runtime(
                build_runtime_error(format!(
                    "failed to resolve current working directory while resolving source path '{path}': {err}"
                ))
                .with_identifier("RunMat:SourceResolveFailed")
                .build(),
            )
        })?;
        let source_path = PathBuf::from(path);
        let candidate = crate::diagnostic_path::resolve_against_base(path, &cwd);

        if let Ok(metadata) = runmat_filesystem::metadata_async(&candidate).await {
            if metadata.is_file() {
                return Ok(candidate);
            }
        }

        if source_path.extension().is_none() {
            let with_ext = candidate.with_extension("m");
            if let Ok(metadata) = runmat_filesystem::metadata_async(&with_ext).await {
                if metadata.is_file() {
                    return Ok(with_ext);
                }
            }
        }

        Ok(candidate)
    }
}

#[cfg(test)]
mod tests {
    #[cfg(not(target_arch = "wasm32"))]
    use super::discover_known_project_symbols;
    #[cfg(not(target_arch = "wasm32"))]
    use super::source_input_text;
    #[cfg(not(target_arch = "wasm32"))]
    use crate::abi::SourceInput;
    #[cfg(not(target_arch = "wasm32"))]
    use crate::RunError;
    #[cfg(not(target_arch = "wasm32"))]
    use std::fs;
    #[cfg(not(target_arch = "wasm32"))]
    use std::path::{Path, PathBuf};
    #[cfg(not(target_arch = "wasm32"))]
    use std::sync::Arc;

    #[cfg(not(target_arch = "wasm32"))]
    struct CwdGuard {
        original: PathBuf,
    }

    #[cfg(not(target_arch = "wasm32"))]
    fn cwd_lock() -> std::sync::MutexGuard<'static, ()> {
        runmat_filesystem::provider_override_lock()
    }

    #[cfg(not(target_arch = "wasm32"))]
    impl Drop for CwdGuard {
        fn drop(&mut self) {
            let _ = std::env::set_current_dir(&self.original);
        }
    }

    #[cfg(not(target_arch = "wasm32"))]
    fn push_cwd(path: &Path) -> CwdGuard {
        let original = std::env::current_dir().expect("read cwd");
        std::env::set_current_dir(path).expect("set cwd");
        CwdGuard { original }
    }

    #[test]
    #[cfg(not(target_arch = "wasm32"))]
    fn source_input_path_resolves_named_manifest_entrypoint() {
        let _guard = cwd_lock();
        let tmp = tempfile::TempDir::new().unwrap();
        fs::create_dir_all(tmp.path().join("src")).unwrap();
        fs::write(tmp.path().join("src/main.m"), "x = 1;").unwrap();
        fs::write(
            tmp.path().join("runmat.toml"),
            r#"
[package]
name = "demo"

[sources]
roots = ["src"]

[entrypoints.main]
path = "src/main"
"#,
        )
        .unwrap();
        let _cwd = push_cwd(tmp.path());
        let resolved =
            futures::executor::block_on(source_input_text(SourceInput::Path("main".to_string())))
                .expect("named entrypoint should resolve");
        assert_eq!(
            PathBuf::from(&resolved.display_name),
            PathBuf::from("src").join("main.m")
        );
        let resolved_path = std::path::PathBuf::from(
            resolved
                .fullpath_name
                .as_deref()
                .expect("path source should carry fullpath name"),
        )
        .canonicalize()
        .unwrap();
        let expected = tmp.path().join("src/main.m").canonicalize().unwrap();
        assert_eq!(
            resolved_path, expected,
            "resolved source path should match manifest entrypoint target"
        );
        assert_eq!(resolved.text, "x = 1;");
    }

    #[test]
    #[cfg(not(target_arch = "wasm32"))]
    fn source_input_path_infers_m_extension_for_relative_path() {
        let _guard = cwd_lock();
        let tmp = tempfile::TempDir::new().unwrap();
        fs::create_dir_all(tmp.path().join("src")).unwrap();
        fs::write(tmp.path().join("src/main.m"), "x = 1;").unwrap();
        let _cwd = push_cwd(tmp.path());

        let resolved = futures::executor::block_on(source_input_text(SourceInput::Path(
            "src/main".to_string(),
        )))
        .expect("path without extension should infer .m");

        assert_eq!(
            PathBuf::from(&resolved.display_name),
            PathBuf::from("src").join("main.m")
        );
        assert_eq!(resolved.text.trim(), "x = 1;");
    }

    #[test]
    #[cfg(not(target_arch = "wasm32"))]
    fn source_input_path_infers_m_extension_from_memory_provider() {
        let _guard = cwd_lock();
        let provider = runmat_filesystem::MemoryFsProvider::new();
        provider.write_project_path("/main.m", b"x = 1;").unwrap();

        runmat_filesystem::with_provider_override(Arc::new(provider), || {
            let resolved = futures::executor::block_on(source_input_text(SourceInput::Path(
                "main".to_string(),
            )))
            .expect("memory provider should resolve path without extension");

            assert_eq!(
                PathBuf::from(&resolved.display_name),
                PathBuf::from("main.m")
            );
            assert_eq!(resolved.text, "x = 1;");
        });
    }

    #[test]
    #[cfg(not(target_arch = "wasm32"))]
    fn source_input_path_errors_for_invalid_named_entrypoint_target() {
        let _guard = cwd_lock();
        let tmp = tempfile::TempDir::new().unwrap();
        fs::create_dir_all(tmp.path().join("src")).unwrap();
        fs::write(
            tmp.path().join("runmat.toml"),
            r#"
[package]
name = "demo"

[sources]
roots = ["src"]

[entrypoints.server]
module = "app.server"
function = "main"
"#,
        )
        .unwrap();
        let _cwd = push_cwd(tmp.path());
        let err =
            futures::executor::block_on(source_input_text(SourceInput::Path("server".to_string())))
                .expect_err("invalid module/function entrypoint should report resolve error");
        let RunError::Runtime(runtime_err) = err else {
            panic!("expected runtime error");
        };
        assert_eq!(
            runtime_err.identifier.as_deref(),
            Some("RunMat:EntrypointResolveFailed")
        );
    }

    #[test]
    #[cfg(not(target_arch = "wasm32"))]
    fn discover_known_project_symbols_reads_manifest_source_context() {
        let _guard = cwd_lock();
        let tmp = tempfile::TempDir::new().unwrap();
        fs::create_dir_all(tmp.path().join("+stats")).unwrap();
        fs::write(
            tmp.path().join("runmat.toml"),
            r#"
[package]
name = "demo"

[sources]
roots = ["."]
"#,
        )
        .unwrap();
        fs::write(
            tmp.path().join("+stats/summarize.m"),
            "function y = summarize(x); y = x; end",
        )
        .unwrap();
        fs::write(tmp.path().join("main.m"), "x = 1;").unwrap();
        let _cwd = push_cwd(tmp.path());

        let symbols = discover_known_project_symbols(Some(
            tmp.path().join("main.m").to_string_lossy().as_ref(),
        ));
        assert!(
            symbols.contains("stats.summarize"),
            "source-context discovery should include project symbols for eval-hook lowering"
        );
    }

    #[test]
    #[cfg(not(target_arch = "wasm32"))]
    fn discover_known_project_symbols_includes_dependency_alias_qualified_names() {
        let _guard = cwd_lock();
        let tmp = tempfile::TempDir::new().unwrap();
        let dep_root = tmp.path().join("deps/statslib");
        fs::create_dir_all(&dep_root).unwrap();
        fs::write(
            tmp.path().join("runmat.toml"),
            r#"
[package]
name = "demo"

[sources]
roots = ["."]

[dependencies]
statsdep = { path = "deps/statslib" }
"#,
        )
        .unwrap();
        fs::write(
            dep_root.join("runmat.toml"),
            r#"
[package]
name = "statslib"

[sources]
roots = ["."]
"#,
        )
        .unwrap();
        fs::write(
            dep_root.join("summarize.m"),
            "function y = summarize(x); y = x; end",
        )
        .unwrap();
        fs::write(tmp.path().join("main.m"), "x = 1;").unwrap();
        let _cwd = push_cwd(tmp.path());

        let symbols = discover_known_project_symbols(Some(
            tmp.path().join("main.m").to_string_lossy().as_ref(),
        ));
        assert!(
            symbols.contains("summarize"),
            "expected base dependency symbol in known-project discovery"
        );
        assert!(
            symbols.contains("statslib.summarize"),
            "expected package-qualified dependency symbol in known-project discovery"
        );
        assert!(
            symbols.contains("statsdep.summarize"),
            "expected dependency-alias-qualified symbol in known-project discovery"
        );
    }
}
