use std::{collections::HashMap, fmt};

use serde::{
    Deserialize, Deserializer, Serialize,
    de::{Unexpected, Visitor},
};

#[derive(Clone, Hash, Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum BuiltinColor {
    Black,
    Red,
    Green,
    Yellow,
    Blue,
    Magenta,
    Cyan,
    White,
    BrightBlack,
    BrightRed,
    BrightGreen,
    BrightYellow,
    BrightBlue,
    BrightMagenta,
    BrightCyan,
    BrightWhite,
    Default,
}

#[derive(Clone, Hash, Debug, PartialEq, Eq)]
pub struct RgbaColor([u8; 4]);

struct RgbaVisitor;
impl<'de> Visitor<'de> for RgbaVisitor {
    type Value = RgbaColor;
    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("A color in the format rgb:RRGGBB or rgba:RRGGBBAA")
    }
    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        (|| {
            if let Some(v) = v.strip_prefix("rgb:") {
                let r = u8::from_str_radix(v.get(0..2)?, 16).ok()?;
                let g = u8::from_str_radix(v.get(2..4)?, 16).ok()?;
                let b = u8::from_str_radix(v.get(4..6)?, 16).ok()?;
                Some(RgbaColor([r, g, b, 255]))
            } else if let Some(v) = v.strip_prefix("rgba:") {
                let r = u8::from_str_radix(v.get(0..2)?, 16).ok()?;
                let g = u8::from_str_radix(v.get(2..4)?, 16).ok()?;
                let b = u8::from_str_radix(v.get(4..6)?, 16).ok()?;
                let a = u8::from_str_radix(v.get(6..8)?, 16).ok()?;
                Some(RgbaColor([r, g, b, a]))
            } else {
                None
            }
        })()
        .ok_or(E::invalid_value(
            Unexpected::Other("Invalid color format"),
            &"",
        ))
    }
}

impl<'de> Deserialize<'de> for RgbaColor {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_string(RgbaVisitor)
    }
}

#[derive(Clone, Hash, Debug, PartialEq, Eq, Deserialize)]
#[serde(untagged)]
pub enum Color {
    BuiltinColor(BuiltinColor),
    Rgb(RgbaColor),
}

impl Color {
    pub fn to_rgba(&self, default: [u8; 4]) -> [u8; 4] {
        match self {
            Color::BuiltinColor(color) => match color {
                BuiltinColor::Black => [0, 0, 0, 255],
                BuiltinColor::Red => [255, 0, 0, 255],
                BuiltinColor::Green => [0, 255, 0, 255],
                BuiltinColor::Yellow => [255, 255, 0, 255],
                BuiltinColor::Blue => [0, 0, 255, 0],
                BuiltinColor::Magenta => [255, 0, 255, 255],
                BuiltinColor::Cyan => [0, 255, 255, 255],
                BuiltinColor::White => [255, 255, 255, 255],
                BuiltinColor::BrightBlack => [0, 0, 0, 255],
                BuiltinColor::BrightRed => [255, 0, 0, 255],
                BuiltinColor::BrightGreen => [0, 255, 0, 255],
                BuiltinColor::BrightYellow => [255, 255, 0, 255],
                BuiltinColor::BrightBlue => [0, 0, 255, 0],
                BuiltinColor::BrightMagenta => [255, 0, 255, 255],
                BuiltinColor::BrightCyan => [0, 255, 255, 255],
                BuiltinColor::BrightWhite => [255, 255, 255, 255],
                BuiltinColor::Default => default,
            },
            Color::Rgb(RgbaColor(c)) => *c,
        }
    }
}

#[derive(Clone, Hash, Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Attribute {
    Underline,
    Reverse,
    Blink,
    Bold,
    Dim,
    Italic,
    FinalFg,
    FinalBg,
    FinalAttr,
}

#[derive(Clone, Hash, Debug, PartialEq, Eq, Deserialize)]
pub struct Face {
    pub fg: Color,
    pub bg: Color,
    pub attributes: Vec<Attribute>,
}

impl Default for Face {
    fn default() -> Self {
        Self {
            fg: Color::BuiltinColor(BuiltinColor::White),
            bg: Color::BuiltinColor(BuiltinColor::Black),
            attributes: Vec::new(),
        }
    }
}

#[derive(Debug, Hash, PartialEq, Eq, Deserialize)]
pub struct Atom {
    pub face: Face,
    pub contents: String,
}

pub type Line = Vec<Atom>;

#[derive(Debug, PartialEq, Eq, Deserialize)]
pub struct Coord {
    line: i32,
    column: i32,
}

#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum MenuShowStyle {
    Prompt,
    Search,
    Inline,
}

#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum InfoShowStyle {
    Prompt,
    Inline,
    InlineAbove,
    InlineBelow,
    MenuDoc,
    Modal,
}

#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SetCursorMode {
    Prompt,
    Buffer,
}

#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case", tag = "method", content = "params")]
/// JSONRPC call *from* Kakoune
pub enum Request {
    Draw(Vec<Line>, Face, Face),
    DrawStatus(Line, Line, Face),
    MenuShow(Vec<Line>, Coord, Face, Face, MenuShowStyle),
    MenuSelect([i32; 1]),
    MenuHide([(); 0]),
    InfoShow(Line, Vec<Line>, Coord, Face, InfoShowStyle),
    InfoHide([(); 0]),
    SetCursor(SetCursorMode, Coord),
    SetUiOptions([HashMap<String, String>; 1]),
    Refresh([bool; 1]),
}

#[derive(Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MouseEvent {
    Move,
    Press,
    Release,
    WheelUp,
    WheelDown,
}

#[derive(Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case", tag = "method", content = "params")]
/// JSONRPC call *to* Kakoune
/// NOTE: contrary to the name, this is a JSONRPC request(method call)
pub enum Response {
    Keys(Vec<String>),
    Resize(u32, u32),
    Mouse(MouseEvent, i32, i32),
    MenuSelect([i32; 0]),
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Rpc<T> {
    pub jsonrpc: String,
    #[serde(flatten)]
    pub inner: T,
}
impl<T> From<T> for Rpc<T> {
    fn from(inner: T) -> Self {
        Rpc {
            jsonrpc: String::from("2.0"),
            inner,
        }
    }
}