//! MATLAB-compatible `setfield` builtin with struct array and object support.
//!
//! Mirrors MATLAB's `setfield` semantics, including nested field creation, struct
//! array indexing via cell arguments, and property assignment on MATLAB-style
//! objects. The builtin performs all updates on host data; GPU-resident values are
//! gathered automatically before mutation. Updated tensors remain on the host.

use crate::builtins::common::spec::{
    BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
    ReductionNaN, ResidencyPolicy, ShapeRequirements,
};
use crate::builtins::structs::type_resolvers::setfield_type;
use crate::{
    build_runtime_error, call_builtin_async, gather_if_needed_async, BuiltinResult, RuntimeError,
};
use runmat_builtins::{
    Access, CellArray, CharArray, ComplexTensor, HandleRef, LogicalArray, ObjectInstance,
    StructValue, Tensor, Value,
};
use runmat_gc_api::GcPtr;
use runmat_macros::runtime_builtin;
use std::convert::TryFrom;

#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::structs::core::setfield")]
pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
    name: "setfield",
    op_kind: GpuOpKind::Custom("setfield"),
    supported_precisions: &[],
    broadcast: BroadcastSemantics::None,
    provider_hooks: &[],
    constant_strategy: ConstantStrategy::InlineLiteral,
    residency: ResidencyPolicy::InheritInputs,
    nan_mode: ReductionNaN::Include,
    two_pass_threshold: None,
    workgroup_size: None,
    accepts_nan_mode: false,
    notes: "Host-only metadata mutation; GPU tensors are gathered before assignment.",
};

#[runmat_macros::register_fusion_spec(builtin_path = "crate::builtins::structs::core::setfield")]
pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
    name: "setfield",
    shape: ShapeRequirements::Any,
    constant_strategy: ConstantStrategy::InlineLiteral,
    elementwise: None,
    reduction: None,
    emits_nan: false,
    notes: "Assignments terminate fusion and gather device data back to the host.",
};

const BUILTIN_NAME: &str = "setfield";

fn setfield_flow(message: impl Into<String>) -> RuntimeError {
    build_runtime_error(message)
        .with_builtin(BUILTIN_NAME)
        .build()
}

fn remap_setfield_flow(err: RuntimeError, prefix: Option<&str>) -> RuntimeError {
    let mut message = err.message().to_string();
    if let Some(prefix) = prefix {
        if !message.starts_with(prefix) {
            message = format!("{prefix}{message}");
        }
    }
    let mut builder = build_runtime_error(message).with_builtin(BUILTIN_NAME);
    if let Some(identifier) = err.identifier() {
        builder = builder.with_identifier(identifier);
    }
    builder.with_source(err).build()
}

fn is_undefined_function(err: &RuntimeError) -> bool {
    err.identifier() == Some("RunMat:UndefinedFunction")
        || err.message().contains("RunMat:UndefinedFunction")
}

#[runtime_builtin(
    name = "setfield",
    category = "structs/core",
    summary = "Assign into struct fields, struct arrays, or MATLAB-style object properties.",
    keywords = "setfield,struct,assignment,object property",
    type_resolver(setfield_type),
    builtin_path = "crate::builtins::structs::core::setfield"
)]
async fn setfield_builtin(base: Value, rest: Vec<Value>) -> BuiltinResult<Value> {
    let parsed = parse_arguments(rest)?;
    let ParsedArguments {
        leading_index,
        steps,
        value,
    } = parsed;
    assign_value(base, leading_index, steps, value).await
}

struct ParsedArguments {
    leading_index: Option<IndexSelector>,
    steps: Vec<FieldStep>,
    value: Value,
}

struct FieldStep {
    name: String,
    index: Option<IndexSelector>,
}

#[derive(Clone)]
struct IndexSelector {
    components: Vec<IndexComponent>,
}

#[derive(Clone)]
enum IndexComponent {
    Scalar(usize),
    End,
}

fn parse_arguments(mut rest: Vec<Value>) -> BuiltinResult<ParsedArguments> {
    if rest.len() < 2 {
        return Err(setfield_flow(
            "setfield: expected at least one field name and a value",
        ));
    }

    let value = rest
        .pop()
        .expect("rest contains at least two elements after early return");

    let mut parsed = ParsedArguments {
        leading_index: None,
        steps: Vec::new(),
        value,
    };

    if let Some(first) = rest.first() {
        if is_index_selector(first) {
            let selector = rest.remove(0);
            parsed.leading_index = Some(parse_index_selector(selector)?);
        }
    }

    if rest.is_empty() {
        return Err(setfield_flow("setfield: expected field name arguments"));
    }

    let mut iter = rest.into_iter().peekable();
    while let Some(arg) = iter.next() {
        let name = parse_field_name(arg)?;
        let mut step = FieldStep { name, index: None };
        if let Some(next) = iter.peek() {
            if is_index_selector(next) {
                let selector = iter.next().unwrap();
                step.index = Some(parse_index_selector(selector)?);
            }
        }
        parsed.steps.push(step);
    }

    if parsed.steps.is_empty() {
        return Err(setfield_flow("setfield: expected field name arguments"));
    }

    Ok(parsed)
}

async fn assign_value(
    base: Value,
    leading_index: Option<IndexSelector>,
    steps: Vec<FieldStep>,
    rhs: Value,
) -> BuiltinResult<Value> {
    if steps.is_empty() {
        return Err(setfield_flow("setfield: expected field name arguments"));
    }
    if let Some(selector) = leading_index {
        assign_with_leading_index(base, &selector, &steps, rhs).await
    } else {
        assign_without_leading_index(base, &steps, rhs).await
    }
}

async fn assign_with_leading_index(
    base: Value,
    selector: &IndexSelector,
    steps: &[FieldStep],
    rhs: Value,
) -> BuiltinResult<Value> {
    match base {
        Value::Cell(cell) => assign_into_struct_array(cell, selector, steps, rhs).await,
        other => Err(setfield_flow(format!(
            "setfield: leading indices require a struct array, got {other:?}"
        ))),
    }
}

