use crate::{combine::parser::Node, lexer::span::Spanned};
use rail_diag::Diagnostics;

// we can change this to just be a std::Result
// for that we need this new State type
#[derive(Debug)]
pub enum State<T: Node> {
    Item(Spanned<T>),
    Synced(Diagnostics),
}

// so we have three possible state
// Ok(Item)         => parsing succeeded
// Ok(Synced)       => parsing failed but we recovered
// Err(Diagnostics) => parsing failed and we didn't recover
pub type ParseResult<T> = Result<State<T>, Diagnostics>;

pub trait PResultExt<T: Node> {
    fn succeeded(item: Spanned<T>) -> ParseResult<T>;
    fn synced(diagnostics: Diagnostics) -> ParseResult<T>;
    fn desynced(diagnostics: Diagnostics) -> ParseResult<T>;

    fn has_failed(&self) -> bool;
    fn is_synced(&self) -> bool;

    fn with_diagnostics(self, diagnostics: Diagnostics) -> ParseResult<T>;

    fn combined<U: Node, O: Node, F: FnOnce(Spanned<T>, Spanned<U>) -> Spanned<O>>(
        self,
        other: ParseResult<U>,
        map: F,
    ) -> ParseResult<O>;

    fn item(&self) -> Option<&Spanned<T>>;
    fn item_mut(&mut self) -> Option<&mut Spanned<T>>;
    fn diags(&self) -> Option<&Diagnostics>;
    fn diags_mut(&mut self) -> Option<&mut Diagnostics>;

    fn map_diags<F>(self, f: F) -> ParseResult<T>
    where
        F: FnOnce(Diagnostics) -> Diagnostics;
    fn map_item<F, U: Node>(self, f: F) -> ParseResult<U>
    where
        F: FnOnce(T) -> U;
    fn map_spanned<F>(self, f: F) -> ParseResult<T>
    where
        F: FnOnce(Spanned<T>) -> Spanned<T>;

    fn inspect_diags<F>(self, f: F) -> ParseResult<T>
    where
        F: FnOnce(&Diagnostics);
    fn inspect_item<F>(self, f: F) -> ParseResult<T>
    where
        F: FnOnce(&T);
}

impl<T: Node> PResultExt<T> for ParseResult<T> {
    fn succeeded(item: Spanned<T>) -> ParseResult<T> {
        Ok(State::Item(item))
    }

    fn synced(diagnostics: Diagnostics) -> ParseResult<T> {
        Ok(State::Synced(diagnostics))
    }

    fn desynced(diagnostics: Diagnostics) -> ParseResult<T> {
        Err(diagnostics)
    }

    fn has_failed(&self) -> bool {
        self.is_err() || self.is_synced()
    }

    fn is_synced(&self) -> bool {
        matches!(self, Ok(State::Synced(_)))
    }

    fn with_diagnostics(self, diagnostics: Diagnostics) -> ParseResult<T> {
        match self {
            Ok(State::Item(item)) => Ok(State::Synced(diagnostics)),
            Ok(State::Synced(diags)) => Ok(State::Synced(diags.joined(diagnostics))),
            Err(diags) => Err(diags.joined(diagnostics)),
        }
    }

    fn combined<U: Node, O: Node, F: FnOnce(Spanned<T>, Spanned<U>) -> Spanned<O>>(
        self,
        other: ParseResult<U>,
        map: F,
    ) -> ParseResult<O> {
        match (self, other) {
            (Ok(State::Item(lhs)), Ok(State::Item(rhs))) => Ok(State::Item(map(lhs, rhs))),
            (Ok(State::Item(lhs)), Ok(State::Synced(diags))) => Ok(State::Synced(diags)),
            (Ok(State::Item(lhs)), Err(diags)) => Err(diags),
            (Ok(State::Synced(diags)), Ok(State::Item(item))) => Ok(State::Synced(diags)),
            (Ok(State::Synced(diags)), Ok(State::Synced(diags2))) => {
                Ok(State::Synced(diags.joined(diags2)))
            }
            (Ok(State::Synced(diags)), Err(diags2)) => Ok(State::Synced(diags.joined(diags2))),
            (Err(diags), Ok(State::Item(item))) => Ok(State::Synced(diags)),
            (Err(diags), Ok(State::Synced(diags2))) => Err(diags.joined(diags2)),
            (Err(diags), Err(diags2)) => Err(diags.joined(diags2)),
        }
    }

    fn diags(&self) -> Option<&Diagnostics> {
        match self {
            Ok(State::Synced(diagnostics)) => Some(diagnostics),
            _ => None,
        }
    }

    fn diags_mut(&mut self) -> Option<&mut Diagnostics> {
        match self {
            Ok(State::Synced(diagnostics)) => Some(diagnostics),
            _ => None,
        }
    }

    fn item(&self) -> Option<&Spanned<T>> {
        match self {
            Ok(State::Item(item)) => Some(item),
            _ => None,
        }
    }

    fn item_mut(&mut self) -> Option<&mut Spanned<T>> {
        match self {
            Ok(State::Item(item)) => Some(item),
            _ => None,
        }
    }

    fn map_diags<F>(self, f: F) -> ParseResult<T>
    where
        F: FnOnce(Diagnostics) -> Diagnostics,
    {
        match self {
            Ok(State::Item(item)) => Ok(State::Item(item)),
            Ok(State::Synced(diagnostics)) => Ok(State::Synced(f(diagnostics))),
            Err(diagnostics) => Err(diagnostics),
        }
    }

    fn map_item<F, U: Node>(self, f: F) -> ParseResult<U>
    where
        F: FnOnce(T) -> U,
    {
        match self {
            Ok(State::Item(item)) => Ok(State::Item(item.map(f))),
            Ok(State::Synced(diagnostics)) => Ok(State::Synced(diagnostics)),
            Err(diagnostics) => Err(diagnostics),
        }
    }

    fn map_spanned<F>(self, f: F) -> ParseResult<T>
    where
        F: FnOnce(Spanned<T>) -> Spanned<T>,
    {
        match self {
            Ok(State::Item(item)) => Ok(State::Item(f(item))),
            Ok(State::Synced(diagnostics)) => Ok(State::Synced(diagnostics)),
            Err(diagnostics) => Err(diagnostics),
        }
    }

    fn inspect_diags<F>(self, f: F) -> ParseResult<T>
    where
        F: FnOnce(&Diagnostics),
    {
        match self {
            Ok(State::Item(item)) => Ok(State::Item(item)),
            Ok(State::Synced(diagnostics)) => {
                f(&diagnostics);
                Ok(State::Synced(diagnostics))
            }
            Err(diagnostics) => {
                f(&diagnostics);
                Err(diagnostics)
            }
        }
    }
    fn inspect_item<F>(self, f: F) -> ParseResult<T>
    where
        F: FnOnce(&T),
    {
        match self {
            Ok(State::Item(item)) => {
                f(&item);
                Ok(State::Item(item))
            }
            Ok(State::Synced(diagnostics)) => Ok(State::Synced(diagnostics)),
            Err(diagnostics) => Err(diagnostics),
        }
    }
}
