module Gargantext.Components.Forest.Tree.Node.Action.Search.SearchField where

import DOM.Simple.Console (log, log2)
import Data.Maybe (Maybe(..), maybe, fromMaybe)
import Data.Nullable (null)
import Data.Newtype (over)
import Data.Set as Set
import Data.String (length)
import Data.Tuple.Nested ((/\))
import Effect (Effect)
import Effect.Aff (launchAff_)
import Effect.Class (liftEffect)
import Reactix as R
import Reactix.DOM.HTML as H

import Gargantext.Components.Forest.Tree.Node.Tools (panel)
import Gargantext.Components.Forest.Tree.Node.Action.Search.Types (DataField(..), Database(..), IMT_org(..), Org(..), SearchQuery(..), allIMTorgs, allOrgs, dataFields, defaultSearchQuery, doc, performSearch, datafield2database, Search)
import Gargantext.Components.Lang (Lang)
import Gargantext.Prelude (Unit, bind, discard, map, pure, show, ($), (<), (<$>), (<>), (==), read)
import Gargantext.Sessions (Session)
import Gargantext.Components.Forest.Tree.Node.Action.Search.Frame (searchIframes)
import Gargantext.Types as GT
import Gargantext.Utils.Reactix as R2

thisModule = "Gargantext.Components.Forest.Tree.Node.Action.Search.SearchField"

defaultSearch :: Search
defaultSearch = { databases: Empty
                , datafield: Nothing
                , node_id  : Nothing
                , lang     : Nothing
                , term     : ""
                , url: ""
                }

type Props =
  -- list of databases to search, or parsers to use on uploads
  ( databases :: Array Database
  , langs     :: Array Lang
  -- State hook for a search, how we get data in and out
  , onSearch  :: GT.AsyncTaskWithType -> Effect Unit
  , search    :: R.State Search
  , session   :: Session
  )

searchField :: Record Props -> R.Element
searchField p = R.createElement searchFieldComponent p []

--searchFieldComponent :: R.Memo Props
--searchFieldComponent = R.memo (R.hooksComponentWithModule thisModule "searchField" cpt) eqProps
searchFieldComponent :: R.Component Props
searchFieldComponent = R.hooksComponentWithModule thisModule "searchField" cpt
  where
    cpt props@{onSearch, search: search@(s /\ _)} _ = do
      iframeRef <- R.useRef    null
      let params = 
                [ searchInput {search}
                , if length s.term < 3  -- search with love : <3
                  then
                    H.div {}[]
                  else
                    H.div {} [ dataFieldNav search dataFields

                             , if isExternal s.datafield
                                 then databaseInput search props.databases
                                 else H.div {} []
 
                             , if isHAL s.datafield
                                 then orgInput search allOrgs
                                 else H.div {} []
 
                             , if isIMT s.datafield
                                 then componentIMT search
                                 else H.div {} []
 
                             , if isCNRS s.datafield
                                 then componentCNRS search
                                 else H.div {} []
 
                             , H.div {} [ searchIframes { iframeRef, search } ]

                             , if needsLang s.datafield
                                then langNav search props.langs
                                else H.div {} []
                             ]

                ]
      let button =  submitButton {onSearch, search, session: props.session}

      pure $ panel params button


componentIMT (search /\ setSearch) =
  R.fragment
  [ H.ul {} $ map liCpt allIMTorgs
  --, filterInput fi
  ]
  where
    liCpt org =
      H.li {}
      [ H.input { type: "checkbox"
                , checked: isIn org search.datafield
                , on: { change: \_ -> ( setSearch $ _ { datafield = updateFilter org search.datafield })
                      }
                }
      , if org == All_IMT
        then H.i {} [H.text  $ " " <> show org]
        else H.text $ " " <> show org
      ]

componentCNRS (search /\ setSearch) =
  R.fragment [
    H.div {} []
  --, filterInput fi
  ]


isExternal :: Maybe DataField -> Boolean
isExternal (Just (External _)) = true
isExternal _ = false

isHAL :: Maybe DataField -> Boolean
isHAL (Just
        ( External 
          ( Just (HAL _ )
          )
        )
      ) = true
isHAL _ = false

isIsTex :: Maybe DataField -> Boolean
isIsTex ( Just
          ( External
            ( Just ( IsTex)
            )
          )
        ) = true
isIsTex _ = false


isIMT :: Maybe DataField -> Boolean
isIMT ( Just 
        ( External
          ( Just 
            ( HAL 
              ( Just ( IMT _)
              )
            )
          )
        )
      ) = true
