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