use espy_macros::{espy, espy_module};
use espy_paws as espy;
use espy_paws::anyhow::Context;
use espy_paws::{Error, ExternFnOwned, Function, Type, Value};
use std::rc::Rc;

#[derive(Debug)]
pub enum LibraryError {
    UnwrapFailed,
    ExpectFailed(Box<str>),
}

impl std::fmt::Display for LibraryError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            LibraryError::UnwrapFailed => write!(f, "attempted to unwrap None value"),
            LibraryError::ExpectFailed(msg) => write!(f, "{msg}"),
        }
    }
}

impl std::error::Error for LibraryError {}

#[espy_module]
pub struct Lib {
    iter: iter::Lib,
    i64: i64::Lib,
    string: string::Lib,
    tuple: tuple::Lib,
    option: option::Lib,
    #[espy(fn)]
    r#typeof: Typeof,
}

#[espy]
pub fn Typeof(argument: Value<'_>) -> Result<Value<'_>, Error> {
    argument.type_of().map(Value::from)
}

mod iter {
    use super::*;

    #[espy_module]
    pub struct Lib {
        #[espy(fn)]
        filter: Filter,
        #[espy(fn)]
        filter_map: FilterMap,
        #[espy(fn)]
        find: Find,
        #[espy(fn)]
        find_map: FindMap,
        #[espy(fn)]
        flat_map: FlatMap,
        #[espy(fn)]
        fold: Fold,
        #[espy(fn)]
        foreach: Foreach,
        #[espy(fn)]
        list: List,
        #[espy(fn)]
        map: Map,
        #[espy(fn)]
        next: Next,
        #[espy(fn)]
        range: Range,
        #[espy(fn)]
        reduce: Reduce,
        #[espy(fn)]
        reduce_once: ReduceOnce,
        #[espy(fn)]
        repeat: Repeat,
        #[espy(fn)]
        skip: Skip,
        #[espy(fn)]
        take: Take,
        #[espy(fn)]
        count: Count,
    }

    #[espy]
    pub fn Foreach<'host>(
        mut iterator: Value<'host>,
        next: Function<'host>,
        foreach: Function<'host>,
    ) -> Result<(), Error> {
        while let Some(result) = next.clone().pushed(iterator).eval()?.option()? {
            iterator = result.try_get(0)?;
            foreach.clone().piped(result.try_get(1)?).eval()?;
        }
        Ok(())
    }

    #[espy]
    pub fn Fold<'host>(
        mut iterator: Value<'host>,
        next: Function<'host>,
        mut accumulator: Value<'host>,
        fold: Function<'host>,
    ) -> Result<Value<'host>, Error> {
        while let Some(result) = next.clone().pushed(iterator).eval()?.option()? {
            iterator = result.try_get(0)?;
            accumulator = fold
                .clone()
                .pushed(accumulator)
                .pushed(result.try_get(1)?)
                .eval()?;
        }
        Ok(accumulator)
    }

    #[espy]
    pub fn Next<'host>(
        iterator: Value<'host>,
        next: Function<'host>,
    ) -> Result<Value<'host>, Error> {
        next.clone()
            .pushed(iterator)
            .eval()?
            .option()?
            .map(|result| {
                let iterator = result.try_get(0)?;
                let result = result.try_get(1)?;
                Ok(Value::from([result, [iterator, next.into()].into()]))
            })
            .transpose()
            .map(Value::from)
    }

    #[espy]
    pub fn Reduce<'host>(
        mut iterator: Value<'host>,
        next: Function<'host>,
        fold: Function<'host>,
    ) -> Result<Value<'host>, Error> {
        let mut accumulator: Option<Value<'host>> = None;
        while let Some(result) = next.clone().pushed(iterator).eval()?.option()? {
            iterator = result.try_get(0)?;
            accumulator = Some(if let Some(accumulator) = accumulator.take() {
                fold.clone()
                    .pushed(accumulator)
                    .pushed(result.try_get(1)?)
                    .eval()?
            } else {
                result.try_get(1)?
            });
        }
        Ok(accumulator.into())
    }

    #[espy]
    pub fn ReduceOnce<'host>(
        iterator: Value<'host>,
        next: Function<'host>,
    ) -> Result<Value<'host>, Error> {
        Ok(next
            .pushed(iterator)
            .eval()?
            .option()?
            .map(|x| x.try_get(1))
            .transpose()?
            .into())
    }

    #[espy]
    pub fn Range<'host>(current: Value<'host>, limit: i64) -> Result<Value<'host>, Error> {
        Ok([
            current,
            Function::owned(Rc::new(RangeIter { limit })).into(),
        ]
        .into())
    }

    #[derive(Debug, Default)]
    pub struct RangeIter {
        limit: i64,
    }

    impl ExternFnOwned for RangeIter {
        fn call<'host>(self: Rc<Self>, argument: Value<'host>) -> Result<Value<'host>, Error> {
            let current = argument.i64()?;
            Ok(Value::from(if current < self.limit {
                Some(Value::from([Value::from(current + 1), current.into()]))
            } else {
                None
            }))
        }

        fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            std::write!(f, "std.iter.range iterator")
        }
    }

    #[espy]
    pub fn Repeat<'host>(argument: Value<'host>) -> [Value<'host>; 2] {
        [argument, Function::borrow(&RepeatIter).into()]
    }

    #[espy]
    pub fn RepeatIter<'host>(argument: Value<'host>) -> Option<Value<'host>> {
        Some(Value::from([argument.clone(), argument]))
    }

    #[espy]
    pub fn Skip<'host>(
        mut iterator: Value<'host>,
        next: Function<'host>,
        limit: i64,
    ) -> Result<[Value<'host>; 2], Error> {
        for _ in 0..limit {
            if let Some(result) = next.clone().pushed(iterator.clone()).eval()?.option()? {
                iterator = result.try_get(0)?;
            } else {
                break;
            }
        }

        Ok([iterator, next.into()])
    }

    #[espy]
    pub fn Count<'host>(mut iterator: Value<'host>, next: Function<'host>) -> Result<i64, Error> {
        let mut count = 0;
        while let Some(result) = next.clone().pushed(iterator).eval()?.option()? {
            iterator = result.try_get(0)?;
            count += 1;
        }
        Ok(count)
    }

    #[espy]
    pub fn Take<'host>(
        iterator: Value<'host>,
        next: Value<'host>,
        count: Value<'host>,
    ) -> [Value<'host>; 2] {
        [
            Value::from([iterator, count]),
            Function::borrow(&TakeIter).pushed(next).into(),
        ]
    }

    #[espy]
    pub fn TakeIter<'host>(
        next: Function<'host>,
        iterator: Value<'host>,
        count: i64,
    ) -> Result<Option<Value<'host>>, Error> {
        Ok(
            if count > 0
                && let Some(result) = next.pushed(iterator).eval()?.option()?
            {
                let iterator = result.try_get(0)?;
                let item = result.try_get(1)?;
                Some(Value::from([
                    Value::from([iterator, Value::from(count - 1)]),
                    item,
                ]))
            } else {
                None
            },
        )
    }

    #[espy]
    pub fn Map<'host>(
        iterator: Value<'host>,
        next: Value<'host>,
        map: Value<'host>,
    ) -> [Value<'host>; 2] {
        [
            iterator,
            Function::borrow(&MapIter).pushed(next).pushed(map).into(),
        ]
    }

    #[espy]
    pub fn MapIter<'host>(
        next: Function<'host>,
        map: Function<'host>,
        iterator: Value<'host>,
    ) -> Result<Option<Value<'host>>, Error> {
        next.pushed(iterator)
            .eval()?
            .option()?
            .map(|x| {
                let iterator = x.try_get(0)?;
                let item = x.try_get(1)?;
                Ok::<_, Error>(Value::from([iterator, map.clone().piped(item).eval()?]))
            })
            .transpose()
    }

    #[espy]
    pub fn Filter<'host>(
        iterator: Value<'host>,
        next: Function<'host>,
        filter: Function<'host>,
    ) -> [Value<'host>; 2] {
        [
            iterator,
            Function::borrow(&FilterIter)
                .pushed(next.into())
                .pushed(filter.into())
                .into(),
        ]
    }

    #[espy]
    pub fn FilterIter<'host>(
        next: Function<'host>,
        filter: Function<'host>,
        mut iterator: Value<'host>,
    ) -> Result<Option<Value<'host>>, Error> {
        while let Some(result) = next.clone().pushed(iterator).eval()?.option()? {
            iterator = result.try_get(0)?;
            let item = result.try_get(1)?;
            if filter.clone().piped(item.clone()).eval()?.bool()? {
                return Ok(Some([iterator, item].into()));
            }
        }
        Ok(None)
    }

    #[espy]
    pub fn FilterMap<'host>(
        iterator: Value<'host>,
        next: Value<'host>,
        filter: Value<'host>,
    ) -> [Value<'host>; 2] {
        [
            iterator,
            Function::borrow(&FilterMapIter)
                .pushed(next)
                .pushed(filter)
                .into(),
        ]
    }

    #[espy]
    pub fn FilterMapIter<'host>(
        next: Function<'host>,
        filter: Function<'host>,
        mut iterator: Value<'host>,
    ) -> Result<Option<Value<'host>>, Error> {
        while let Some(result) = next.clone().pushed(iterator).eval()?.option()? {
            iterator = result.try_get(0)?;
            let item = result.try_get(1)?;
            if let Some(item) = filter.clone().piped(item.clone()).eval()?.option()? {
                return Ok(Some([iterator, item].into()));
            }
        }
        Ok(None)
    }

    #[espy]
    pub fn FlatMap<'host>(
        iterator: Value<'host>,
        next: Value<'host>,
        map: Value<'host>,
    ) -> [Value<'host>; 2] {
        [
            Value::from([iterator]),
            Function::borrow(&FlatMapIter)
                .pushed(next)
                .pushed(map)
                .into(),
        ]
    }

    #[espy]
    pub fn FlatMapIter<'host>(
        next: Function<'host>,
        map: Function<'host>,
        argument: Value<'host>,
    ) -> Result<Option<Value<'host>>, Error> {
        let mut iterator = argument.try_get(0)?;
        let mut sub_iterator = argument.get(1).zip(argument.get(2));
        loop {
            if let Some((sub_iterator, sub_next)) = sub_iterator
                && let Some(result) = sub_next
                    .clone()
                    .function()?
                    .pushed(sub_iterator)
                    .eval()?
                    .option()?
            {
                let sub_iterator = result.try_get(0)?;
                let item = result.try_get(1)?;
                return Ok(Some(
                    [[iterator, sub_iterator, sub_next].into(), item].into(),
                ));
            }
            let Some(result) = next.clone().pushed(iterator).eval()?.option()? else {
                break;
            };
            iterator = result.try_get(0)?;
            let result = map.clone().piped(result.try_get(1)?).eval()?;
            let iterator = result.try_get(0)?;
            let next = result.try_get(1)?;
            sub_iterator = Some((iterator, next));
        }
        Ok(None)
    }

    #[espy]
    pub fn List<'host>(
        iterator_pair: Value<'host>,
        map: Value<'host>,
    ) -> Result<[Value<'host>; 2], Error> {
        let iterator = iterator_pair.try_get(0)?;
        let next = iterator_pair.try_get(1)?;

        Ok([
            Value::from([iterator]),
            Function::borrow(&FlatMapIter)
                .pushed(next)
                .pushed(map)
                .into(),
        ])
    }

    #[espy]
    pub fn Find<'host>(
        mut iterator: Value<'host>,
        next: Function<'host>,
        filter: Function<'host>,
    ) -> Result<Option<Value<'host>>, Error> {
        while let Some(result) = next.clone().pushed(iterator).eval()?.option()? {
            iterator = result.try_get(0)?;
            let item = result.try_get(1)?;
            if filter.clone().piped(item.clone()).eval()?.bool()? {
                return Ok(Some(item));
            }
        }

        Ok(None)
    }

    #[espy]
    pub fn FindMap<'host>(
        mut iterator: Value<'host>,
        next: Function<'host>,
        filter: Function<'host>,
    ) -> Result<Option<Value<'host>>, Error> {
        while let Some(result) = next.clone().pushed(iterator).eval()?.option()? {
            iterator = result.try_get(0)?;
            let item = result.try_get(1)?;
            if let Some(item) = filter.clone().piped(item.clone()).eval()?.option()? {
                return Ok(Some(item));
            }
        }

        Ok(None)
    }
}

