use masl_diagnostics::diagnostic::Diagnostic;
use masl_diagnostics::span::Span;
use masl_hir::document::{Expr, HirDocument, Resource};

/// Validates encapsulation contracts: traversals pointing to grants rather than relations.
/// Emits Warning W0600 if a traversal terminates on a relation.
pub fn check_encapsulation(hir: &HirDocument, diags: &mut Vec<Diagnostic>) {
    for res in hir.resources.values() {
        for grant in res.grants.values() {
            check_expr(&grant.expression, res, hir, diags);
        }
    }
    for expr in hir.aliases.values() {
        check_expr(
            expr,
            &Resource {
                name: String::new(),
                relations: std::collections::HashMap::new(),
                grants: std::collections::HashMap::new(),
                span: Span::dummy(),
            },
            hir,
            diags,
        );
    }
}

fn check_expr(
    expr: &Expr,
    current_resource: &Resource,
    hir: &HirDocument,
    diags: &mut Vec<Diagnostic>,
) {
    match expr {
        Expr::Traversal { steps, span } if steps.len() >= 2 => {
            // Find resource containing the relation/grant
            let mut current_type = &current_resource.name;
            for step in steps.iter().take(steps.len() - 1) {
                if let Some(target) = hir
                    .resources
                    .get(current_type)
                    .and_then(|res| res.relations.get(step))
                    .and_then(|r| r.types.first())
                {
                    current_type = &target.target_name;
                } else if let Some(target) = hir
                    .subjects
                    .get(current_type)
                    .and_then(|sub| sub.relations.get(step))
                    .and_then(|r| r.types.first())
                {
                    current_type = &target.target_name;
                }
            }

            // Verify terminal step is a grant, not a relation
            let terminal = steps.last().unwrap();
            if let Some(res) = hir.resources.get(current_type)
                && res.relations.contains_key(terminal)
                && !res.grants.contains_key(terminal)
            {
                diags.push(Diagnostic::warning(
                            "W0600",
                            format!(
                                "Traversal accesses internal relation '{}' on resource '{}' instead of public grant",
                                terminal, res.name
                            ),
                            *span,
                        ));
            }
        }
        Expr::Not { operand, .. } => check_expr(operand, current_resource, hir, diags),
        Expr::Union { operands, .. } => {
            for o in operands {
                check_expr(o, current_resource, hir, diags);
            }
        }
        Expr::Intersection { operands, .. } => {
            for o in operands {
                check_expr(o, current_resource, hir, diags);
            }
        }
        Expr::Difference { left, right, .. } => {
            check_expr(left, current_resource, hir, diags);
            check_expr(right, current_resource, hir, diags);
        }
        _ => {}
    }
}