async fn assign_without_leading_index(
    base: Value,
    steps: &[FieldStep],
    rhs: Value,
) -> BuiltinResult<Value> {
    match base {
        Value::Struct(struct_value) => assign_into_struct(struct_value, steps, rhs).await,
        Value::Object(object) => assign_into_object(object, steps, rhs).await,
        Value::Cell(cell) if is_struct_array(&cell) => {
            if cell.data.is_empty() {
                Err(setfield_flow(
                    "setfield: struct array is empty; supply indices in a cell array",
                ))
            } else {
                let selector = IndexSelector {
                    components: vec![IndexComponent::Scalar(1)],
                };
                assign_into_struct_array(cell, &selector, steps, rhs).await
            }
        }
        Value::HandleObject(handle) => assign_into_handle(handle, steps, rhs).await,
        Value::Listener(_) => Err(setfield_flow(
            "setfield: listeners do not support direct field assignment",
        )),
        other => Err(setfield_flow(format!(
            "setfield unsupported on this value for field '{}': {other:?}",
            steps.first().map(|s| s.name.as_str()).unwrap_or_default()
        ))),
    }
}

async fn assign_into_struct_array(
    mut cell: CellArray,
    selector: &IndexSelector,
    steps: &[FieldStep],
    rhs: Value,
) -> BuiltinResult<Value> {
    if selector.components.is_empty() {
        return Err(setfield_flow(
            "setfield: index cell must contain at least one element",
        ));
    }

    let resolved = resolve_indices(&Value::Cell(cell.clone()), selector)?;

    let position = match resolved.len() {
        1 => {
            let idx = resolved[0];
            if idx == 0 || idx > cell.data.len() {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            idx - 1
        }
        2 => {
            let row = resolved[0];
            let col = resolved[1];
            if row == 0 || row > cell.rows || col == 0 || col > cell.cols {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            (row - 1) * cell.cols + (col - 1)
        }
        _ => {
            return Err(setfield_flow(
                "setfield: indexing with more than two indices is not supported yet",
            ));
        }
    };

    let handle = cell
        .data
        .get(position)
        .ok_or_else(|| setfield_flow("Index exceeds the number of array elements."))?
        .clone();

    let current = unsafe { &*handle.as_raw() }.clone();
    let updated = assign_into_value(current, steps, rhs).await?;
    cell.data[position] = allocate_cell_handle(updated)?;
    Ok(Value::Cell(cell))
}

#[async_recursion::async_recursion(?Send)]
async fn assign_into_value(value: Value, steps: &[FieldStep], rhs: Value) -> BuiltinResult<Value> {
    if steps.is_empty() {
        return Ok(rhs);
    }
    match value {
        Value::Struct(struct_value) => assign_into_struct(struct_value, steps, rhs).await,
        Value::Object(object) => assign_into_object(object, steps, rhs).await,
        Value::Cell(cell) => assign_into_cell(cell, steps, rhs).await,
        Value::HandleObject(handle) => assign_into_handle(handle, steps, rhs).await,
        Value::Listener(_) => Err(setfield_flow(
            "setfield: listeners do not support nested field assignment",
        )),
        other => Err(setfield_flow(format!(
            "Struct contents assignment to a {other:?} object is not supported."
        ))),
    }
}

#[async_recursion::async_recursion(?Send)]
async fn assign_into_struct(
    mut struct_value: StructValue,
    steps: &[FieldStep],
    rhs: Value,
) -> BuiltinResult<Value> {
    let (first, rest) = steps
        .split_first()
        .expect("steps is non-empty when assign_into_struct is called");

    if rest.is_empty() {
        if let Some(selector) = &first.index {
            let current = struct_value
                .fields
                .get(&first.name)
                .cloned()
                .ok_or_else(|| format!("Reference to non-existent field '{}'.", first.name))?;
            let updated = assign_with_selector(current, selector, &[], rhs).await?;
            struct_value.fields.insert(first.name.clone(), updated);
        } else {
            struct_value.fields.insert(first.name.clone(), rhs);
        }
        return Ok(Value::Struct(struct_value));
    }

    if let Some(selector) = &first.index {
        let current = struct_value
            .fields
            .get(&first.name)
            .cloned()
            .ok_or_else(|| format!("Reference to non-existent field '{}'.", first.name))?;
        let updated = assign_with_selector(current, selector, rest, rhs).await?;
        struct_value.fields.insert(first.name.clone(), updated);
        return Ok(Value::Struct(struct_value));
    }

    let current = struct_value
        .fields
        .get(&first.name)
        .cloned()
        .unwrap_or_else(|| Value::Struct(StructValue::new()));
    let updated = assign_into_value(current, rest, rhs).await?;
    struct_value.fields.insert(first.name.clone(), updated);
    Ok(Value::Struct(struct_value))
}

async fn assign_into_object(
    mut object: ObjectInstance,
    steps: &[FieldStep],
    rhs: Value,
) -> BuiltinResult<Value> {
    let (first, rest) = steps
        .split_first()
        .expect("steps is non-empty when assign_into_object is called");

    if first.index.is_some() {
        return Err(setfield_flow(
            "setfield: indexing into object properties is not currently supported",
        ));
    }

    if rest.is_empty() {
        write_object_property(&mut object, &first.name, rhs).await?;
        return Ok(Value::Object(object));
    }

    let current = read_object_property(&object, &first.name).await?;
    let updated = assign_into_value(current, rest, rhs).await?;
    write_object_property(&mut object, &first.name, updated).await?;
    Ok(Value::Object(object))
}

async fn assign_into_cell(
    cell: CellArray,
    steps: &[FieldStep],
    rhs: Value,
) -> BuiltinResult<Value> {
    let (first, rest) = steps
        .split_first()
        .expect("steps is non-empty when assign_into_cell is called");

    let selector = first.index.as_ref().ok_or_else(|| {
        setfield_flow("setfield: cell array assignments require indices in a cell array")
    })?;
    if rest.is_empty() {
        assign_with_selector(Value::Cell(cell), selector, &[], rhs).await
    } else {
        assign_with_selector(Value::Cell(cell), selector, rest, rhs).await
    }
}

#[async_recursion::async_recursion(?Send)]
async fn assign_with_selector(
    value: Value,
    selector: &IndexSelector,
    rest: &[FieldStep],
    rhs: Value,
) -> BuiltinResult<Value> {
    let host_value = gather_if_needed_async(&value)
        .await
        .map_err(|flow| remap_setfield_flow(flow, Some("setfield: ")))?;
    match host_value {
        Value::Cell(mut cell) => {
            let resolved = resolve_indices(&Value::Cell(cell.clone()), selector)?;
            let position = match resolved.len() {
                1 => {
                    let idx = resolved[0];
                    if idx == 0 || idx > cell.data.len() {
                        return Err(setfield_flow("Index exceeds the number of array elements."));
                    }
                    idx - 1
                }
                2 => {
                    let row = resolved[0];
                    let col = resolved[1];
                    if row == 0 || row > cell.rows || col == 0 || col > cell.cols {
                        return Err(setfield_flow("Index exceeds the number of array elements."));
                    }
                    (row - 1) * cell.cols + (col - 1)
                }
                _ => {
                    return Err(setfield_flow(
                        "setfield: indexing with more than two indices is not supported yet",
                    ));
                }
            };

            let handle = cell
                .data
                .get(position)
                .ok_or_else(|| setfield_flow("Index exceeds the number of array elements."))?
                .clone();
            let existing = unsafe { &*handle.as_raw() }.clone();
            let new_value = if rest.is_empty() {
                rhs
            } else {
                assign_into_value(existing, rest, rhs).await?
            };
            cell.data[position] = allocate_cell_handle(new_value)?;
            Ok(Value::Cell(cell))
        }
        Value::Tensor(mut tensor) => {
            if !rest.is_empty() {
                return Err(setfield_flow(
                    "setfield: cannot traverse deeper fields after indexing into a numeric tensor",
                ));
            }
            assign_tensor_element(&mut tensor, selector, rhs)?;
            Ok(Value::Tensor(tensor))
        }
        Value::LogicalArray(mut logical) => {
            if !rest.is_empty() {
                return Err(setfield_flow(
                    "setfield: cannot traverse deeper fields after indexing into a logical array",
                ));
            }
            assign_logical_element(&mut logical, selector, rhs)?;
            Ok(Value::LogicalArray(logical))
        }
        Value::StringArray(mut sa) => {
            if !rest.is_empty() {
                return Err(setfield_flow(
                    "setfield: cannot traverse deeper fields after indexing into a string array",
                ));
            }
            assign_string_array_element(&mut sa, selector, rhs)?;
            Ok(Value::StringArray(sa))
        }
        Value::CharArray(mut ca) => {
            if !rest.is_empty() {
                return Err(setfield_flow(
                    "setfield: cannot traverse deeper fields after indexing into a char array",
                ));
            }
            assign_char_array_element(&mut ca, selector, rhs)?;
            Ok(Value::CharArray(ca))
        }
        Value::ComplexTensor(mut tensor) => {
            if !rest.is_empty() {
                return Err(setfield_flow(
                    "setfield: cannot traverse deeper fields after indexing into a complex tensor",
                ));
            }
            assign_complex_tensor_element(&mut tensor, selector, rhs)?;
            Ok(Value::ComplexTensor(tensor))
        }
        other => Err(setfield_flow(format!(
            "Struct contents assignment to a {other:?} object is not supported."
        ))),
    }
}

fn assign_tensor_element(
    tensor: &mut Tensor,
    selector: &IndexSelector,
    rhs: Value,
) -> BuiltinResult<()> {
    let resolved = resolve_indices(&Value::Tensor(tensor.clone()), selector)?;
    let value = value_to_scalar(rhs)?;
    match resolved.len() {
        1 => {
            let idx = resolved[0];
            if idx == 0 || idx > tensor.data.len() {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            tensor.data[idx - 1] = value;
            Ok(())
        }
        2 => {
            let row = resolved[0];
            let col = resolved[1];
            if row == 0 || row > tensor.rows() || col == 0 || col > tensor.cols() {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            let pos = (row - 1) + (col - 1) * tensor.rows();
            tensor
                .data
                .get_mut(pos)
                .map(|slot| *slot = value)
                .ok_or_else(|| setfield_flow("Index exceeds the number of array elements."))
        }
        _ => Err(setfield_flow(
            "setfield: indexing with more than two indices is not supported yet",
        )),
    }
}

fn assign_logical_element(
    logical: &mut LogicalArray,
    selector: &IndexSelector,
    rhs: Value,
) -> BuiltinResult<()> {
    let resolved = resolve_indices(&Value::LogicalArray(logical.clone()), selector)?;
    let value = value_to_bool(rhs)?;
    match resolved.len() {
        1 => {
            let idx = resolved[0];
            if idx == 0 || idx > logical.data.len() {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            logical.data[idx - 1] = if value { 1 } else { 0 };
            Ok(())
        }
        2 => {
            if logical.shape.len() < 2 {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            let row = resolved[0];
            let col = resolved[1];
            let rows = logical.shape[0];
            let cols = logical.shape[1];
            if row == 0 || row > rows || col == 0 || col > cols {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            let pos = (row - 1) + (col - 1) * rows;
            if pos >= logical.data.len() {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            logical.data[pos] = if value { 1 } else { 0 };
            Ok(())
        }
        _ => Err(setfield_flow(
            "setfield: indexing with more than two indices is not supported yet",
        )),
    }
}

fn assign_string_array_element(
    array: &mut runmat_builtins::StringArray,
    selector: &IndexSelector,
    rhs: Value,
) -> BuiltinResult<()> {
    let resolved = resolve_indices(&Value::StringArray(array.clone()), selector)?;
    let text = String::try_from(&rhs).map_err(|_| {
        setfield_flow("setfield: string assignments require text-compatible values")
    })?;
    match resolved.len() {
        1 => {
            let idx = resolved[0];
            if idx == 0 || idx > array.data.len() {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            array.data[idx - 1] = text;
            Ok(())
        }
        2 => {
            let row = resolved[0];
            let col = resolved[1];
            if row == 0 || row > array.rows || col == 0 || col > array.cols {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            let pos = (row - 1) + (col - 1) * array.rows;
            if pos >= array.data.len() {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            array.data[pos] = text;
            Ok(())
        }
        _ => Err(setfield_flow(
            "setfield: indexing with more than two indices is not supported yet",
        )),
    }
}

fn assign_char_array_element(
    array: &mut CharArray,
    selector: &IndexSelector,
    rhs: Value,
) -> BuiltinResult<()> {
    let resolved = resolve_indices(&Value::CharArray(array.clone()), selector)?;
    let text = String::try_from(&rhs)
        .map_err(|_| setfield_flow("setfield: char assignments require text-compatible values"))?;
    if text.chars().count() != 1 {
        return Err(setfield_flow(
            "setfield: char array assignments require single characters",
        ));
    }
    let ch = text.chars().next().unwrap();
    match resolved.len() {
        1 => {
            let idx = resolved[0];
            if idx == 0 || idx > array.data.len() {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            array.data[idx - 1] = ch;
            Ok(())
        }
        2 => {
            let row = resolved[0];
            let col = resolved[1];
            if row == 0 || row > array.rows || col == 0 || col > array.cols {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            let pos = (row - 1) * array.cols + (col - 1);
            if pos >= array.data.len() {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            array.data[pos] = ch;
            Ok(())
        }
        _ => Err(setfield_flow(
            "setfield: indexing with more than two indices is not supported yet",
        )),
    }
}

fn assign_complex_tensor_element(
    tensor: &mut ComplexTensor,
    selector: &IndexSelector,
    rhs: Value,
) -> BuiltinResult<()> {
    let resolved = resolve_indices(&Value::ComplexTensor(tensor.clone()), selector)?;
    let (re, im) = match rhs {
        Value::Complex(r, i) => (r, i),
        Value::Num(n) => (n, 0.0),
        Value::Int(i) => (i.to_f64(), 0.0),
        other => {
            return Err(setfield_flow(format!(
                "setfield: cannot assign {other:?} into a complex tensor element"
            )));
        }
    };
    match resolved.len() {
        1 => {
            let idx = resolved[0];
            if idx == 0 || idx > tensor.data.len() {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            tensor.data[idx - 1] = (re, im);
            Ok(())
        }
        2 => {
            let row = resolved[0];
            let col = resolved[1];
            if row == 0 || row > tensor.rows || col == 0 || col > tensor.cols {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            let pos = (row - 1) + (col - 1) * tensor.rows;
            if pos >= tensor.data.len() {
                return Err(setfield_flow("Index exceeds the number of array elements."));
            }
            tensor.data[pos] = (re, im);
            Ok(())
        }
        _ => Err(setfield_flow(
            "setfield: indexing with more than two indices is not supported yet",
        )),
    }
}

async fn read_object_property(obj: &ObjectInstance, name: &str) -> BuiltinResult<Value> {
    if let Some((prop, _owner)) = runmat_builtins::lookup_property(&obj.class_name, name) {
        if prop.is_static {
            return Err(setfield_flow(format!(
                "You cannot access the static property '{}' through an instance of class '{}'.",
                name, obj.class_name
            )));
        }
        if prop.get_access == Access::Private {
            return Err(setfield_flow(format!(
                "You cannot get the '{}' property of '{}' class.",
                name, obj.class_name
            )));
        }
        if prop.is_dependent {
            let getter = format!("get.{name}");
            match call_builtin_async(&getter, &[Value::Object(obj.clone())]).await {
                Ok(value) => return Ok(value),
                Err(err) => {
                    if !is_undefined_function(&err) {
                        return Err(remap_setfield_flow(err, None));
                    }
                }
            }
            if let Some(value) = obj.properties.get(&format!("{name}_backing")) {
                return Ok(value.clone());
            }
        }
    }

    if let Some(value) = obj.properties.get(name) {
        return Ok(value.clone());
    }

    if let Some((prop, _owner)) = runmat_builtins::lookup_property(&obj.class_name, name) {
        if prop.get_access == Access::Private {
            return Err(setfield_flow(format!(
                "You cannot get the '{}' property of '{}' class.",
                name, obj.class_name
            )));
        }
        return Err(setfield_flow(format!(
            "No public property '{}' for class '{}'.",
            name, obj.class_name
        )));
    }

    Err(setfield_flow(format!(
        "Undefined property '{}' for class {}",
        name, obj.class_name
    )))
}

async fn write_object_property(
    obj: &mut ObjectInstance,
    name: &str,
    rhs: Value,
) -> BuiltinResult<()> {
    if let Some((prop, _owner)) = runmat_builtins::lookup_property(&obj.class_name, name) {
        if prop.is_static {
            return Err(setfield_flow(format!(
                "Property '{}' is static; use classref('{}').{}",
                name, obj.class_name, name
            )));
        }
        if prop.set_access == Access::Private {
            return Err(setfield_flow(format!("Property '{name}' is private")));
        }
        if prop.is_dependent {
            let setter = format!("set.{name}");
            match call_builtin_async(&setter, &[Value::Object(obj.clone()), rhs.clone()]).await {
                Ok(value) => {
                    if let Value::Object(updated) = value {
                        *obj = updated;
                        return Ok(());
                    }
                    return Err(setfield_flow(format!(
                        "Dependent property setter for '{}' must return the updated object",
                        name
                    )));
                }
                Err(err) => {
                    if !is_undefined_function(&err) {
                        return Err(remap_setfield_flow(err, None));
                    }
                }
            }
            obj.properties.insert(format!("{name}_backing"), rhs);
            return Ok(());
        }
    }

    obj.properties.insert(name.to_string(), rhs);
    Ok(())
}

async fn assign_into_handle(
    handle: HandleRef,
    steps: &[FieldStep],
    rhs: Value,
) -> BuiltinResult<Value> {
    if steps.is_empty() {
        return Err(setfield_flow(
            "setfield: expected at least one field name when assigning into a handle",
        ));
    }
    if !handle.valid {
        return Err(setfield_flow(format!(
            "Invalid or deleted handle object '{}'.",
            handle.class_name
        )));
    }
    let current = unsafe { &*handle.target.as_raw() }.clone();
    let updated = assign_into_value(current, steps, rhs).await?;
    let raw = unsafe { handle.target.as_raw_mut() };
    if raw.is_null() {
        return Err(setfield_flow("setfield: handle target is null"));
    }
    unsafe {
        *raw = updated;
    }
    Ok(Value::HandleObject(handle))
}

fn is_index_selector(value: &Value) -> bool {
    matches!(value, Value::Cell(_))
}

fn parse_index_selector(value: Value) -> BuiltinResult<IndexSelector> {
    let Value::Cell(cell) = value else {
        return Err(setfield_flow(
            "setfield: indices must be provided in a cell array",
        ));
    };
    let mut components = Vec::with_capacity(cell.data.len());
    for handle in &cell.data {
        let entry = unsafe { &*handle.as_raw() };
        components.push(parse_index_component(entry)?);
    }
    Ok(IndexSelector { components })
}

fn parse_index_component(value: &Value) -> BuiltinResult<IndexComponent> {
    match value {
        Value::CharArray(ca) => {
            let text: String = ca.data.iter().collect();
            parse_index_text(text.trim())
        }
        Value::String(s) => parse_index_text(s.trim()),
        Value::StringArray(sa) if sa.data.len() == 1 => parse_index_text(sa.data[0].trim()),
        _ => {
            let idx = parse_positive_scalar(value).map_err(|err| {
                setfield_flow(format!(
                    "setfield: invalid index element ({})",
                    err.message()
                ))
            })?;
            Ok(IndexComponent::Scalar(idx))
        }
    }
}

fn parse_index_text(text: &str) -> BuiltinResult<IndexComponent> {
    if text.eq_ignore_ascii_case("end") {
        return Ok(IndexComponent::End);
    }
    if text == ":" {
        return Err(setfield_flow(
            "setfield: ':' indexing is not currently supported",
        ));
    }
    if text.is_empty() {
        return Err(setfield_flow("setfield: index elements must not be empty"));
    }
    if let Ok(value) = text.parse::<usize>() {
        if value == 0 {
            return Err(setfield_flow("setfield: index must be >= 1"));
        }
        return Ok(IndexComponent::Scalar(value));
    }
    Err(setfield_flow(format!(
        "setfield: invalid index element '{}'",
        text
    )))
}

fn parse_positive_scalar(value: &Value) -> BuiltinResult<usize> {
    let number = match value {
        Value::Int(i) => i.to_i64() as f64,
        Value::Num(n) => *n,
        Value::Tensor(t) if t.data.len() == 1 => t.data[0],
        _ => {
            let repr = format!("{value:?}");
            return Err(setfield_flow(format!(
                "expected positive integer index, got {repr}"
            )));
        }
    };

    if !number.is_finite() {
        return Err(setfield_flow("index must be a finite number"));
    }
    if number.fract() != 0.0 {
        return Err(setfield_flow("index must be an integer"));
    }
    if number <= 0.0 {
        return Err(setfield_flow("index must be >= 1"));
    }
    if number > usize::MAX as f64 {
        return Err(setfield_flow("index exceeds platform limits"));
    }
    Ok(number as usize)
}

fn parse_field_name(value: Value) -> BuiltinResult<String> {
    match value {
        Value::String(s) => Ok(s),
        Value::StringArray(sa) => {
            if sa.data.len() == 1 {
                Ok(sa.data[0].clone())
            } else {
                Err(setfield_flow(
                    "setfield: field names must be scalar string arrays or character vectors",
                ))
            }
        }
        Value::CharArray(ca) => {
            if ca.rows == 1 {
                Ok(ca.data.iter().collect())
            } else {
                Err(setfield_flow(
                    "setfield: field names must be 1-by-N character vectors",
                ))
            }
        }
        other => Err(setfield_flow(format!(
            "setfield: expected field name, got {other:?}"
        ))),
    }
}

fn resolve_indices(value: &Value, selector: &IndexSelector) -> BuiltinResult<Vec<usize>> {
    let dims = selector.components.len();
    let mut resolved = Vec::with_capacity(dims);
    for (dim_idx, component) in selector.components.iter().enumerate() {
        let index = match component {
            IndexComponent::Scalar(idx) => *idx,
            IndexComponent::End => dimension_length(value, dims, dim_idx)?,
        };
        resolved.push(index);
    }
    Ok(resolved)
}

fn dimension_length(value: &Value, dims: usize, dim_idx: usize) -> BuiltinResult<usize> {
    match value {
        Value::Tensor(tensor) => tensor_dimension_length(tensor, dims, dim_idx),
        Value::Cell(cell) => cell_dimension_length(cell, dims, dim_idx),
        Value::StringArray(array) => string_array_dimension_length(array, dims, dim_idx),
        Value::LogicalArray(logical) => logical_array_dimension_length(logical, dims, dim_idx),
        Value::CharArray(array) => char_array_dimension_length(array, dims, dim_idx),
        Value::ComplexTensor(tensor) => complex_tensor_dimension_length(tensor, dims, dim_idx),
        Value::Num(_) | Value::Int(_) | Value::Bool(_) => {
            if dims == 1 {
                Ok(1)
            } else {
                Err(setfield_flow(
                    "setfield: indexing with more than one dimension is not supported for scalars",
                ))
            }
        }
        other => Err(setfield_flow(format!(
            "Struct contents assignment to a {other:?} object is not supported."
        ))),
    }
}

fn tensor_dimension_length(tensor: &Tensor, dims: usize, dim_idx: usize) -> BuiltinResult<usize> {
    if dims == 1 {
        let total = tensor.data.len();
        if total == 0 {
            return Err(setfield_flow(
                "Index exceeds the number of array elements (0).",
            ));
        }
        return Ok(total);
    }
    if dims > 2 {
        return Err(setfield_flow(
            "setfield: indexing with more than two indices is not supported yet",
        ));
    }
    let len = if dim_idx == 0 {
        tensor.rows()
    } else {
        tensor.cols()
    };
    if len == 0 {
        return Err(setfield_flow(
            "Index exceeds the number of array elements (0).",
        ));
    }
    Ok(len)
}

fn cell_dimension_length(cell: &CellArray, dims: usize, dim_idx: usize) -> BuiltinResult<usize> {
    if dims == 1 {
        let total = cell.data.len();
        if total == 0 {
            return Err(setfield_flow(
                "Index exceeds the number of array elements (0).",
            ));
        }
        return Ok(total);
    }
    if dims > 2 {
        return Err(setfield_flow(
            "setfield: indexing with more than two indices is not supported yet",
        ));
    }
    let len = if dim_idx == 0 { cell.rows } else { cell.cols };
    if len == 0 {
        return Err(setfield_flow(
            "Index exceeds the number of array elements (0).",
        ));
    }
    Ok(len)
}

fn string_array_dimension_length(
    array: &runmat_builtins::StringArray,
    dims: usize,
    dim_idx: usize,
) -> BuiltinResult<usize> {
    if dims == 1 {
        let total = array.data.len();
        if total == 0 {
            return Err(setfield_flow(
                "Index exceeds the number of array elements (0).",
            ));
        }
        return Ok(total);
    }
    if dims > 2 {
        return Err(setfield_flow(
            "setfield: indexing with more than two indices is not supported yet",
        ));
    }
    let len = if dim_idx == 0 { array.rows } else { array.cols };
    if len == 0 {
        return Err(setfield_flow(
            "Index exceeds the number of array elements (0).",
        ));
    }
    Ok(len)
}

fn logical_array_dimension_length(
    array: &LogicalArray,
    dims: usize,
    dim_idx: usize,
) -> BuiltinResult<usize> {
    if dims == 1 {
        let total = array.data.len();
        if total == 0 {
            return Err(setfield_flow(
                "Index exceeds the number of array elements (0).",
            ));
        }
        return Ok(total);
    }
    if dims > 2 {
        return Err(setfield_flow(
            "setfield: indexing with more than two indices is not supported yet",
        ));
    }
    if array.shape.len() < dims {
        return Err(setfield_flow(
            "Index exceeds the number of array elements (0).",
        ));
    }
    let len = array.shape[dim_idx];
    if len == 0 {
        return Err(setfield_flow(
            "Index exceeds the number of array elements (0).",
        ));
    }
    Ok(len)
}

fn char_array_dimension_length(
    array: &CharArray,
    dims: usize,
    dim_idx: usize,
) -> BuiltinResult<usize> {
    if dims == 1 {
        let total = array.data.len();
        if total == 0 {
            return Err(setfield_flow(
                "Index exceeds the number of array elements (0).",
            ));
        }
        return Ok(total);
    }
    if dims > 2 {
        return Err(setfield_flow(
            "setfield: indexing with more than two indices is not supported yet",
        ));
    }
    let len = if dim_idx == 0 { array.rows } else { array.cols };
    if len == 0 {
        return Err(setfield_flow(
            "Index exceeds the number of array elements (0).",
        ));
    }
    Ok(len)
}

fn complex_tensor_dimension_length(
    tensor: &ComplexTensor,
    dims: usize,
    dim_idx: usize,
) -> BuiltinResult<usize> {
    if dims == 1 {
        let total = tensor.data.len();
        if total == 0 {
            return Err(setfield_flow(
                "Index exceeds the number of array elements (0).",
            ));
        }
        return Ok(total);
    }
    if dims > 2 {
        return Err(setfield_flow(
            "setfield: indexing with more than two indices is not supported yet",
        ));
    }
    let len = if dim_idx == 0 {
        tensor.rows
    } else {
        tensor.cols
    };
    if len == 0 {
        return Err(setfield_flow(
            "Index exceeds the number of array elements (0).",
        ));
    }
    Ok(len)
}

fn value_to_scalar(value: Value) -> BuiltinResult<f64> {
    match value {
        Value::Num(n) => Ok(n),
        Value::Int(i) => Ok(i.to_f64()),
        Value::Bool(b) => Ok(if b { 1.0 } else { 0.0 }),
        Value::Tensor(t) if t.data.len() == 1 => Ok(t.data[0]),
        other => Err(setfield_flow(format!(
            "setfield: cannot assign {other:?} into a numeric tensor element"
        ))),
    }
}

fn value_to_bool(value: Value) -> BuiltinResult<bool> {
    match value {
        Value::Bool(b) => Ok(b),
        Value::Num(n) => Ok(n != 0.0),
        Value::Int(i) => Ok(i.to_i64() != 0),
        Value::Tensor(t) if t.data.len() == 1 => Ok(t.data[0] != 0.0),
        other => Err(setfield_flow(format!(
            "setfield: cannot assign {other:?} into a logical array element"
        ))),
    }
}

fn allocate_cell_handle(value: Value) -> BuiltinResult<GcPtr<Value>> {
    runmat_gc::gc_allocate(value).map_err(|e| {
        setfield_flow(format!(
            "setfield: failed to allocate cell element in GC: {e}"
        ))
    })
}

fn is_struct_array(cell: &CellArray) -> bool {
    cell.data
        .iter()
        .all(|handle| matches!(unsafe { &*handle.as_raw() }, Value::Struct(_)))
}

#[cfg(test)]
pub(crate) mod tests {
    use super::*;
    use runmat_builtins::{
        Access, CellArray, ClassDef, HandleRef, IntValue, ObjectInstance, PropertyDef, StructValue,
    };
    use runmat_gc::gc_allocate;

    fn error_message(err: crate::RuntimeError) -> String {
        err.message().to_string()
    }

    fn run_setfield(base: Value, rest: Vec<Value>) -> BuiltinResult<Value> {
        futures::executor::block_on(setfield_builtin(base, rest))
    }

    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
    #[test]
    fn setfield_creates_scalar_field() {
        let struct_value = StructValue::new();
        let updated = run_setfield(
            Value::Struct(struct_value),
            vec![Value::from("answer"), Value::Num(42.0)],
        )
        .expect("setfield");
        match updated {
            Value::Struct(st) => {
                assert_eq!(
                    st.fields.get("answer"),
                    Some(&Value::Num(42.0)),
                    "field should be inserted"
                );
            }
            other => panic!("expected struct result, got {other:?}"),
        }
    }

    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
    #[test]
    fn setfield_creates_nested_structs() {
        let struct_value = StructValue::new();
        let updated = run_setfield(
            Value::Struct(struct_value),
            vec![
                Value::from("solver"),
                Value::from("name"),
                Value::from("cg"),
            ],
        )
        .expect("setfield");
        match updated {
            Value::Struct(st) => {
                let solver = st.fields.get("solver").expect("solver field");
                match solver {
                    Value::Struct(inner) => {
                        assert_eq!(
                            inner.fields.get("name"),
                            Some(&Value::from("cg")),
                            "inner field should exist"
                        );
                    }
                    other => panic!("expected inner struct, got {other:?}"),
                }
            }
            other => panic!("expected struct result, got {other:?}"),
        }
    }

    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
    #[test]
    fn setfield_updates_struct_array_element() {
        let mut a = StructValue::new();
        a.fields
            .insert("id".to_string(), Value::Int(IntValue::I32(1)));
        let mut b = StructValue::new();
        b.fields
            .insert("id".to_string(), Value::Int(IntValue::I32(2)));
        let array = CellArray::new_with_shape(vec![Value::Struct(a), Value::Struct(b)], vec![1, 2])
            .unwrap();
        let indices =
            CellArray::new_with_shape(vec![Value::Int(IntValue::I32(2))], vec![1, 1]).unwrap();
        let updated = run_setfield(
            Value::Cell(array),
            vec![
                Value::Cell(indices),
                Value::from("id"),
                Value::Int(IntValue::I32(42)),
            ],
        )
        .expect("setfield");
        match updated {
            Value::Cell(cell) => {
                let second = unsafe { &*cell.data[1].as_raw() }.clone();
                match second {
                    Value::Struct(st) => {
                        assert_eq!(st.fields.get("id"), Some(&Value::Int(IntValue::I32(42))));
                    }
                    other => panic!("expected struct element, got {other:?}"),
                }
            }
            other => panic!("expected cell array, got {other:?}"),
        }
    }

    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
    #[test]
    fn setfield_assigns_into_cell_then_struct() {
        let mut inner1 = StructValue::new();
        inner1.fields.insert("value".to_string(), Value::Num(1.0));
        let mut inner2 = StructValue::new();
        inner2.fields.insert("value".to_string(), Value::Num(2.0));
        let cell = CellArray::new_with_shape(
            vec![Value::Struct(inner1), Value::Struct(inner2)],
            vec![1, 2],
        )
        .unwrap();
        let mut root = StructValue::new();
        root.fields.insert("samples".to_string(), Value::Cell(cell));

        let index_cell =
            CellArray::new_with_shape(vec![Value::Int(IntValue::I32(2))], vec![1, 1]).unwrap();
        let updated = run_setfield(
            Value::Struct(root),
            vec![
                Value::from("samples"),
                Value::Cell(index_cell),
                Value::from("value"),
                Value::Num(10.0),
            ],
        )
        .expect("setfield");

        match updated {
            Value::Struct(st) => {
                let samples = st.fields.get("samples").expect("samples field");
                match samples {
                    Value::Cell(cell) => {
                        let value = unsafe { &*cell.data[1].as_raw() }.clone();
                        match value {
                            Value::Struct(inner) => {
                                assert_eq!(inner.fields.get("value"), Some(&Value::Num(10.0)));
                            }
                            other => panic!("expected struct, got {other:?}"),
                        }
                    }
                    other => panic!("expected cell array, got {other:?}"),
                }
            }
            other => panic!("expected struct, got {other:?}"),
        }
    }

    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
    #[test]
    fn setfield_struct_array_with_end_index() {
        let mut first = StructValue::new();
        first
            .fields
            .insert("id".to_string(), Value::Int(IntValue::I32(1)));
        let mut second = StructValue::new();
        second
            .fields
            .insert("id".to_string(), Value::Int(IntValue::I32(2)));
        let array = CellArray::new_with_shape(
            vec![Value::Struct(first), Value::Struct(second)],
            vec![1, 2],
        )
        .unwrap();
        let index_cell = CellArray::new_with_shape(vec![Value::from("end")], vec![1, 1]).unwrap();
        let updated = run_setfield(
            Value::Cell(array),
            vec![
                Value::Cell(index_cell),
                Value::from("id"),
                Value::Int(IntValue::I32(99)),
            ],
        )
        .expect("setfield");
        match updated {
            Value::Cell(cell) => {
                let second = unsafe { &*cell.data[1].as_raw() }.clone();
                match second {
                    Value::Struct(st) => {
                        assert_eq!(st.fields.get("id"), Some(&Value::Int(IntValue::I32(99))));
                    }
                    other => panic!("expected struct element, got {other:?}"),
                }
            }
            other => panic!("expected cell array result, got {other:?}"),
        }
    }

    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
    #[test]
    fn setfield_assigns_object_property() {
        let mut class_def = ClassDef {
            name: "Simple".to_string(),
            parent: None,
            properties: Default::default(),
            methods: Default::default(),
        };
        class_def.properties.insert(
            "x".to_string(),
            PropertyDef {
                name: "x".to_string(),
                is_static: false,
                is_dependent: false,
                get_access: Access::Public,
                set_access: Access::Public,
                default_value: None,
            },
        );
        runmat_builtins::register_class(class_def);

        let mut obj = ObjectInstance::new("Simple".to_string());
        obj.properties.insert("x".to_string(), Value::Num(0.0));

        let updated = run_setfield(Value::Object(obj), vec![Value::from("x"), Value::Num(5.0)])
            .expect("setfield");

        match updated {
            Value::Object(o) => {
                assert_eq!(o.properties.get("x"), Some(&Value::Num(5.0)));
            }
            other => panic!("expected object result, got {other:?}"),
        }
    }

    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
    #[test]
    fn setfield_errors_when_indexing_missing_field() {
        let struct_value = StructValue::new();
        let index_cell =
            CellArray::new_with_shape(vec![Value::Int(IntValue::I32(1))], vec![1, 1]).unwrap();
        let err = error_message(
            run_setfield(
                Value::Struct(struct_value),
                vec![
                    Value::from("missing"),
                    Value::Cell(index_cell),
                    Value::Num(1.0),
                ],
            )
            .expect_err("setfield should fail when field is missing"),
        );
        assert!(
            err.contains("Reference to non-existent field 'missing'."),
            "unexpected error message: {err}"
        );
    }

    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
    #[test]
    fn setfield_errors_on_static_property_assignment() {
        let mut class_def = ClassDef {
            name: "StaticSetfield".to_string(),
            parent: None,
            properties: Default::default(),
            methods: Default::default(),
        };
        class_def.properties.insert(
            "version".to_string(),
            PropertyDef {
                name: "version".to_string(),
                is_static: true,
                is_dependent: false,
                get_access: Access::Public,
                set_access: Access::Public,
                default_value: None,
            },
        );
        runmat_builtins::register_class(class_def);

        let obj = ObjectInstance::new("StaticSetfield".to_string());
        let err = error_message(
            run_setfield(
                Value::Object(obj),
                vec![Value::from("version"), Value::Num(2.0)],
            )
            .expect_err("setfield should reject static property writes"),
        );
        assert!(
            err.contains("Property 'version' is static"),
            "unexpected error message: {err}"
        );
    }

    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
    #[test]
    fn setfield_updates_handle_target() {
        let mut inner = StructValue::new();
        inner.fields.insert("x".to_string(), Value::Num(0.0));
        let gc_ptr = gc_allocate(Value::Struct(inner)).expect("gc allocation");
        let handle_ptr = gc_ptr.clone();
        let handle = HandleRef {
            class_name: "PointHandle".to_string(),
            target: handle_ptr,
            valid: true,
        };

        let updated = run_setfield(
            Value::HandleObject(handle.clone()),
            vec![Value::from("x"), Value::Num(7.0)],
        )
        .expect("setfield handle update");

        match updated {
            Value::HandleObject(h) => assert!(h.valid),
            other => panic!("expected handle, got {other:?}"),
        }

        let pointee = unsafe { &*gc_ptr.as_raw() };
        match pointee {
            Value::Struct(st) => {
                assert_eq!(st.fields.get("x"), Some(&Value::Num(7.0)));
            }
            other => panic!("expected struct pointee, got {other:?}"),
        }
    }

    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
    #[test]
    #[cfg(feature = "wgpu")]
    fn setfield_gpu_tensor_indexing_gathers_to_host() {
        use runmat_accelerate::backend::wgpu::provider::{
            register_wgpu_provider, WgpuProviderOptions,
        };
        use runmat_accelerate_api::HostTensorView;

        if runmat_accelerate_api::provider().is_none()
            && register_wgpu_provider(WgpuProviderOptions::default()).is_err()
        {
            runmat_accelerate::simple_provider::register_inprocess_provider();
        }

        let provider = runmat_accelerate_api::provider().expect("accel provider");
        let data = [1.0, 2.0, 3.0, 4.0];
        let shape = [2usize, 2usize];
        let view = HostTensorView {
            data: &data,
            shape: &shape,
        };
        let handle = provider.upload(&view).expect("upload");

        let mut root = StructValue::new();
        root.fields
            .insert("values".to_string(), Value::GpuTensor(handle));

        let index_cell = CellArray::new_with_shape(
            vec![Value::Int(IntValue::I32(2)), Value::Int(IntValue::I32(2))],
            vec![1, 2],
        )
        .unwrap();

        let updated = run_setfield(
            Value::Struct(root),
            vec![
                Value::from("values"),
                Value::Cell(index_cell),
                Value::Num(99.0),
            ],
        )
        .expect("setfield gpu value");

        match updated {
            Value::Struct(st) => {
                let values = st.fields.get("values").expect("values field");
                match values {
                    Value::Tensor(tensor) => {
                        assert_eq!(tensor.shape, vec![2, 2]);
                        assert_eq!(tensor.data[3], 99.0);
                    }
                    other => panic!("expected tensor after gather, got {other:?}"),
                }
            }
            other => panic!("expected struct result, got {other:?}"),
        }
    }
}
