// Copyright (c) 2026, Salesforce, Inc.,
// All rights reserved.
// For full license text, see the LICENSE.txt file

//! Docker network port management
//!
//! This module provides types and utilities for handling Docker network ports
//! with different visibility modes. It supports both exposed and published ports
//! for container networking in test environments.
//!
//! ## Primary types
//!
//! - [`Port`]: network port type alias for u16
//! - [`PortVisibility`]: enum for port visibility modes (Exposed/Published)
//! - [`PortAccess`]: struct for port configuration and access control
//!

use crate::portpicker;

/// Represents a network port.
pub type Port = u16;

/// Visility of a port inside a Docker network.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum PortVisibility {
    /// Represents visibility for exposed ports.
    /// In Docker terminology, an exposed port is visible just inside
    /// Docker networks without matching the same port number in the host machine.
    #[default]
    Exposed,

    /// Represents visibility for published ports.
    /// In Docker terminology, a published port is visible for
    /// Docker networks and matches the same port number in the host machine.
    Published,
}

impl PortVisibility {
    /// Returns [`true`] if the visibility is [`PortVisibility::Exposed`].
    pub fn is_exposed(&self) -> bool {
        matches!(*self, PortVisibility::Exposed)
    }

    /// Returns [`true`] if the visibility is [`PortVisibility::Published`].
    pub fn is_published(&self) -> bool {
        matches!(*self, PortVisibility::Published)
    }
}

/// A type representing the port access inside a Docker network.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PortAccess {
    port: Port,
    public_port: Option<Port>,
    visibility: PortVisibility,
}

impl PortAccess {
    /// Create a new [`PortAccess`] for a given port and visibility.
    pub const fn new(port: Port, visibility: PortVisibility) -> Self {
        Self {
            port,
            public_port: None,
            visibility,
        }
    }

    /// Creates a [`PortAccess`] for a `port` with exposed visibility.
    pub const fn exposed(port: Port) -> Self {
        Self {
            port,
            public_port: None,
            visibility: PortVisibility::Exposed,
        }
    }

    /// Creates a [`PortAccess`] for a `port` with published visibility.
    pub const fn published(port: Port) -> Self {
        Self {
            port,
            public_port: None,
            visibility: PortVisibility::Published,
        }
    }

    /// Create a new [`PortAccess`] for a given port and fixed public port.
    pub(crate) const fn published_with_fixed_public_port(port: Port, public_port: Port) -> Self {
        Self {
            port,
            public_port: Some(public_port),
            visibility: PortVisibility::Published,
        }
    }

    /// Returns the port.
    pub const fn port(&self) -> Port {
        self.port
    }

    /// Defines the public port for the given internal port of the container. If the given
    /// [PortAccess](PortAccess) already provides one, it is used. Otherwise, a free port is picked
    /// from the OS
    pub(crate) fn public_port(&self) -> Option<Port> {
        match self.visibility {
            PortVisibility::Exposed => None,
            PortVisibility::Published => self.public_port.or_else(portpicker::pick_unused_port),
        }
    }

    /// Returns the visibility.
    pub const fn visibility(&self) -> PortVisibility {
        self.visibility
    }
}