mod i64 {
    use super::*;

    #[espy_module]
    pub struct Lib {
        #[espy(fn)]
        abs: Abs,
        #[espy(fn)]
        min: Min,
        #[espy(fn)]
        max: Max,
        #[espy(fn)]
        remainder: Remainder,
    }

    #[espy]
    pub fn Abs(x: i64) -> Result<i64, Error> {
        x.checked_abs().ok_or(Error::custom(
            "i64 cannot represent the absolute value of i64::MIN",
        ))
    }

    #[espy]
    pub fn Min(a: i64, b: i64) -> i64 {
        a.min(b)
    }

    #[espy]
    pub fn Max(a: i64, b: i64) -> i64 {
        a.max(b)
    }

    #[espy]
    pub fn Remainder(a: i64, b: i64) -> Result<i64, Error> {
        Ok(a.checked_rem(b).ok_or_else(|| {
            espy_paws::anyhow::anyhow!("cannot calculate the remainder of {a} / {b}")
        })?)
    }
}

mod string {
    use super::*;

    #[espy_module]
    pub struct Lib {
        #[espy(fn)]
        concat: Concat,
        #[espy(fn)]
        from_i64: FromI64,
        #[espy(fn)]
        parse_i64: ParseI64,
        #[espy(fn)]
        split: Split,
        #[espy(fn)]
        split_whitespace: SplitWhitespace,
        #[espy(fn)]
        trim_whitespace: TrimWhitespace,
    }

