// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

//! Physical expression for struct-aware casting of columns.

use crate::physical_expr::PhysicalExpr;
use arrow::{
    compute::CastOptions,
    datatypes::{DataType, FieldRef, Schema},
    record_batch::RecordBatch,
};
use datafusion_common::{
    Result, ScalarValue, format::DEFAULT_CAST_OPTIONS, nested_struct::cast_column,
};
use datafusion_expr_common::columnar_value::ColumnarValue;
use std::{
    any::Any,
    fmt::{self, Display},
    hash::Hash,
    sync::Arc,
};
/// A physical expression that applies [`cast_column`] to its input.
///
/// [`CastColumnExpr`] extends the regular [`CastExpr`](super::CastExpr) by
/// retaining schema metadata for both the input and output fields. This allows
/// the evaluator to perform struct-aware casts that honour nested field
/// ordering, preserve nullability, and fill missing fields with null values.
///
/// This expression is intended for schema rewriting scenarios where the
/// planner already resolved the input column but needs to adapt its physical
/// representation to a new [`arrow::datatypes::Field`]. It mirrors the behaviour of the
/// [`datafusion_common::nested_struct::cast_column`] helper while integrating
/// with the `PhysicalExpr` trait so it can participate in the execution plan
/// like any other column expression.
#[derive(Debug, Clone, Eq)]
pub struct CastColumnExpr {
    /// The physical expression producing the value to cast.
    expr: Arc<dyn PhysicalExpr>,
    /// The logical field of the input column.
    input_field: FieldRef,
    /// The field metadata describing the desired output column.
    target_field: FieldRef,
    /// Options forwarded to [`cast_column`].
    cast_options: CastOptions<'static>,
}

// Manually derive `PartialEq`/`Hash` as `Arc<dyn PhysicalExpr>` does not
// implement these traits by default for the trait object.
impl PartialEq for CastColumnExpr {
    fn eq(&self, other: &Self) -> bool {
        self.expr.eq(&other.expr)
            && self.input_field.eq(&other.input_field)
            && self.target_field.eq(&other.target_field)
            && self.cast_options.eq(&other.cast_options)
    }
}

impl Hash for CastColumnExpr {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.expr.hash(state);
        self.input_field.hash(state);
        self.target_field.hash(state);
        self.cast_options.hash(state);
    }
}

impl CastColumnExpr {
    /// Create a new [`CastColumnExpr`].
    pub fn new(
        expr: Arc<dyn PhysicalExpr>,
        input_field: FieldRef,
        target_field: FieldRef,
        cast_options: Option<CastOptions<'static>>,
    ) -> Self {
        Self {
            expr,
            input_field,
            target_field,
            cast_options: cast_options.unwrap_or(DEFAULT_CAST_OPTIONS),
        }
    }

    /// The expression that produces the value to be cast.
    pub fn expr(&self) -> &Arc<dyn PhysicalExpr> {
        &self.expr
    }

    /// Field metadata describing the resolved input column.
    pub fn input_field(&self) -> &FieldRef {
        &self.input_field
    }

    /// Field metadata describing the output column after casting.
    pub fn target_field(&self) -> &FieldRef {
        &self.target_field
    }
}

impl Display for CastColumnExpr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "CAST_COLUMN({} AS {:?})",
            self.expr,
            self.target_field.data_type()
        )
    }
}

