extern crate alloc;

use std::borrow::Cow;

use facet_core::{NumericType, PrimitiveType, ScalarType, Type, UserType};
use facet_reflect::{Partial, Span};

use crate::{DeserializeError, DeserializeErrorKind, FormatDeserializer, ScalarValue};

fn number_out_of_range(
    value: impl core::fmt::Display,
    target_type: &'static str,
) -> DeserializeError {
    DeserializeError {
        span: None,
        path: None,
        kind: DeserializeErrorKind::NumberOutOfRange {
            value: value.to_string().into(),
            target_type,
        },
    }
}

fn narrow_int<T, U>(value: U, target_type: &'static str) -> Result<T, DeserializeError>
where
    T: TryFrom<U>,
    U: Copy + core::fmt::Display,
{
    T::try_from(value).map_err(|_| number_out_of_range(value, target_type))
}

/// Set a scalar value into a `Partial`, handling type coercion.
///
/// This is a non-generic inner function that handles the core logic of `set_scalar`.
/// It's extracted to reduce monomorphization bloat - each parser type only needs
/// a thin wrapper that converts the error type.
///
/// Note: `ScalarValue::Str` and `ScalarValue::Bytes` cases delegate to `facet_dessert`
/// for string/bytes handling.
#[allow(clippy::result_large_err)]
pub(crate) fn set_scalar_inner<'input, const BORROW: bool>(
    mut wip: Partial<'input, BORROW>,
    scalar: ScalarValue<'input>,
) -> Result<Partial<'input, BORROW>, SetScalarResult<'input, BORROW>> {
    let shape = wip.shape();
    let scalar_type = shape.scalar_type();

    match scalar {
        ScalarValue::Null => {
            wip = wip.set_default()?;
        }
        ScalarValue::Bool(b) => {
            wip = wip.set(b)?;
        }
        ScalarValue::Char(c) => {
            wip = wip.set(c)?;
        }
        ScalarValue::I64(n) => {
            match scalar_type {
                // Handle signed types
                Some(ScalarType::I8) => wip = wip.set(narrow_int::<i8, _>(n, "i8")?)?,
                Some(ScalarType::I16) => wip = wip.set(narrow_int::<i16, _>(n, "i16")?)?,
                Some(ScalarType::I32) => wip = wip.set(narrow_int::<i32, _>(n, "i32")?)?,
                Some(ScalarType::I64) => wip = wip.set(n)?,
                Some(ScalarType::I128) => wip = wip.set(n as i128)?,
                Some(ScalarType::ISize) => wip = wip.set(narrow_int::<isize, _>(n, "isize")?)?,
                // Handle unsigned types (I64 can fit in unsigned if non-negative)
                Some(ScalarType::U8) => wip = wip.set(narrow_int::<u8, _>(n, "u8")?)?,
                Some(ScalarType::U16) => wip = wip.set(narrow_int::<u16, _>(n, "u16")?)?,
                Some(ScalarType::U32) => wip = wip.set(narrow_int::<u32, _>(n, "u32")?)?,
                Some(ScalarType::U64) => wip = wip.set(narrow_int::<u64, _>(n, "u64")?)?,
                Some(ScalarType::U128) => wip = wip.set(narrow_int::<u128, _>(n, "u128")?)?,
                Some(ScalarType::USize) => wip = wip.set(narrow_int::<usize, _>(n, "usize")?)?,
                // Handle floats
                Some(ScalarType::F32) => wip = wip.set(n as f32)?,
                Some(ScalarType::F64) => wip = wip.set(n as f64)?,
                Some(ScalarType::String) => {
                    wip = wip.set(alloc::string::ToString::to_string(&n))?
                }
                _ => wip = wip.set(n)?,
            }
        }
        ScalarValue::U64(n) => {
            match scalar_type {
                // Handle unsigned types
                Some(ScalarType::U8) => wip = wip.set(narrow_int::<u8, _>(n, "u8")?)?,
                Some(ScalarType::U16) => wip = wip.set(narrow_int::<u16, _>(n, "u16")?)?,
                Some(ScalarType::U32) => wip = wip.set(narrow_int::<u32, _>(n, "u32")?)?,
                Some(ScalarType::U64) => wip = wip.set(n)?,
                Some(ScalarType::U128) => wip = wip.set(n as u128)?,
                Some(ScalarType::USize) => wip = wip.set(narrow_int::<usize, _>(n, "usize")?)?,
                // Handle signed types (U64 can fit in signed if small enough)
                Some(ScalarType::I8) => wip = wip.set(narrow_int::<i8, _>(n, "i8")?)?,
                Some(ScalarType::I16) => wip = wip.set(narrow_int::<i16, _>(n, "i16")?)?,
                Some(ScalarType::I32) => wip = wip.set(narrow_int::<i32, _>(n, "i32")?)?,
                Some(ScalarType::I64) => wip = wip.set(narrow_int::<i64, _>(n, "i64")?)?,
                Some(ScalarType::I128) => wip = wip.set(n as i128)?,
                Some(ScalarType::ISize) => wip = wip.set(narrow_int::<isize, _>(n, "isize")?)?,
                // Handle floats
                Some(ScalarType::F32) => wip = wip.set(n as f32)?,
                Some(ScalarType::F64) => wip = wip.set(n as f64)?,
                Some(ScalarType::String) => {
                    wip = wip.set(alloc::string::ToString::to_string(&n))?
                }
                _ => wip = wip.set(n)?,
            }
        }
        ScalarValue::U128(n) => match scalar_type {
            Some(ScalarType::U8) => wip = wip.set(narrow_int::<u8, _>(n, "u8")?)?,
            Some(ScalarType::U16) => wip = wip.set(narrow_int::<u16, _>(n, "u16")?)?,
            Some(ScalarType::U32) => wip = wip.set(narrow_int::<u32, _>(n, "u32")?)?,
            Some(ScalarType::U64) => wip = wip.set(narrow_int::<u64, _>(n, "u64")?)?,
            Some(ScalarType::U128) => wip = wip.set(n)?,
            Some(ScalarType::USize) => wip = wip.set(narrow_int::<usize, _>(n, "usize")?)?,
            Some(ScalarType::I8) => wip = wip.set(narrow_int::<i8, _>(n, "i8")?)?,
            Some(ScalarType::I16) => wip = wip.set(narrow_int::<i16, _>(n, "i16")?)?,
            Some(ScalarType::I32) => wip = wip.set(narrow_int::<i32, _>(n, "i32")?)?,
            Some(ScalarType::I64) => wip = wip.set(narrow_int::<i64, _>(n, "i64")?)?,
            Some(ScalarType::I128) => wip = wip.set(narrow_int::<i128, _>(n, "i128")?)?,
            Some(ScalarType::ISize) => wip = wip.set(narrow_int::<isize, _>(n, "isize")?)?,
            Some(ScalarType::F32) => wip = wip.set(n as f32)?,
            Some(ScalarType::F64) => wip = wip.set(n as f64)?,
            Some(ScalarType::String) => wip = wip.set(alloc::string::ToString::to_string(&n))?,
            _ => wip = wip.set(n)?,
        },
        ScalarValue::I128(n) => match scalar_type {
            Some(ScalarType::I8) => wip = wip.set(narrow_int::<i8, _>(n, "i8")?)?,
            Some(ScalarType::I16) => wip = wip.set(narrow_int::<i16, _>(n, "i16")?)?,
            Some(ScalarType::I32) => wip = wip.set(narrow_int::<i32, _>(n, "i32")?)?,
            Some(ScalarType::I64) => wip = wip.set(narrow_int::<i64, _>(n, "i64")?)?,
            Some(ScalarType::I128) => wip = wip.set(n)?,
            Some(ScalarType::ISize) => wip = wip.set(narrow_int::<isize, _>(n, "isize")?)?,
            Some(ScalarType::U8) => wip = wip.set(narrow_int::<u8, _>(n, "u8")?)?,
            Some(ScalarType::U16) => wip = wip.set(narrow_int::<u16, _>(n, "u16")?)?,
            Some(ScalarType::U32) => wip = wip.set(narrow_int::<u32, _>(n, "u32")?)?,
            Some(ScalarType::U64) => wip = wip.set(narrow_int::<u64, _>(n, "u64")?)?,
            Some(ScalarType::U128) => wip = wip.set(narrow_int::<u128, _>(n, "u128")?)?,
            Some(ScalarType::USize) => wip = wip.set(narrow_int::<usize, _>(n, "usize")?)?,
            Some(ScalarType::F32) => wip = wip.set(n as f32)?,
            Some(ScalarType::F64) => wip = wip.set(n as f64)?,
            Some(ScalarType::String) => wip = wip.set(alloc::string::ToString::to_string(&n))?,
            _ => wip = wip.set(n)?,
        },
        ScalarValue::F64(n) => {
            match scalar_type {
                Some(ScalarType::F32) => wip = wip.set(n as f32)?,
                Some(ScalarType::F64) => wip = wip.set(n)?,
                _ if shape.vtable.has_try_from() && shape.inner.is_some() => {
                    // For opaque types with try_from (like NotNan, OrderedFloat), use
                    // begin_inner() + set + end() to trigger conversion
                    let inner_shape = shape.inner.unwrap();
                    wip = wip.begin_inner()?;
                    if inner_shape.is_type::<f32>() {
                        wip = wip.set(n as f32)?;
                    } else {
                        wip = wip.set(n)?;
                    }
                    wip = wip.end()?;
                }
                _ if shape.vtable.has_parse() => {
                    // For types that support parsing (like Decimal), convert to string
                    // and use parse_from_str to preserve their parsing semantics
                    wip = wip.parse_from_str(&alloc::string::ToString::to_string(&n))?;
                }
                _ => wip = wip.set(n)?,
            }
        }
        ScalarValue::Str(s) => {
            // Try parse_from_str first if the type supports it
            if shape.vtable.has_parse() {
                wip = wip.parse_from_str(s.as_ref())?;
            } else {
                // Delegate to set_string_value - this requires the caller to handle it
                return Err(SetScalarResult::NeedsStringValue { wip, s });
            }
        }
        ScalarValue::Bytes(b) => {
            // First try parse_from_bytes if the type supports it (e.g., UUID from 16 bytes)
            if shape.vtable.has_parse_bytes() {
                wip = wip.parse_from_bytes(b.as_ref())?;
            } else {
                // Delegate to set_bytes_value - this requires the caller to handle it
                return Err(SetScalarResult::NeedsBytesValue { wip, b });
            }
        }
        ScalarValue::Unit => {
            // Unit value - set to default/unit value
            wip = wip.set_default()?;
        }
    }

    Ok(wip)
}

