use std::borrow::Cow;

use crate::{
    common::CodegenCx,
    debuginfo::{
        metadata::{
            enums::tag_base_type,
            file_metadata, size_and_align_of, type_di_node,
            type_map::{self, Stub, StubInfo, UniqueTypeId},
            unknown_file_metadata, DINodeCreationResult, SmallVec, NO_GENERICS,
            UNKNOWN_LINE_NUMBER,
        },
        utils::{create_DIArray, get_namespace_for_item, DIB},
    },
    llvm::{
        self,
        debuginfo::{DIFile, DIFlags, DIType},
    },
};
use libc::c_uint;
use rustc_codegen_ssa::{
    debuginfo::{type_names::compute_debuginfo_type_name, wants_c_like_enum_debuginfo},
    traits::ConstMethods,
};
use rustc_middle::{
    bug,
    ty::{
        self,
        layout::{LayoutOf, TyAndLayout},
    },
};
use rustc_target::abi::{Size, TagEncoding, VariantIdx, Variants};
use smallvec::smallvec;

/// Build the debuginfo node for an enum type. The listing below shows how such a
/// type looks like at the LLVM IR/DWARF level. It is a `DW_TAG_structure_type`
/// with a single `DW_TAG_variant_part` that in turn contains a `DW_TAG_variant`
/// for each variant of the enum. The variant-part also contains a single member
/// describing the discriminant, and a nested struct type for each of the variants.
///
/// ```txt
///  ---> DW_TAG_structure_type              (top-level type for enum)
///         DW_TAG_variant_part              (variant part)
///           DW_AT_discr                    (reference to discriminant DW_TAG_member)
///           DW_TAG_member                  (discriminant member)
///           DW_TAG_variant                 (variant 1)
///           DW_TAG_variant                 (variant 2)
///           DW_TAG_variant                 (variant 3)
///         DW_TAG_structure_type            (type of variant 1)
///         DW_TAG_structure_type            (type of variant 2)
///         DW_TAG_structure_type            (type of variant 3)
/// ```
pub(super) fn build_enum_type_di_node<'ll, 'tcx>(
    cx: &CodegenCx<'ll, 'tcx>,
    unique_type_id: UniqueTypeId<'tcx>,
) -> DINodeCreationResult<'ll> {
    let enum_type = unique_type_id.expect_ty();
    let &ty::Adt(enum_adt_def, _) = enum_type.kind() else {
        bug!("build_enum_type_di_node() called with non-enum type: `{:?}`", enum_type)
        };

    let containing_scope = get_namespace_for_item(cx, enum_adt_def.did());
    let enum_type_and_layout = cx.layout_of(enum_type);
    let enum_type_name = compute_debuginfo_type_name(cx.tcx, enum_type, false);

    debug_assert!(!wants_c_like_enum_debuginfo(enum_type_and_layout));

    type_map::build_type_with_children(
        cx,
        type_map::stub(
            cx,
            Stub::Struct,
            unique_type_id,
            &enum_type_name,
            size_and_align_of(enum_type_and_layout),
            Some(containing_scope),
            DIFlags::FlagZero,
        ),
        |cx, enum_type_di_node| {
            // Build the struct type for each variant. These will be referenced by the
            // DW_TAG_variant DIEs inside of the DW_TAG_variant_part DIE.
            // We also called the names for the corresponding DW_TAG_variant DIEs here.
            let variant_member_infos: SmallVec<_> = enum_adt_def
                .variant_range()
                .map(|variant_index| VariantMemberInfo {
                    variant_index,
                    variant_name: Cow::from(enum_adt_def.variant(variant_index).name.as_str()),
                    variant_struct_type_di_node: super::build_enum_variant_struct_type_di_node(
                        cx,
                        enum_type_and_layout,
                        enum_type_di_node,
                        variant_index,
                        enum_adt_def.variant(variant_index),
                        enum_type_and_layout.for_variant(cx, variant_index),
                    ),
                    source_info: None,
                })
                .collect();

            smallvec![build_enum_variant_part_di_node(
                cx,
                enum_type_and_layout,
                enum_type_di_node,
                &variant_member_infos[..],
            )]
        },
        // We don't seem to be emitting generic args on the enum type, it seems. Rather
        // they get attached to the struct type of each variant.
        NO_GENERICS,
    )
}

