use core::{marker::PhantomData, mem::size_of, num::ParseIntError};

use crate::err::Error;

pub trait FallibleMap<I, O> {
    fn out_size(&self) -> usize {
        size_of::<O>()
    }

    /// Attempts to map a value from type `I` to type `O`.
    fn try_map(&self, val: I) -> Result<O, Error>;
}

impl<I, O, F> FallibleMap<I, O> for F
where
    F: Fn(I) -> Result<O, Error>,
{
    fn try_map(&self, val: I) -> Result<O, Error> {
        (self)(val)
    }
}

#[derive(Debug, Clone, Copy, Default)]
pub struct FuncMapper<F> {
    func: F,
}

impl<F> FuncMapper<F> {
    pub const fn new(func: F) -> Self {
        Self { func }
    }
}

impl<F, I, O> FallibleMap<I, O> for FuncMapper<F>
where
    F: Fn(I) -> O,
{
    fn try_map(&self, val: I) -> Result<O, Error> {
        Ok((self.func)(val))
    }
}

/// Adapts infallible functions to the [`FallibleMap`] trait system.
pub const fn mapper<F>(func: F) -> FuncMapper<F> {
    FuncMapper::new(func)
}

#[derive(Debug, Clone, Copy, Default)]
pub struct Select0;

impl Select0 {
    pub const fn new() -> Self {
        Self {}
    }
}

impl<I1, I2> FallibleMap<(I1, I2), I1> for Select0 {
    fn try_map(&self, val: (I1, I2)) -> Result<I1, Error> {
        Ok(val.0)
    }
}

/// Selects the first element (index 0) from a tuple.
pub const fn select0() -> Select0 {
    Select0::new()
}

#[derive(Debug, Clone, Copy, Default)]
pub struct Select1;

impl Select1 {
    pub const fn new() -> Self {
        Self {}
    }
}

impl<I1, I2> FallibleMap<(I1, I2), I2> for Select1 {
    fn try_map(&self, val: (I1, I2)) -> Result<I2, Error> {
        Ok(val.1)
    }
}

/// Selects the second element (index 1) from a tuple.
pub const fn select1() -> Select1 {
    Select1::new()
}

#[derive(Debug, Clone, Copy, Default)]
pub struct SelectEq;

impl SelectEq {
    pub const fn new() -> Self {
        Self {}
    }
}

impl<I1, I2> FallibleMap<(I1, I2), (I1, I2)> for SelectEq
where
    I1: PartialEq<I2>,
{
    fn try_map(&self, val: (I1, I2)) -> Result<(I1, I2), Error> {
        if val.0 == val.1 {
            Ok(val)
        } else {
            Err(Error::SelectEq)
        }
    }
}

/// Validates that both elements of a tuple are equal.
pub const fn select_eq() -> SelectEq {
    SelectEq::new()
}

#[derive(Debug, Clone, Copy, Default)]
pub struct SelectNeq;

impl SelectNeq {
    pub const fn new() -> Self {
        Self {}
    }
}

impl<I1, I2> FallibleMap<(I1, I2), (I1, I2)> for SelectNeq
where
    I1: PartialEq<I2>,
{
    fn try_map(&self, val: (I1, I2)) -> Result<(I1, I2), Error> {
        if val.0 != val.1 {
            Ok(val)
        } else {
            Err(Error::SelectNeq)
        }
    }
}

/// Validates that both elements of a tuple are not equal.
pub const fn select_neq() -> SelectNeq {
    SelectNeq::new()
}

#[derive(Debug)]
pub struct FromStr<T>(PhantomData<T>);

impl<T> Clone for FromStr<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> Copy for FromStr<T> {}

impl<T> Default for FromStr<T> {
    fn default() -> Self {
        Self(Default::default())
    }
}

impl<T> FromStr<T> {
    pub const fn new() -> Self {
        Self(PhantomData)
    }
}

