{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecursiveDo       #-}
{-# LANGUAGE TypeFamilies      #-}

-- | This makes it easy to create auto-save operations
--   not limited to saves.
module Reflex.Bulmex.Input.Debounce
  ( InputStates(..)
  , defStateAttr
  , withInputDebounceEvt
  , withInput
  )
where

import           Control.Monad.Fix
import           Control.Monad.IO.Class (MonadIO)
import qualified Data.Text              as Text
import           Data.Time
import           Reflex
import           Reflex.Bulmex.Event

-- | Allows widgets to react according with what's happening
data InputStates
  = InputStarted
  | InputBuffered
  | InputProcessed
  | InputAborted
  | InputInitial

-- | do a debounced action, create a widget with the dynamic inputstates
--  that indicates the state of doing the action
--  This allows you to automatically request the server once user is
--  finished with typing for example,
--   while not flooding the server with requests trough debouncing
withInputDebounceEvt
  :: ( PostBuild t m
     , MonadFix m
     , MonadHold t m
     , TriggerEvent t m
     , MonadIO (Performable m)
     , PerformEvent t m
     )
  => NominalDiffTime -- ^ Delay before posting the request
  -> (result -> Bool) -- ^ Was the final requess successfull?
  -> (Dynamic t InputStates -> m (b, Event t inputEvt)) -- ^ Widget body reacting to states
  -> (b -> Event t inputEvt -> m (Event t result)) -- ^ Action function
  -> m (Event t result, b)
withInputDebounceEvt debtime succF stateF =
  withInput (debounce debtime) succF
    $ const
    $ fmap (\(one', two) -> (one', two, one'))
    . stateF

-- | Maps input state to bulmex classes: InputStarted = is-warning for example
defStateAttr :: InputStates -> Text.Text
defStateAttr InputStarted   = "is-warning"
defStateAttr InputBuffered  = "is-warning" -- is-loading
defStateAttr InputProcessed = "is-success"
defStateAttr InputAborted   = "is-danger"
defStateAttr InputInitial   = ""

-- | A general debounce widget
--  This looks a lot like 'actionForm', but it's not the same because
--  form allows user code to decide what
withInput
  :: (PostBuild t m, MonadFix m, MonadHold t m)
  => (Event t inputEvt -> m (Event t inputEvt)) -- ^ change input timeline, eg pure for no change
  -> (result -> Bool) -- ^ Was the final requess successfull?
  -> (  Event t result
     -> Dynamic t InputStates
     -> m (actArgs, Event t inputEvt, finalRes)
     ) -- ^ Widget body reacting to states
  -> (actArgs -> Event t inputEvt -> m (Event t result)) -- ^ Action function
  -> m (Event t result, finalRes)
withInput timeF isSuccessF createTypeEvt reqFunc = mdo
  (someData, typeEvtImmediate, result) <- createTypeEvt postFinished areaState
  typeEvtDeb                           <- timeF typeEvtImmediate
  postFinished                         <- reqFunc someData typeEvtDeb
  areaState                            <-
    holdDyn InputInitial
    $ leftmost
    $ [ InputStarted <$ typeEvtImmediate
      , InputBuffered <$ typeEvtDeb
      , InputProcessed <$ (blockFalse $ isSuccessF <$> postFinished)
      , InputAborted <$ (blockFalse $ not . isSuccessF <$> postFinished)
      ]
  pure (postFinished, result)