// None of these tests use #[wasm_bindgen_test], so they cannot run in the
// browser via wasm-pack. Excluding them from wasm32 avoids compiling a full
// runmat-runtime wasm binary per test file with zero executable tests.
#![cfg(not(target_arch = "wasm32"))]

use runmat_builtins::Value;
use runmat_core::{ExecutionStreamKind, RunError, RunMatSession, SessionExecutionResult};
use runmat_gc::gc_test_context;

/// Test basic semicolon suppression behavior
#[test]
fn test_semicolon_suppresses_output() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();

    // Expression without semicolon should return a value
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "2 + 3").unwrap();
    assert!(result.value.is_some());
    assert_eq!(result.value.unwrap().to_string(), "5");

    // Expression with semicolon should suppress output
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "2 + 3;").unwrap();
    assert!(result.value.is_none()); // No value returned due to semicolon suppression
}

/// Test semicolon suppression with assignments
#[test]
fn test_assignment_with_semicolon() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();

    // Assignment without semicolon should return the assigned value
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "x = 42").unwrap();
    assert!(result.value.is_some());
    assert_eq!(result.value.as_ref().unwrap().to_string(), "42");
    let stdout = collect_stdout_texts(&result);
    assert_eq!(stdout, vec!["x = 42"]);

    // Assignment with semicolon should suppress output
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "y = 42;").unwrap();
    assert!(result.value.is_none()); // No value returned due to semicolon suppression
    let stdout = collect_stdout_texts(&result);
    assert!(stdout.is_empty());

    // But the variable should still be assigned
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "y").unwrap();
    assert!(result.value.is_some());
    assert_eq!(result.value.unwrap().to_string(), "42");
}

#[test]
fn test_repeated_unsuppressed_assignment_does_not_duplicate_stdout() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();

    let first = runmat_core::execute_text_request_for_testing(&mut engine, "x = 42").unwrap();
    let second = runmat_core::execute_text_request_for_testing(&mut engine, "x = 42").unwrap();
    let third = runmat_core::execute_text_request_for_testing(&mut engine, "x = 42").unwrap();

    assert_eq!(collect_stdout_texts(&first), vec!["x = 42"]);
    assert_eq!(collect_stdout_texts(&second), vec!["x = 42"]);
    assert_eq!(collect_stdout_texts(&third), vec!["x = 42"]);
}

#[test]
fn test_repeated_arrayfun_assignment_does_not_duplicate_stdout() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();

    let source = "A = [1 2 3; 4 5 6];\nB = arrayfun(@(x) x.^2, A)";

    let first = runmat_core::execute_text_request_for_testing(&mut engine, source).unwrap();
    let second = runmat_core::execute_text_request_for_testing(&mut engine, source).unwrap();
    let third = runmat_core::execute_text_request_for_testing(&mut engine, source).unwrap();

    let first_stdout = collect_stdout_texts(&first);
    let second_stdout = collect_stdout_texts(&second);
    let third_stdout = collect_stdout_texts(&third);

    assert_eq!(first_stdout.len(), 1);
    assert_eq!(second_stdout, first_stdout);
    assert_eq!(third_stdout, first_stdout);
}

#[test]
fn test_semicolon_with_trailing_newline_is_suppressed() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "z = 5;\n").unwrap();
    assert!(result.value.is_none());
    assert!(collect_stdout_texts(&result).is_empty());
}

/// Test mixed expressions with and without semicolons
#[test]
fn test_mixed_semicolon_behavior() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();

    // Set up some variables
    runmat_core::execute_text_request_for_testing(&mut engine, "a = 10;").unwrap(); // No output
    runmat_core::execute_text_request_for_testing(&mut engine, "b = 20;").unwrap(); // No output

    let result = runmat_core::execute_text_request_for_testing(&mut engine, "a").unwrap();
    assert!(result.value.is_some());
    assert_eq!(result.value.unwrap().to_string(), "10");
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "b").unwrap();
    assert!(result.value.is_some());
    assert_eq!(result.value.unwrap().to_string(), "20");

    // Expression without semicolon should show result
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "a + b").unwrap();
    assert!(result.value.is_some());
    assert_eq!(result.value.unwrap().to_string(), "30");

    // Same expression with semicolon should suppress output
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "a + b;").unwrap();
    assert!(result.value.is_none());
}

#[test]
fn test_fprintf_does_not_print_ans() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();
    let result =
        runmat_core::execute_text_request_for_testing(&mut engine, "fprintf('foo')").unwrap();
    let stdout = collect_stdout_texts(&result);
    assert_eq!(stdout, vec!["foo"]);
    assert!(stdout.iter().all(|line| !line.contains("ans =")));
}

#[test]
fn test_disp_streams_include_line_breaks() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();
    let result =
        runmat_core::execute_text_request_for_testing(&mut engine, "disp('alpha')\ndisp('beta')")
            .unwrap();
    let stdout = collect_stdout_stream(&result);
    assert_eq!(stdout, "alpha\nbeta\n");
}

#[test]
fn test_fprintf_streams_remain_exact_chunks() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();
    let result =
        runmat_core::execute_text_request_for_testing(&mut engine, "fprintf('a'); fprintf('b');")
            .unwrap();
    let stdout = collect_stdout_stream(&result);
    assert_eq!(stdout, "ab");
}

/// Test semicolon suppression with function calls
#[test]
fn test_function_call_semicolon_suppression() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();

    // Function call without semicolon should return result
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "sin(0)").unwrap();
    assert!(result.value.is_some());
    assert_eq!(result.value.unwrap().to_string(), "0");

    // Function call with semicolon should suppress output
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "sin(0);").unwrap();
    assert!(result.value.is_none());
}