    #[espy]
    pub fn Concat(argument: Value<'_>) -> Result<Rc<str>, Error> {
        argument
            .tuple()?
            .values()
            .map(|s| {
                s.as_str()
                    .ok_or_else(|| Error::type_error(s, &Type::String.into()))
            })
            .collect::<Result<String, _>>()
            .map(|s| s.into())
    }

    #[espy]
    pub fn FromI64(x: i64) -> Rc<str> {
        x.to_string().into()
    }

    #[espy]
    pub fn ParseI64(x: espy_paws::String) -> Option<i64> {
        x.parse::<i64>().ok()
    }

    #[espy]
    pub fn Split<'host>(
        string: espy_paws::String,
        pattern: espy_paws::String,
    ) -> [Value<'host>; 2] {
        [
            Value::from(0),
            Function::owned(Rc::new(SplitIter { pattern, string })).into(),
        ]
    }

    #[derive(Debug, Default)]
    pub struct SplitIter {
        pattern: espy_paws::String,
        string: espy_paws::String,
    }

    impl ExternFnOwned for SplitIter {
        fn call<'host>(self: Rc<Self>, argument: Value<'host>) -> Result<Value<'host>, Error> {
            let cursor = argument.i64()? as usize;
            Ok(Value::from(self.string.get(cursor..).map(|remaining| {
                let (result, cursor) = remaining
                    .split_once(&*self.pattern)
                    .map_or((remaining, self.string.len() + 1), |(result, _)| {
                        (result, cursor + result.len() + self.pattern.len())
                    });
                Value::from([
                    (cursor as i64).into(),
                    Value::String(Rc::<str>::from(result).into()),
                ])
            })))
        }

        fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            std::write!(f, "std.string.split iterator")
        }
    }

    #[espy]
    pub fn SplitWhitespace<'host>(string: espy_paws::String) -> [Value<'host>; 2] {
        [
            Value::from(0),
            Function::owned(Rc::new(SplitWhitespaceIter { string })).into(),
        ]
    }

    #[derive(Debug, Default)]
    pub struct SplitWhitespaceIter {
        string: espy_paws::String,
    }

    impl ExternFnOwned for SplitWhitespaceIter {
        fn call<'host>(self: Rc<Self>, argument: Value<'host>) -> Result<Value<'host>, Error> {
            let cursor = argument.i64()? as usize;
            Ok(Value::from(self.string.get(cursor..).map(|remaining| {
                let (result, cursor) = remaining.split_whitespace().next().map_or(
                    (remaining, self.string.len()),
                    |result| {
                        (
                            result,
                            cursor
                                + result.len()
                                + (result.as_ptr() as usize - remaining.as_ptr() as usize),
                        )
                    },
                );

                Value::from([
                    Value::from(if cursor == self.string.len() {
                        i64::MAX
                    } else {
                        cursor as i64
                    }),
                    espy_paws::String::owned(result).into(),
                ])
            })))
        }

        fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            std::write!(f, "std.string.split_whitespace iterator")
        }
    }

    #[espy]
    pub fn TrimWhitespace(string: espy_paws::String) -> espy_paws::String {
        espy_paws::String::owned(string.trim())
    }
}