/// Build the debuginfo node for a generator environment. It looks the same as the debuginfo for
/// an enum. See [build_enum_type_di_node] for more information.
///
/// ```txt
///
///  ---> DW_TAG_structure_type              (top-level type for the generator)
///         DW_TAG_variant_part              (variant part)
///           DW_AT_discr                    (reference to discriminant DW_TAG_member)
///           DW_TAG_member                  (discriminant member)
///           DW_TAG_variant                 (variant 1)
///           DW_TAG_variant                 (variant 2)
///           DW_TAG_variant                 (variant 3)
///         DW_TAG_structure_type            (type of variant 1)
///         DW_TAG_structure_type            (type of variant 2)
///         DW_TAG_structure_type            (type of variant 3)
///
/// ```
pub(super) fn build_generator_di_node<'ll, 'tcx>(
    cx: &CodegenCx<'ll, 'tcx>,
    unique_type_id: UniqueTypeId<'tcx>,
) -> DINodeCreationResult<'ll> {
    let generator_type = unique_type_id.expect_ty();
    let &ty::Generator(generator_def_id, _, _ ) = generator_type.kind() else {
        bug!("build_generator_di_node() called with non-generator type: `{:?}`", generator_type)
        };

    let containing_scope = get_namespace_for_item(cx, generator_def_id);
    let generator_type_and_layout = cx.layout_of(generator_type);

    debug_assert!(!wants_c_like_enum_debuginfo(generator_type_and_layout));

    let generator_type_name = compute_debuginfo_type_name(cx.tcx, generator_type, false);

    type_map::build_type_with_children(
        cx,
        type_map::stub(
            cx,
            Stub::Struct,
            unique_type_id,
            &generator_type_name,
            size_and_align_of(generator_type_and_layout),
            Some(containing_scope),
            DIFlags::FlagZero,
        ),
        |cx, generator_type_di_node| {
            let (generator_layout, state_specific_upvar_names) =
                cx.tcx.generator_layout_and_saved_local_names(generator_def_id);

            let Variants::Multiple { tag_encoding: TagEncoding::Direct, ref variants, .. } = generator_type_and_layout.variants else {
                bug!(
                    "Encountered generator with non-direct-tag layout: {:?}",
                    generator_type_and_layout
                )
            };

            let common_upvar_names =
                cx.tcx.closure_saved_names_of_captured_variables(generator_def_id);

            // Build variant struct types
            let variant_struct_type_di_nodes: SmallVec<_> = variants
                .indices()
                .map(|variant_index| {
                    // FIXME: This is problematic because just a number is not a valid identifier.
                    //        GeneratorSubsts::variant_name(variant_index), would be consistent
                    //        with enums?
                    let variant_name = format!("{}", variant_index.as_usize()).into();

                    let span = generator_layout.variant_source_info[variant_index].span;
                    let source_info = if !span.is_dummy() {
                        let loc = cx.lookup_debug_loc(span.lo());
                        Some((file_metadata(cx, &loc.file), loc.line))
                    } else {
                        None
                    };

                    VariantMemberInfo {
                        variant_index,
                        variant_name,
                        variant_struct_type_di_node:
                            super::build_generator_variant_struct_type_di_node(
                                cx,
                                variant_index,
                                generator_type_and_layout,
                                generator_type_di_node,
                                generator_layout,
                                &state_specific_upvar_names,
                                &common_upvar_names,
                            ),
                        source_info,
                    }
                })
                .collect();

            smallvec![build_enum_variant_part_di_node(
                cx,
                generator_type_and_layout,
                generator_type_di_node,
                &variant_struct_type_di_nodes[..],
            )]
        },
        // We don't seem to be emitting generic args on the generator type, it seems. Rather
        // they get attached to the struct type of each variant.
        NO_GENERICS,
    )
}

