packages feed

tinytools-0.1.0.6: src/Potato/Flow/Controller/Goat.hs

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RecursiveDo     #-}

module Potato.Flow.Controller.Goat (
  GoatFocusedArea(..)
  , goatState_hasUnsavedChanges
  , makeGoatState
  , goatState_pFState
  , goatState_selectedTool
  , GoatState(..)

  -- endo style
  , endoGoatCmdSetDefaultParams
  , endoGoatCmdMarkSaved
  , endoGoatCmdSetTool 
  , endoGoatCmdSetDebugLabel
  , endoGoatCmdSetCanvasRegionDim
  , endoGoatCmdWSEvent
  , endoGoatCmdNewFolder 
  , endoGoatCmdLoad
  , endoGoatCmdSetFocusedArea
  , endoGoatCmdMouse
  , endoGoatCmdKeyboard

  -- exposed for testing
  , potatoHandlerInputFromGoatState
) where

import           Relude

import           Potato.Data.Text.Unicode
import           Potato.Flow.BroadPhase
import           Potato.Flow.Configuration
import           Potato.Flow.Controller.Handler
import           Potato.Flow.Controller.Input
import           Potato.Flow.Controller.Manipulator.Box
import           Potato.Flow.Controller.Manipulator.Common
import           Potato.Flow.Controller.Manipulator.Layers
import           Potato.Flow.Controller.Manipulator.Line
import           Potato.Flow.Controller.Manipulator.Pan
import           Potato.Flow.Controller.Manipulator.Select
import           Potato.Flow.Controller.OwlLayers
import           Potato.Flow.Controller.Types
import           Potato.Flow.Llama
import           Potato.Flow.Math
import           Potato.Flow.Owl
import           Potato.Flow.OwlItem
import           Potato.Flow.OwlState
import           Potato.Flow.OwlWorkspace
import           Potato.Flow.Render
import           Potato.Flow.Methods.SEltMethods
import           Potato.Flow.Types
import  Potato.Flow.Preview 
import Potato.Flow.Methods.LlamaWorks

import           Control.Exception                           (assert)
import           Data.Default
import qualified Data.IntMap                                 as IM
import qualified Data.IntSet                                 as IS
import           Data.Maybe
import qualified Data.Sequence                               as Seq
import qualified Data.Text                                   as T


catMaybesSeq :: Seq (Maybe a) -> Seq a
catMaybesSeq = fmap fromJust . Seq.filter isJust

data GoatFocusedArea =
  GoatFocusedArea_Layers
  | GoatFocusedArea_Canvas
  | GoatFocusedArea_Other -- focus is some area that is not owned by tinytools (e.g. the params widgets)
  | GoatFocusedArea_None
  deriving (Eq, Show)

-- TODO move into its own file
data GoatState = GoatState {

    -- TODO make GoatTab
    -- unique to each document
    _goatState_workspace                 :: OwlPFWorkspace
    , _goatState_pan                     :: XY -- panPos is position of upper left corner of canvas relative to screen
    , _goatState_selection               :: Selection
    , _goatState_canvasSelection         :: CanvasSelection
    , _goatState_broadPhaseState         :: BroadPhaseState
    , _goatState_layersState             :: LayersState
    , _goatState_renderedCanvas          :: RenderedCanvasRegion
    , _goatState_renderedSelection       :: RenderedCanvasRegion -- TODO need sparse variant
    , _goatState_handler                 :: SomePotatoHandler
    , _goatState_layersHandler           :: SomePotatoHandler
    -- TODO consider moving into _goatState_workspace
    , _goatState_attachmentMap           :: AttachmentMap -- map of targets to things attached to it. This is a cache that gets updated over time and can be regenerated from the current OwlTree
    , _goatState_renderCache             :: RenderCache

    -- shared across documents
    , _goatState_configuration           :: PotatoConfiguration -- maybe also move PotatoDefaultParameters into this
    , _goatState_potatoDefaultParameters :: PotatoDefaultParameters
    , _goatState_mouseDrag               :: MouseDrag -- last mouse dragging state, this is a little questionable, arguably we should only store stuff needed, not the entire mouseDrag
    , _goatState_screenRegion            :: XY -- the screen dimensions
    , _goatState_clipboard               :: Maybe SEltTree
    , _goatState_focusedArea             :: GoatFocusedArea

    -- TODO you broke this with endo refactor
    -- TODO this isn't even used right now... DELETE ME
    , _goatState_unbrokenInput       :: Text -- grapheme clusters are inputed as several keyboard character events so we track these inputs here

    -- debug stuff (shared across documents)
    , _goatState_debugLabel              :: Text

  } deriving (Show)

