-- | Core types for the libp2p Switch (central coordinator).
--
-- Defines connection states, direction, muxer session abstraction,
-- connection records, switch events, and the Switch itself.
-- See docs/08-switch.md for the full specification.
module Network.LibP2P.Switch.Types
  ( ConnState (..)
  , Direction (..)
  , MuxerSession (..)
  , Connection (..)
  , SwitchEvent (..)
  , StreamHandler
  , Switch (..)
  , DialError (..)
  , BackoffEntry (..)
  , ResourceError (..)
  , ActiveListener (..)
  ) where

import Control.Concurrent.Async (Async)
import Control.Concurrent.STM (TChan, TMVar, TVar)
import Data.Map.Strict (Map)
import Data.Time.Clock (UTCTime)
import Network.LibP2P.Crypto.Key (KeyPair)
import Network.LibP2P.Crypto.PeerId (PeerId)
import Network.LibP2P.Multiaddr.Multiaddr (Multiaddr)
import Network.LibP2P.MultistreamSelect.Negotiation (ProtocolId, StreamIO)
import Network.LibP2P.Protocol.Identify.Message (IdentifyInfo)
import Network.LibP2P.Switch.ResourceManager (Direction (..), ResourceError (..), ResourceManager)
import Network.LibP2P.Transport.Transport (Listener, Transport)

-- | Connection state machine (docs/08-switch.md §Connection States).
--
-- Connecting → ConnOpen → Closing → Closed
data ConnState
  = Connecting  -- ^ Raw transport established, upgrade in progress
  | ConnOpen    -- ^ Fully upgraded, streams can be opened/accepted
  | Closing     -- ^ Go Away sent/received, draining existing streams
  | ConnClosed  -- ^ Transport connection closed, resources freed
  deriving (Int -> ConnState -> ShowS
[ConnState] -> ShowS
ConnState -> String
(Int -> ConnState -> ShowS)
-> (ConnState -> String)
-> ([ConnState] -> ShowS)
-> Show ConnState
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ConnState -> ShowS
showsPrec :: Int -> ConnState -> ShowS
$cshow :: ConnState -> String
show :: ConnState -> String
$cshowList :: [ConnState] -> ShowS
showList :: [ConnState] -> ShowS
Show, ConnState -> ConnState -> Bool
(ConnState -> ConnState -> Bool)
-> (ConnState -> ConnState -> Bool) -> Eq ConnState
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ConnState -> ConnState -> Bool
== :: ConnState -> ConnState -> Bool
$c/= :: ConnState -> ConnState -> Bool
/= :: ConnState -> ConnState -> Bool
Eq)

-- | Abstract muxer session interface.
--
-- Decouples Switch from a specific muxer (Yamux, mplex, etc.).
-- Each muxer implementation provides a MuxerSession adapter.
data MuxerSession = MuxerSession
  { MuxerSession -> IO StreamIO
muxOpenStream   :: !(IO StreamIO)   -- ^ Open a new outbound stream
  , MuxerSession -> IO StreamIO
muxAcceptStream :: !(IO StreamIO)   -- ^ Accept an inbound stream (blocks)
  , MuxerSession -> IO ()
muxClose        :: !(IO ())         -- ^ Close the muxer session
  }

-- | An upgraded (secure + multiplexed) connection to a remote peer.
data Connection = Connection
  { Connection -> PeerId
connPeerId     :: !PeerId          -- ^ Remote peer identity
  , Connection -> Direction
connDirection  :: !Direction        -- ^ Inbound or outbound
  , Connection -> Multiaddr
connLocalAddr  :: !Multiaddr       -- ^ Local multiaddr
  , Connection -> Multiaddr
connRemoteAddr :: !Multiaddr       -- ^ Remote multiaddr
  , Connection -> ProtocolId
connSecurity   :: !ProtocolId      -- ^ Negotiated security protocol (e.g. "/noise")
  , Connection -> ProtocolId
connMuxer      :: !ProtocolId      -- ^ Negotiated muxer protocol (e.g. "/yamux/1.0.0")
  , Connection -> MuxerSession
connSession    :: !MuxerSession    -- ^ Muxer session for opening/accepting streams
  , Connection -> TVar ConnState
connState      :: !(TVar ConnState) -- ^ Mutable connection state
  }

-- | A protocol stream handler.
--
-- Receives the stream I/O and the remote peer's identity.
type StreamHandler = StreamIO -> PeerId -> IO ()

-- | Events emitted by the Switch for observability.
data SwitchEvent
  = Connected    !PeerId !Direction !Multiaddr  -- ^ Connection fully upgraded
  | Disconnected !PeerId !Direction !Multiaddr  -- ^ Connection closed
  deriving (Int -> SwitchEvent -> ShowS
[SwitchEvent] -> ShowS
SwitchEvent -> String
(Int -> SwitchEvent -> ShowS)
-> (SwitchEvent -> String)
-> ([SwitchEvent] -> ShowS)
-> Show SwitchEvent
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SwitchEvent -> ShowS
showsPrec :: Int -> SwitchEvent -> ShowS
$cshow :: SwitchEvent -> String
show :: SwitchEvent -> String
$cshowList :: [SwitchEvent] -> ShowS
showList :: [SwitchEvent] -> ShowS
Show, SwitchEvent -> SwitchEvent -> Bool
(SwitchEvent -> SwitchEvent -> Bool)
-> (SwitchEvent -> SwitchEvent -> Bool) -> Eq SwitchEvent
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SwitchEvent -> SwitchEvent -> Bool
== :: SwitchEvent -> SwitchEvent -> Bool
$c/= :: SwitchEvent -> SwitchEvent -> Bool
/= :: SwitchEvent -> SwitchEvent -> Bool
Eq)

