use core::{fmt, marker::PhantomData};

use crate::prelude::*;

/// Creates a new fallible lender that repeats elements endlessly
/// by applying the provided closure, the repeater,
/// `F: FnMut() -> A`.
///
/// The [`repeat_with()`] function calls the repeater over and
/// over again.
///
/// The [`FallibleLender`] version of
/// [`iter::repeat_with()`](core::iter::repeat_with): the
/// closure returns a value directly (not a `Result`).
///
/// To create a lender that endlessly repeats errors generated
/// by a closure, use [`repeat_with_err()`].
///
/// # Examples
/// ```rust
/// # use lender::prelude::*;
/// let mut lender = lender::fallible_repeat_with::<
///     '_, fallible_lend!(&'lend i32), String, _,
/// >(|| &0);
/// assert_eq!(lender.next().unwrap(), Some(&0));
/// ```
#[inline]
pub fn repeat_with<'a, L, E, F>(f: F) -> RepeatWith<'a, L, E, F>
where
    L: ?Sized + CovariantFallibleLending + 'a,
    F: FnMut() -> FallibleLend<'a, L>,
{
    RepeatWith {
        f,
        _marker: PhantomData,
    }
}

/// Creates a new fallible lender that endlessly repeats errors
/// generated by a closure.
///
/// This is the error counterpart to [`repeat_with()`]: it calls
/// the closure on every call to `next` and yields the result as
/// an error.
///
/// # Examples
/// ```rust
/// # use lender::prelude::*;
/// let mut count = 0;
/// let mut lender = lender::fallible_repeat_with_err::<
///     fallible_lend!(&'lend i32), _,
/// >(move || { count += 1; format!("error #{count}") });
/// assert_eq!(lender.next(), Err("error #1".to_string()));
/// assert_eq!(lender.next(), Err("error #2".to_string()));
/// ```
#[inline]
pub fn repeat_with_err<L, F>(f: F) -> RepeatWithErr<L, F>
where
    L: ?Sized + CovariantFallibleLending,
{
    RepeatWithErr {
        f,
        _marker: PhantomData,
    }
}

/// A fallible lender that repeats an element endlessly by
/// applying a closure.
///
/// This `struct` is created by the
/// [`fallible_repeat_with()`](crate::fallible_repeat_with)
/// function.
#[must_use = "lenders are lazy and do nothing unless consumed"]
pub struct RepeatWith<'a, L: ?Sized, E, F> {
    f: F,
    #[allow(clippy::type_complexity)]
    _marker: core::marker::PhantomData<(&'a L, fn() -> E)>,
}

impl<L: ?Sized, E, F: Clone> Clone for RepeatWith<'_, L, E, F> {
    #[inline]
    fn clone(&self) -> Self {
        Self {
            f: self.f.clone(),
            _marker: PhantomData,
        }
    }
}

impl<L: ?Sized, E, F> fmt::Debug for RepeatWith<'_, L, E, F> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("FallibleRepeatWith").finish_non_exhaustive()
    }
}

impl<'lend, 'a, L, E, F> FallibleLending<'lend> for RepeatWith<'a, L, E, F>
where
    L: ?Sized + CovariantFallibleLending + 'a,
    F: FnMut() -> FallibleLend<'a, L>,
{
    type Lend = FallibleLend<'lend, L>;
}

impl<'a, L, E, F> FallibleLender for RepeatWith<'a, L, E, F>
where
    L: ?Sized + CovariantFallibleLending + 'a,
    F: FnMut() -> FallibleLend<'a, L>,
{
    type Error = E;
    // SAFETY: the lend is the return type of F
    crate::unsafe_assume_covariance_fallible!();

    #[inline]
    fn next(&mut self) -> Result<Option<FallibleLend<'_, Self>>, Self::Error> {
        Ok(Some(
            // SAFETY: 'a: 'lend
            unsafe { core::mem::transmute::<FallibleLend<'a, L>, FallibleLend<'_, L>>((self.f)()) },
        ))
    }

    #[inline(always)]
    fn size_hint(&self) -> (usize, Option<usize>) {
        (usize::MAX, None)
    }

    /// Advances the lender by `n` elements.
    ///
    /// Unlike [`Repeat::advance_by`](crate::FallibleRepeat),
    /// which is a no-op, this method calls the closure `n` times
    /// (discarding the results) because the closure may have
    /// side effects.
    #[inline]
    fn advance_by(&mut self, n: usize) -> Result<Result<(), core::num::NonZeroUsize>, Self::Error> {
        for _ in 0..n {
            (self.f)();
        }
        Ok(Ok(()))
    }
}

