-----------------------------------------------------------------------------
{-# LANGUAGE CPP               #-}
{-# LANGUAGE OverloadedStrings #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Miso.Reload
-- Copyright   :  (C) 2016-2026 David M. Johnson
-- License     :  BSD3-style (see the file LICENSE)
-- Maintainer  :  David M. Johnson <code@dmj.io>
-- Stability   :  experimental
-- Portability :  non-portable
--
-- = Overview
--
-- "Miso.Reload" supports hot-reloading of miso applications during
-- interactive development with GHC WASM browser mode (@ghciwatch@ +
-- WASM GHCi). It provides two entry points that replace @startApp@ in
-- your @main@:
--
-- ['reload'] clears @\<head\>@ and @\<body\>@ — full reset on every @:r@; model is lost
-- ['live'] clears @\<body\>@ only — model state survives @:r@
--
-- = reload
--
-- Clears both @\<head\>@ and @\<body\>@, kills any running scheduler thread,
-- and re-mounts the component from scratch. All application state is lost.
-- Use this when you are actively changing the @model@ type.
--
-- @
-- main :: IO ()
-- main = 'reload' 'Miso.Event.Types.defaultEvents' app
-- @
--
-- = live
--
-- Clears only @\<body\>@, then re-mounts the component using the __old
-- model__ value recovered from the previous GHCi session via a C-heap
-- stable pointer. @\<head\>@ injections (stylesheets, scripts) from the
-- previous session are preserved.
--
-- @
-- main :: IO ()
-- main = 'live' 'Miso.Event.Types.defaultEvents' app
-- @
--
-- __Warning__: 'live' is unsafe if you change the @model@ type between
-- reloads (adding, removing, or changing a field's type). Such a change
-- will produce a segfault because the old in-memory model is coerced
-- directly into the new type. Use 'reload' whenever you alter the model
-- schema.
--
-- = See also
--
-- * <https://github.com/haskell-miso/miso-sampler miso-sampler> — reference project demonstrating 'live'
-- * "Miso.Runtime" — 'Miso.Runtime.initComponent' and component lifecycle
-- * "Miso.Event.Types" — 'Miso.Event.Types.defaultEvents' used as first argument
----------------------------------------------------------------------------
module Miso.Reload
  ( -- ** Functions
    reload
  , live
  ) where
-----------------------------------------------------------------------------
import           Control.Concurrent
import           Control.Monad
-----------------------------------------------------------------------------
import           Miso.DSL ((!), jsg, setField)
import qualified Miso.FFI.Internal as FFI
import           Miso.Types (Component(..), Events, App)
import           Miso.String (MisoString)
import           Miso.Runtime (componentModel, initComponent, topLevelComponentId, Hydrate(..))
import           Miso.Runtime.Internal (components, schedulerThread)
-----------------------------------------------------------------------------
import           Miso.Lens
-----------------------------------------------------------------------------
import qualified Data.IntMap.Strict as IM
import           Data.IORef
import           Foreign hiding (void)
import           Foreign.C.Types
-----------------------------------------------------------------------------
foreign import ccall unsafe "miso_x_store"
  x_store :: StablePtr a -> IO ()
-----------------------------------------------------------------------------
foreign import ccall unsafe "miso_x_get"
  x_get :: IO (StablePtr a)
-----------------------------------------------------------------------------
foreign import ccall unsafe "miso_x_exists"
  x_exists :: IO CInt
-----------------------------------------------------------------------------
foreign import ccall unsafe "miso_x_clear"
  x_clear :: IO ()
-----------------------------------------------------------------------------
#define MISO_JS_PATH "js/miso.js"
-----------------------------------------------------------------------------
-- | Clears the \<body\> and \<head\> on each 'reload'.
--
-- Meant to be used with WASM browser mode.
--
-- @
-- main :: IO ()
-- main = 'reload' 'defaultEvents' app
-- @
--
-- N.B. This also resets the internal 'component' state. This means all currently
-- mounted components become unmounted and t'ComponentId' are reset to their
-- original form factory.
--
-- If you'd like to preserve application state between calls to GHCi `:r`, see 'live'.
--
-- @since 1.9.0.0
reload
  :: Eq model
  => Events
  -- ^ Event delegation map (typically 'Miso.Event.Types.defaultEvents')
  -> App model action
  -- ^ Top-level application component to (re-)mount
  -> IO ()
reload :: forall model action.
Eq model =>
Events -> App model action -> IO ()
reload Events
events App model action
vcomp = do
  CInt
exists <- IO CInt
x_exists
  Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (CInt
exists CInt -> CInt -> Bool
forall a. Eq a => a -> a -> Bool
== CInt
1) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
    (Any
_, IORef ThreadId
oldSchedulerRef) <- StablePtr (Any, IORef ThreadId) -> IO (Any, IORef ThreadId)
forall a. StablePtr a -> IO a
deRefStablePtr (StablePtr (Any, IORef ThreadId) -> IO (Any, IORef ThreadId))
-> IO (StablePtr (Any, IORef ThreadId)) -> IO (Any, IORef ThreadId)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< IO (StablePtr (Any, IORef ThreadId))
forall a. IO (StablePtr a)
x_get
    ThreadId -> IO ()
killThread (ThreadId -> IO ()) -> IO ThreadId -> IO ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< IORef ThreadId -> IO ThreadId
forall a. IORef a -> IO a
readIORef IORef ThreadId
oldSchedulerRef
    IO ()
x_clear
  IO ()
clearPage
  IO () -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (Events -> Hydrate -> Bool -> App model action -> IO ()
forall parent model action.
(Eq parent, Eq model) =>
Events
-> Hydrate -> Bool -> Component parent () model action -> IO ()
initComponent Events
events Hydrate
Draw Bool
False App model action
vcomp)
  StablePtr
  (IORef (IntMap (ComponentState Any Any Any Any)), IORef ThreadId)
-> IO ()
forall a. StablePtr a -> IO ()
x_store (StablePtr
   (IORef (IntMap (ComponentState Any Any Any Any)), IORef ThreadId)
 -> IO ())
-> IO
     (StablePtr
        (IORef (IntMap (ComponentState Any Any Any Any)), IORef ThreadId))
-> IO ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< (IORef (IntMap (ComponentState Any Any Any Any)), IORef ThreadId)
-> IO
     (StablePtr
        (IORef (IntMap (ComponentState Any Any Any Any)), IORef ThreadId))
forall a. a -> IO (StablePtr a)
newStablePtr (IORef (IntMap (ComponentState Any Any Any Any))
forall parent props model action.
IORef (IntMap (ComponentState parent props model action))
components, IORef ThreadId
schedulerThread)
-----------------------------------------------------------------------------
-- | Live reloading. Persists all t'Component' `model` between successive GHCi reloads.
--
-- This means application state should persist between GHCi reloads
--
-- Schema changes to 'model' are currently unsupported. If you're
-- changing fields in 'model' (adding, removing, changing a field's type), this
-- will more than likely segfault. If you change the 'view' or 'update' functions
-- it will be fine.
--
-- Use 'reload' if you're changing the 'model' frequently and 'live'
-- if you're adjusting the 'view' / 'update' function logic.
--
-- @
-- main :: IO ()
-- main = 'live' 'defaultEvents' app
-- @
--
-- @since 1.9.0.0
live
  :: Eq model
  => Events
  -- ^ Event delegation map (typically 'Miso.Event.Types.defaultEvents')
  -> App model action
  -- ^ Top-level application component to (re-)mount with preserved model state
  -> IO ()
live :: forall model action.
Eq model =>
Events -> App model action -> IO ()
live Events
events App model action
vcomp = do
  CInt
exists <- IO CInt
x_exists
  if CInt
exists CInt -> CInt -> Bool
forall a. Eq a => a -> a -> Bool
== CInt
1
    then do
      -- clearBody (only clear the body)
      IO ()
clearBody

      -- Deref old state, update new state, set pointer in C heap.
      (IORef (IntMap (ComponentState Any Any model Any))
oldComponentsRef, IORef ThreadId
oldSchedulerRef) <- StablePtr
  (IORef (IntMap (ComponentState Any Any model Any)), IORef ThreadId)
-> IO
     (IORef (IntMap (ComponentState Any Any model Any)), IORef ThreadId)
forall a. StablePtr a -> IO a
deRefStablePtr (StablePtr
   (IORef (IntMap (ComponentState Any Any model Any)), IORef ThreadId)
 -> IO
      (IORef (IntMap (ComponentState Any Any model Any)),
       IORef ThreadId))
-> IO
     (StablePtr
        (IORef (IntMap (ComponentState Any Any model Any)),
         IORef ThreadId))
-> IO
     (IORef (IntMap (ComponentState Any Any model Any)), IORef ThreadId)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< IO
  (StablePtr
     (IORef (IntMap (ComponentState Any Any model Any)),
      IORef ThreadId))
forall a. IO (StablePtr a)
x_get
      ThreadId -> IO ()
killThread (ThreadId -> IO ()) -> IO ThreadId -> IO ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< IORef ThreadId -> IO ThreadId
forall a. IORef a -> IO a
readIORef IORef ThreadId
oldSchedulerRef

      IntMap (ComponentState Any Any model Any)
_oldState <- IORef (IntMap (ComponentState Any Any model Any))
-> IO (IntMap (ComponentState Any Any model Any))
forall a. IORef a -> IO a
readIORef IORef (IntMap (ComponentState Any Any model Any))
oldComponentsRef
      let oldModel :: model
oldModel = (IntMap (ComponentState Any Any model Any)
_oldState IntMap (ComponentState Any Any model Any)
-> Key -> ComponentState Any Any model Any
forall a. IntMap a -> Key -> a
IM.! Key
topLevelComponentId) ComponentState Any Any model Any
-> Lens (ComponentState Any Any model Any) model -> model
forall record field. record -> Lens record field -> field
^. Lens (ComponentState Any Any model Any) model
forall parent props model action.
Lens (ComponentState parent props model action) model
componentModel
          initialVComp :: App model action
initialVComp = App model action
vcomp { model = oldModel }

      -- Overwrite new components state with old components state
      IORef (IntMap (ComponentState Any Any model Any))
-> IntMap (ComponentState Any Any model Any) -> IO ()
forall a. IORef a -> a -> IO ()
atomicWriteIORef IORef (IntMap (ComponentState Any Any model Any))
forall parent props model action.
IORef (IntMap (ComponentState parent props model action))
components IntMap (ComponentState Any Any model Any)
_oldState

      -- Perform initial draw, this will fetch the model from the old component state
      -- and overwrite the old state with the new state for everything else.
      Events -> Hydrate -> Bool -> App model action -> IO ()
forall parent model action.
(Eq parent, Eq model) =>
Events
-> Hydrate -> Bool -> Component parent () model action -> IO ()
initComponent Events
events Hydrate
Draw Bool
True App model action
initialVComp

      -- Don't forget to flush (native mobile needs this too)
      IO ()
FFI.flush

      -- Clear and set static ptr to use new state (new CAF state)
      IO ()
x_clear
      StablePtr
  (IORef (IntMap (ComponentState Any Any Any Any)), IORef ThreadId)
-> IO ()
forall a. StablePtr a -> IO ()
x_store (StablePtr
   (IORef (IntMap (ComponentState Any Any Any Any)), IORef ThreadId)
 -> IO ())
-> IO
     (StablePtr
        (IORef (IntMap (ComponentState Any Any Any Any)), IORef ThreadId))
-> IO ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< (IORef (IntMap (ComponentState Any Any Any Any)), IORef ThreadId)
-> IO
     (StablePtr
        (IORef (IntMap (ComponentState Any Any Any Any)), IORef ThreadId))
forall a. a -> IO (StablePtr a)
newStablePtr (IORef (IntMap (ComponentState Any Any Any Any))
forall parent props model action.
IORef (IntMap (ComponentState parent props model action))
components, IORef ThreadId
schedulerThread)
    else do
      -- This means it is initial load, just store the pointer.
      IO () -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (Events -> Hydrate -> Bool -> App model action -> IO ()
forall parent model action.
(Eq parent, Eq model) =>
Events
-> Hydrate -> Bool -> Component parent () model action -> IO ()
initComponent Events
events Hydrate
Draw Bool
False App model action
vcomp)
      StablePtr
  (IORef (IntMap (ComponentState Any Any Any Any)), IORef ThreadId)
-> IO ()
forall a. StablePtr a -> IO ()
x_store (StablePtr
   (IORef (IntMap (ComponentState Any Any Any Any)), IORef ThreadId)
 -> IO ())
-> IO
     (StablePtr
        (IORef (IntMap (ComponentState Any Any Any Any)), IORef ThreadId))
-> IO ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< (IORef (IntMap (ComponentState Any Any Any Any)), IORef ThreadId)
-> IO
     (StablePtr
        (IORef (IntMap (ComponentState Any Any Any Any)), IORef ThreadId))
forall a. a -> IO (StablePtr a)
newStablePtr (IORef (IntMap (ComponentState Any Any Any Any))
forall parent props model action.
IORef (IntMap (ComponentState parent props model action))
components, IORef ThreadId
schedulerThread)
-----------------------------------------------------------------------------
clearPage, clearBody, clearHead :: IO ()
clearPage :: IO ()
clearPage = IO ()
clearBody IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> IO ()
clearHead
clearBody :: IO ()
clearBody = do
  JSVal
body_ <- MisoString -> IO JSVal
jsg MisoString
"document" IO JSVal -> MisoString -> IO JSVal
forall o. ToObject o => o -> MisoString -> IO JSVal
! (MisoString
"body" :: MisoString)
  JSVal -> MisoString -> MisoString -> IO ()
forall o v.
(ToObject o, ToJSVal v) =>
o -> MisoString -> v -> IO ()
setField JSVal
body_ MisoString
"innerHTML" (MisoString
"" :: MisoString)
clearHead :: IO ()
clearHead = do
  JSVal
head_ <- MisoString -> IO JSVal
jsg MisoString
"document" IO JSVal -> MisoString -> IO JSVal
forall o. ToObject o => o -> MisoString -> IO JSVal
! (MisoString
"head" :: MisoString)
  JSVal -> MisoString -> MisoString -> IO ()
forall o v.
(ToObject o, ToJSVal v) =>
o -> MisoString -> v -> IO ()
setField JSVal
head_ MisoString
"innerHTML" (MisoString
"" :: MisoString)
-----------------------------------------------------------------------------