// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

//! Symbol visibility.
//!
//! Visibility is generic: the [`Visibility`] trait is the data a definition
//! carries to describe who may reference it, plus a single behaviour method,
//! [`Visibility::is_visible_from`], that decides reachability. Languages that
//! need richer access control (`pub(crate)`, `protected`, region-scoped
//! privacy) implement their own visibility type; [`SymbolVisibility`] is the
//! built-in default for simple public/private languages.

use {
  crate::core::SymbolPath,
  std::{fmt::Debug, hash::Hash},
};

/// Visibility marker carried by symbol definitions.
///
/// A visibility value is a small, cheap handle — a plain enum, an interned
/// [`laburnum::Ident`], or a [`laburnum::Span`] — never owned heap data, which
/// is why the trait requires [`Copy`].
///
/// The only behaviour is [`is_visible_from`](Visibility::is_visible_from),
/// which has a default that treats every symbol as visible. Languages with
/// access control override it.
pub trait Visibility:
  Copy + Debug + Clone + PartialEq + Eq + Hash + Send + Sync + 'static
{
  /// Whether a definition carrying this visibility, located at `defined_at`,
  /// is reachable from a reference originating at `referenced_from`.
  ///
  /// The default treats every definition as visible. Override for languages
  /// with access control.
  fn is_visible_from<P: SymbolPath>(
    &self,
    defined_at: &P,
    referenced_from: &P,
  ) -> bool {
    let _ = (defined_at, referenced_from);
    true
  }
}

/// Built-in public/private visibility.
///
/// The default visibility type for [`Symbol`](crate::core::Symbol). Suitable
/// for languages whose access control is a simple public/private distinction.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum SymbolVisibility {
  #[default]
  Private,
  Public,
}

impl Visibility for SymbolVisibility {
  fn is_visible_from<P: SymbolPath>(
    &self,
    defined_at: &P,
    referenced_from: &P,
  ) -> bool {
    match self {
      | SymbolVisibility::Public => true,
      // Private: reachable only from within the defining scope and its
      // descendants. Relies on the [`SymbolPath`]/`PartitionSortKey` invariant
      // that a parent path is a prefix of its descendants' paths.
      | SymbolVisibility::Private => {
        laburnum::PartitionSortKey::is_prefix_of(defined_at, referenced_from)
      },
    }
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn public_is_visible_everywhere() {
    let defined = "a.b".to_string();
    let from = "x.y".to_string();
    assert!(SymbolVisibility::Public.is_visible_from(&defined, &from));
  }

  #[test]
  fn private_visible_within_subtree() {
    let defined = "a.b".to_string();
    let from = "a.b.c".to_string();
    assert!(SymbolVisibility::Private.is_visible_from(&defined, &from));
  }

  #[test]
  fn private_hidden_outside_subtree() {
    let defined = "a.b".to_string();
    let from = "a.c".to_string();
    assert!(!SymbolVisibility::Private.is_visible_from(&defined, &from));
  }

  #[test]
  fn default_is_private() {
    assert_eq!(SymbolVisibility::default(), SymbolVisibility::Private);
  }
}