mod tuple {
    use super::*;

    #[espy_module]
    pub struct Lib {
        #[espy(fn)]
        iter: Iter,
        #[espy(fn)]
        iter_pairs: IterPairs,
        #[espy(fn)]
        collect: Collect,
        #[espy(fn)]
        collect_pairs: CollectPairs,
        #[espy(fn)]
        named: Named,
        #[espy(fn)]
        empty: Empty,
        #[espy(fn)]
        empty_named: EmptyNamed,
        #[espy(fn)]
        count: Count,
    }

    #[espy]
    pub fn Iter<'host>(argument: espy_paws::Tuple<Value<'host>>) -> [Value<'host>; 2] {
        [
            Value::I64(0),
            Function::borrow(&IterIter).pushed(argument.into()).into(),
        ]
    }

    #[espy]
    pub fn IterIter<'host>(
        tuple: espy_paws::Tuple<Value<'host>>,
        index: i64,
    ) -> Option<Value<'host>> {
        tuple
            .get(index as usize)
            .map(|value| Value::from([index.saturating_add(1).into(), value]))
    }

    #[espy]
    pub fn IterPairs<'host>(
        argument: espy_paws::Tuple<Value<'host>>,
    ) -> Result<[Value<'host>; 2], Error> {
        argument.as_named().context("recieved unnamed tuple")?;
        Ok([
            Value::I64(0),
            Function::borrow(&IterPairsIter)
                .pushed(argument.into())
                .into(),
        ])
    }

    #[espy]
    pub fn IterPairsIter<'host>(
        tuple: espy_paws::Tuple<Value<'host>>,
        index: i64,
    ) -> Result<Option<Value<'host>>, Error> {
        let Some(tuple) = tuple.as_named() else {
            Err(Error::custom(
                "std.tuple.iter_pairs's iterator function must recieve a named tuple as its first argument, but it recieved a named tuple instead",
            ))?
        };
        let result = tuple
            .get(index as usize)
            .cloned()
            .map(|(name, value)| Value::from([name.into(), value]));
        Ok(result.map(|value| Value::from([index.saturating_add(1).into(), value])))
    }

    #[espy]
    pub fn Collect<'host>(
        mut iterator: Value<'host>,
        next: Function<'host>,
    ) -> Result<Rc<[Value<'host>]>, Error> {
        // A vector can be resized more intelligently than concatenating tuples.
        let mut buffer = Vec::new();
        loop {
            let f = next.clone().pushed(iterator);
            let Some(result) = f.eval()?.option()? else {
                break;
            };
            iterator = result.try_get(0)?;
            buffer.push(result.try_get(1)?);
        }
        Ok(Rc::<[Value]>::from(&buffer[..]))
    }

    #[espy]
    pub fn CollectPairs<'host>(
        mut iterator: Value<'host>,
        next: Function<'host>,
    ) -> Result<Rc<[(espy_paws::String, Value<'host>)]>, Error> {
        // A vector can be resized more intelligently than concatenating tuples.
        let mut buffer = Vec::new();
        while let Some(result) = next.clone().pushed(iterator).eval()?.option()? {
            iterator = result.try_get(0)?;
            let pair = result.try_get(1)?;
            buffer.push((pair.try_get(0)?.string()?, pair.try_get(1)?));
        }
        Ok(Rc::<[(espy_paws::String, Value)]>::from(&buffer[..]))
    }

    #[espy]
    pub fn Named<'host>(
        name: espy_paws::String,
        value: Value<'host>,
    ) -> Rc<[(espy_paws::String, Value<'host>)]> {
        [(name, value)].into()
    }

    #[espy]
    pub fn Empty<'host>(_: ()) -> espy_paws::Tuple<Value<'host>> {
        let empty: [Value; 0] = [];
        espy_paws::Tuple::from(empty)
    }

    #[espy]
    pub fn EmptyNamed<'host>(_: ()) -> espy_paws::Tuple<Value<'host>> {
        let empty: [(Rc<str>, Value); 0] = [];
        espy_paws::Tuple::from(empty)
    }

    #[espy]
    pub fn Count<'host>(argument: espy_paws::Tuple<Value<'host>>) -> i64 {
        argument.len() as i64
    }
}