impl<I, O> FallibleMap<I, O> for FromStr<O>
where
    O: core::str::FromStr,
    I: AsRef<str>,
{
    fn try_map(&self, val: I) -> Result<O, Error> {
        let val: &str = val.as_ref();

        val.parse::<O>().map_err(|_| Error::FromStr)
    }
}

/// Converts strings to typed values using [`FromStr`](core::str::FromStr).
///
/// [`FromStr`] is a zero-cost adapter that safely parses strings into strongly-typed
/// values. It wraps the standard library's [`FromStr`](core::str::FromStr) trait implementation to provide
/// a consistent interface for transformation pipelines and parser combinators.
pub const fn from_str<T>() -> FromStr<T> {
    FromStr::new()
}

#[derive(Debug)]
pub struct IntoMapper<T>(PhantomData<T>);

impl<T> Clone for IntoMapper<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> Copy for IntoMapper<T> {}

impl<T> IntoMapper<T> {
    pub const fn new() -> Self {
        Self(PhantomData)
    }
}

impl<T> Default for IntoMapper<T> {
    fn default() -> Self {
        Self(Default::default())
    }
}

impl<I, O> FallibleMap<I, O> for IntoMapper<O>
where
    O: From<I>,
{
    fn try_map(&self, val: I) -> Result<O, Error> {
        Ok(val.into())
    }
}

/// A zero-cost adapter that converts type using the [`into`](core::convert::Into::into) method.
pub const fn into<T>() -> IntoMapper<T> {
    IntoMapper::new()
}

#[derive(Debug)]
pub struct TryIntoMapper<T>(PhantomData<T>);

impl<T> Clone for TryIntoMapper<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> Copy for TryIntoMapper<T> {}

impl<T> TryIntoMapper<T> {
    pub const fn new() -> Self {
        Self(PhantomData)
    }
}

impl<T> Default for TryIntoMapper<T> {
    fn default() -> Self {
        Self(Default::default())
    }
}

impl<I, O> FallibleMap<I, O> for TryIntoMapper<O>
where
    O: TryFrom<I>,
{
    fn try_map(&self, val: I) -> Result<O, Error> {
        val.try_into().map_err(|_| Error::TryInto)
    }
}

/// A zero-cost adapter that converts type using the [`try_into`](core::convert::TryInto::try_into) method.
pub const fn try_into<T>() -> TryIntoMapper<T> {
    TryIntoMapper::new()
}

pub trait TryFromStrRadix
where
    Self: Sized,
{
    fn from_str_radix(src: &str, radix: u32) -> Result<Self, ParseIntError>;
}

macro_rules! impl_from_str_radix {
    ($int:ty) => {
        impl $crate::map::TryFromStrRadix for $int {
            #[inline(always)]
            fn from_str_radix(src: &str, radix: u32) -> Result<Self, ParseIntError> {
                <$int>::from_str_radix(src, radix)
            }
        }
    };
}

impl_from_str_radix!(i8);
impl_from_str_radix!(i16);
impl_from_str_radix!(i32);
impl_from_str_radix!(i64);
impl_from_str_radix!(isize);
impl_from_str_radix!(u8);
impl_from_str_radix!(u16);
impl_from_str_radix!(u32);
impl_from_str_radix!(u64);
impl_from_str_radix!(usize);

#[derive(Debug)]
pub struct FromStrRadix<T> {
    radix: u32,
    marker: PhantomData<T>,
}

impl<T> Clone for FromStrRadix<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> Copy for FromStrRadix<T> {}

impl<T> Default for FromStrRadix<T> {
    fn default() -> Self {
        Self {
            radix: Default::default(),
            marker: Default::default(),
        }
    }
}

impl<T> FromStrRadix<T>
where
    T: TryFromStrRadix,
{
    pub const fn new(radix: u32) -> Self {
        Self {
            radix,
            marker: PhantomData,
        }
    }

    pub const fn radix(&self) -> u32 {
        self.radix
    }
}

impl<I, O> FallibleMap<I, O> for FromStrRadix<O>
where
    O: TryFromStrRadix,
    I: AsRef<str>,
{
    #[inline(always)]
    fn try_map(&self, val: I) -> Result<O, Error> {
        O::from_str_radix(val.as_ref(), self.radix()).map_err(|_| Error::FromStr)
    }
}