makeGoatState :: XY -> (OwlPFState, ControllerMeta) -> GoatState
makeGoatState (V2 screenx screeny) (initialstate, controllermeta) = goat where
    initialowlpfstate = initialstate
    -- initialize broadphase with initial state
    initialAsSuperOwlChanges = IM.mapWithKey (\rid (oem, oe) -> Just $ SuperOwl rid oem oe) . _owlTree_mapping . _owlPFState_owlTree $ initialstate
    (_, initialbp) = update_bPTree initialowlpfstate initialAsSuperOwlChanges emptyBPTree
    initiallayersstate = makeLayersStateFromOwlPFState initialowlpfstate (_controllerMeta_layers controllermeta)

    -- TODO DELETE
    -- TODO wrap this in a helper function in Render
    -- TODO we want to render the whole screen, not just the canvas
    initialCanvasBox = _sCanvas_box . _owlPFState_canvas $ initialowlpfstate
    initialselts = fmap (\(_, oelt) -> _owlItem_subItem oelt) . toList . _owlTree_mapping . _owlPFState_owlTree $ initialowlpfstate
    initialemptyrcr = emptyRenderedCanvasRegion initialCanvasBox
    initialrendercontext = RenderContext {
      _renderContext_owlTree = hasOwlTree_owlTree initialowlpfstate
      , _renderContext_layerMetaMap = _layersState_meta initiallayersstate
      , _renderContext_broadPhase = initialbp -- this is ignored but we may as well set in correctly
      , _renderContext_renderedCanvasRegion = initialemptyrcr
      , _renderContext_cache = emptyRenderCache
    }
    
    -- TODO this is incorrect becaues it doesn't use pan or screenx/y
    initialrc = _renderContext_renderedCanvasRegion $ render initialCanvasBox initialselts initialrendercontext

    -- TODO use this, except this breaks the initial render whne opening tinytools-vty for some reason (it should rerender in the endoSetCanvasSide function aftre postbuild where the screenx/y get set corrcetly but it dosen't)
    --initialrc = _renderContext_renderedCanvasRegion $ render_new (LBox (-(_controllerMeta_pan controllermeta)) sr) (traceShowId $ initialselts) initialrendercontext


    goat = GoatState {
        _goatState_workspace      = fst $ loadOwlPFStateIntoWorkspace (initialstate) emptyWorkspace
        , _goatState_pan             = _controllerMeta_pan controllermeta
        , _goatState_mouseDrag       = def
        , _goatState_handler         = SomePotatoHandler EmptyHandler
        , _goatState_layersHandler   = SomePotatoHandler (def :: LayersHandler)
        , _goatState_configuration = def
        , _goatState_potatoDefaultParameters = def
        , _goatState_attachmentMap = owlTree_makeAttachmentMap (_owlPFState_owlTree initialstate)
        , _goatState_debugLabel      = ""
        , _goatState_selection       = isParliament_empty
        , _goatState_canvasSelection = CanvasSelection Seq.empty
        , _goatState_broadPhaseState = initialbp
        , _goatState_renderedCanvas = initialrc
        , _goatState_renderedSelection = initialemptyrcr
        , _goatState_layersState     = initiallayersstate
        , _goatState_renderCache = emptyRenderCache
        , _goatState_clipboard = Nothing
        , _goatState_focusedArea = GoatFocusedArea_None
        , _goatState_unbrokenInput = ""
        , _goatState_screenRegion = V2 screenx screeny - (_controllerMeta_pan controllermeta)
      }


goatState_pFState :: GoatState -> OwlPFState
goatState_pFState = _owlPFWorkspace_owlPFState . _goatState_workspace

-- TODO instance GoatState HasOwlTree
goatState_owlTree :: GoatState -> OwlTree
goatState_owlTree = _owlPFState_owlTree . goatState_pFState

goatState_hasUnsavedChanges :: GoatState -> Bool
goatState_hasUnsavedChanges = llamaStack_hasUnsavedChanges . _owlPFWorkspace_llamaStack . _goatState_workspace

goatState_selectedTool :: GoatState -> Tool
goatState_selectedTool = fromMaybe Tool_Select . pHandlerTool . _goatState_handler

goatState_hasLocalPreview :: GoatState -> Bool
goatState_hasLocalPreview = owlPFWorkspace_hasLocalPreview . _goatState_workspace


makeHandlerFromNewTool :: GoatState -> Tool -> SomePotatoHandler
makeHandlerFromNewTool GoatState{..} = \case
  Tool_Box    -> SomePotatoHandler $ def { _boxHandler_creation = BoxCreationType_Box }
  Tool_Line   -> SomePotatoHandler $ def { _autoLineHandler_isCreation = True }
  Tool_Select -> makeHandlerFromSelection _goatState_canvasSelection
  Tool_Text   -> SomePotatoHandler $ def { _boxHandler_creation = BoxCreationType_Text }
  Tool_TextArea -> SomePotatoHandler $ def { _boxHandler_creation = BoxCreationType_TextArea }
  Tool_Pan           -> SomePotatoHandler $ (def :: PanHandler)


-- TODO rename to makeHandlerFromCanvasSelection
makeHandlerFromSelection :: CanvasSelection -> SomePotatoHandler
makeHandlerFromSelection selection = case computeSelectionType selection of
  SMTBox         -> SomePotatoHandler $ (def :: BoxHandler)
  SMTBoxText     -> SomePotatoHandler $ (def :: BoxHandler)
  SMTLine        -> SomePotatoHandler $ (def :: AutoLineHandler)
  SMTTextArea    -> SomePotatoHandler $ (def :: BoxHandler)
  SMTBoundingBox -> SomePotatoHandler $ (def :: BoxHandler)
  SMTNone        -> SomePotatoHandler EmptyHandler

maybeUpdateHandlerFromSelection :: SomePotatoHandler -> CanvasSelection -> SomePotatoHandler
maybeUpdateHandlerFromSelection sph selection = case sph of
  -- TODO instead, just check if there is a preview operation or not
  SomePotatoHandler h -> if handlerActiveState_isActive $ pIsHandlerActive h
    then sph
    else makeHandlerFromSelection selection

makeClipboard :: GoatState -> Maybe SEltTree
makeClipboard goatState@GoatState {..} = r where
  r = if isParliament_null _goatState_selection
    then _goatState_clipboard
    else Just $ superOwlParliament_toSEltTree (goatState_owlTree goatState) _goatState_selection

-- TODO move to Potato.Flow.Methods.LlamaWorks
deleteSelectionEvent :: GoatState -> Maybe WSEvent
deleteSelectionEvent gs@GoatState {..} = if isParliament_null _goatState_selection
  then Nothing
  else Just $ WSEApplyPreview dummyShepard dummyShift $ Preview PO_StartAndCommit $ removeEltAndUpdateAttachments_to_llama (goatState_pFState gs) _goatState_attachmentMap (superOwlParliament_toOwlParliament _goatState_selection)

potatoHandlerInputFromGoatState :: GoatState -> PotatoHandlerInput
potatoHandlerInputFromGoatState GoatState {..} = r where
  last_workspace = _goatState_workspace
  last_pFState = _owlPFWorkspace_owlPFState last_workspace
  r = PotatoHandlerInput {
    _potatoHandlerInput_pFState       = last_pFState
    , _potatoHandlerInput_potatoDefaultParameters = _goatState_potatoDefaultParameters
    , _potatoHandlerInput_broadPhase  = _goatState_broadPhaseState
    , _potatoHandlerInput_renderCache = _goatState_renderCache

    -- the screen region in canvas space
    , _potatoHandlerInput_screenRegion = LBox (-_goatState_pan) _goatState_screenRegion

    , _potatoHandlerInput_layersState     = _goatState_layersState
    , _potatoHandlerInput_selection   = _goatState_selection
    , _potatoHandlerInput_canvasSelection = _goatState_canvasSelection
  }


-- | filters out keyboard input based on the configuration
-- must provide last character-unbroken sequence of text input in order to detect grapheme cluster
-- relies on assumption 🙈
-- let ...(n-1)(n) be a sequence of codepoints that is a grapheme cluster
-- then ...(n-1) is also a grapheme cluster
potatoModifyKeyboardKey :: PotatoConfiguration -> Text -> KeyboardData -> Maybe KeyboardData
potatoModifyKeyboardKey PotatoConfiguration {..} lastUnbrokenCharacters k = case k of
  KeyboardData (KeyboardKey_Char c) mods -> r where
    fulltext = T.snoc lastUnbrokenCharacters c
    r = if not _potatoConfiguration_allowGraphemeClusters && endsInGraphemeCluster fulltext
      then Nothing
      else case _potatoConfiguration_allowOrReplaceUnicodeWideChars of
        Nothing -> Just k
        Just x -> if getCharWidth c > 1
          then maybe Nothing (\nc -> Just (KeyboardData (KeyboardKey_Char nc) mods))  x
          else Just k
  _ -> Just k






endoGoatCmdSetDefaultParams :: SetPotatoDefaultParameters -> GoatState -> GoatState
endoGoatCmdSetDefaultParams spdp gs = gs {
    _goatState_potatoDefaultParameters = potatoDefaultParameters_set (_goatState_potatoDefaultParameters gs) spdp
  }

endoGoatCmdMarkSaved :: () -> GoatState -> GoatState
endoGoatCmdMarkSaved _ gs = gs {
    _goatState_workspace = markWorkspaceSaved (_goatState_workspace gs)
  }

-- TODO do I need to do anything else after setting handler??
endoGoatCmdSetTool :: Tool -> GoatState -> GoatState
endoGoatCmdSetTool tool gs = gs {
    _goatState_handler = makeHandlerFromNewTool gs tool
  }

endoGoatCmdSetDebugLabel :: Text -> GoatState -> GoatState
endoGoatCmdSetDebugLabel x gs = gs {
    _goatState_debugLabel = x
  }

endoGoatCmdSetCanvasRegionDim :: V2 Int -> GoatState -> GoatState
endoGoatCmdSetCanvasRegionDim x gs = r where
  -- set the screen region
  -- rerender
  gs_1 = gs {
      _goatState_screenRegion = x
    }
  r = goat_rerenderAfterMove gs_1
  



endoGoatCmdWSEvent :: WSEvent -> GoatState -> GoatState
endoGoatCmdWSEvent wsev gs = goat_applyWSEvent WSEventType_Local_Refresh wsev gs

endoGoatCmdNewFolder :: Text -> GoatState -> GoatState
endoGoatCmdNewFolder x gs = goat_applyWSEvent WSEventType_Local_Refresh newFolderEv gs where
  pfs = goatState_pFState gs
  folderPos = lastPositionInSelection (_owlPFState_owlTree pfs) (_goatState_selection gs)
  newFolderEv = WSEApplyPreview dummyShepard dummyShift $ Preview PO_StartAndCommit $ makeAddFolderLlama pfs (folderPos, x)

endoGoatCmdLoad :: (SPotatoFlow, ControllerMeta) -> GoatState -> GoatState
endoGoatCmdLoad (spf, cm) gs = r where
  gs_after_load = goat_applyWSEvent WSEventType_Local_Refresh (WSELoad spf) gs
  gs_for_move = gs_after_load {
      _goatState_pan = _controllerMeta_pan cm
      , _goatState_layersState = makeLayersStateFromOwlPFState (goatState_pFState gs_after_load) (_controllerMeta_layers cm)
      -- NOTE _goatState_layersHandler gets set by goat_applyWSEvent during refresh
    }
  -- rerenderAfterMove since we may have loaded a different pan
  r = goat_rerenderAfterMove gs_for_move

goat_setFocusedArea :: GoatFocusedArea -> GoatState -> GoatState
goat_setFocusedArea gfa goatState = r where
  didchange = gfa /= _goatState_focusedArea goatState
  goatstatewithnewfocus = goatState { _goatState_focusedArea = gfa }
  noactionneeded = goatstatewithnewfocus
  potatoHandlerInput = potatoHandlerInputFromGoatState goatState
  r = if didchange && pHandlerName (_goatState_layersHandler goatState) == handlerName_layersRename
    -- special case to force confirmation on layersRename handler which is special because it does not generate a Preview
    then let
        goatState_afterAction = case pHandleKeyboard (_goatState_layersHandler goatState) potatoHandlerInput (KeyboardData KeyboardKey_Return []) of
          Nothing -> noactionneeded
          Just pho -> goat_processLayersHandlerOutput pho goatstatewithnewfocus
      in assert (_goatState_focusedArea goatState == GoatFocusedArea_Layers) $ goatState_afterAction
    else noactionneeded


endoGoatCmdSetFocusedArea :: GoatFocusedArea -> GoatState -> GoatState
endoGoatCmdSetFocusedArea = goat_setFocusedArea


endoGoatCmdMouse :: LMouseData -> GoatState -> GoatState
endoGoatCmdMouse mouseData goatState = r where
  sameSource = _mouseDrag_isLayerMouse (_goatState_mouseDrag goatState) == _lMouseData_isLayerMouse mouseData
  mouseSourceFailure = _mouseDrag_state (_goatState_mouseDrag goatState) /= MouseDragState_Up && not sameSource
  mouseDrag = case _mouseDrag_state (_goatState_mouseDrag goatState) of
    MouseDragState_Up        -> newDrag mouseData
    MouseDragState_Cancelled -> (continueDrag mouseData (_goatState_mouseDrag goatState)) { _mouseDrag_state = MouseDragState_Cancelled }

    _                        ->  continueDrag mouseData (_goatState_mouseDrag goatState)
  canvasDrag = toRelMouseDrag last_pFState (_goatState_pan goatState) mouseDrag
  goatState_withFocusedArea = goat_setFocusedArea (if isLayerMouse then GoatFocusedArea_Layers else GoatFocusedArea_Canvas) goatState
  goatState_withNewMouse = goatState_withFocusedArea {
      _goatState_mouseDrag = mouseDrag
    }
  noChangeOutput = goatState_withNewMouse
  -- TODO maybe split this case out to endoGoatCmdLayerMouse
  isLayerMouse = _mouseDrag_isLayerMouse mouseDrag

  potatoHandlerInput = potatoHandlerInputFromGoatState goatState_withNewMouse
  last_pFState = goatState_pFState goatState_withNewMouse
  handler = _goatState_handler goatState_withNewMouse


  r = case _mouseDrag_state mouseDrag of

    -- TODO soft failure
    _ | mouseSourceFailure -> error "invalid mouse sequence due to source"

    -- TODO assert false here? I'm pretty sure this should never happen
    -- if mouse was cancelled, update _goatState_mouseDrag accordingly
    MouseDragState_Cancelled -> if _lMouseData_isRelease mouseData
      then goatState_withNewMouse { _goatState_mouseDrag = def }
      else noChangeOutput -- still cancelled

    -- if mouse is intended for layers
    _ | isLayerMouse -> case pHandleMouse (_goatState_layersHandler goatState) potatoHandlerInput (RelMouseDrag mouseDrag) of
      Just pho -> goat_processLayersHandlerOutput pho goatState_withNewMouse
      Nothing  -> noChangeOutput

    -- if middle mouse button, create a temporary PanHandler
    MouseDragState_Down | _lMouseData_button mouseData == MouseButton_Middle -> r where
      panhandler = def { _panHandler_maybePrevHandler = Just (SomePotatoHandler handler) }
      r = case pHandleMouse panhandler potatoHandlerInput canvasDrag of
        Just pho -> goat_processCanvasHandlerOutput pho goatState_withNewMouse
        Nothing -> error "PanHandler expected to capture mouse input"

    -- pass onto canvas handler
    _ -> case pHandleMouse handler potatoHandlerInput canvasDrag of
      Just pho -> goat_processCanvasHandlerOutput pho goatState_withNewMouse

      -- input not captured by handler, pass onto select or select+drag
      Nothing | _mouseDrag_state mouseDrag == MouseDragState_Down -> case pHandleMouse (def :: SelectHandler) potatoHandlerInput canvasDrag of
        Just pho -> goat_processCanvasHandlerOutput pho goatState_withNewMouse
        Nothing -> error "handler was expected to capture this mouse state"

      Nothing -> error $ "handler " <> show (pHandlerName handler) <> "was expected to capture mouse state " <> show (_mouseDrag_state mouseDrag)




endoGoatCmdKeyboard :: KeyboardData -> GoatState -> GoatState
endoGoatCmdKeyboard kbd' goatState = r_d0 where
  -- TODO you need to do reset logic for this (basically, reset it anytime there was a non-keyboard event)
  last_unbrokenInput = _goatState_unbrokenInput goatState
  next_unbrokenInput = case kbd' of
    KeyboardData (KeyboardKey_Char c) _ -> T.snoc last_unbrokenInput c
    _ -> ""
  mkbd =   potatoModifyKeyboardKey (_goatState_configuration goatState) last_unbrokenInput kbd'
  goatState_withKeyboard =  goatState { _goatState_unbrokenInput = next_unbrokenInput}
  potatoHandlerInput = potatoHandlerInputFromGoatState goatState_withKeyboard
  last_pFState = goatState_pFState goatState_withKeyboard
  -- TODO rename to canvasHandler
  handler = _goatState_handler goatState_withKeyboard

  r_d0 = case mkbd of
    Nothing -> goatState_withKeyboard
    -- special case, treat escape cancel mouse drag as a mouse input
    Just (KeyboardData KeyboardKey_Esc _) | mouseDrag_isActive (_goatState_mouseDrag goatState_withKeyboard) -> r where
      canceledMouse = cancelDrag (_goatState_mouseDrag goatState_withKeyboard)
      goatState_withNewMouse = goatState_withKeyboard {
          _goatState_mouseDrag = canceledMouse

          -- escape will cancel mouse focus
          -- TODO this isn't correct, you have some handlers that cancel into each other, you should only reset to GoatFocusedArea_None if they canceled to Nothing
          , _goatState_focusedArea = GoatFocusedArea_None

        }

      -- TODO use _goatState_focusedArea instead
      r = if _mouseDrag_isLayerMouse (_goatState_mouseDrag goatState_withKeyboard)
        then case pHandleMouse (_goatState_layersHandler goatState_withKeyboard) potatoHandlerInput (RelMouseDrag canceledMouse) of
          Just pho -> goat_processLayersHandlerOutput pho goatState_withNewMouse
          -- TODO I think this is fine, but maybe you should you clear the handler instead?
          Nothing  -> goatState_withNewMouse
        else case pHandleMouse handler potatoHandlerInput (toRelMouseDrag last_pFState (_goatState_pan goatState_withKeyboard) canceledMouse) of
          Just pho -> goat_processCanvasHandlerOutput pho goatState_withNewMouse
          -- TODO I think this is fine, but maybe you should you clear the handler instead?
          Nothing  -> goatState_withNewMouse

    -- we are in the middle of mouse drag, ignore all keyboard inputs
    -- perhaps a better way to do this is to have handlers capture all inputs when active
    Just _ | mouseDrag_isActive (_goatState_mouseDrag goatState_withKeyboard) -> goatState_withKeyboard

    Just kbd ->
      let
        maybeHandleLayers = do
          guard $ _mouseDrag_isLayerMouse (_goatState_mouseDrag goatState_withKeyboard)
          pho <- pHandleKeyboard (_goatState_layersHandler goatState_withKeyboard) potatoHandlerInput kbd
          return $ goat_processLayersHandlerOutput pho goatState_withKeyboard
      in case maybeHandleLayers of
        Just x -> x
        Nothing -> case pHandleKeyboard handler potatoHandlerInput kbd of
          Just pho -> goat_processCanvasHandlerOutput pho goatState_withKeyboard
          -- input not captured by handler
          -- TODO consider wrapping this all up in KeyboardHandler or something? Unfortunately, copy needs to modify goatState_withKeyboard which PotatoHandlerOutput can't atm
          Nothing -> case kbd of
            KeyboardData KeyboardKey_Esc _ -> case _mouseDrag_state (_goatState_mouseDrag goatState_withKeyboard) of
                    x | x == MouseDragState_Up || x == MouseDragState_Cancelled -> goat_setSelection False isParliament_empty goatState_withKeyboard
                    _                        -> goatState_withKeyboard

            KeyboardData k [] | k == KeyboardKey_Delete || k == KeyboardKey_Backspace -> case deleteSelectionEvent goatState_withKeyboard of
              Nothing -> goatState_withKeyboard
              Just wsev -> goat_applyWSEvent WSEventType_Local_Refresh wsev goatState_withKeyboard
            KeyboardData (KeyboardKey_Char 'c') [KeyModifier_Ctrl] -> r where
              copied = makeClipboard goatState_withKeyboard
              r = goatState_withKeyboard { _goatState_clipboard = copied }
            KeyboardData (KeyboardKey_Char 'x') [KeyModifier_Ctrl] -> r where
              copied = makeClipboard goatState_withKeyboard
              goatState_withClipboard = goatState_withKeyboard { _goatState_clipboard = copied }
              r = case deleteSelectionEvent goatState_withKeyboard of
                Nothing -> goatState_withClipboard
                Just wsev -> goat_applyWSEvent WSEventType_Local_Refresh wsev goatState_withClipboard
            KeyboardData (KeyboardKey_Char 'v') [KeyModifier_Ctrl] -> case _goatState_clipboard goatState_withKeyboard of
              Nothing    -> goatState_withKeyboard
              Just stree -> r where
                offsetstree = offsetSEltTree (V2 1 1) stree
                minitree' = owlTree_fromSEltTree offsetstree
                -- reindex the tree so there are no collisions with the current state
                maxid1 = owlTree_maxId minitree' + 1
                maxid2 = owlPFState_nextId (_owlPFWorkspace_owlPFState (_goatState_workspace goatState_withKeyboard))
                minitree = owlTree_reindex (max maxid1 maxid2) minitree'
                spot = lastPositionInSelection (goatState_owlTree goatState_withKeyboard) (_goatState_selection goatState_withKeyboard)
                treePastaEv = WSEApplyPreview dummyShepard dummyShift $ Preview PO_StartAndCommit $ makePFCLlama $ OwlPFCNewTree (minitree, spot)
                r = goat_applyWSEvent WSEventType_Local_Refresh treePastaEv (goatState_withKeyboard { _goatState_clipboard = Just offsetstree })
            KeyboardData (KeyboardKey_Char 'z') [KeyModifier_Ctrl] -> r where
              r = goat_applyWSEvent WSEventType_Local_Refresh WSEUndo goatState_withKeyboard
            KeyboardData (KeyboardKey_Char 'y') [KeyModifier_Ctrl] -> r where
              r = goat_applyWSEvent WSEventType_Local_Refresh WSERedo goatState_withKeyboard
            -- tool hotkeys
            KeyboardData (KeyboardKey_Char key) _ -> r where
              mtool = case key of
                'v' -> Just Tool_Select
                'p' -> Just Tool_Pan
                'b' -> Just Tool_Box
                'l' -> Just Tool_Line
                't' -> Just Tool_Text
                'n' -> Just Tool_TextArea
                _   -> Nothing
              newHandler = maybe (_goatState_handler goatState_withKeyboard) (makeHandlerFromNewTool goatState_withKeyboard) mtool
              -- TODO should I call a goat_setHandler function instead?
              r = goatState_withKeyboard { _goatState_handler = newHandler }
            _ -> goatState_withKeyboard


goat_renderCanvas_move :: RenderContext -> XY -> XY -> (RenderContext, Bool)
goat_renderCanvas_move rc@RenderContext {..} pan sr = r where
  newBox = LBox (-pan) sr
  didScreenRegionMove = _renderedCanvasRegion_box _renderContext_renderedCanvasRegion /= newBox
  r = if didScreenRegionMove
    then (moveRenderedCanvasRegion newBox rc, True)
    else (rc, False)


goat_renderCanvas_update :: RenderContext -> NeedsUpdateSet -> SuperOwlChanges -> RenderContext
goat_renderCanvas_update rc needsupdateaabbs cslmap = r where
  r = if IM.null cslmap
    then rc
    else updateCanvas cslmap needsupdateaabbs rc


-- USAGE this function is a little sneaky, we pass in the canvas RenderContext and alter it to use it for selection rendering
-- So you want to pull out the _renderContext_renderedCanvasRegion but throw away the rest of the render context from the output
-- I guess you also want to pull out _renderContext_cache?
-- In the future, selection rendering will have its own context and you won't want to do this I guess?
goat_renderCanvas_selection :: RenderContext -> SuperOwlParliament -> RenderContext
goat_renderCanvas_selection rc_from_canvas next_selection = r where
  newBox = _renderedCanvasRegion_box $ _renderContext_renderedCanvasRegion rc_from_canvas
  rendercontext_forSelection = rc_from_canvas {
      -- NOTE this will render hidden stuff that's selected via layers!!
      _renderContext_layerMetaMap = IM.empty
      -- empty canvas to render our selection in, we just re-render everything for now (in the future you can try and do partial rendering though)
      , _renderContext_renderedCanvasRegion = emptyRenderedCanvasRegion newBox
    }
  selectionselts = toList . fmap _superOwl_id $ unSuperOwlParliament next_selection
  r = render_new newBox selectionselts rendercontext_forSelection

  -- TODO just DELETE this...
  {- TODO render only parts of selection that have changed TODO broken
  next_renderedSelection' = if didScreenRegionMove
    then moveRenderedCanvasRegion next_broadPhaseState (owlTree_withCacheResetOnAttachments) newBox _goatState_renderedSelection
    else _goatState_renderedSelection
  prevSelChangeMap = IM.fromList . toList . fmap (\sowl -> (_superOwl_id sowl, Nothing)) $ unSuperOwlParliament _goatState_selection
  curSelChangeMap = IM.fromList . toList . fmap (\sowl -> (_superOwl_id sowl, Just sowl)) $ unSuperOwlParliament next_selection
  -- TODO you can be even smarter about this by combining cslmap_forRendering I think
  cslmapForSelectionRendering = curSelChangeMap `IM.union` prevSelChangeMap
  -- you need to do something like this but this is wrong....
  --(needsupdateaabbsforrenderselection, _) = update_bPTree cslmapForSelectionRendering (_broadPhaseState_bPTree next_broadPhaseState)
  needsupdateaabbsforrenderselection = needsupdateaabbs
  next_renderedSelection = if IM.null cslmapForSelectionRendering
    then next_renderedSelection'
    else updateCanvas cslmapForSelectionRendering needsupdateaabbsforrenderselection next_broadPhaseState pFState_withCacheResetOnAttachments next_renderedSelection'
  -}

renderContextFromGoatState :: GoatState -> RenderContext
renderContextFromGoatState goatState = RenderContext {
    _renderContext_cache = _goatState_renderCache goatState
    , _renderContext_owlTree = _owlPFState_owlTree (goatState_pFState goatState)
    , _renderContext_layerMetaMap = _layersState_meta (_goatState_layersState goatState)
    , _renderContext_broadPhase = _goatState_broadPhaseState goatState
    , _renderContext_renderedCanvasRegion = _goatState_renderedCanvas goatState
  }

goat_addPan :: XY -> GoatState -> GoatState
goat_addPan (V2 dx dy) goatState = r where
  -- set the pan
  -- render move
  -- render selection
  V2 cx0 cy0 = _goatState_pan goatState
  next_pan = V2 (cx0+dx) (cy0 + dy) 

  gs_1 = goatState {
      _goatState_pan = next_pan
    }
  
  r = goat_rerenderAfterMove gs_1

goat_rerenderAfterMove :: GoatState -> GoatState
goat_rerenderAfterMove goatState = r where
  -- render move
  -- render selection
  rc = renderContextFromGoatState goatState
  (rc_aftermove, _) = goat_renderCanvas_move rc (_goatState_pan goatState) (_goatState_screenRegion goatState)
  rc_afterselection = goat_renderCanvas_selection rc_aftermove (_goatState_selection goatState)
  r = goatState {
      _goatState_renderedCanvas = _renderContext_renderedCanvasRegion rc_aftermove
      , _goatState_renderedSelection = _renderContext_renderedCanvasRegion rc_afterselection
    }

computeCanvasSelection :: GoatState -> CanvasSelection
computeCanvasSelection goatState = r where
  pfs = goatState_pFState goatState
  filterHiddenOrLocked sowl = not $ layerMetaMap_isInheritHiddenOrLocked (_owlPFState_owlTree pfs) (_superOwl_id sowl) (_layersState_meta (_goatState_layersState goatState))
  r = superOwlParliament_convertToCanvasSelection (_owlPFState_owlTree pfs) filterHiddenOrLocked (_goatState_selection goatState)


goat_autoExpandFoldersOfSelection :: GoatState -> GoatState
goat_autoExpandFoldersOfSelection goatState = r where
  -- auto expand folders for selected elements + (this will also auto expand when you drag or paste stuff into a folder)
  -- NOTE this will prevent you from ever collapsing a folder that has a selected child in it (that's not true, you can still collapse it but it will rexpand the moment you make any changes, which might be kind of buggy)
  -- so maybe auto expand should only happen on newly created elements or add a way to detect for newly selected elements (e.g. diff between old selection)
  next_layersState = expandAllCollapsedParents (_goatState_selection goatState) (goatState_pFState goatState) (_goatState_layersState goatState)
  r = goatState { _goatState_layersState = next_layersState }


goat_setSelection :: Bool -> SuperOwlParliament -> GoatState -> GoatState
goat_setSelection add selection goatState = r where

  -- set the new selection
  ot = hasOwlTree_owlTree . goatState_pFState $ goatState
  next_selection = SuperOwlParliament . Seq.sortBy (owlTree_superOwl_comparePosition ot) . unSuperOwlParliament $ if add
    then superOwlParliament_disjointUnionAndCorrect ot (_goatState_selection goatState) selection
    else selection
  goatState_afterSelection = goat_autoExpandFoldersOfSelection goatState { _goatState_selection = next_selection }

  -- set the new canvas selection
  -- create new handler as appropriate
  -- rerender selection
  next_canvasSelection = computeCanvasSelection goatState_afterSelection
  next_handler = maybeUpdateHandlerFromSelection (_goatState_handler goatState_afterSelection) next_canvasSelection
  -- MAYBE TODO consider rendering selected hidden/locked stuff too (it's still possible to select them via layers)? 
  -- Except we removed it from the BroadPhase already. And it would be weird because you bulk selected you would edit only the non-hidden/locked stuff
  rc_afterselection = goat_renderCanvas_selection (renderContextFromGoatState goatState_afterSelection) (SuperOwlParliament $ unCanvasSelection next_canvasSelection)
  r = goatState_afterSelection {
      _goatState_handler = next_handler
      , _goatState_renderedSelection = _renderContext_renderedCanvasRegion rc_afterselection
      , _goatState_canvasSelection = next_canvasSelection
    }



goat_setLayersStateWithChangesFromToggleHide :: LayersState -> SuperOwlChanges -> GoatState -> GoatState
goat_setLayersStateWithChangesFromToggleHide ls changes goatState = r where
  
  -- set the layers state
  goatState_afterLayers = goatState {
      _goatState_layersState = ls
    }

  -- set the canvas selection and handler
  next_canvasSelection = computeCanvasSelection $ goatState_afterLayers
  goatState_afterSelection = goatState_afterLayers {
      _goatState_canvasSelection = next_canvasSelection  
      , _goatState_handler = assert (not . handlerActiveState_isActive $ pIsHandlerActive (_goatState_handler goatState_afterLayers)) makeHandlerFromSelection next_canvasSelection
    }

  -- set the broadphase
  (needsupdateaabbs, next_broadPhaseState) = update_bPTree (goatState_pFState goatState_afterSelection) changes (_broadPhaseState_bPTree (_goatState_broadPhaseState goatState_afterSelection))
  goatState_afterUpdateBroadPhase = goatState_afterSelection { _goatState_broadPhaseState = next_broadPhaseState }

  -- render changes
  -- render selection
  rc = renderContextFromGoatState goatState_afterUpdateBroadPhase
  rc_afterupdate = goat_renderCanvas_update rc needsupdateaabbs changes
  rc_afterselection = goat_renderCanvas_selection rc_afterupdate ( SuperOwlParliament . unCanvasSelection $ next_canvasSelection)
  r = goatState_afterUpdateBroadPhase {
      -- MAYBE TODO refresh the layers handler (), this might be relevant if we support shared lock/hide state of layers in the future
      --, _goatState_layersHandler = fromMaybe (SomePotatoHandler (def :: LayersHandler)) ....
      _goatState_renderedCanvas = _renderContext_renderedCanvasRegion rc_afterupdate
      , _goatState_renderedSelection = _renderContext_renderedCanvasRegion rc_afterselection
    }


goat_processHandlerOutput_noSetHandler :: PotatoHandlerOutput -> GoatState -> GoatState
goat_processHandlerOutput_noSetHandler pho goatState = r where

  r = case _potatoHandlerOutput_action pho of
    HOA_Select x y -> goat_setSelection x y goatState
    HOA_Pan x -> goat_addPan x goatState
    HOA_Layers x y -> goat_setLayersStateWithChangesFromToggleHide x y goatState
    HOA_Preview p -> goat_applyWSEvent WSEventType_Local_NoRefresh (WSEApplyPreview dummyShepard dummyShift p) goatState
    HOA_Nothing -> goatState



goat_processLayersHandlerOutput :: PotatoHandlerOutput -> GoatState -> GoatState
goat_processLayersHandlerOutput pho goatState = r where
  gs' = goatState { 
      _goatState_layersHandler = fromMaybe (_goatState_layersHandler goatState) (_potatoHandlerOutput_nextHandler pho) 
    }
  gs'' = goat_processHandlerOutput_noSetHandler pho gs'

  -- layers output may have changed our selection so update the handler
  prevcanvasselection = _goatState_canvasSelection goatState
  nextcanvasselection = computeCanvasSelection gs''
  r = if prevcanvasselection == nextcanvasselection
    then gs''
    else gs'' { _goatState_handler = makeHandlerFromSelection nextcanvasselection }



goat_processCanvasHandlerOutput :: PotatoHandlerOutput -> GoatState -> GoatState
goat_processCanvasHandlerOutput pho goatState = r where
  canvasSelection = computeCanvasSelection goatState

  next_handler = case _potatoHandlerOutput_nextHandler pho of
    Nothing -> makeHandlerFromSelection canvasSelection
    Just x -> x
  goatState' = goatState { 
      _goatState_handler = next_handler 
    }
  r = goat_processHandlerOutput_noSetHandler pho $ goatState'


data WSEventType = WSEventType_Local_NoRefresh | WSEventType_Local_Refresh | WSEventType_Remote_Refresh deriving (Eq, Show)

wSEventType_isRemote :: WSEventType -> Bool
wSEventType_isRemote WSEventType_Remote_Refresh = True
wSEventType_isRemote _ = False

wSEventType_needsRefresh :: WSEventType -> Bool
wSEventType_needsRefresh WSEventType_Local_Refresh = True
wSEventType_needsRefresh WSEventType_Remote_Refresh = True
wSEventType_needsRefresh _ = False

goat_applyWSEvent :: WSEventType -> WSEvent -> GoatState -> GoatState
goat_applyWSEvent wsetype wse goatState = goatState_final where

  -- apply the event
  last_pFState = goatState_pFState goatState
  (workspace_afterEvent, cslmap_afterEvent) = updateOwlPFWorkspace wse (_goatState_workspace goatState)
  goatState_afterEvent = goatState { _goatState_workspace = workspace_afterEvent }
  pFState_afterEvent = _owlPFWorkspace_owlPFState workspace_afterEvent


  -- compute selection based on changes from updating OwlPFState (i.e. auto select newly created stuff if appropriate)
  (isNewSelection, next_selection) = if IM.null cslmap_afterEvent
    then (False, _goatState_selection goatState_afterEvent)
    else r where

      -- extract elements that got created
      newEltFoldMapFn rid v = case v of
        Nothing     -> []
        Just sowl -> if IM.member rid (_owlTree_mapping . _owlPFState_owlTree $ last_pFState) then [] else [sowl]

      -- NOTE, undoing a deleted element counts as a newly created element (and will be auto-selected)
      newlyCreatedSEltls = IM.foldMapWithKey newEltFoldMapFn cslmap_afterEvent

      sortedNewlyCreatedSEltls = SuperOwlParliament $ Seq.sortBy (owlTree_superOwl_comparePosition $ _owlPFState_owlTree $ pFState_afterEvent) (Seq.fromList newlyCreatedSEltls)
      -- pretty sure this does the same thing..
      --sortedNewlyCreatedSEltls = makeSortedSuperOwlParliament (_owlPFState_owlTree $ pFState_afterEvent) (Seq.fromList newlyCreatedSEltls)

      wasLoad = case wse of
        WSELoad _ -> True
        _             -> False

      -- TODO add `|| wasRemoteChange` condition
      r = if wasLoad || null newlyCreatedSEltls || wSEventType_isRemote wsetype
        -- if there are no newly created elts, we still need to update the selection
        then (\x -> (False, SuperOwlParliament x)) $ catMaybesSeq . flip fmap (unSuperOwlParliament (_goatState_selection goatState_afterEvent)) $ \sowl ->
          case IM.lookup (_superOwl_id sowl) cslmap_afterEvent of
            -- no changes means not deleted
            Nothing       -> Just sowl
            -- if deleted, remove it
            Just Nothing  -> Nothing
            -- it was changed, update selection to newest version
            Just (Just x) -> Just x
        else (True, sortedNewlyCreatedSEltls)
  -- for now, newly created stuff is the same as anything that got auto selected
  --newlyCreatedRids = IS.fromList . toList . fmap _superOwl_id . unSuperOwlParliament $ selectionAfterChanges
  goatState_afterSelection = goatState_afterEvent { _goatState_selection = next_selection }

  -- | refresh the handler if there was a non-canvas or non-local state change |
  goatState_afterRefreshHandler = if wSEventType_needsRefresh wsetype
    then let
        layersHandler = _goatState_layersHandler goatState_afterSelection
        canvasHandler = _goatState_handler goatState_afterSelection 

        -- safe for now, since `potatoHandlerInputFromGoatState` does not use `_goatState_handler/_goatState_layersHandler finalGoatState` which is set to `next_handler/next_layersHandler`
        next_potatoHandlerInput = potatoHandlerInputFromGoatState goatState_afterSelection
        canvasSelection = computeCanvasSelection goatState_afterSelection
        refreshedCanvasHandler = fromMaybe (makeHandlerFromSelection canvasSelection) ( pRefreshHandler canvasHandler next_potatoHandlerInput)
        refreshedLayersHandler = fromMaybe (SomePotatoHandler (def :: LayersHandler)) (pRefreshHandler layersHandler next_potatoHandlerInput)

        -- TODO cancel the preview

        
      in goatState_afterSelection {
          _goatState_handler = refreshedCanvasHandler
          , _goatState_layersHandler = refreshedLayersHandler
        }
    else goatState_afterSelection

  -- | update LayersState based from SuperOwlChanges after applying events |
  next_layersState' = updateLayers pFState_afterEvent cslmap_afterEvent (_goatState_layersState goatState_afterRefreshHandler)
  goatState_afterSetLayersState = goat_autoExpandFoldersOfSelection $ goatState_afterRefreshHandler { _goatState_layersState = next_layersState' }

  -- | set the new handler based on the new Selection and LayersState
  next_canvasSelection = computeCanvasSelection goatState_afterSetLayersState -- (TODO pretty sure this is the same as `canvasSelection = computeCanvasSelection goatState_afterSelection` above..)

  -- we check both the pIsHandlerActive and goatState_hasLocalPreview condition to see if we want to recreate the handler
  -- actually, we could only check pIsHandlerActive if all handlers properly reported their state
  -- TODO the issue with not (goatState_hasLocalPreview goatState_afterSetLayersState) is that we may actually want to regen the handler we just haven't commit its preview yet (i.e. box creation)
  --  NO that's not true because in those cases you can return an explicit commit action or you return HOA_Nothing which should also commit when the handler is replaced
  --  ☝🏽 pretty this TODO is not relevant anymore 🤷🏼‍♀️
  (next_handler, next_workspace) = if (not . handlerActiveState_isActive) (pIsHandlerActive (_goatState_handler goatState_afterSetLayersState)) && not (goatState_hasLocalPreview goatState_afterSetLayersState)
    -- if we replaced the handler, commit its local preview if there was one
    then (makeHandlerFromSelection next_canvasSelection, maybeCommitLocalPreviewToLlamaStackAndClear $ _goatState_workspace goatState_afterSetLayersState)
    else (_goatState_handler goatState_afterSetLayersState, _goatState_workspace goatState_afterSetLayersState)
  goatState_afterSetHandler = goatState_afterSetLayersState {
      _goatState_handler = next_handler
      , _goatState_canvasSelection = next_canvasSelection
      , _goatState_workspace = next_workspace
    }

  -- | update AttachmentMap based on new state and clear the cache on these changes |
  (needsupdateaabbs, goatState_afterUpdateAttachmentsAndRenderState) = goat_updateAttachmentsAndRenderStateFromChanges cslmap_afterEvent goatState_afterSetHandler

  -- | render everything
  rc = renderContextFromGoatState goatState_afterUpdateAttachmentsAndRenderState
  rc_afterRenderCanvas = goat_renderCanvas_update rc needsupdateaabbs cslmap_afterEvent
  rc_afterRenderSelection = goat_renderCanvas_selection rc (_goatState_selection goatState_afterUpdateAttachmentsAndRenderState)

  -- | DONE
  goatState_final = goatState_afterUpdateAttachmentsAndRenderState {
      _goatState_renderedCanvas = _renderContext_renderedCanvasRegion rc_afterRenderCanvas
      , _goatState_renderedSelection = _renderContext_renderedCanvasRegion rc_afterRenderSelection
    }


-- this one also updates attachment map based on changes
goat_updateAttachmentsAndRenderStateFromChanges :: SuperOwlChanges -> GoatState -> ([AABB], GoatState)
goat_updateAttachmentsAndRenderStateFromChanges cslmap_afterEvent goatState = r where

  pFState_afterEvent = goatState_pFState goatState

  -- | update AttachmentMap based on new state and clear the cache on these changes |
  next_attachmentMap = updateAttachmentMapFromSuperOwlChanges cslmap_afterEvent (_goatState_attachmentMap goatState)
  -- we need to union with `_goatState_attachmentMap` as next_attachmentMap does not contain deleted targets and stuff we detached from
  attachmentMapForComputingChanges = IM.unionWith IS.union next_attachmentMap (_goatState_attachmentMap goatState)
  --attachmentChanges = trace "ATTACHMENTS" $ traceShow (IM.size cslmap_afterEvent) $ traceShowId $ getChangesFromAttachmentMap (_owlPFState_owlTree pFState_afterEvent) attachmentMapForComputingChanges cslmap_afterEvent
  attachmentChanges = getChangesFromAttachmentMap (_owlPFState_owlTree pFState_afterEvent) attachmentMapForComputingChanges cslmap_afterEvent

  -- | compute SuperOwlChanges for rendering |
  cslmap_withAttachments = IM.union cslmap_afterEvent attachmentChanges

  -- | clear the cache at places that have changed
  renderCache_resetOnChangesAndAttachments = renderCache_clearAtKeys (_goatState_renderCache goatState) (IM.keys cslmap_withAttachments)

  -- | update the BroadPhase
  (needsupdateaabbs, next_broadPhaseState) = update_bPTree (_owlPFState_owlTree pFState_afterEvent) cslmap_withAttachments (_broadPhaseState_bPTree (_goatState_broadPhaseState goatState))

  r = (needsupdateaabbs, goatState {
      _goatState_attachmentMap = next_attachmentMap
      , _goatState_broadPhaseState = next_broadPhaseState
      , _goatState_renderCache = renderCache_resetOnChangesAndAttachments
    })