module Gargantext.Components.Nodes.Graph
  ( node
  ) where

import Gargantext.Prelude

import Data.Array as A
import Data.Int as I
import Data.Maybe (Maybe(..), isJust, maybe)
import Data.Sequence as Seq
import Data.Tuple (Tuple(..))
import DOM.Simple (document, querySelector)
import Gargantext.Components.App.Store as AppStore
import Gargantext.Components.Bootstrap as B
import Gargantext.Components.GraphExplorer.API as GraphAPI
import Gargantext.Components.GraphExplorer.Layout (convert, layout, transformGraph)
import Gargantext.Components.GraphExplorer.Store as GraphStore
import Gargantext.Components.GraphExplorer.Types as GET
import Gargantext.Config.REST (logRESTError)
import Gargantext.Hooks.FirstEffect (useFirstEffect')
import Gargantext.Hooks.Loader (useLoaderEffect)
import Gargantext.Hooks.Session (useSession)
import Gargantext.Hooks.Sigmax.ForceAtlas2 as ForceAtlas
import Gargantext.Hooks.Sigmax.Noverlap as Noverlap
import Gargantext.Hooks.Sigmax as Sigmax
import Gargantext.Hooks.Sigmax.Types as SigmaxT
import Gargantext.Utils (getter)
import Gargantext.Utils.Range as Range
import Gargantext.Utils.Reactix as R2
import Reactix as R
import Record as Record
import Toestand as T

type Props =
  ( graphId :: GET.GraphId
  )

here :: R2.Here
here = R2.here "Gargantext.Components.Nodes.Graph"

node :: R2.Leaf ( key :: String | Props )
node = R2.leaf nodeCpt
nodeCpt :: R.Component ( key :: String | Props )
nodeCpt = here.component "node" cpt where
  cpt { graphId } _ = do
    -- | States
    -- |
    { graphVersion
    } <- AppStore.use

    session <- useSession

    graphVersion'   <- R2.useLive' graphVersion
    state <- T.useBox Nothing
    cache <- T.useBox (GET.defaultCacheParams :: GET.CacheParams)

    -- | Computed
    -- |
    let errorHandler = logRESTError here "[node]"

    -- | Hooks
    -- |

    -- load Local Storage cache (if exists)
    useFirstEffect' $
      R2.loadLocalStorageState R2.graphParamsKey cache

    useLoaderEffect
      { errorHandler
      , loader: GraphAPI.getNodes session graphVersion'
      , path: graphId
      , state
      }

    -- @XXX: reset "main-page__main-route" wrapper margin
    --       see Gargantext.Components.Router) (@TODO?)
    R.useLayoutEffect1 [] do
      let mEl = querySelector document ".main-page__main-route"
      -- Mount
      mEl >>= maybe R.nothing (flip R2.addClass ["p-0"])
      -- Unmount
      pure $
        mEl >>= maybe R.nothing (flip R2.removeClass ["p-0"])

    -- | Render
    -- |
    pure $ renderNode { cache, graphId, state }

type RenderNodeProps = (
  cache   :: T.Box GET.CacheParams,
  graphId :: GET.GraphId,
  state   :: T.Box (Maybe GET.HyperdataGraph)
)

renderNode :: R2.Leaf RenderNodeProps
renderNode = R2.leaf renderNodeCpt
renderNodeCpt :: R.Component RenderNodeProps
renderNodeCpt = here.component "renderNode" cpt where
  cpt { cache, graphId, state } _ = do
    cache' <- T.useLive T.unequal cache
    state' <- T.useLive T.unequal state

    pure $
      B.cloak
      { isDisplayed: isJust state'
      , idlingPhaseDuration: Just 150
      , cloakSlot:
          B.preloader
          {}

      , defaultSlot:
          R2.fromMaybe state' \loaded ->
            let
              GET.HyperdataGraph { graph: hyperdataGraph } = loaded
              Tuple mMetaData graph = convert hyperdataGraph
            in
              hydrateStore
              { cacheParams: cache'
              , graph
              , graphId
              , hyperdataGraph: loaded
              , mMetaData
              }
      }

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

