content: {-# LANGUAGE RankNTypes #-}
  module Matterhorn.State.ListWindow
    ( listWindowActivateCurrent
    , listWindowActivate
    , listWindowSearchString
    , listWindowMove
    , exitListWindow
    , enterListWindowMode
    , resetListWindowSearch
    , onEventListWindow
    )
  where
  
  import           Prelude ()
  import           Matterhorn.Prelude
  
  import           Brick ( BrickEvent(VtyEvent) )
  import qualified Brick.Widgets.List as L
  import qualified Brick.Widgets.Edit as E
  import qualified Data.Text.Zipper as Z
  import qualified Data.Vector as Vec
  import           Lens.Micro.Platform ( Lens', (%=), (.=) )
  import           Network.Mattermost.Types ( Session, TeamId )
  import qualified Graphics.Vty as Vty
  
  import           Matterhorn.Types
  import           Brick.Keybindings
  import           Matterhorn.State.Common
  import           Matterhorn.State.Editing ( editingKeybindings )
  
  
  -- | Activate the specified list window's selected item by invoking the
  -- window's configured enter keypress handler function.
  listWindowActivateCurrent :: TeamId -> Lens' ChatState (ListWindowState a b) -> MH ()
  listWindowActivateCurrent tId which = do
    mItem <- L.listSelectedElement <$> use (which.listWindowSearchResults)
    case mItem of
        Nothing -> return ()
        Just (_, val) -> listWindowActivate tId which val
  
  -- | Activate the specified list window's selected item by invoking the
  -- window's configured enter keypress handler function.
  listWindowActivate :: TeamId -> Lens' ChatState (ListWindowState a b) -> a -> MH ()
  listWindowActivate tId which val = do
      handler <- use (which.listWindowEnterHandler)
      activated <- handler val
      if activated
         then popMode tId
         else return ()
  
  -- | Get the current search string for the specified window.
  listWindowSearchString :: Lens' ChatState (ListWindowState a b) -> MH Text
  listWindowSearchString which =
      (head . E.getEditContents) <$> use (which.listWindowSearchInput)
  
  -- | Move the list cursor in the specified window.
  listWindowMove :: Lens' ChatState (ListWindowState a b)
                  -- ^ Which window
                  -> (L.List Name a -> L.List Name a)
                  -- ^ How to transform the list in the window
                  -> MH ()
  listWindowMove which how = which.listWindowSearchResults %= how
  
  -- | Clear the state of the specified list window and return to the
  -- Main mode.
  exitListWindow :: TeamId
                  -> Lens' ChatState (ListWindowState a b)
                  -- ^ Which window to reset
                  -> MH ()
  exitListWindow tId which = do
      newList <- use (which.listWindowNewList)
      which.listWindowSearchResults .= newList mempty
      which.listWindowEnterHandler .= (const $ return False)
      popMode tId
  
  -- | Initialize a list window with the specified arguments and switch
  -- to the specified mode.
  enterListWindowMode :: TeamId
                       -> (Lens' ChatState (ListWindowState a b))
                       -- ^ Which window to initialize
                       -> Mode
                       -- ^ The mode to change to
                       -> b
                       -- ^ The window's initial search scope
                       -> (a -> MH Bool)
                       -- ^ The window's enter keypress handler
                       -> (b -> Session -> Text -> IO (Vec.Vector a))
                       -- ^ The window's results fetcher function
                       -> MH ()
  enterListWindowMode tId which mode scope enterHandler fetcher = do
      which.listWindowSearchScope .= scope
      which.listWindowSearchInput.E.editContentsL %= Z.clearZipper
      which.listWindowEnterHandler .= enterHandler
      which.listWindowFetchResults .= fetcher
      which.listWindowSearching .= False
      newList <- use (which.listWindowNewList)
      which.listWindowSearchResults .= newList mempty
      pushMode tId mode
      resetListWindowSearch which
  
  -- | Reset the window's search by initiating a new search request for
  -- the string that is currently in the window's editor. This does
  -- nothing if a search for this window is already in progress.
  resetListWindowSearch :: Lens' ChatState (ListWindowState a b) -> MH ()
  resetListWindowSearch which = do
      searchPending <- use (which.listWindowSearching)
  
      when (not searchPending) $ do
          searchString <- listWindowSearchString which
          which.listWindowSearching .= True
          newList <- use (which.listWindowNewList)
          session <- getSession
          scope <- use (which.listWindowSearchScope)
          fetcher <- use (which.listWindowFetchResults)
          doAsyncWith Preempt $ do
              results <- fetcher scope session searchString
              return $ Just $ Work "resetListWindowSearch" $ do
                  which.listWindowSearchResults .= newList results
                  which.listWindowSearching .= False
  
                  -- Now that the results are available, check to see if the
                  -- search string changed since this request was submitted.
                  -- If so, issue another search.
                  afterSearchString <- listWindowSearchString which
                  when (searchString /= afterSearchString) $ resetListWindowSearch which
  
  -- | Generically handle an event for the list window state targeted
  -- by the specified lens. Automatically dispatches new searches in the
  -- window's editor if the editor contents change.
  onEventListWindow :: Lens' ChatState (ListWindowState a b)
                     -- ^ Which window to dispatch to?
                     -> (KeyConfig KeyEvent -> KeyDispatcher KeyEvent MH)
                     -- ^ The keybinding builder
                     -> Vty.Event
                     -- ^ The event
                     -> MH Bool
  onEventListWindow which keybindings =
      handleEventWith [ mhHandleKeyboardEvent keybindings
                      , handleEditorEvent which
                      ]
  
  handleEditorEvent :: Lens' ChatState (ListWindowState a b) -> Vty.Event -> MH Bool
  handleEditorEvent which e = do
      -- Get the editor content before the event.
      before <- listWindowSearchString which
  
      -- First find a matching keybinding in the keybinding list.
      handled <- mhHandleKeyboardEvent (editingKeybindings (which.listWindowSearchInput)) e
  
      -- If we didn't find a matching binding, just handle the event as a
      -- normal editor input event.
      when (not handled) $
          mhZoom (which.listWindowSearchInput) E.handleEditorEvent (VtyEvent e)
  
      -- Get the editor content after the event. If the string changed,
      -- start a new search.
      after <- listWindowSearchString which
      when (before /= after) $ resetListWindowSearch which
  
      return True
  