/// Result of `set_scalar_inner` - either success, an error, or delegation to string/bytes handling.
pub(crate) enum SetScalarResult<'input, const BORROW: bool> {
    /// Need to call `set_string_value` with these parameters.
    NeedsStringValue {
        wip: Partial<'input, BORROW>,
        s: Cow<'input, str>,
    },
    /// Need to call `set_bytes_value` with these parameters.
    NeedsBytesValue {
        wip: Partial<'input, BORROW>,
        b: Cow<'input, [u8]>,
    },
    /// An error occurred.
    Error(DeserializeError),
}

impl<'input, const BORROW: bool> From<DeserializeError> for SetScalarResult<'input, BORROW> {
    fn from(e: DeserializeError) -> Self {
        SetScalarResult::Error(e)
    }
}

impl<'input, const BORROW: bool> From<facet_reflect::ReflectError>
    for SetScalarResult<'input, BORROW>
{
    fn from(e: facet_reflect::ReflectError) -> Self {
        SetScalarResult::Error(e.into())
    }
}

/// Result of `deserialize_map_key_terminal_inner` - either success or delegation.
pub(crate) enum MapKeyTerminalResult<'input, const BORROW: bool> {
    /// Need to call `set_string_value` with these parameters.
    NeedsSetString {
        wip: Partial<'input, BORROW>,
        s: Cow<'input, str>,
    },
    /// An error occurred.
    Error(DeserializeError),
}

