use rustc_middle::arena::Arena;
use rustc_middle::bug;
use rustc_middle::dep_graph::{DepKindVTable, DepNodeKey, KeyFingerprintStyle};
use rustc_middle::query::QueryCache;

use crate::GetQueryVTable;
use crate::plumbing::promote_from_disk_inner;

/// [`DepKindVTable`] constructors for special dep kinds that aren't queries.
#[expect(non_snake_case, reason = "use non-snake case to avoid collision with query names")]
mod non_query {
    use super::*;

    // We use this for most things when incr. comp. is turned off.
    pub(crate) fn Null<'tcx>() -> DepKindVTable<'tcx> {
        DepKindVTable {
            is_eval_always: false,
            key_fingerprint_style: KeyFingerprintStyle::Unit,
            force_from_dep_node_fn: Some(|_, dep_node, _| {
                bug!("force_from_dep_node: encountered {dep_node:?}")
            }),
            promote_from_disk_fn: None,
        }
    }

    // We use this for the forever-red node.
    pub(crate) fn Red<'tcx>() -> DepKindVTable<'tcx> {
        DepKindVTable {
            is_eval_always: false,
            key_fingerprint_style: KeyFingerprintStyle::Unit,
            force_from_dep_node_fn: Some(|_, dep_node, _| {
                bug!("force_from_dep_node: encountered {dep_node:?}")
            }),
            promote_from_disk_fn: None,
        }
    }

    pub(crate) fn SideEffect<'tcx>() -> DepKindVTable<'tcx> {
        DepKindVTable {
            is_eval_always: false,
            key_fingerprint_style: KeyFingerprintStyle::Unit,
            force_from_dep_node_fn: Some(|tcx, _, prev_index| {
                tcx.dep_graph.force_side_effect(tcx, prev_index);
                true
            }),
            promote_from_disk_fn: None,
        }
    }

    pub(crate) fn AnonZeroDeps<'tcx>() -> DepKindVTable<'tcx> {
        DepKindVTable {
            is_eval_always: false,
            key_fingerprint_style: KeyFingerprintStyle::Opaque,
            force_from_dep_node_fn: Some(|_, _, _| bug!("cannot force an anon node")),
            promote_from_disk_fn: None,
        }
    }

    pub(crate) fn TraitSelect<'tcx>() -> DepKindVTable<'tcx> {
        DepKindVTable {
            is_eval_always: false,
            key_fingerprint_style: KeyFingerprintStyle::Unit,
            force_from_dep_node_fn: None,
            promote_from_disk_fn: None,
        }
    }

    pub(crate) fn CompileCodegenUnit<'tcx>() -> DepKindVTable<'tcx> {
        DepKindVTable {
            is_eval_always: false,
            key_fingerprint_style: KeyFingerprintStyle::Opaque,
            force_from_dep_node_fn: None,
            promote_from_disk_fn: None,
        }
    }

    pub(crate) fn CompileMonoItem<'tcx>() -> DepKindVTable<'tcx> {
        DepKindVTable {
            is_eval_always: false,
            key_fingerprint_style: KeyFingerprintStyle::Opaque,
            force_from_dep_node_fn: None,
            promote_from_disk_fn: None,
        }
    }

    pub(crate) fn Metadata<'tcx>() -> DepKindVTable<'tcx> {
        DepKindVTable {
            is_eval_always: false,
            key_fingerprint_style: KeyFingerprintStyle::Unit,
            force_from_dep_node_fn: None,
            promote_from_disk_fn: None,
        }
    }
}

/// Shared implementation of the [`DepKindVTable`] constructor for queries.
/// Called from macro-generated code for each query.
pub(crate) fn make_dep_kind_vtable_for_query<'tcx, Q>(
    is_cache_on_disk: bool,
    is_eval_always: bool,
    is_no_force: bool,
) -> DepKindVTable<'tcx>
where
    Q: GetQueryVTable<'tcx>,
{
    // A query dep-node can only be forced or promoted if it can recover a key
    // from its key fingerprint.
    let key_fingerprint_style = <Q::Cache as QueryCache>::Key::key_fingerprint_style();
    let can_recover = key_fingerprint_style.is_maybe_recoverable();

    DepKindVTable {
        is_eval_always,
        key_fingerprint_style,
        force_from_dep_node_fn: (can_recover && !is_no_force).then_some(
            |tcx, dep_node, _prev_index| {
                let query = Q::query_vtable(tcx);
                crate::execution::force_query_dep_node(tcx, query, dep_node)
            },
        ),
        promote_from_disk_fn: (can_recover && is_cache_on_disk).then_some(|tcx, dep_node| {
            let query = Q::query_vtable(tcx);
            promote_from_disk_inner(tcx, query, dep_node)
        }),
    }
}

macro_rules! define_dep_kind_vtables {
    (
        queries {
            $(
                $(#[$attr:meta])*
                fn $name:ident($K:ty) -> $V:ty
                {
                    // Search for (QMODLIST) to find all occurrences of this query modifier list.
                    arena_cache: $arena_cache:literal,
                    cache_on_disk: $cache_on_disk:literal,
                    depth_limit: $depth_limit:literal,
                    desc: $desc:expr,
                    eval_always: $eval_always:literal,
                    feedable: $feedable:literal,
                    handle_cycle_error: $handle_cycle_error:literal,
                    no_force: $no_force:literal,
                    no_hash: $no_hash:literal,
                    returns_error_guaranteed: $returns_error_guaranteed:literal,
                    separate_provide_extern: $separate_provide_extern:literal,
                }
            )*
        }
        non_queries {
            $(
                $(#[$nq_attr:meta])*
                $nq_name:ident,
            )*
        }
    ) => {{
        // The small number of non-query vtables: `Null`, `Red`, etc.
        let nq_vtables = [
            $(
                non_query::$nq_name(),
            )*
        ];

        // The large number of query vtables.
        let q_vtables: [DepKindVTable<'tcx>; _] = [
            $(
                $crate::dep_kind_vtables::make_dep_kind_vtable_for_query::<
                    $crate::query_impl::$name::VTableGetter,
                >(
                    $cache_on_disk,
                    $eval_always,
                    $no_force,
                )
            ),*
        ];

        (nq_vtables, q_vtables)
    }}
}

// Create an array of vtables, one for each dep kind (non-query and query).
pub fn make_dep_kind_vtables<'tcx>(arena: &'tcx Arena<'tcx>) -> &'tcx [DepKindVTable<'tcx>] {
    let (nq_vtables, q_vtables) =
        rustc_middle::queries::rustc_with_all_queries! { define_dep_kind_vtables! };

    // Non-query vtables must come before query vtables, to match the order of `DepKind`.
    arena.alloc_from_iter(nq_vtables.into_iter().chain(q_vtables.into_iter()))
}