isIMT _ = false

isCNRS :: Maybe DataField -> Boolean
isCNRS ( Just
         ( External
          ( Just
            ( HAL
              ( Just ( CNRS _)
              )
            )
          )
        )
      ) = true
isCNRS _ = false

needsLang :: Maybe DataField -> Boolean
needsLang (Just Gargantext) = true
needsLang (Just Web)        = true
needsLang ( Just
            ( External
              ( Just (HAL _)
              )
            )
          ) = true
needsLang _                 = false


isIn :: IMT_org -> Maybe DataField -> Boolean
isIn org ( Just
           ( External
             ( Just
               ( HAL
                 ( Just
                   ( IMT imtOrgs )
                 )
               )
             )
           )
         ) = Set.member org imtOrgs
isIn _ _ = false

updateFilter :: IMT_org -> Maybe DataField -> Maybe DataField
updateFilter org (Just (External (Just (HAL (Just (IMT imtOrgs)))))) =
 (Just (External (Just (HAL (Just $ IMT imtOrgs')))))
  where
    imtOrgs' = if Set.member org imtOrgs
                  then
                    if org == All_IMT
                       then Set.empty
                       else Set.delete All_IMT $ Set.delete org imtOrgs
                  else
                    if org == All_IMT
                       then Set.fromFoldable allIMTorgs
                       else Set.insert org imtOrgs

updateFilter org _ = (Just (External (Just (HAL (Just (IMT imtOrgs'))))))
  where
    imtOrgs' = if org == All_IMT
                  then Set.fromFoldable allIMTorgs
                  else Set.fromFoldable [org]

------------------------------------------------------------------------
langNav :: R.State Search -> Array Lang -> R.Element
langNav ({lang} /\ setSearch) langs =
  R.fragment [ H.div {className: "text-primary center"} [H.text "with lang"]
             , H.div {className: "nav nav-tabs"} (liItem <$> langs)
             ]
    where
      liItem :: Lang -> R.Element
      liItem  lang' =
        H.div { className : "nav-item nav-link" <> if (Just lang') == lang then " active" else ""
              , on: { click: \_ -> setSearch $ _ { lang = Just lang' } }
              } [ H.text (show lang') ]

------------------------------------------------------------------------
dataFieldNav :: R.State Search -> Array DataField -> R.Element
dataFieldNav ({datafield} /\ setSearch) datafields =
  R.fragment [ H.div { className: "text-primary center"} [H.text "with DataField"]
                     , H.div {className: "nav nav-tabs"} (liItem <$> dataFields)
                     , H.div {className: "center"} [ H.text
                                                   $ maybe "TODO: add Doc Instance" doc datafield 
                                                   ]
             ]
    where
      liItem :: DataField -> R.Element
      liItem  df' =
        H.div { className : "nav-item nav-link"
                          <> if (Just df') == datafield
                               then " active"
                               else ""
            , on: { click: \_ -> setSearch $ _ { datafield = Just df'
                                               , databases = datafield2database df'
                                               }
                  }
            -- just one database query for now
            -- a list a selected database needs more ergonomy
            } [ H.text (show df') ]

------------------------------------------------------------------------
{-
databaseNav  :: R.State Search
              -> Array Database
              -> R.Element
databaseNav ({datafield} /\ setSearch) dbs =
  R.fragment [ H.div {className: "text-primary center"} [H.text "with DataField"]
             , H.div { className: "nav nav-tabs"} (liItem <$> dbs)
             , H.div {className:"center"} [ H.text $ maybe "" doc db ]
             ]
    where

      db = case datafield of
        (Just (External (Just x))) -> Just x
        _                          -> Nothing

      liItem :: Database -> R.Element
      liItem  df' =
        H.div { className : "nav-item nav-link" <> if (Just $ External $ Just df') == datafield then " active" else ""
            , on: { click: \_ -> setSearch $ _ { datafield = Just $ External $ Just df' } }
            } [ H.text (show df') ]
            -}

databaseInput :: R.State Search
              -> Array Database
              -> R.Element
databaseInput (search /\ setSearch) dbs =
   H.div { className: "form-group" }
   [ H.div {className: "text-primary center"} [H.text "in database"]
   , R2.select { className: "form-control"
               , on: { change: onChange }
               } (liItem <$> dbs)
   , H.div {className:"center"} [ H.text $ maybe "" doc db ]
   ]
    where
      db = case search.datafield of
        (Just (External (Just x))) -> Just x
        _                          -> Nothing

      liItem :: Database -> R.Element
      liItem  db' = H.option {className : "text-primary center"} [ H.text (show db') ]

      onChange e = do
        let value = read $ R.unsafeEventValue e
        setSearch $ _ { datafield = Just $ External value
                      , databases = fromMaybe Empty value
                      }


orgInput :: R.State Search -> Array Org -> R.Element
orgInput ({datafield} /\ setSearch) orgs =
  H.div { className: "form-group" }
  [ H.div {className: "text-primary center"} [H.text "filter with organization: "]
  , R2.select { className: "form-control"
              , on: { change: onChange }
              } (liItem <$> orgs)
  ]
    where
      liItem :: Org -> R.Element
      liItem  org = H.option {className : "text-primary center"} [ H.text (show org) ]
      onChange e = do
        let value = R.unsafeEventValue e
        setSearch $ _ { datafield = Just $ External $ Just $ HAL $ read value }

{-
filterInput :: R.State String -> R.Element
filterInput (term /\ setTerm) =
  H.div { className: "form-group" }
        [ H.input { defaultValue: term
                  , className: "form-control"
                  , type: "text"
                  , on: { change: setTerm <<< const <<< R.unsafeEventValue }
                  , "required pattern": "[[0-9]+[ ]+]*"
                  -- TODO                          ^FIXME not sure about the regex comprehension: that should match "123 2334 44545" only (Integers separated by one space)
                  -- form validation with CSS
                  -- DOC: https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation
                  , placeholder : "Filter with struct_Ids as integer" 
                  }
         ]
-}

type SearchInputProps =
  (
    search :: R.State Search
  )

searchInput :: Record SearchInputProps -> R.Element
searchInput p = R.createElement searchInputComponent p []

searchInputComponent :: R.Component SearchInputProps
searchInputComponent = R.hooksComponentWithModule thisModule "searchInput" cpt
  where
    cpt {search: (search /\ setSearch)} _ = do
      pure $
        H.div { className : "" }
        [ H.input { className: "form-control"
                  , defaultValue: search.term
                  , on: { change : onChange setSearch }
                  , placeholder: "Your Query here"
                  , type: "text"
                  }
        ]
    onChange setSearch e = do
      let value = R.unsafeEventValue e
      setSearch $ _ { term = value }

type SubmitButtonProps =
  ( onSearch :: GT.AsyncTaskWithType -> Effect Unit
  , search   :: R.State Search
  , session  :: Session
  )

submitButton :: Record SubmitButtonProps -> R.Element
submitButton p = R.createElement submitButtonComponent p []

submitButtonComponent :: R.Component SubmitButtonProps
submitButtonComponent = R.hooksComponentWithModule thisModule "submitButton" cpt
  where
    cpt {onSearch, search: (mySearch /\ _), session} _ =
      pure $
        H.button { className: "btn btn-primary"
                 , "type"   : "button"
                 , on       : {click: doSearch onSearch session mySearch}
                 , style    : { width: "100%" }
                 } [ H.text "Launch Search" ]

    doSearch os s q = \_ -> do
      log2 "[submitButton] searching" q
      triggerSearch os s q
      --case search.term of
      --  "" -> setSearch $ const defaultSearch
      --  _  -> setSearch $ const q

triggerSearch :: (GT.AsyncTaskWithType -> Effect Unit)
              -> Session
              -> Search
              -> Effect Unit
triggerSearch os s q =
  launchAff_ $ do
    liftEffect $ do
      let here = "[triggerSearch] Searching "
      log2 (here <> "databases: ") (show q.databases)
      log2 (here <> "datafield: ") (show q.datafield)
      log2 (here <> "term: ")            q.term
      log2 (here <> "lang: ")      (show q.lang)

    case q.node_id of
      Nothing -> liftEffect $ log "[triggerSearch] node_id is Nothing, don't know what to do"
      Just id -> do
        task <- performSearch s id $ searchQuery q
        liftEffect $ do
          log2 "[triggerSearch] task" task
          os task

    --liftEffect $ do
    --  log2 "Return:" r
    --  modalShow "addCorpus"

searchQuery :: Search -> SearchQuery
searchQuery {datafield: Nothing, term} =
  over SearchQuery (_ {query=term}) defaultSearchQuery
searchQuery {databases, datafield, lang, term, node_id} =
  over SearchQuery (_ { databases= databases
                      , datafield= datafield
                      , lang     = lang
                      , query    = term
                      , node_id  = node_id
                      }) defaultSearchQuery