{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE OverloadedRecordDot #-}

module MonadicBang.Internal.Effect.Offer where

import Control.Algebra
import Control.Carrier.State.Strict
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as M

-- | Offers a number of things that can be yoinked, but only once
data Offer k v m a where
  Yoink :: k -> Offer k v m (Maybe v)

yoink :: Has (Offer k v) sig m => k -> m (Maybe v)
yoink = send . Yoink

newtype OfferC k v m a = OfferC {getOfferState :: StateC (Map k v) m a}
  deriving newtype (Functor, Applicative, Monad)

-- Returns the result of the computation, along with the remaining offers
runOffer :: Map k v -> OfferC k v m a -> m (Map k v, a)
runOffer o (OfferC s) = runState o s

instance (Algebra sig m, Ord k) => Algebra (Offer k v :+: sig) (OfferC k v m) where
  alg hdl sig ctx = case sig of
    L (Yoink k) -> OfferC do
      (mv, remaining) <- M.updateLookupWithKey (\_ _ -> Nothing) k <$> get
      put remaining
      pure (mv <$ ctx)
    R other -> OfferC (alg ((.getOfferState) . hdl) (R other) ctx)
  {-# INLINE alg #-}
