use oxc_ast::{AstKind, ast::IdentifierReference};
use oxc_diagnostics::OxcDiagnostic;
use oxc_semantic::{NodeId, Reference};
use oxc_span::GetSpan;
use oxc_syntax::reference::ReferenceId;

use crate::Semantic;

use super::{ConformanceTest, TestResult};

/// Tests reflexivity between [`IdentifierReference`] AST nodes and their corresponding
/// [`Reference`]s.
///
/// Performs the following checks:
/// 1. All [`IdentifierReference`]s have been populated with a [`ReferenceId`], even if the
///    referenced symbol could not be resolved.
///
/// 2. When an [`IdentifierReference`] is used to find a [`Reference`] in the symbol table, the AST
///    node id associated with that [`Reference`] should be the [`IdentifierReference`]'s AST node
///    id.
#[derive(Debug, Clone, Default)]
pub struct IdentifierReferenceTest;

/// [`IdentifierReference::reference_id`] returned [`None`].
fn missing_reference_id(reference: &IdentifierReference) -> TestResult {
    OxcDiagnostic::error("After semantic analysis, all IdentifierReferences should have a reference_id, even if a symbol could not be resolved.")
        .with_label(reference.span().label("This reference's reference_id is None"))
        .into()
}

/// The [`NodeId`] of the [`IdentifierReference`] did not match the [`NodeId`] of the
/// [`Reference`].
fn node_id_mismatch(
    identifier_reference_id: NodeId,
    identifier_reference: &IdentifierReference,
    reference_id: ReferenceId,
    reference: &Reference,
) -> TestResult {
    OxcDiagnostic::error(
        "NodeId mismatch between an IdentifierReference and its corresponding Reference",
    )
    .with_label(
        identifier_reference
            .span
            .label(format!("This IdentifierReference's NodeId is {identifier_reference_id:?}")),
    )
    .with_help(format!(
        "The Reference with id {reference_id:?} has a NodeId of {:?}",
        reference.node_id()
    ))
    .into()
}

impl ConformanceTest for IdentifierReferenceTest {
    fn name(&self) -> &'static str {
        "identifier-reference"
    }

    fn run_on_node<'a>(
        &self,
        node: &oxc_semantic::AstNode<'a>,
        semantic: &Semantic<'a>,
    ) -> TestResult {
        let AstKind::IdentifierReference(id) = node.kind() else {
            return TestResult::Pass;
        };
        let Some(reference_id) = id.reference_id.get() else {
            return missing_reference_id(id);
        };

        let reference = semantic.scoping().get_reference(reference_id);
        if reference.node_id() != node.id() {
            return node_id_mismatch(node.id(), id, reference_id, reference);
        }

        TestResult::Pass
    }
}