-- | Errors that can occur during a dial operation (docs/08-switch.md §Dialing).
data DialError
  = DialBackoff               -- ^ Peer is in backoff period (recently failed)
  | DialNoAddresses           -- ^ No addresses provided for dialing
  | DialNoTransport !Multiaddr -- ^ No registered transport can handle this address
  | DialAllFailed ![String]   -- ^ All dial attempts failed
  | DialUpgradeFailed !String -- ^ Connection upgrade pipeline failed
  | DialSwitchClosed          -- ^ Switch has been shut down
  | DialResourceLimit !ResourceError  -- ^ Resource limit exceeded
  | DialPeerIdMismatch !PeerId !PeerId  -- ^ Expected vs actual remote PeerId
  deriving (Int -> DialError -> ShowS
[DialError] -> ShowS
DialError -> String
(Int -> DialError -> ShowS)
-> (DialError -> String)
-> ([DialError] -> ShowS)
-> Show DialError
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> DialError -> ShowS
showsPrec :: Int -> DialError -> ShowS
$cshow :: DialError -> String
show :: DialError -> String
$cshowList :: [DialError] -> ShowS
showList :: [DialError] -> ShowS
Show, DialError -> DialError -> Bool
(DialError -> DialError -> Bool)
-> (DialError -> DialError -> Bool) -> Eq DialError
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: DialError -> DialError -> Bool
== :: DialError -> DialError -> Bool
$c/= :: DialError -> DialError -> Bool
/= :: DialError -> DialError -> Bool
Eq)

-- | Per-peer dial backoff state (docs/08-switch.md §Dial Backoff).
--
-- After a failed dial, subsequent dials are rejected until the backoff
-- expires. Duration doubles on each consecutive failure up to 300s max.
data BackoffEntry = BackoffEntry
  { BackoffEntry -> UTCTime
beExpiry   :: !UTCTime  -- ^ When the backoff period ends
  , BackoffEntry -> Int
beAttempts :: !Int      -- ^ Number of consecutive failures (for exponential calc)
  } deriving (Int -> BackoffEntry -> ShowS
[BackoffEntry] -> ShowS
BackoffEntry -> String
(Int -> BackoffEntry -> ShowS)
-> (BackoffEntry -> String)
-> ([BackoffEntry] -> ShowS)
-> Show BackoffEntry
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> BackoffEntry -> ShowS
showsPrec :: Int -> BackoffEntry -> ShowS
$cshow :: BackoffEntry -> String
show :: BackoffEntry -> String
$cshowList :: [BackoffEntry] -> ShowS
showList :: [BackoffEntry] -> ShowS
Show, BackoffEntry -> BackoffEntry -> Bool
(BackoffEntry -> BackoffEntry -> Bool)
-> (BackoffEntry -> BackoffEntry -> Bool) -> Eq BackoffEntry
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: BackoffEntry -> BackoffEntry -> Bool
== :: BackoffEntry -> BackoffEntry -> Bool
$c/= :: BackoffEntry -> BackoffEntry -> Bool
/= :: BackoffEntry -> BackoffEntry -> Bool
Eq)

-- | An active listener bound to an address with its accept loop thread.
data ActiveListener = ActiveListener
  { ActiveListener -> Listener
alListener   :: !Listener      -- ^ The transport listener (accept, close)
  , ActiveListener -> Async ()
alAcceptLoop :: !(Async ())    -- ^ Background thread accepting connections
  , ActiveListener -> Multiaddr
alAddress    :: !Multiaddr     -- ^ Actual bound address (port 0 resolved)
  }

-- | The Switch: central coordinator of the libp2p networking stack.
--
-- Manages transports, connection pool, protocol handlers, and events.
-- All mutable state is STM-based for safe concurrent access.
data Switch = Switch
  { Switch -> PeerId
swLocalPeerId  :: !PeerId                                            -- ^ This node's peer identity
  , Switch -> KeyPair
swIdentityKey  :: !KeyPair                                           -- ^ This node's key pair
  , Switch -> TVar [Transport]
swTransports   :: !(TVar [Transport])                                -- ^ Registered transports
  , Switch -> TVar (Map PeerId [Connection])
swConnPool     :: !(TVar (Map PeerId [Connection]))                  -- ^ Active connections per peer
  , Switch -> TVar (Map ProtocolId StreamHandler)
swProtocols    :: !(TVar (Map ProtocolId StreamHandler))             -- ^ Protocol registry
  , Switch -> TChan SwitchEvent
swEvents       :: !(TChan SwitchEvent)                               -- ^ Event broadcast channel
  , Switch -> TVar Bool
swClosed       :: !(TVar Bool)                                       -- ^ Whether the switch is shut down
  , Switch -> TVar (Map PeerId BackoffEntry)
swDialBackoffs :: !(TVar (Map PeerId BackoffEntry))                  -- ^ Per-peer dial backoff state
  , Switch -> TVar (Map PeerId (TMVar (Either DialError Connection)))
swPendingDials :: !(TVar (Map PeerId (TMVar (Either DialError Connection)))) -- ^ In-flight dials for dedup
  , Switch -> ResourceManager
swResourceMgr  :: !ResourceManager                                   -- ^ Hierarchical resource manager
  , Switch -> TVar (Map PeerId IdentifyInfo)
swPeerStore    :: !(TVar (Map PeerId IdentifyInfo))                  -- ^ Identify info per peer
  , Switch -> TVar [Connection -> IO ()]
swNotifiers    :: !(TVar [Connection -> IO ()])                      -- ^ Callbacks on new connection
  , Switch -> TVar [ActiveListener]
swListeners    :: !(TVar [ActiveListener])                           -- ^ Active listeners
  }