/// A trait that abstracts over integer types' `from_str_radix` functionality.
///
/// This trait is implemented for all standard integer types and provides a consistent
/// interface for parsing integers from strings with a specified radix (base).
#[inline(always)]
pub const fn from_str_radix<T: TryFromStrRadix>(radix: u32) -> FromStrRadix<T> {
    FromStrRadix::new(radix)
}

#[derive(Debug)]
pub struct FromUtf8<T>(PhantomData<T>);

impl<T> FromUtf8<T> {
    pub const fn new() -> Self {
        Self(PhantomData)
    }
}

impl<T> Clone for FromUtf8<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> Copy for FromUtf8<T> {}

impl<T> Default for FromUtf8<T> {
    fn default() -> Self {
        Self(Default::default())
    }
}

impl<'a> FallibleMap<&'a [u8], &'a str> for FromUtf8<&'a str> {
    fn try_map(&self, val: &'a [u8]) -> Result<&'a str, Error> {
        core::str::from_utf8(val).map_err(|_| Error::Utf8Error)
    }
}

#[cfg(feature = "alloc")]
impl<'a> FallibleMap<&'a [u8], crate::alloc::String> for FromUtf8<crate::alloc::String> {
    fn try_map(&self, val: &'a [u8]) -> Result<crate::alloc::String, Error> {
        crate::alloc::String::from_utf8(val.to_vec()).map_err(|_| Error::Utf8Error)
    }
}

/// A mapper that converts byte slices to UTF-8 [`String`](crate::alloc::String).
#[inline(always)]
pub const fn from_utf8<T>() -> FromUtf8<T> {
    FromUtf8::new()
}

#[derive(Debug)]
pub struct FromUtf8Lossy<T>(PhantomData<T>);

impl<T> FromUtf8Lossy<T> {
    pub const fn new() -> Self {
        Self(PhantomData)
    }
}

impl<T> Clone for FromUtf8Lossy<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> Copy for FromUtf8Lossy<T> {}

impl<T> Default for FromUtf8Lossy<T> {
    fn default() -> Self {
        Self(Default::default())
    }
}

#[cfg(feature = "alloc")]
impl<'a> FallibleMap<&'a [u8], crate::alloc::Cow<'a, str>>
    for FromUtf8Lossy<crate::alloc::Cow<'a, str>>
{
    fn try_map(&self, val: &'a [u8]) -> Result<crate::alloc::Cow<'a, str>, Error> {
        Ok(crate::alloc::String::from_utf8_lossy(val))
    }
}

/// A mapper that converts byte slices to UTF-8 [`String`](crate::alloc::String) with lossy conversion.
#[cfg(feature = "alloc")]
#[inline(always)]
pub const fn from_utf8_lossy<T>() -> FromUtf8Lossy<T> {
    FromUtf8Lossy::new()
}

#[derive(Debug)]
pub struct FromLeBytes<T>(PhantomData<T>);

impl<T> FromLeBytes<T> {
    pub const fn new() -> Self {
        Self(PhantomData)
    }

    pub const fn size(&self) -> usize {
        size_of::<T>()
    }
}

impl<T> Clone for FromLeBytes<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> Copy for FromLeBytes<T> {}

impl<T> Default for FromLeBytes<T> {
    fn default() -> Self {
        Self(Default::default())
    }
}

#[derive(Debug)]
pub struct FromBeBytes<T>(PhantomData<T>);

impl<T> FromBeBytes<T> {
    pub const fn new() -> Self {
        Self(PhantomData)
    }

    pub const fn size(&self) -> usize {
        size_of::<T>()
    }
}

impl<T> Clone for FromBeBytes<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> Copy for FromBeBytes<T> {}

impl<T> Default for FromBeBytes<T> {
    fn default() -> Self {
        Self(Default::default())
    }
}

#[derive(Debug)]
pub struct FromNeBytes<T>(PhantomData<T>);

impl<T> FromNeBytes<T> {
    pub const fn new() -> Self {
        Self(PhantomData)
    }

    pub const fn size(&self) -> usize {
        size_of::<T>()
    }
}

impl<T> Copy for FromNeBytes<T> {}

impl<T> Clone for FromNeBytes<T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<T> Default for FromNeBytes<T> {
    fn default() -> Self {
        Self(Default::default())
    }
}

macro_rules! impl_from_bytes {
    (le $ty:ty, $size:literal) => {
        impl<'a> FallibleMap<&'a [u8], $ty> for FromLeBytes<$ty> {
            fn try_map(&self, val: &'a [u8]) -> Result<$ty, $crate::err::Error> {
                debug_assert_eq!($size, self.size());
                let bytes = val
                    .chunks_exact($size)
                    .next()
                    .ok_or_else(|| $crate::err::Error::FromLeBytes)
                    .map(|v| {
                        <&[u8; $size]>::try_from(v).map_err(|_| $crate::err::Error::FromLeBytes)
                    })??;
                Ok(<$ty>::from_le_bytes(*bytes))
            }
        }
    };
    (be $ty:ty, $size:literal) => {
        impl<'a> FallibleMap<&'a [u8], $ty> for FromBeBytes<$ty> {
            fn try_map(&self, val: &'a [u8]) -> Result<$ty, $crate::err::Error> {
                debug_assert_eq!($size, self.size());
                let bytes = val
                    .chunks_exact($size)
                    .next()
                    .ok_or_else(|| $crate::err::Error::FromBeBytes)
                    .map(|v| {
                        <&[u8; $size]>::try_from(v).map_err(|_| $crate::err::Error::FromBeBytes)
                    })??;
                Ok(<$ty>::from_be_bytes(*bytes))
            }
        }
    };
    (ne $ty:ty, $size:literal) => {
        impl<'a> FallibleMap<&'a [u8], $ty> for FromNeBytes<$ty> {
            fn try_map(&self, val: &'a [u8]) -> Result<$ty, $crate::err::Error> {
                debug_assert_eq!($size, self.size());
                let bytes = val
                    .chunks_exact($size)
                    .next()
                    .ok_or_else(|| $crate::err::Error::FromNeBytes)
                    .map(|v| {
                        <&[u8; $size]>::try_from(v).map_err(|_| $crate::err::Error::FromNeBytes)
                    })??;
                Ok(<$ty>::from_ne_bytes(*bytes))
            }
        }
    };
}

impl_from_bytes!(le i8, 1);
impl_from_bytes!(le u8, 1);
impl_from_bytes!(le i16, 2);
impl_from_bytes!(le u16, 2);
impl_from_bytes!(le i32, 4);
impl_from_bytes!(le u32, 4);
impl_from_bytes!(le i64, 8);
impl_from_bytes!(le u64, 8);
impl_from_bytes!(le f32, 4);
impl_from_bytes!(le f64, 8);
impl_from_bytes!(le i128, 16);
impl_from_bytes!(le u128, 16);
impl_from_bytes!(le isize, 8);
impl_from_bytes!(le usize, 8);
impl_from_bytes!(be i8, 1);
impl_from_bytes!(be u8, 1);
impl_from_bytes!(be i16, 2);
impl_from_bytes!(be u16, 2);
impl_from_bytes!(be i32, 4);
impl_from_bytes!(be u32, 4);
impl_from_bytes!(be i64, 8);
impl_from_bytes!(be u64, 8);
impl_from_bytes!(be f32, 4);
impl_from_bytes!(be f64, 8);
impl_from_bytes!(be i128, 16);
impl_from_bytes!(be u128, 16);
impl_from_bytes!(be isize, 8);
impl_from_bytes!(be usize, 8);
impl_from_bytes!(ne i8, 1);
impl_from_bytes!(ne u8, 1);
impl_from_bytes!(ne i16, 2);
impl_from_bytes!(ne u16, 2);
impl_from_bytes!(ne i32, 4);
impl_from_bytes!(ne u32, 4);
impl_from_bytes!(ne i64, 8);
impl_from_bytes!(ne u64, 8);
impl_from_bytes!(ne f32, 4);
impl_from_bytes!(ne f64, 8);
impl_from_bytes!(ne i128, 16);
impl_from_bytes!(ne u128, 16);
impl_from_bytes!(ne isize, 8);
impl_from_bytes!(ne usize, 8);

///
/// Map an integer value from its memory representation as a byte array in little endianness.
///
/// # Example
///
/// ```
/// # use neure::prelude::*;
/// #
/// # fn main() -> Result<(), Box<dyn core::error::Error>> {
///     let data = [0x01, 0x02, 0x03, 0x04];
///     let parser = regex::consume(4).try_map(map::from_le_bytes::<i32>());
///
///     assert_eq!(BytesCtx::new(&data).ctor(&parser)?, 0x04030201);
///
///     Ok(())
/// # }
/// ```
#[inline(always)]
pub const fn from_le_bytes<T>() -> FromLeBytes<T> {
    FromLeBytes::new()
}

///
/// Map an integer value from its memory representation as a byte array in bigger endianness.
///
/// # Example
///
/// ```
/// # use neure::prelude::*;
/// #
/// # fn main() -> Result<(), Box<dyn core::error::Error>> {
///     let data = [0x01, 0x02, 0x03, 0x04];
///     let parser = regex::consume(4).try_map(map::from_be_bytes::<i32>());
///
///     assert_eq!(BytesCtx::new(&data).ctor(&parser)?, 0x01020304);
///
///     Ok(())
/// # }
/// ```
#[inline(always)]
pub const fn from_be_bytes<T>() -> FromBeBytes<T> {
    FromBeBytes::new()
}

///
/// Map an integer value from its memory representation as a byte array in native endianness.
///
#[inline(always)]
pub const fn from_ne_bytes<T>() -> FromNeBytes<T> {
    FromNeBytes::new()
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Bounded<T> {
    min: T,
    max: T,
}

impl<T> Bounded<T> {
    pub const fn new(min: T, max: T) -> Self {
        Self { min, max }
    }
}

impl<T> FallibleMap<T, T> for Bounded<T>
where
    T: PartialOrd,
{
    fn try_map(&self, val: T) -> Result<T, Error> {
        if self.min <= val && val < self.max {
            Ok(val)
        } else {
            Err(Error::SelectEq)
        }
    }
}

/// A mapper that validates values against a specified range.
///
/// This struct checks if a value falls within the range `[min, max)` -
/// inclusive of the minimum value and exclusive of the maximum value.
///
/// # Example
/// ```
/// # use neure::prelude::*;
/// #
/// # fn main() -> Result<(), Box<dyn core::error::Error>> {
///     let mut ctx = CharsCtx::new("1,3,5,7,9");
///
///     let parser = neu::digit(10)
///         .many1()
///         .try_map(map::from_str::<i32>())
///         .try_map(map::bounded(1, 8))
///         .sep(",");
///
///     assert_eq!(ctx.ctor(&parser)?, vec![1, 3, 5, 7]);
///
/// #   Ok(())
/// # }
/// ```
#[inline(always)]
pub const fn bounded<T: PartialOrd>(min: T, max: T) -> Bounded<T> {
    Bounded::new(min, max)
}

#[derive(Debug)]
pub struct WithDefault<I, O, F, M> {
    func: F,
    mapper: M,
    marker: PhantomData<(I, O)>,
}

impl<I, O, F, M> Clone for WithDefault<I, O, F, M>
where
    F: Clone,
    M: Clone,
{
    fn clone(&self) -> Self {
        Self {
            func: self.func.clone(),
            mapper: self.mapper.clone(),
            marker: self.marker,
        }
    }
}

impl<I, O, F, M> Copy for WithDefault<I, O, F, M>
where
    F: Copy,
    M: Copy,
{
}

impl<I, O, F, M> WithDefault<I, O, F, M>
where
    F: Fn() -> O,
    M: FallibleMap<I, O>,
{
    pub const fn new(func: F, mapper: M) -> Self {
        Self {
            func,
            mapper,
            marker: PhantomData,
        }
    }
}

impl<I, O, F, M> FallibleMap<I, O> for WithDefault<I, O, F, M>
where
    F: Fn() -> O,
    M: FallibleMap<I, O>,
{
    fn out_size(&self) -> usize {
        self.mapper.out_size()
    }

    fn try_map(&self, val: I) -> Result<O, Error> {
        if let Ok(val) = self.mapper.try_map(val) {
            Ok(val)
        } else {
            Ok((self.func)())
        }
    }
}

pub trait WithDefaultHelper<I, O>: Sized {
    fn with_default<F>(self, func: F) -> WithDefault<I, O, F, Self>
    where
        F: Fn() -> O;
}

impl<I, O, T: Sized> WithDefaultHelper<I, O> for T
where
    Self: FallibleMap<I, O>,
{
    fn with_default<F>(self, func: F) -> WithDefault<I, O, F, Self>
    where
        F: Fn() -> O,
    {
        with_default(func, self)
    }
}

/// A mapper that provides a fallback default value when the primary mapping fails.
///
/// This struct wraps another mapper and a default value provider function. When mapping,
/// it first attempts to use the inner mapper. If that fails, it invokes the default
/// function to produce a fallback value.
///
/// # Example
/// ```
/// # use neure::{map::WithDefaultHelper, prelude::*};
/// #
/// # fn main() -> Result<(), Box<dyn core::error::Error>> {
///     let mut ctx = CharsCtx::new("1,3,5,7,9");
///
///     let parser = regex!(['0' - '9']+)
///         .try_map(map::from_str::<i32>())
///         .try_map(map::bounded(1, 5).with_default(|| 0))
///         .sep(",");
///
///     assert_eq!(ctx.ctor(&parser)?, vec![1, 3, 0, 0, 0]);
///
/// #   Ok(())
/// # }
/// ```
#[inline(always)]
pub const fn with_default<I, O, F, M>(func: F, mapper: M) -> WithDefault<I, O, F, M>
where
    F: Fn() -> O,
    M: FallibleMap<I, O>,
{
    WithDefault::new(func, mapper)
}

#[derive(Debug, Clone, Copy, Default)]
pub struct FixedSize(pub usize);

impl FixedSize {
    pub const fn new(size: usize) -> Self {
        Self(size)
    }
}

impl<T> FallibleMap<T, T> for FixedSize {
    fn out_size(&self) -> usize {
        self.0
    }

    fn try_map(&self, val: T) -> Result<T, Error> {
        Ok(val)
    }
}

/// A wrapper that specifies a fixed output size for parsers.
///
/// This struct is used when you need to explicitly define the size of data
/// to consume from a byte stream, particularly for parsers that cannot
/// determine their size automatically.
///
/// The mapper itself is an identity function - it simply returns the input
/// unchanged while providing the specified size information to the parser system.
///
/// # Example
/// ```
/// # use neure::{
/// #     map::{FallibleMap, fixed_size, from_be_bytes},
/// #     prelude::*,
/// # };
/// #
/// # fn main() -> Result<(), Box<dyn core::error::Error>> {
///     fn parse<'a, P, O>(parser: P, bc: &mut BytesCtx<'a>) -> Result<O, neure::err::Error>
///     where
///         P: FallibleMap<&'a [u8], O>,
///     {
///         bc.ctor(&regex::consume(parser.out_size()).try_map(parser))
///     }
///
///     let bc = &mut BytesCtx::new(b"\x0bhelloworld!");
///
///     let length = parse(from_be_bytes::<u8>(), bc)?;
///
///     assert_eq!(parse(fixed_size(length as usize), bc)?, b"helloworld!");
///
///     Ok(())
/// # }
/// ```
pub const fn fixed_size(size: usize) -> FixedSize {
    FixedSize::new(size)
}