mod option {
    use super::*;

    #[espy_module]
    pub struct Lib {
        #[espy(fn)]
        expect: Expect,
        #[espy(fn)]
        unwrap: Unwrap,
        #[espy(fn)]
        unwrap_or: UnwrapOr,
        #[espy(fn)]
        maybe: Maybe,
    }

    #[espy]
    pub fn Expect<'host>(
        argument: Option<Value<'host>>,
        message: espy_paws::String,
    ) -> Result<Value<'host>, Error> {
        argument
            .ok_or_else(|| LibraryError::ExpectFailed(Box::from(&*message)))
            .map_err(Error::other)
    }

    #[espy]
    pub fn Unwrap<'host>(argument: Option<Value<'host>>) -> Result<Value<'host>, Error> {
        argument
            .ok_or(LibraryError::UnwrapFailed)
            .map_err(Error::other)
    }

    #[espy]
    pub fn UnwrapOr<'host>(argument: Option<Value<'host>>, or: Value<'host>) -> Value<'host> {
        argument.unwrap_or(or)
    }

    #[espy]
    pub fn Iter<'host>(argument: Value<'host>) -> [Value<'host>; 2] {
        [argument, Function::borrow(&IterIter).into()]
    }

    #[espy]
    pub fn IterIter<'host>(argument: Option<Value<'host>>) -> Option<Value<'host>> {
        argument.map(|x| Value::from([None::<()>.into(), x]))
    }

    #[espy]
    pub fn Maybe<'host>(
        a: Option<Value<'host>>,
        f: Function<'host>,
    ) -> Result<Value<'host>, Error> {
        if let Some(a) = a {
            f.piped(a).eval()
        } else {
            Ok(None::<()>.into())
        }
    }
}