impl<'a, L, E, F> DoubleEndedFallibleLender for RepeatWith<'a, L, E, F>
where
    L: ?Sized + CovariantFallibleLending + 'a,
    F: FnMut() -> FallibleLend<'a, L>,
{
    #[inline(always)]
    fn next_back(&mut self) -> Result<Option<FallibleLend<'_, Self>>, Self::Error> {
        self.next()
    }

    #[inline]
    fn advance_back_by(
        &mut self,
        n: usize,
    ) -> Result<Result<(), core::num::NonZeroUsize>, Self::Error> {
        for _ in 0..n {
            (self.f)();
        }
        Ok(Ok(()))
    }
}

impl<'a, L, E, F> FusedFallibleLender for RepeatWith<'a, L, E, F>
where
    L: ?Sized + CovariantFallibleLending + 'a,
    F: FnMut() -> FallibleLend<'a, L>,
{
}

/// A fallible lender that endlessly repeats errors generated
/// by a closure.
///
/// This `struct` is created by the [`repeat_with_err()`]
/// function.
#[must_use = "lenders are lazy and do nothing unless consumed"]
pub struct RepeatWithErr<L: ?Sized, F> {
    f: F,
    _marker: PhantomData<fn() -> L>,
}

impl<L: ?Sized, F: Clone> Clone for RepeatWithErr<L, F> {
    #[inline]
    fn clone(&self) -> Self {
        Self {
            f: self.f.clone(),
            _marker: PhantomData,
        }
    }
}

impl<L: ?Sized, F> fmt::Debug for RepeatWithErr<L, F> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("FallibleRepeatWithErr")
            .finish_non_exhaustive()
    }
}

impl<'lend, L, E, F> FallibleLending<'lend> for RepeatWithErr<L, F>
where
    L: ?Sized + CovariantFallibleLending,
    F: FnMut() -> E,
{
    type Lend = FallibleLend<'lend, L>;
}

impl<L, E, F> FallibleLender for RepeatWithErr<L, F>
where
    L: ?Sized + CovariantFallibleLending,
    F: FnMut() -> E,
{
    type Error = E;
    // SAFETY: the lend is the type parameter L
    crate::unsafe_assume_covariance_fallible!();

    #[inline]
    fn next(&mut self) -> Result<Option<FallibleLend<'_, Self>>, Self::Error> {
        Err((self.f)())
    }

    #[inline(always)]
    fn size_hint(&self) -> (usize, Option<usize>) {
        (0, Some(0))
    }

    #[inline]
    fn advance_by(&mut self, n: usize) -> Result<Result<(), core::num::NonZeroUsize>, Self::Error> {
        if n > 0 { Err((self.f)()) } else { Ok(Ok(())) }
    }
}

impl<L, E, F> DoubleEndedFallibleLender for RepeatWithErr<L, F>
where
    L: ?Sized + CovariantFallibleLending,
    F: FnMut() -> E,
{
    #[inline(always)]
    fn next_back(&mut self) -> Result<Option<FallibleLend<'_, Self>>, Self::Error> {
        self.next()
    }

    #[inline]
    fn advance_back_by(
        &mut self,
        n: usize,
    ) -> Result<Result<(), core::num::NonZeroUsize>, Self::Error> {
        if n > 0 { Err((self.f)()) } else { Ok(Ok(())) }
    }
}

impl<L, E, F> FusedFallibleLender for RepeatWithErr<L, F>
where
    L: ?Sized + CovariantFallibleLending,
    F: FnMut() -> E,
{
}

impl<L, E, F> ExactSizeFallibleLender for RepeatWithErr<L, F>
where
    L: ?Sized + CovariantFallibleLending,
    F: FnMut() -> E,
{
}
