use emmylua_parser::{
    LuaAstNode, LuaAstToken, LuaDocDescriptionOwner, LuaDocFieldKey, LuaDocTagField,
    LuaDocTagOperator, LuaDocType, NumberResult, VisibilityKind,
};

use crate::{
    AnalyzeError, DiagnosticCode, LuaMemberFeature, LuaMemberId, LuaSignatureId, LuaTypeCache,
    OperatorFunction, TypeOps,
    compilation::analyzer::doc::preprocess_description,
    db_index::{
        LuaMember, LuaMemberKey, LuaMemberOwner, LuaOperator, LuaOperatorMetaMethod,
        LuaSemanticDeclId, LuaType,
    },
};

use super::{DocAnalyzer, infer_type::infer_type};

pub fn analyze_field(analyzer: &mut DocAnalyzer, tag: LuaDocTagField) -> Option<()> {
    let file_id = analyzer.file_id;
    let current_type_id = match &analyzer.current_type_id {
        Some(id) => id.clone(),
        None => {
            analyzer.get_db().get_diagnostic_index_mut().add_diagnostic(
                file_id,
                AnalyzeError {
                    kind: DiagnosticCode::AnnotationUsageError,
                    message: t!("`@field` must be used under a `@class`").to_string(),
                    range: tag.get_range(),
                },
            );
            return None;
        }
    };

    let owner_id = LuaMemberOwner::Type(current_type_id.clone());
    let visibility_kind = if let Some(visibility_token) = tag.get_visibility_token() {
        visibility_token.get_visibility()
    } else {
        get_visibility_from_field_attrib(&tag)
    };

    let member_id = LuaMemberId::new(tag.get_syntax_id(), file_id);

    let nullable = tag.is_nullable();
    let type_node = tag.get_type()?;
    let (mut field_type, property_owner) = match &type_node {
        LuaDocType::Func(doc_func) => {
            let typ = infer_type(&mut analyzer.type_context, type_node.clone());
            let signature_id = LuaSignatureId::from_doc_func(file_id, doc_func);
            (typ, LuaSemanticDeclId::Signature(signature_id))
        }
        _ => (
            infer_type(&mut analyzer.type_context, type_node),
            LuaSemanticDeclId::Member(member_id),
        ),
    };
    if nullable && !field_type.is_nullable() {
        field_type = TypeOps::Union.apply(analyzer.get_db(), &field_type, &LuaType::Nil);
    }

    let mut description = String::new();

    for desc in tag.get_descriptions() {
        let desc_text = desc.get_description_text();
        if !desc_text.is_empty() {
            let text = preprocess_description(&desc_text, Some(&property_owner));
            if !description.is_empty() {
                description.push_str("\n\n");
            }

            description.push_str(&text);
        }
    }

    let field_key = tag.get_field_key()?;
    let key = match field_key {
        LuaDocFieldKey::Name(name_token) => {
            LuaMemberKey::Name(name_token.get_name_text().to_string().into())
        }
        LuaDocFieldKey::String(string_token) => LuaMemberKey::Name(string_token.get_value().into()),
        LuaDocFieldKey::Integer(int_token) => {
            if let NumberResult::Int(idx) = int_token.get_number_value() {
                LuaMemberKey::Integer(idx)
            } else {
                return None;
            }
        }
        LuaDocFieldKey::Type(doc_type) => {
            let range = doc_type.get_range();
            let key_type_ref = infer_type(&mut analyzer.type_context, doc_type);
            if key_type_ref.is_unknown() {
                return None;
            }

            let operator = LuaOperator::new(
                current_type_id.clone().into(),
                LuaOperatorMetaMethod::Index,
                file_id,
                range,
                OperatorFunction::BinOp {
                    param: key_type_ref.clone(),
                    ret: field_type.clone(),
                },
            );
            analyzer
                .get_db()
                .get_operator_index_mut()
                .add_operator(operator);
            LuaMemberKey::ExprType(key_type_ref)
        }
    };

    let decl_feature = if analyzer.is_meta {
        LuaMemberFeature::MetaFieldDecl
    } else {
        LuaMemberFeature::FileFieldDecl
    };

    let member = LuaMember::new(member_id, key.clone(), decl_feature, None);
    analyzer
        .get_db()
        .get_reference_index_mut()
        .add_index_reference(key, file_id, tag.get_syntax_id());

    analyzer
        .get_db()
        .get_member_index_mut()
        .add_member(owner_id, member);

    analyzer
        .get_db()
        .get_type_index_mut()
        .bind_type(member_id.into(), LuaTypeCache::DocType(field_type.clone()));

    if let Some(visibility_kind) = visibility_kind {
        analyzer.get_db().get_property_index_mut().add_visibility(
            file_id,
            property_owner.clone(),
            visibility_kind,
        );
    }

    if !description.is_empty() {
        // 不需要传入`owner`, 当前`owner`的效果是判断是否为`signature`, 如果是则不移除`['#', '@']`首字符
        // 但以`field`定义的必须移除首字符
        let description = preprocess_description(&description, None);
        analyzer.get_db().get_property_index_mut().add_description(
            file_id,
            property_owner.clone(),
            description,
        );
    }

    Some(())
}