impl<'input, const BORROW: bool> From<DeserializeError> for MapKeyTerminalResult<'input, BORROW> {
    fn from(e: DeserializeError) -> Self {
        MapKeyTerminalResult::Error(e)
    }
}

impl<'input, const BORROW: bool> From<facet_reflect::ReflectError>
    for MapKeyTerminalResult<'input, BORROW>
{
    fn from(e: facet_reflect::ReflectError) -> Self {
        MapKeyTerminalResult::Error(e.into())
    }
}

/// Handle terminal cases of map key deserialization (enum, numeric, string).
///
/// This is a non-generic inner function that handles the final step of `deserialize_map_key`
/// when recursion is not needed. It's extracted to reduce monomorphization bloat.
///
/// The function handles:
/// - Enum types: use `select_variant_named`
/// - Numeric types: parse the string key as a number
/// - String types: delegate to `set_string_value` (returns `NeedsSetString`)
#[allow(clippy::result_large_err)]
pub(crate) fn deserialize_map_key_terminal_inner<'input, const BORROW: bool>(
    mut wip: Partial<'input, BORROW>,
    key: Cow<'input, str>,
    span: Span,
) -> Result<Partial<'input, BORROW>, MapKeyTerminalResult<'input, BORROW>> {
    let shape = wip.shape();

    // Check if target is an enum - use select_variant_named for unit variants
    if let Type::User(UserType::Enum(_)) = &shape.ty {
        wip = wip.select_variant_named(&key)?;
        return Ok(wip);
    }

    // Check if target is a numeric type - parse the string key as a number.
    // The key must be parsed to the exact target width: `Partial::set` stores
    // the value as-is and does not convert between numeric sizes.
    if let Type::Primitive(PrimitiveType::Numeric(num_ty)) = &shape.ty {
        macro_rules! set_parsed_key {
            ($ty:ty, $expected:literal) => {{
                let n: $ty = key.parse().map_err(|_| DeserializeError {
                    span: Some(span),
                    path: None,
                    kind: DeserializeErrorKind::UnexpectedToken {
                        expected: $expected,
                        got: alloc::format!("string '{}'", key).into(),
                    },
                })?;
                wip = wip.set(n)?;
                return Ok(wip);
            }};
        }
        match ScalarType::try_from_shape(shape) {
            Some(ScalarType::I8) => set_parsed_key!(i8, "valid integer for map key"),
            Some(ScalarType::I16) => set_parsed_key!(i16, "valid integer for map key"),
            Some(ScalarType::I32) => set_parsed_key!(i32, "valid integer for map key"),
            Some(ScalarType::I64) => set_parsed_key!(i64, "valid integer for map key"),
            Some(ScalarType::I128) => set_parsed_key!(i128, "valid integer for map key"),
            Some(ScalarType::ISize) => set_parsed_key!(isize, "valid integer for map key"),
            Some(ScalarType::U8) => set_parsed_key!(u8, "valid unsigned integer for map key"),
            Some(ScalarType::U16) => set_parsed_key!(u16, "valid unsigned integer for map key"),
            Some(ScalarType::U32) => set_parsed_key!(u32, "valid unsigned integer for map key"),
            Some(ScalarType::U64) => set_parsed_key!(u64, "valid unsigned integer for map key"),
            Some(ScalarType::U128) => set_parsed_key!(u128, "valid unsigned integer for map key"),
            Some(ScalarType::USize) => {
                set_parsed_key!(usize, "valid unsigned integer for map key")
            }
            Some(ScalarType::F32) => set_parsed_key!(f32, "valid float for map key"),
            Some(ScalarType::F64) => set_parsed_key!(f64, "valid float for map key"),
            _ => {}
        }
        match num_ty {
            // Fallback for numeric primitives without a ScalarType mapping
            NumericType::Integer { signed } => {
                if *signed {
                    set_parsed_key!(i64, "valid integer for map key")
                } else {
                    set_parsed_key!(u64, "valid unsigned integer for map key")
                }
            }
            NumericType::Float => set_parsed_key!(f64, "valid float for map key"),
            // A numeric primitive kind added since this match was written.
            _ => {
                return Err(DeserializeError {
                    span: Some(span),
                    path: None,
                    kind: DeserializeErrorKind::UnexpectedToken {
                        expected: "an integer or float numeric type for a map key",
                        got: alloc::format!("unsupported numeric type for key '{key}'").into(),
                    },
                }
                .into());
            }
        }
    }

    // Default: treat as string - delegate to set_string_value
    Err(MapKeyTerminalResult::NeedsSetString { wip, s: key })
}

