-- |
-- Module: M.IO.Obs
-- Description: General observer pattern implementation
-- Copyright: (c) axionbuster, 2025
-- License: BSD-3-Clause
--
-- This module provides a general-purpose observer pattern implementation
-- for monitoring and reacting to state changes in STM transactions.
module M.IO.Obs
  ( -- * Observer
    obs,
  )
where

import Control.Monad
import Effectful
import Effectful.Concurrent.STM

-- | A general observer that monitors changes in a shared variable and reacts to them.
--
-- The observer watches a target variable for changes, compares values using a custom
-- comparison function, and executes an action when changes are detected.
--
-- @
-- obs targetvar              -- Variable to observe
--     compareandtransform    -- STM function to compare and transform values
--     reacttochange          -- Action to execute when changes occur
-- @
obs ::
  (Concurrent :> es, Eq a) =>
  -- | Target variable to observe for changes
  TVar a ->
  -- | Function to compare and transform old and new values in STM
  --
  -- Old value is passed first; returned value gets committed and remembered
  (a -> a -> STM a) ->
  -- | Action to execute when changes are detected, with old and new values
  --
  -- Old value is passed first
  (a -> a -> Eff es ()) ->
  Eff es b
obs :: forall (es :: [Effect]) a b.
(Concurrent :> es, Eq a) =>
TVar a -> (a -> a -> STM a) -> (a -> a -> Eff es ()) -> Eff es b
obs TVar a
a a -> a -> STM a
b a -> a -> Eff es ()
c = do
  TVar a
old <- TVar a -> Eff es a
forall (es :: [Effect]) a. (Concurrent :> es) => TVar a -> Eff es a
readTVarIO TVar a
a Eff es a -> (a -> Eff es (TVar a)) -> Eff es (TVar a)
forall a b. Eff es a -> (a -> Eff es b) -> Eff es b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= a -> Eff es (TVar a)
forall (es :: [Effect]) a.
(Concurrent :> es) =>
a -> Eff es (TVar a)
newTVarIO
  Eff es () -> Eff es b
forall (f :: * -> *) a b. Applicative f => f a -> f b
forever do
    (a
ex, a
ez) <- STM (a, a) -> Eff es (a, a)
forall (es :: [Effect]) a. (Concurrent :> es) => STM a -> Eff es a
atomically do
      a
x <- TVar a -> STM a
forall a. TVar a -> STM a
readTVar TVar a
old
      a
y <- TVar a -> STM a
forall a. TVar a -> STM a
readTVar TVar a
a
      Bool -> STM ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (a
x a -> a -> Bool
forall a. Eq a => a -> a -> Bool
/= a
y)
      a
z <- a -> a -> STM a
b a
x a
y
      TVar a -> a -> STM ()
forall a. TVar a -> a -> STM ()
writeTVar TVar a
old a
z
      (a, a) -> STM (a, a)
forall a. a -> STM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (a
x, a
z)
    a -> a -> Eff es ()
c a
ex a
ez