pub fn analyze_operator(analyzer: &mut DocAnalyzer, tag: LuaDocTagOperator) -> Option<()> {
    let current_type_id = analyzer.current_type_id.clone()?;
    let name_token = tag.get_name_token()?;
    let op_kind = LuaOperatorMetaMethod::from_operator_name(name_token.get_name_text())?;
    let params: Vec<LuaType> = tag
        .get_param_list()
        .map(|list| {
            list.get_types()
                .map(|doc_type| infer_type(&mut analyzer.type_context, doc_type))
                .collect()
        })
        .unwrap_or_default();

    let return_type = if let Some(return_type) = tag.get_return_type() {
        infer_type(&mut analyzer.type_context, return_type)
    } else {
        LuaType::Unknown
    };

    let func = match op_kind {
        LuaOperatorMetaMethod::Unm
        | LuaOperatorMetaMethod::BNot
        | LuaOperatorMetaMethod::Len
        | LuaOperatorMetaMethod::Pairs => OperatorFunction::UnOp { ret: return_type },
        LuaOperatorMetaMethod::Add
        | LuaOperatorMetaMethod::Sub
        | LuaOperatorMetaMethod::Mul
        | LuaOperatorMetaMethod::Div
        | LuaOperatorMetaMethod::Mod
        | LuaOperatorMetaMethod::Pow
        | LuaOperatorMetaMethod::IDiv
        | LuaOperatorMetaMethod::BAnd
        | LuaOperatorMetaMethod::BOr
        | LuaOperatorMetaMethod::BXor
        | LuaOperatorMetaMethod::Shl
        | LuaOperatorMetaMethod::Shr
        | LuaOperatorMetaMethod::Concat
        | LuaOperatorMetaMethod::Eq
        | LuaOperatorMetaMethod::Lt
        | LuaOperatorMetaMethod::Le
        | LuaOperatorMetaMethod::Index => OperatorFunction::BinOp {
            param: params.into_iter().next().unwrap_or(LuaType::Any),
            ret: return_type,
        },
        LuaOperatorMetaMethod::Call => OperatorFunction::Call {
            params,
            ret: return_type,
        },
    };

    let operator = LuaOperator::new(
        current_type_id.into(),
        op_kind,
        analyzer.file_id,
        name_token.get_range(),
        func,
    );

    analyzer
        .get_db()
        .get_operator_index_mut()
        .add_operator(operator);

    Some(())
}

fn get_visibility_from_field_attrib(tag: &LuaDocTagField) -> Option<VisibilityKind> {
    if let Some(attrib) = tag.get_type_flag() {
        for token in attrib.get_attrib_tokens() {
            let visibility = VisibilityKind::to_visibility_kind(token.get_name_text());
            if visibility.is_some() {
                return visibility;
            }
        }
    }

    None
}