/// Builds the DW_TAG_variant_part of an enum or generator debuginfo node:
///
/// ```txt
///       DW_TAG_structure_type              (top-level type for enum)
/// --->    DW_TAG_variant_part              (variant part)
///           DW_AT_discr                    (reference to discriminant DW_TAG_member)
///           DW_TAG_member                  (discriminant member)
///           DW_TAG_variant                 (variant 1)
///           DW_TAG_variant                 (variant 2)
///           DW_TAG_variant                 (variant 3)
///         DW_TAG_structure_type            (type of variant 1)
///         DW_TAG_structure_type            (type of variant 2)
///         DW_TAG_structure_type            (type of variant 3)
/// ```
fn build_enum_variant_part_di_node<'ll, 'tcx>(
    cx: &CodegenCx<'ll, 'tcx>,
    enum_type_and_layout: TyAndLayout<'tcx>,
    enum_type_di_node: &'ll DIType,
    variant_member_infos: &[VariantMemberInfo<'_, 'll>],
) -> &'ll DIType {
    let tag_member_di_node =
        build_discr_member_di_node(cx, enum_type_and_layout, enum_type_di_node);

    let variant_part_unique_type_id =
        UniqueTypeId::for_enum_variant_part(cx.tcx, enum_type_and_layout.ty);

    let stub = StubInfo::new(
        cx,
        variant_part_unique_type_id,
        |cx, variant_part_unique_type_id_str| unsafe {
            let variant_part_name = "";
            llvm::LLVMRustDIBuilderCreateVariantPart(
                DIB(cx),
                enum_type_di_node,
                variant_part_name.as_ptr().cast(),
                variant_part_name.len(),
                unknown_file_metadata(cx),
                UNKNOWN_LINE_NUMBER,
                enum_type_and_layout.size.bits(),
                enum_type_and_layout.align.abi.bits() as u32,
                DIFlags::FlagZero,
                tag_member_di_node,
                create_DIArray(DIB(cx), &[]),
                variant_part_unique_type_id_str.as_ptr().cast(),
                variant_part_unique_type_id_str.len(),
            )
        },
    );

    type_map::build_type_with_children(
        cx,
        stub,
        |cx, variant_part_di_node| {
            variant_member_infos
                .iter()
                .map(|variant_member_info| {
                    build_enum_variant_member_di_node(
                        cx,
                        enum_type_and_layout,
                        variant_part_di_node,
                        variant_member_info,
                    )
                })
                .collect()
        },
        NO_GENERICS,
    )
    .di_node
}

/// Builds the DW_TAG_member describing where we can find the tag of an enum.
/// Returns `None` if the enum does not have a tag.
///
/// ```txt
///
///       DW_TAG_structure_type              (top-level type for enum)
///         DW_TAG_variant_part              (variant part)
///           DW_AT_discr                    (reference to discriminant DW_TAG_member)
/// --->      DW_TAG_member                  (discriminant member)
///           DW_TAG_variant                 (variant 1)
///           DW_TAG_variant                 (variant 2)
///           DW_TAG_variant                 (variant 3)
///         DW_TAG_structure_type            (type of variant 1)
///         DW_TAG_structure_type            (type of variant 2)
///         DW_TAG_structure_type            (type of variant 3)
///
/// ```
fn build_discr_member_di_node<'ll, 'tcx>(
    cx: &CodegenCx<'ll, 'tcx>,
    enum_or_generator_type_and_layout: TyAndLayout<'tcx>,
    enum_or_generator_type_di_node: &'ll DIType,
) -> Option<&'ll DIType> {
    let tag_name = match enum_or_generator_type_and_layout.ty.kind() {
        ty::Generator(..) => "__state",
        _ => "",
    };

    // NOTE: This is actually wrong. This will become a member of
    //       of the DW_TAG_variant_part. But, due to LLVM's API, that
    //       can only be constructed with this DW_TAG_member already in created.
    //       In LLVM IR the wrong scope will be listed but when DWARF is
    //       generated from it, the DW_TAG_member will be a child the
    //       DW_TAG_variant_part.
    let containing_scope = enum_or_generator_type_di_node;

    match enum_or_generator_type_and_layout.layout.variants() {
        // A single-variant enum has no discriminant.
        &Variants::Single { .. } => None,

        &Variants::Multiple { tag_field, .. } => {
            let tag_base_type = tag_base_type(cx, enum_or_generator_type_and_layout);
            let (size, align) = cx.size_and_align_of(tag_base_type);

            unsafe {
                Some(llvm::LLVMRustDIBuilderCreateMemberType(
                    DIB(cx),
                    containing_scope,
                    tag_name.as_ptr().cast(),
                    tag_name.len(),
                    unknown_file_metadata(cx),
                    UNKNOWN_LINE_NUMBER,
                    size.bits(),
                    align.bits() as u32,
                    enum_or_generator_type_and_layout.fields.offset(tag_field).bits(),
                    DIFlags::FlagArtificial,
                    type_di_node(cx, tag_base_type),
                ))
            }
        }
    }
}

