use std::ops::Deref;

use emmylua_parser::{LuaIndexKey, LuaSyntaxId, LuaSyntaxKind, NumberResult};
use rowan::{TextRange, TextSize};
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;

use super::lua_member_feature::LuaMemberFeature;
use crate::{
    DbIndex, FileId, GlobalId, InferFailReason, LuaInferCache, LuaType,
    semantic::try_infer_expr_for_index,
};

#[derive(Debug)]
pub struct LuaMember {
    member_id: LuaMemberId,
    key: LuaMemberKey,
    feature: LuaMemberFeature,
    global_id: Option<GlobalId>,
}

impl LuaMember {
    pub fn new(
        member_id: LuaMemberId,
        key: LuaMemberKey,
        decl_feature: LuaMemberFeature,
        global_path: Option<GlobalId>,
    ) -> Self {
        Self {
            member_id,
            key,
            feature: decl_feature,
            global_id: global_path,
        }
    }

    pub fn get_key(&self) -> &LuaMemberKey {
        &self.key
    }

    pub fn get_file_id(&self) -> FileId {
        self.member_id.file_id
    }

    pub fn get_range(&self) -> TextRange {
        self.member_id.get_syntax_id().get_range()
    }

    pub fn get_sort_key(&self) -> u64 {
        let file_id = self.member_id.file_id.id;
        let pos = u32::from(self.member_id.id.get_range().start());
        (file_id as u64) << 32 | pos as u64
    }

    pub fn get_syntax_id(&self) -> LuaSyntaxId {
        *self.member_id.get_syntax_id()
    }

    pub fn get_id(&self) -> LuaMemberId {
        self.member_id
    }

    pub fn is_field(&self) -> bool {
        LuaSyntaxKind::DocTagField == self.member_id.get_syntax_id().get_kind()
    }

    pub fn get_feature(&self) -> LuaMemberFeature {
        self.feature
    }

    pub fn get_global_id(&self) -> Option<&GlobalId> {
        self.global_id.as_ref()
    }
}

#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, Serialize, Deserialize)]
pub struct LuaMemberId {
    pub file_id: FileId,
    id: LuaSyntaxId,
}

impl LuaMemberId {
    pub fn new(id: LuaSyntaxId, file_id: FileId) -> Self {
        Self { id, file_id }
    }

    pub fn get_syntax_id(&self) -> &LuaSyntaxId {
        &self.id
    }

    pub fn get_position(&self) -> TextSize {
        self.id.get_range().start()
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum LuaMemberKey {
    None,
    Integer(i64),
    Name(SmolStr),
    TypeKey(LuaType),
}

impl LuaMemberKey {
    pub fn from_index_key(
        db: &DbIndex,
        cache: &mut LuaInferCache,
        key: &LuaIndexKey,
    ) -> Result<Self, InferFailReason> {
        match key {
            LuaIndexKey::Name(name) => Ok(LuaMemberKey::Name(name.get_name_text().into())),
            LuaIndexKey::String(str) => Ok(LuaMemberKey::Name(str.get_value().into())),
            LuaIndexKey::Integer(i) => {
                if let NumberResult::Int(idx) = i.get_number_value() {
                    Ok(LuaMemberKey::Integer(idx))
                } else {
                    Err(InferFailReason::FieldNotFound)
                }
            }
            LuaIndexKey::Idx(idx) => Ok(LuaMemberKey::Integer(*idx as i64)),
            LuaIndexKey::Expr(expr) => {
                let Some(typ) = try_infer_expr_for_index(db, cache, expr.clone())? else {
                    return Err(InferFailReason::None);
                };
                match typ {
                    LuaType::StringConst(s) => Ok(LuaMemberKey::Name(s.deref().clone())),
                    LuaType::DocStringConst(s) => Ok(LuaMemberKey::Name(s.deref().clone())),
                    LuaType::IntegerConst(i) => Ok(LuaMemberKey::Integer(i)),
                    LuaType::DocIntegerConst(i) => Ok(LuaMemberKey::Integer(i)),
                    _ => Ok(LuaMemberKey::TypeKey(typ)),
                }
            }
        }
    }

    pub fn is_none(&self) -> bool {
        matches!(self, LuaMemberKey::None)
    }

    pub fn is_name(&self) -> bool {
        matches!(self, LuaMemberKey::Name(_))
    }

    pub fn is_integer(&self) -> bool {
        matches!(self, LuaMemberKey::Integer(_))
    }

    pub fn is_expr(&self) -> bool {
        matches!(self, LuaMemberKey::TypeKey(_))
    }

    pub fn get_name(&self) -> Option<&str> {
        match self {
            LuaMemberKey::Name(name) => Some(name.as_ref()),
            _ => None,
        }
    }

    pub fn get_integer(&self) -> Option<i64> {
        match self {
            LuaMemberKey::Integer(i) => Some(*i),
            _ => None,
        }
    }

    pub fn to_index_type(&self) -> Option<LuaType> {
        match self {
            LuaMemberKey::Integer(i) => Some(LuaType::IntegerConst(*i)),
            LuaMemberKey::Name(name) => Some(LuaType::StringConst(name.clone().into())),
            LuaMemberKey::TypeKey(typ) => Some(typ.clone()),
            LuaMemberKey::None => None,
        }
    }

    pub fn to_path(&self) -> String {
        match self {
            LuaMemberKey::Name(name) => name.to_string(),
            LuaMemberKey::Integer(i) => {
                format!("[{}]", i)
            }
            LuaMemberKey::None => "".to_string(),
            LuaMemberKey::TypeKey(_) => "".to_string(),
        }
    }
}

impl PartialOrd for LuaMemberKey {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for LuaMemberKey {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        use LuaMemberKey::*;
        match (self, other) {
            (None, None) => std::cmp::Ordering::Equal,
            (None, _) => std::cmp::Ordering::Less,
            (_, None) => std::cmp::Ordering::Greater,
            (Integer(a), Integer(b)) => a.cmp(b),
            (Integer(_), _) => std::cmp::Ordering::Less,
            (_, Integer(_)) => std::cmp::Ordering::Greater,
            (Name(a), Name(b)) => a.cmp(b),
            (Name(_), _) => std::cmp::Ordering::Less,
            (_, Name(_)) => std::cmp::Ordering::Greater,
            (TypeKey(_), TypeKey(_)) => std::cmp::Ordering::Equal,
        }
    }
}

impl From<String> for LuaMemberKey {
    fn from(name: String) -> Self {
        LuaMemberKey::Name(name.into())
    }
}

impl From<i64> for LuaMemberKey {
    fn from(i: i64) -> Self {
        LuaMemberKey::Integer(i)
    }
}

impl From<&str> for LuaMemberKey {
    fn from(name: &str) -> Self {
        LuaMemberKey::Name(name.to_string().into())
    }
}