impl<'parser, 'input, const BORROW: bool> FormatDeserializer<'parser, 'input, BORROW> {
    /// Set a scalar value into a `Partial`, handling type coercion.
    ///
    /// This is a thin wrapper around `set_scalar_inner` that handles the
    /// string/bytes delegation cases and converts error types.
    pub(crate) fn set_scalar(
        &mut self,
        wip: Partial<'input, BORROW>,
        scalar: ScalarValue<'input>,
    ) -> Result<Partial<'input, BORROW>, DeserializeError> {
        match set_scalar_inner(wip, scalar) {
            Ok(wip) => Ok(wip),
            Err(SetScalarResult::NeedsStringValue { wip, s }) => self.set_string_value(wip, s),
            Err(SetScalarResult::NeedsBytesValue { wip, b }) => self.set_bytes_value(wip, b),
            Err(SetScalarResult::Error(e)) => Err(e),
        }
    }

    /// Set a string value, handling `&str`, `Cow<str>`, and `String` appropriately.
    pub(crate) fn set_string_value(
        &mut self,
        wip: Partial<'input, BORROW>,
        s: Cow<'input, str>,
    ) -> Result<Partial<'input, BORROW>, DeserializeError> {
        facet_dessert::set_string_value(wip, s, Some(self.last_span)).map_err(|e| match e {
            facet_dessert::DessertError::Reflect { error, span } => DeserializeError {
                span,
                path: Some(error.path),
                kind: DeserializeErrorKind::Reflect {
                    kind: error.kind,
                    context: "",
                },
            },
            facet_dessert::DessertError::CannotBorrow { message } => DeserializeError {
                span: None,
                path: None,
                kind: DeserializeErrorKind::CannotBorrow { reason: message },
            },
            other => DeserializeError {
                span: None,
                path: None,
                kind: DeserializeErrorKind::CannotBorrow {
                    reason: Cow::Owned(alloc::format!("dessert error: {other}")),
                },
            },
        })
    }

    /// Set a bytes value with proper handling for borrowed vs owned data.
    ///
    /// This handles `&[u8]`, `Cow<[u8]>`, and `Vec<u8>` appropriately based on
    /// whether borrowing is enabled and whether the data is borrowed or owned.
    pub(crate) fn set_bytes_value(
        &mut self,
        wip: Partial<'input, BORROW>,
        b: Cow<'input, [u8]>,
    ) -> Result<Partial<'input, BORROW>, DeserializeError> {
        facet_dessert::set_bytes_value(wip, b, Some(self.last_span)).map_err(|e| match e {
            facet_dessert::DessertError::Reflect { error, span } => DeserializeError {
                span,
                path: Some(error.path),
                kind: DeserializeErrorKind::Reflect {
                    kind: error.kind,
                    context: "",
                },
            },
            facet_dessert::DessertError::CannotBorrow { message } => DeserializeError {
                span: None,
                path: None,
                kind: DeserializeErrorKind::CannotBorrow { reason: message },
            },
            other => DeserializeError {
                span: None,
                path: None,
                kind: DeserializeErrorKind::CannotBorrow {
                    reason: Cow::Owned(alloc::format!("dessert error: {other}")),
                },
            },
        })
    }
}
