use libterm::{Color, TermKey};
use lysp::{
    error::{MachineError, ValueConversionError},
    vm::{
        Value,
        value::{IdentifierValue, convert::TryFromValue},
    },
};

use crate::{
    TopMode,
    buffer::{Mode, SetMode},
    error::Error,
    keymap::KeySeq,
};

pub enum Movement {
    LineStart,
    LineEnd,
    NextLine,
    PrevLine,
    NextPage,
    PrevPage,
    FirstLine,
    LastLine,
    NextChar,
    PrevChar,
}

impl FromValue for Movement {
    fn from_value(value: &Value) -> Result<Self, Error> {
        let value = IdentifierValue::try_from_value(value)?;
        match value.as_ref() {
            "line-start" => Ok(Self::LineStart),
            "line-end" => Ok(Self::LineEnd),
            "next-line" => Ok(Self::NextLine),
            "prev-line" => Ok(Self::PrevLine),
            "next-page" => Ok(Self::NextPage),
            "prev-page" => Ok(Self::PrevPage),
            "first-line" => Ok(Self::FirstLine),
            "last-line" => Ok(Self::LastLine),
            "next-char" => Ok(Self::NextChar),
            "prev-char" => Ok(Self::PrevChar),
            _ => Err(Error::NoPath),
        }
    }
}

pub trait AsValue {
    fn as_value(&self) -> impl Into<Value>;
}

pub trait FromValue: Sized {
    fn from_value(value: &Value) -> Result<Self, Error>;
}

impl AsValue for TopMode {
    fn as_value(&self) -> impl Into<Value> {
        IdentifierValue::from(match self {
            Self::Normal => "normal",
            Self::Command => "command",
        })
    }
}

impl AsValue for Mode {
    fn as_value(&self) -> impl Into<Value> {
        IdentifierValue::from(match self {
            Self::Normal => "normal",
            Self::Insert => "insert",
        })
    }
}

impl FromValue for SetMode {
    fn from_value(value: &Value) -> Result<Self, Error> {
        let value = IdentifierValue::try_from_value(value)?;
        match value.as_ref() {
            "normal" => Ok(Self::Normal),
            "insert" | "insert-before" => Ok(Self::InsertBefore),
            "insert-after" => Ok(Self::InsertAfter),
            _ => todo!(),
        }
    }
}

impl AsValue for TermKey {
    fn as_value(&self) -> impl Into<Value> {
        IdentifierValue::from(match self {
            TermKey::Insert => "insert".to_owned(),
            TermKey::Up => "up".to_owned(),
            TermKey::Down => "down".to_owned(),
            TermKey::Left => "left".to_owned(),
            TermKey::Right => "right".to_owned(),
            TermKey::Delete => "delete".to_owned(),
            TermKey::Char(char) => format!("{char}"),
            _ => "".to_owned(),
        })
    }
}

impl AsValue for Color {
    fn as_value(&self) -> impl Into<Value> {
        IdentifierValue::from(match self {
            Self::Green => "green",
            Self::Red => "red",
            Self::Cyan => "cyan",
            Self::Blue => "blue",
            Self::Default => "default",
            Self::Black => "black",
            Self::White => "white",
            Self::Yellow => "yellow",
            Self::Magenta => "magenta",
        })
    }
}
impl FromValue for Color {
    fn from_value(value: &Value) -> Result<Self, Error> {
        let name = IdentifierValue::try_from_value(value)?;
        match name.as_ref() {
            "red" => Ok(Self::Red),
            "green" => Ok(Self::Green),
            "blue" => Ok(Self::Blue),
            "yellow" => Ok(Self::Yellow),
            "magenta" => Ok(Self::Magenta),
            "cyan" => Ok(Self::Cyan),
            "black" => Ok(Self::Black),
            "white" => Ok(Self::White),
            "default" => Ok(Self::Default),
            _ => Err(MachineError::ValueConversion(ValueConversionError {
                expected: "color symbol".into(),
                got: value.clone(),
            })
            .into()),
        }
    }
}

impl FromValue for TermKey {
    fn from_value(value: &Value) -> Result<Self, Error> {
        let identifier = IdentifierValue::try_from_value(value)?;
        let identifier = identifier.as_ref();
        if identifier.len() == 1
            && let Some(char) = identifier.chars().next()
        {
            match char {
                ch if ch.is_alphanumeric() => return Ok(Self::Char(ch)),
                _ => (),
            }
        }

        match identifier {
            "left" => Ok(Self::Left),
            "right" => Ok(Self::Right),
            "up" => Ok(Self::Up),
            "down" => Ok(Self::Down),
            "end" => Ok(Self::End),
            "home" => Ok(Self::Home),
            "newline" => Ok(Self::Char('\r')),
            "backspace" => Ok(Self::Char('\x7F')),
            _ => Err(Error::NoPath),
        }
    }
}

impl FromValue for KeySeq {
    fn from_value(mut value: &Value) -> Result<Self, Error> {
        match value {
            Value::Cons(_) => {
                let mut seq = KeySeq::empty();
                while !value.is_nil() {
                    let Value::Cons(cons) = value else {
                        break;
                    };
                    let key = TermKey::from_value(&cons.0)?;
                    seq.push(key);
                    value = &cons.1;
                }
                Ok(seq)
            }
            Value::Identifier(_) => {
                let key = TermKey::from_value(value)?;
                Ok(KeySeq::one(key))
            }
            _ => todo!(),
        }
    }
}

#[cfg(test)]
mod tests {
    use libterm::TermKey;
    use lysp::vm::Value;

    use crate::{keymap::KeySeq, script::FromValue};

    #[test]
    fn test_from_value_for_keyseq() {
        let v = Value::list_or_nil([Value::Identifier("a".into()), Value::Identifier("a".into())]);
        let v = KeySeq::from_value(&v).unwrap();
        let mut s = KeySeq::empty();
        s.push(TermKey::Char('a'));
        s.push(TermKey::Char('a'));
        assert_eq!(v, s);
    }
}