impl PhysicalExpr for CastColumnExpr {
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
        Ok(self.target_field.data_type().clone())
    }

    fn nullable(&self, _input_schema: &Schema) -> Result<bool> {
        Ok(self.target_field.is_nullable())
    }

    fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
        let value = self.expr.evaluate(batch)?;
        match value {
            ColumnarValue::Array(array) => {
                let casted =
                    cast_column(&array, self.target_field.as_ref(), &self.cast_options)?;
                Ok(ColumnarValue::Array(casted))
            }
            ColumnarValue::Scalar(scalar) => {
                let as_array = scalar.to_array_of_size(1)?;
                let casted = cast_column(
                    &as_array,
                    self.target_field.as_ref(),
                    &self.cast_options,
                )?;
                let result = ScalarValue::try_from_array(casted.as_ref(), 0)?;
                Ok(ColumnarValue::Scalar(result))
            }
        }
    }

    fn return_field(&self, _input_schema: &Schema) -> Result<FieldRef> {
        Ok(Arc::clone(&self.target_field))
    }

    fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
        vec![&self.expr]
    }

    fn with_new_children(
        self: Arc<Self>,
        mut children: Vec<Arc<dyn PhysicalExpr>>,
    ) -> Result<Arc<dyn PhysicalExpr>> {
        assert_eq!(children.len(), 1);
        let child = children.pop().expect("CastColumnExpr child");
        Ok(Arc::new(Self::new(
            child,
            Arc::clone(&self.input_field),
            Arc::clone(&self.target_field),
            Some(self.cast_options.clone()),
        )))
    }

    fn fmt_sql(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        Display::fmt(self, f)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use crate::expressions::{Column, Literal};
    use arrow::{
        array::{Array, ArrayRef, BooleanArray, Int32Array, StringArray, StructArray},
        datatypes::{DataType, Field, Fields, SchemaRef},
    };
    use datafusion_common::{
        Result as DFResult, ScalarValue,
        cast::{as_int64_array, as_string_array, as_struct_array, as_uint8_array},
    };

    fn make_schema(field: &Field) -> SchemaRef {
        Arc::new(Schema::new(vec![field.clone()]))
    }

    fn make_struct_array(fields: Fields, arrays: Vec<ArrayRef>) -> StructArray {
        StructArray::new(fields, arrays, None)
    }

    #[test]
    fn cast_primitive_array() -> DFResult<()> {
        let input_field = Field::new("a", DataType::Int32, true);
        let target_field = Field::new("a", DataType::Int64, true);
        let schema = make_schema(&input_field);

        let values = Arc::new(Int32Array::from(vec![Some(1), None, Some(3)]));
        let batch = RecordBatch::try_new(Arc::clone(&schema), vec![values])?;

        let column = Arc::new(Column::new_with_schema("a", schema.as_ref())?);
        let expr = CastColumnExpr::new(
            column,
            Arc::new(input_field.clone()),
            Arc::new(target_field.clone()),
            None,
        );

        let result = expr.evaluate(&batch)?;
        let ColumnarValue::Array(array) = result else {
            panic!("expected array");
        };
        let casted = as_int64_array(array.as_ref())?;
        assert_eq!(casted.value(0), 1);
        assert!(casted.is_null(1));
        assert_eq!(casted.value(2), 3);
        Ok(())
    }

    #[test]
    fn cast_struct_array_missing_child() -> DFResult<()> {
        let source_a = Field::new("a", DataType::Int32, true);
        let source_b = Field::new("b", DataType::Utf8, true);
        let input_field = Field::new(
            "s",
            DataType::Struct(
                vec![Arc::new(source_a.clone()), Arc::new(source_b.clone())].into(),
            ),
            true,
        );
        let target_a = Field::new("a", DataType::Int64, true);
        let target_c = Field::new("c", DataType::Utf8, true);
        let target_field = Field::new(
            "s",
            DataType::Struct(
                vec![Arc::new(target_a.clone()), Arc::new(target_c.clone())].into(),
            ),
            true,
        );

        let schema = make_schema(&input_field);
        let struct_array = make_struct_array(
            vec![Arc::new(source_a.clone()), Arc::new(source_b.clone())].into(),
            vec![
                Arc::new(Int32Array::from(vec![Some(1), None])) as ArrayRef,
                Arc::new(StringArray::from(vec![Some("alpha"), Some("beta")]))
                    as ArrayRef,
            ],
        );
        let batch = RecordBatch::try_new(
            Arc::clone(&schema),
            vec![Arc::new(struct_array) as Arc<_>],
        )?;

        let column = Arc::new(Column::new_with_schema("s", schema.as_ref())?);
        let expr = CastColumnExpr::new(
            column,
            Arc::new(input_field.clone()),
            Arc::new(target_field.clone()),
            None,
        );

        let result = expr.evaluate(&batch)?;
        let ColumnarValue::Array(array) = result else {
            panic!("expected array");
        };
        let struct_array = as_struct_array(array.as_ref())?;
        let cast_a = as_int64_array(struct_array.column_by_name("a").unwrap().as_ref())?;
        assert_eq!(cast_a.value(0), 1);
        assert!(cast_a.is_null(1));

        let cast_c = as_string_array(struct_array.column_by_name("c").unwrap().as_ref())?;
        assert!(cast_c.is_null(0));
        assert!(cast_c.is_null(1));
        Ok(())
    }

    #[test]
    fn cast_nested_struct_array() -> DFResult<()> {
        let inner_source = Field::new(
            "inner",
            DataType::Struct(
                vec![Arc::new(Field::new("x", DataType::Int32, true))].into(),
            ),
            true,
        );
        let outer_field = Field::new(
            "root",
            DataType::Struct(vec![Arc::new(inner_source.clone())].into()),
            true,
        );

        let inner_target = Field::new(
            "inner",
            DataType::Struct(
                vec![
                    Arc::new(Field::new("x", DataType::Int64, true)),
                    Arc::new(Field::new("y", DataType::Boolean, true)),
                ]
                .into(),
            ),
            true,
        );
        let target_field = Field::new(
            "root",
            DataType::Struct(vec![Arc::new(inner_target.clone())].into()),
            true,
        );

        let schema = make_schema(&outer_field);

        let inner_struct = make_struct_array(
            vec![Arc::new(Field::new("x", DataType::Int32, true))].into(),
            vec![Arc::new(Int32Array::from(vec![Some(7), None])) as ArrayRef],
        );
        let outer_struct = make_struct_array(
            vec![Arc::new(inner_source.clone())].into(),
            vec![Arc::new(inner_struct) as ArrayRef],
        );
        let batch = RecordBatch::try_new(
            Arc::clone(&schema),
            vec![Arc::new(outer_struct) as ArrayRef],
        )?;

        let column = Arc::new(Column::new_with_schema("root", schema.as_ref())?);
        let expr = CastColumnExpr::new(
            column,
            Arc::new(outer_field.clone()),
            Arc::new(target_field.clone()),
            None,
        );

        let result = expr.evaluate(&batch)?;
        let ColumnarValue::Array(array) = result else {
            panic!("expected array");
        };
        let struct_array = as_struct_array(array.as_ref())?;
        let inner =
            as_struct_array(struct_array.column_by_name("inner").unwrap().as_ref())?;
        let x = as_int64_array(inner.column_by_name("x").unwrap().as_ref())?;
        assert_eq!(x.value(0), 7);
        assert!(x.is_null(1));
        let y = inner.column_by_name("y").unwrap();
        let y = y
            .as_any()
            .downcast_ref::<BooleanArray>()
            .expect("boolean array");
        assert!(y.is_null(0));
        assert!(y.is_null(1));
        Ok(())
    }

    #[test]
    fn cast_struct_scalar() -> DFResult<()> {
        let source_field = Field::new("a", DataType::Int32, true);
        let input_field = Field::new(
            "s",
            DataType::Struct(vec![Arc::new(source_field.clone())].into()),
            true,
        );
        let target_field = Field::new(
            "s",
            DataType::Struct(
                vec![Arc::new(Field::new("a", DataType::UInt8, true))].into(),
            ),
            true,
        );

        let schema = make_schema(&input_field);
        let scalar_struct = StructArray::new(
            vec![Arc::new(source_field.clone())].into(),
            vec![Arc::new(Int32Array::from(vec![Some(9)])) as ArrayRef],
            None,
        );
        let literal =
            Arc::new(Literal::new(ScalarValue::Struct(Arc::new(scalar_struct))));
        let expr = CastColumnExpr::new(
            literal,
            Arc::new(input_field.clone()),
            Arc::new(target_field.clone()),
            None,
        );

        let batch = RecordBatch::new_empty(Arc::clone(&schema));
        let result = expr.evaluate(&batch)?;
        let ColumnarValue::Scalar(ScalarValue::Struct(array)) = result else {
            panic!("expected struct scalar");
        };
        let casted = array.column_by_name("a").unwrap();
        let casted = as_uint8_array(casted.as_ref())?;
        assert_eq!(casted.value(0), 9);
        Ok(())
    }
}
