packages feed

concoct-0.1.0: src/Concoct/View/Rebuild.hs

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}

-- |
-- Module      : Concoct.View.Rebuild
-- Copyright   : (c) Matt Hunzinger, 2026
-- License     : BSD-style (see the LICENSE file in the distribution)
--
-- Maintainer  : matt@hunzinger.me
-- Stability   : provisional
-- Portability : non-portable (GHC extensions)
module Concoct.View.Rebuild (Rebuild (..), runRebuild) where

import Concoct.View.Build
import Concoct.View.Internal
import Concoct.View.Skip
import Concoct.View.Unmount
import Control.Monad
import Control.Monad.State

newtype Rebuild m a = Rebuild {unRebuild :: StateT ViewState m a}
  deriving (Functor, Applicative, Monad)

instance (MonadIO m) => MonadView m (Rebuild m) where
  useState _ = Rebuild $ rebuildState
  useRef _ = Rebuild $ rebuildRef
  useEffect deps f = Rebuild $ do
    mdep <- gets $ peekStack . viewStack
    case mdep of
      Just oldDeps -> do
        deps' <- lift deps
        when (oldDeps /= deps') $ do
          modify $ \vs -> vs {viewStack = setStack deps' (viewStack vs)}
          lift $ f deps'
      Nothing -> error "useEffect: Dependencies not found in stack during rebuild"
  useOnUnmount _ = return ()
  component v = Rebuild $ rebuildComponent unRebuild v
  liftView = Rebuild . lift
  switchView cond vTrue vFalse = Rebuild $ do
    vs <- get
    let (mcond, s') = popStack (viewStack vs)
    case mcond of
      Just oldCond -> do
        cond' <- lift cond
        if oldCond == cond'
          then do
            put vs {viewStack = pushStack cond' s'}
            if cond' then unRebuild vTrue else unRebuild vFalse
          else do
            put vs {viewStack = pushStack cond' s'}
            if oldCond
              then unSkip vTrue
              else unSkip vFalse
            if cond'
              then unBuild vTrue
              else unBuild vFalse
      Nothing -> error "switchView: Condition not found in stack during rebuild"
  listView items f = Rebuild $ do
    vs <- get
    let (mOldItems, s') = popStack (viewStack vs)
    case mOldItems of
      Just oldItems -> do
        items' <- lift items
        put vs {viewStack = pushStack items' s'}
        -- Update shared items
        let common = zip oldItems items'
            oldLen = length oldItems
            newLen = length items'
        forM_ common $ \(old, new) ->
          if old == new
            then unSkip (f old)
            else do
              unSkip (f old)
              unBuild (f new)
        -- Unmount removed items
        when (oldLen > newLen) $
          forM_ (drop newLen oldItems) $ \old -> do
            vs' <- get
            (_, stack') <- lift $ runUnmount (f old) (viewStack vs')
            put vs' {viewStack = stack'}
        -- Build added items
        when (newLen > oldLen) $
          forM_ (drop oldLen items') $ \new ->
            unBuild (f new)
      Nothing -> error "listView: Items not found in stack during rebuild"

runRebuild :: (MonadIO m) => Rebuild m a -> ViewState -> m (a, ViewState)
runRebuild vr = runStateT (unRebuild vr)