use std::panic;

use tracing::instrument;

pub use self::dep_node::{
    DepKind, DepKindVTable, DepNode, WorkProductId, dep_kind_from_label, label_strs,
};
pub use self::dep_node_key::DepNodeKey;
pub use self::graph::{
    DepGraph, DepGraphData, DepNodeIndex, QuerySideEffect, TaskDepsRef, WorkProduct,
    WorkProductMap, hash_result,
};
use self::graph::{MarkFrame, print_markframe_trace};
pub use self::retained::RetainedDepGraph;
pub use self::serialized::{SerializedDepGraph, SerializedDepNodeIndex};
pub use crate::dep_graph::debug::{DepNodeFilter, EdgeFilter};
use crate::ty::print::with_reduced_queries;
use crate::ty::{self, TyCtxt};

mod debug;
pub(crate) mod dep_node;
mod dep_node_key;
mod edges;
mod graph;
mod retained;
mod serialized;

/// Describes the contents of the fingerprint generated by a given query.
///
/// This is mainly for determining whether and how we can reconstruct a key
/// from the fingerprint.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum KeyFingerprintStyle {
    /// The fingerprint is actually a DefPathHash.
    DefPathHash,
    /// The fingerprint is actually a HirId.
    HirId,
    /// Query key was `()` or equivalent, so fingerprint is just zero.
    Unit,
    /// The fingerprint is an opaque hash, and a key cannot be reconstructed from it.
    Opaque,
}

impl KeyFingerprintStyle {
    #[inline]
    pub const fn reconstructible(self) -> bool {
        match self {
            KeyFingerprintStyle::DefPathHash
            | KeyFingerprintStyle::Unit
            | KeyFingerprintStyle::HirId => true,
            KeyFingerprintStyle::Opaque => false,
        }
    }
}

/// Execute the operation with provided dependencies.
fn with_deps<OP, R>(task_deps: TaskDepsRef<'_>, op: OP) -> R
where
    OP: FnOnce() -> R,
{
    ty::tls::with_context(|icx| {
        let icx = ty::tls::ImplicitCtxt { task_deps, ..icx.clone() };
        ty::tls::enter_context(&icx, op)
    })
}

/// Access dependencies from current implicit context.
fn read_deps<OP>(op: OP)
where
    OP: for<'a> FnOnce(TaskDepsRef<'a>),
{
    ty::tls::with_context_opt(|icx| {
        let Some(icx) = icx else { return };
        op(icx.task_deps)
    })
}

impl<'tcx> TyCtxt<'tcx> {
    #[inline]
    pub fn dep_kind_vtable(self, dk: DepKind) -> &'tcx DepKindVTable<'tcx> {
        &self.dep_kind_vtables[dk.as_usize()]
    }

    fn with_reduced_queries<T>(self, f: impl FnOnce() -> T) -> T {
        with_reduced_queries!(f())
    }

    #[inline(always)]
    pub fn key_fingerprint_style(self, kind: DepKind) -> KeyFingerprintStyle {
        self.dep_kind_vtable(kind).key_fingerprint_style
    }

    /// Try to force a dep node to execute and see if it's green.
    ///
    /// Returns true if the query has actually been forced. It is valid that a query
    /// fails to be forced, e.g. when the query key cannot be reconstructed from the
    /// dep-node or when the query kind outright does not support it.
    #[inline]
    #[instrument(skip(self, frame), level = "debug")]
    fn try_force_from_dep_node(
        self,
        dep_node: DepNode,
        prev_index: SerializedDepNodeIndex,
        frame: &MarkFrame<'_>,
    ) -> bool {
        if let Some(force_fn) = self.dep_kind_vtable(dep_node.kind).force_from_dep_node {
            match panic::catch_unwind(panic::AssertUnwindSafe(|| {
                force_fn(self, dep_node, prev_index)
            })) {
                Err(value) => {
                    if !value.is::<rustc_errors::FatalErrorMarker>() {
                        print_markframe_trace(&self.dep_graph, frame);
                    }
                    panic::resume_unwind(value)
                }
                Ok(query_has_been_forced) => query_has_been_forced,
            }
        } else {
            false
        }
    }

    /// Load data from the on-disk cache.
    fn try_load_from_on_disk_cache(self, dep_node: &DepNode) {
        if let Some(try_load_fn) = self.dep_kind_vtable(dep_node.kind).try_load_from_on_disk_cache {
            try_load_fn(self, *dep_node)
        }
    }
}
