module Gargantext.Components.Bootstrap.FormSelect
  ( formSelect
  , formSelect'
  ) where

import Gargantext.Prelude

import Data.Foldable (elem, intercalate)
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Gargantext.Components.Bootstrap.Types (ComponentStatus(..), Sizing(..))
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Reactix.DOM.HTML as H
import Unsafe.Coerce (unsafeCoerce)

type Props =
  ( callback    :: String -> Effect Unit
  , value       :: String
  | Options
  )

type Options =
  ( status      :: ComponentStatus
  , className   :: String
  , type        :: String
  , placeholder :: String
  , size        :: Sizing
  )

options :: Record Options
options =
  { status      : Enabled
  , className   : ""
  , type        : "text"
  , placeholder : ""
  , size        : MediumSize
  }

-- | Structural Component for the Bootstrap select
-- |
-- |  ```purescript
-- |  formSelect { callback, value }
-- |  [
-- |    H.option
-- |    { value: "foo" }
-- |    [ H.text "foo" ]
-- |  ,
-- |    H.option
-- |    { value: "bar" }
-- |    [ H.text "bar" ]
-- |  ]
-- |  ```
-- |
-- |
-- | (?) note that it handled `value` as a String, as it is the KISS solution
-- |     here. Please use `formSelect'` for any other type
-- |
-- | https://getbootstrap.com/docs/4.1/components/forms/
formSelect :: forall r. R2.OptComponent Options Props r
formSelect = R2.optComponent component options

componentName :: String
componentName = "b-form-select"

bootstrapName :: String
bootstrapName = "form-control"

component :: R.Component Props
component = R.hooksComponent componentName cpt where
  cpt props@{ callback
            , status
            } children = do
    -- Computed
    let
      className = intercalate " "
        -- provided custom className
        [ props.className
        -- BEM classNames
        , componentName
        , componentName <> "--" <> show status
        -- Bootstrap specific classNames
        , bootstrapName
        , bootstrapName <> "-" <> show props.size
        ]

    -- Behaviors
    let
      change = onChange status callback

    -- Render
    pure $
      R2.select
      { className
      , on: { change }
      , disabled: elem status [ Disabled, Idled ]
      , readOnly: elem status [ Idled ]
      , type: props.type
      , value: props.value
      }
      children

-- | * Change event will effectively be triggered according to the
-- | component status props
-- | * Also directly returns the newly input value
-- | (usage not so different from `targetValue` of ReactBasic)
onChange :: forall event.
     ComponentStatus
  -> (String -> Effect Unit)
  -> event
  -> Effect Unit
onChange status callback event = do
  if   status == Enabled
  then callback $ (unsafeCoerce event).target.value
  else R.nothing


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

type AnyTypeProps a =
  ( callback :: a -> Effect Unit
  , value :: a
  , list :: Array a
  | Options
  )

-- | Derived component for `formSelect` with any value type (with `Read`
-- | and `Show` constraint)
-- |
-- |
-- |  ```purescript
-- |  formSelect'
-- |  { callback: flip T.write_ box
-- |  , value: anyType
-- |  , list: [ anyType, anyType, ... ]
-- |  }
-- |  ```
-- |
-- |  (?) Note that HTML option tags will be automatically added thanks to
-- |      to the provided `list` prop. You can add additional HTML option within
-- |      the `children` prop
formSelect' :: forall r a.
     Read a
  => Show a
  => R2.OptComponent Options (AnyTypeProps a) r
formSelect' = R2.optComponent component' options

component' :: forall a.
     Read a
  => Show a
  => R.Component (AnyTypeProps a)
component' = R.hooksComponent (componentName <> "__helper") cpt where
  cpt props@{ callback
            , list
            , status
            , value
            } children = do
    -- Computed
    let
      className = intercalate " "
      -- provided custom className
        [ props.className
        -- BEM classNames
        , componentName
        , componentName <> "--" <> show status
        -- Bootstrap specific classNames
        , bootstrapName
        , bootstrapName <> "-" <> show props.size
        ]

    -- Behaviors
    let
      change = onChange' status callback

    -- Render
    pure $
      R2.select
      { className
      , on: { change }
      , disabled: elem status [ Disabled ]
      , readOnly: elem status [ Idled ]
      , type: props.type
      , value: show value
      }
      (
        children
      <>
        flip map list \raw ->
          H.option
          { value: show raw }
          [ H.text $ show raw ]
      )

-- | * Change event will effectively be triggered according to the
-- | component status props
-- | * Also directly returns the newly input value
-- | (usage not so different from `targetValue` of ReactBasic)
onChange' :: forall event a.
     Read a
  => Show a
  => ComponentStatus
  -> (a -> Effect Unit)
  -> event
  -> Effect Unit
onChange' status callback event = do
  if   status == Enabled
  then event # unsafeCoerce >>> _.target.value >>> read >>> case _ of
    Nothing -> R.nothing
    Just v  -> callback v
  else R.nothing