module Gargantext.Components.InputWithAutocomplete where

import DOM.Simple (contains)
import DOM.Simple as DOM
import DOM.Simple.Event as DE
import Data.Maybe (Maybe(..), maybe)
import Data.Nullable (Nullable, null, toMaybe)
import Data.String as S
import Effect (Effect)
import FFI.Simple ((..))
import Gargantext.Utils.Reactix as R2
import Prelude
import Reactix as R
import Reactix.DOM.HTML as H
import Toestand as T

here :: R2.Here
here = R2.here "Gargantext.Components.InputWithAutocomplete"

type Completions = Array String

type Props =
  ( autoFocus :: Boolean
  , autocompleteSearch :: String -> Effect Completions
  , classes :: String
  , onAutocompleteClick :: String -> Effect Unit
  , onEnterPress :: String -> Effect Unit
  , placeholder :: String
  , pattern :: String
  , title :: String
  , state :: T.Box String
  )

inputWithAutocomplete :: R2.Component Props
inputWithAutocomplete = R2.component inputWithAutocompleteCpt

inputWithAutocompleteCpt :: R.Component Props
inputWithAutocompleteCpt = here.component "inputWithAutocomplete" cpt
  where
  cpt
    { autoFocus
    , autocompleteSearch
    , classes
    , onAutocompleteClick
    , onEnterPress
    , placeholder
    , pattern
    , title
    , state
    }
    children = do
    -- States
    state' <- T.useLive T.unequal state
    containerRef <- R.useRef null
    inputRef <- R.useRef null
    completions <- T.useBox []

    R.useEffectOnce' $ do
      cs <- autocompleteSearch state'
      T.write_ cs completions

    -- Render
    pure $
      H.div
        { className: "input-with-autocomplete " <> classes
        , ref: containerRef
        }
        ( [ completionsCpt { completions, onAutocompleteClick, state } []
          , H.input
              { type: "text"
              , ref: inputRef
              , autoFocus
              , className: "form-control"
              , value: state'
              , pattern
              , title
              , placeholder
              , on:
                  { focus: onFocus completions state'
                  , input: onInput completions
                  , change: onInput completions
                  , keyDown: onInputKeyDown inputRef
                  , keyUp: onInputKeyUp inputRef
                  , blur: onBlur completions containerRef
                  }
              }
          ] <> children
        )

    -- Helpers
    where
    -- (!) `onBlur` DOM.Event is triggered before any `onClick` DOM.Event
    --     So when a completion is being clicked, the UX will be broken
    --
    --        ↳ As a solution we chose to check if the click is made from
    --          the autocompletion list
    onBlur
      :: forall event
       . T.Box Completions
      -> R.Ref (Nullable DOM.Element)
      -> event
      -> Effect Unit
    onBlur completions containerRef event =

      if isInnerEvent then
        pure $ (event .. "preventDefault")
      else
        T.write_ [] completions

      where
      mContains = do
        a <- toMaybe $ R.readRef containerRef
        b <- toMaybe (event .. "relatedTarget")
        Just (contains a b)

      isInnerEvent = maybe false identity mContains

    onFocus :: forall event. T.Box Completions -> String -> event -> Effect Unit
    onFocus completions st _ = do
      cs <- autocompleteSearch st
      T.write_ cs completions

    onInput :: forall event. T.Box Completions -> event -> Effect Unit
    onInput completions e = do
      let val = R.unsafeEventValue e
      T.write_ val state
      cs <- autocompleteSearch val
      T.write_ cs completions

    onInputKeyUp :: R.Ref (Nullable DOM.Element) -> DE.KeyboardEvent -> Effect Boolean
    onInputKeyUp inputRef e = do
      if DE.key e == "Enter" then do
        R2.preventDefault e
        R2.stopPropagation e
        let val = R.unsafeEventValue e
        let mInput = toMaybe $ R.readRef inputRef
        T.write_ val state
        onEnterPress val
        case mInput of
          Nothing -> pure false
          Just input -> do
            R2.blur input
            pure false
      else
        pure false

    onInputKeyDown :: R.Ref (Nullable DOM.Element) -> DE.KeyboardEvent -> Effect Boolean
    onInputKeyDown _ e = do
      if DE.key e == "Enter" then do
        R2.preventDefault e
        R2.stopPropagation e
        pure false
      else
        pure false

---------------------------------------------------------

type CompletionsProps =
  ( completions :: T.Box Completions
  , onAutocompleteClick :: String -> Effect Unit
  , state :: T.Box String
  )

completionsCpt :: R2.Component CompletionsProps
completionsCpt = R.createElement completionsCptCpt

completionsCptCpt :: R.Component CompletionsProps
completionsCptCpt = here.component "completionsCpt" cpt
  where
  cpt { completions, onAutocompleteClick, state } _ = do
    -- State
    completions' <- T.useLive T.unequal completions

    let className = "completions shadow" <> (if completions' == [] then "d-none" else "")

    -- Render
    pure $
      H.div
        { className }
        [ H.div { className: "list-group" } (cCpt <$> completions')
        ]

    -- Helpers
    where

    cCpt c =
      H.button
        { type: "button"
        , className: "list-group-item"
        , on: { click: onClick c }
        }
        [ H.text c ]

    onClick c _ = do
      T.write_ c state
      T.write_ [] completions
      onAutocompleteClick c