type HydrateStoreProps =
  ( cacheParams     :: GET.CacheParams
  , graph           :: SigmaxT.SGraph
  , graphId         :: GET.GraphId
  , hyperdataGraph  :: GET.HyperdataGraph
  , mMetaData       :: Maybe GET.MetaData
  )

hydrateStore :: R2.Leaf HydrateStoreProps
hydrateStore = R2.leaf hydrateStoreCpt
hydrateStoreCpt :: R.Component HydrateStoreProps
hydrateStoreCpt = here.component "hydrateStore" cpt where
  cpt { mMetaData
      , graph
      , graphId
      , hyperdataGraph
      , cacheParams
      } _ = do
    -- | Computed
    -- |
    let
      startForceAtlas = maybe true
        (\(GET.MetaData { startForceAtlas: sfa }) -> sfa) mMetaData

      forceAtlasState
        = if startForceAtlas
          --then SigmaxT.InitialLoading
          then SigmaxT.InitialRunning
          else SigmaxT.InitialStopped

    -- | Hooks
    -- |

    sigmaRef <- Sigmax.initSigma >>= R.useRef
    fa2Ref <- R.useRef (Nothing :: Maybe ForceAtlas.FA2Layout)
    noverlapRef <- R.useRef (Nothing :: Maybe Noverlap.NoverlapLayout)

    -- | Precompute some values
    -- |

    let edgesConfluenceSorted = A.sortWith (_.confluence) $ Seq.toUnfoldable $ SigmaxT.graphEdges graph
    let edgeConfluenceMin = maybe 0.0 _.confluence $ A.head edgesConfluenceSorted
    let edgeConfluenceMax = maybe 100.0 _.confluence $ A.last edgesConfluenceSorted
    let edgeConfluenceRange = Range.Closed { min: edgeConfluenceMin, max: edgeConfluenceMax }

    --let edgesWeightSorted = A.sortWith (_.weight) $ Seq.toUnfoldable $ SigmaxT.graphEdges graph
    --let edgeWeightMin = maybe 0.0 _.weight $ A.head edgesWeightSorted
    --let edgeWeightMax = maybe 100.0 _.weight $ A.last edgesWeightSorted
    --let edgeWeightRange = Range.Closed { min: edgeWeightMin, max: edgeWeightMax }
    -- let edgeWeightRange = Range.Closed {
    --       min: 0.0
    --     , max: I.toNumber $ Seq.length $ SigmaxT.graphEdges graph
    --     }

    let nodesSorted = A.sortWith (_.size) $ Seq.toUnfoldable $ SigmaxT.graphNodes graph
    let nodeSizeMin = maybe 0.0 _.size $ A.head nodesSorted
    let nodeSizeMax = maybe 100.0 _.size $ A.last nodesSorted
    let nodeSizeRange = Range.Closed { min: nodeSizeMin, max: nodeSizeMax }

    let edgeWeight = Range.Closed
          { min: 0.0
          , max: I.toNumber $ Seq.length $ SigmaxT.graphEdges graph
          }

    let transformedGraph = transformGraph graph { edgeConfluence': GraphStore.options.edgeConfluence
                                                , edgeWeight': edgeWeight
                                                , nodeSize': GraphStore.options.nodeSize
                                                , removedNodeIds': GraphStore.options.removedNodeIds
                                                , selectedNodeIds': GraphStore.options.selectedNodeIds
                                                , showEdges': GraphStore.options.showEdges }

    -- Hydrate GraphStore
    (state :: Record GraphStore.State) <- pure $
      -- Data
      { graph
      , graphId
      , mMetaData
      , hyperdataGraph
      , transformedGraph
      -- Controls
      , startForceAtlas
      , forceAtlasState
      , noverlapState: SigmaxT.NoverlapPaused
      , edgeWeight
      , edgeConfluenceRange
      , nodeSizeRange
      -- (cache options)
      , expandSelection: getter _.expandSelection cacheParams
      , expandNeighborhood: getter _.expandNeighborhood cacheParams
      -- (default options)
      } `Record.merge` GraphStore.options

    -- | Render
    -- |
    pure $


      GraphStore.provide
      state
      [
        layout
        { fa2Ref
        , noverlapRef
        , sigmaRef }
      ]