/// Test semicolon suppression with matrix operations
#[test]
fn test_matrix_semicolon_suppression() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();

    // Matrix creation without semicolon should show result
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "[1, 2, 3]").unwrap();
    let Some(Value::Tensor(tensor)) = result.value else {
        panic!("expected tensor result for row-vector literal");
    };
    assert_eq!(tensor.shape, vec![1, 3]);
    assert_eq!(tensor.data, vec![1.0, 2.0, 3.0]);

    // Matrix creation with semicolon should suppress output
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "[1, 2, 3];").unwrap();
    assert!(result.value.is_none());
}

/// Test semicolon suppression with complex expressions
#[test]
fn test_complex_expression_semicolon_suppression() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();

    // Complex expression without semicolon
    let result =
        runmat_core::execute_text_request_for_testing(&mut engine, "(2 + 3) * (4 - 1)").unwrap();
    assert!(result.value.is_some());
    assert_eq!(result.value.unwrap().to_string(), "15");

    // Same complex expression with semicolon
    let result =
        runmat_core::execute_text_request_for_testing(&mut engine, "(2 + 3) * (4 - 1);").unwrap();
    assert!(result.value.is_none());
}

/// Test that errors are always shown regardless of semicolons
#[test]
fn test_errors_always_shown() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();

    // Error without semicolon
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "undefined_var");
    assert_undefined_variable_identifier(result);

    // Error with semicolon should still be shown
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "undefined_var;");
    assert_undefined_variable_identifier(result);
}

/// Test that type information is shown for semicolon-suppressed assignments
#[test]
fn test_type_info_display() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();

    // Test scalar assignment with semicolon
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "x = 42;").unwrap();
    assert!(result.value.is_none());
    assert_eq!(result.type_info, Some("scalar".to_string()));

    // Test matrix assignment with semicolon
    let result =
        runmat_core::execute_text_request_for_testing(&mut engine, "y = [1, 2; 3, 4];").unwrap();
    assert!(result.value.is_none());
    assert_eq!(result.type_info, Some("2x2 matrix".to_string()));

    // Test vector assignment with semicolon
    let result =
        runmat_core::execute_text_request_for_testing(&mut engine, "z = [1, 2, 3];").unwrap();
    assert!(result.value.is_none());
    assert_eq!(result.type_info, Some("1x3 vector".to_string()));

    // Test char array assignment with semicolon
    let result =
        runmat_core::execute_text_request_for_testing(&mut engine, "w = 'hello';").unwrap();
    assert!(result.value.is_none());
    assert_eq!(result.type_info, Some("1x5 char array".to_string()));

    // Test that assignments without semicolon still show values, not type info
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "a = 100").unwrap();
    assert!(result.value.is_some());
    assert_eq!(result.value.as_ref().unwrap().to_string(), "100");
    assert_eq!(result.type_info, None);
    let stdout = collect_stdout_texts(&result);
    assert_eq!(stdout, vec!["a = 100"]);
}

/// Test that statement-only constructs (like if/while) are not affected by semicolons
#[test]
fn test_control_flow_not_affected() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();

    // Set up a variable
    runmat_core::execute_text_request_for_testing(&mut engine, "x = 5;").unwrap();

    // Control flow statements shouldn't return values regardless of semicolons
    let result =
        runmat_core::execute_text_request_for_testing(&mut engine, "if x > 0; y = 1; end").unwrap();
    assert!(result.value.is_none()); // Control flow doesn't return values

    let result =
        runmat_core::execute_text_request_for_testing(&mut engine, "if x > 0; y = 2; end;")
            .unwrap();
    assert!(result.value.is_none()); // Still no values

    // But the assignment inside should work
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "y").unwrap();
    assert!(result.value.is_some());
    assert_eq!(result.value.unwrap().to_string(), "2");
}

fn collect_stdout_texts(result: &SessionExecutionResult) -> Vec<String> {
    result
        .streams
        .iter()
        .filter(|entry| entry.stream == ExecutionStreamKind::Stdout)
        .map(|entry| entry.text.trim().to_string())
        .collect()
}

fn collect_stdout_stream(result: &SessionExecutionResult) -> String {
    result
        .streams
        .iter()
        .filter(|entry| entry.stream == ExecutionStreamKind::Stdout)
        .map(|entry| entry.text.as_str())
        .collect::<String>()
}

fn assert_undefined_variable_identifier(result: Result<SessionExecutionResult, RunError>) {
    match result {
        Err(RunError::Semantic(err)) => {
            assert_eq!(err.identifier.as_deref(), Some("RunMat:UndefinedVariable"));
        }
        Err(RunError::Runtime(err)) => {
            assert_eq!(err.identifier(), Some("RunMat:UndefinedVariable"));
        }
        Err(other) => {
            panic!("expected semantic/runtime undefined-variable error, got: {other:?}");
        }
        Ok(exec) => {
            let err = exec
                .error
                .expect("expected execution-level undefined-variable error");
            assert_eq!(err.identifier(), Some("RunMat:UndefinedVariable"));
        }
    }
}

#[test]
fn test_unsuppressed_assignment_and_expression_emit_outputs() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();
    let result = runmat_core::execute_text_request_for_testing(&mut engine, "x = 1\nx").unwrap();
    let stdout = collect_stdout_texts(&result);
    assert_eq!(stdout, vec!["x = 1", "x = 1"]);
}

#[test]
fn test_multi_assign_emits_each_variable() {
    let mut engine = gc_test_context(RunMatSession::new).unwrap();
    let result =
        runmat_core::execute_text_request_for_testing(&mut engine, "[a, b] = deal(1, 2)").unwrap();
    let stdout = collect_stdout_texts(&result);
    assert_eq!(stdout, vec!["a = 1", "b = 2"]);
}