/// Build the debuginfo node for `DW_TAG_variant`:
///
/// ```txt
///       DW_TAG_structure_type              (top-level type for enum)
///         DW_TAG_variant_part              (variant part)
///           DW_AT_discr                    (reference to discriminant DW_TAG_member)
///           DW_TAG_member                  (discriminant member)
///  --->     DW_TAG_variant                 (variant 1)
///  --->     DW_TAG_variant                 (variant 2)
///  --->     DW_TAG_variant                 (variant 3)
///         DW_TAG_structure_type            (type of variant 1)
///         DW_TAG_structure_type            (type of variant 2)
///         DW_TAG_structure_type            (type of variant 3)
/// ```
///
/// This node looks like:
///
/// ```txt
/// DW_TAG_variant
///   DW_AT_discr_value           0
///   DW_TAG_member
///     DW_AT_name                  None
///     DW_AT_type                  <0x000002a1>
///     DW_AT_alignment             0x00000002
///     DW_AT_data_member_location  0
/// ```
///
/// The DW_AT_discr_value is optional, and is omitted if
///   - This is the only variant of a univariant enum (i.e. their is no discriminant)
///   - This is the "untagged" variant of a niche-layout enum
///     (where only the other variants are identified by a single value)
///
/// There is only ever a single member, the type of which is a struct that describes the
/// fields of the variant (excluding the discriminant). The name of the member is the name
/// of the variant as given in the source code. The DW_AT_data_member_location is always
/// zero.
///
/// Note that the LLVM DIBuilder API is a bit unintuitive here. The DW_TAG_variant subtree
/// (including the DW_TAG_member) is built by a single call to
/// `LLVMRustDIBuilderCreateVariantMemberType()`.
fn build_enum_variant_member_di_node<'ll, 'tcx>(
    cx: &CodegenCx<'ll, 'tcx>,
    enum_type_and_layout: TyAndLayout<'tcx>,
    variant_part_di_node: &'ll DIType,
    variant_member_info: &VariantMemberInfo<'_, 'll>,
) -> &'ll DIType {
    let variant_index = variant_member_info.variant_index;
    let discr_value = super::compute_discriminant_value(cx, enum_type_and_layout, variant_index);

    let (file_di_node, line_number) = variant_member_info
        .source_info
        .unwrap_or_else(|| (unknown_file_metadata(cx), UNKNOWN_LINE_NUMBER));

    unsafe {
        llvm::LLVMRustDIBuilderCreateVariantMemberType(
            DIB(cx),
            variant_part_di_node,
            variant_member_info.variant_name.as_ptr().cast(),
            variant_member_info.variant_name.len(),
            file_di_node,
            line_number,
            enum_type_and_layout.size.bits(),
            enum_type_and_layout.align.abi.bits() as u32,
            Size::ZERO.bits(),
            discr_value.opt_single_val().map(|value| {
                // NOTE(eddyb) do *NOT* remove this assert, until
                // we pass the full 128-bit value to LLVM, otherwise
                // truncation will be silent and remain undetected.
                assert_eq!(value as u64 as u128, value);
                cx.const_u64(value as u64)
            }),
            DIFlags::FlagZero,
            variant_member_info.variant_struct_type_di_node,
        )
    }
}

/// Information needed for building a `DW_TAG_variant`:
///
/// ```txt
///       DW_TAG_structure_type              (top-level type for enum)
///         DW_TAG_variant_part              (variant part)
///           DW_AT_discr                    (reference to discriminant DW_TAG_member)
///           DW_TAG_member                  (discriminant member)
///  --->     DW_TAG_variant                 (variant 1)
///  --->     DW_TAG_variant                 (variant 2)
///  --->     DW_TAG_variant                 (variant 3)
///         DW_TAG_structure_type            (type of variant 1)
///         DW_TAG_structure_type            (type of variant 2)
///         DW_TAG_structure_type            (type of variant 3)
/// ```
struct VariantMemberInfo<'a, 'll> {
    variant_index: VariantIdx,
    variant_name: Cow<'a, str>,
    variant_struct_type_di_node: &'ll DIType,
    source_info: Option<(&'ll DIFile, c_uint)>,
}
