{-# LANGUAGE TemplateHaskell #-}

module Polysemy.View
  ( -- * Effect
    View (..)

    -- * Actions
  , see

    -- * Interpretations
  , viewToState
  , viewToInput
  ) where

import Polysemy
import Polysemy.Input
import Polysemy.State
import Polysemy.Tagged


------------------------------------------------------------------------------
-- | A 'View' is an expensive computation that should be cached.
data View v m a where
  See :: View v m v

makeSem ''View


------------------------------------------------------------------------------
-- | Transform a 'View' into an 'Input'.
viewToInput
    :: forall v i r a
     . Member (Input i) r
    => (i -> v)
    -> Sem (View v ': r) a
    -> Sem r a
viewToInput f = interpret $ \case
  See -> f <$> input


------------------------------------------------------------------------------
-- | Get a 'View' as an exensive computation over an underlying 'State' effect.
-- This 'View' is only invalidated when the underlying 'State' changes.
viewToState
    :: forall v s r a
     . Member (State s) r
    => (s -> Sem r v)
    -> Sem (View v ': r) a
    -> Sem r a
viewToState f = do
  evalState Dirty
    . untag @"view" @(State (Cached v))
    . intercept @(State s)
      ( \case
        Get -> get
        Put s -> do
          put s
          tag @"view" @(State (Cached v)) $ put $ Dirty @v
      )
    . reinterpret @(View v)
      ( \case
          See -> do
            dirty <- tagged @"view" $ get @(Cached v)
            case dirty of
              Dirty -> do
                s <- get
                v' <- raise $ f s
                tagged @"view" $ put $ Cached v'
                pure v'
              Cached v -> pure v
      )


data Cached a = Cached a | Dirty
  deriving (Eq, Ord, Show, Functor)