open System
open Dash.NET
open Dash.NET.DCC
open Dash.NET.Giraffe

let mkStoreId = sprintf "%s-store"
let mkButtonId = sprintf "%s-button"
let mkClickText =
    let upperFirst (s: string) =
        sprintf "%s%s" ((string s.[0]).ToUpper()) (s.Substring(1))
    upperFirst >> sprintf "%s clicks"
let mkOutputId = sprintf "%s-clicks"

let stores = [ "memory"; "local"; "session" ]

let layout =
    Html.div [
        Attr.children [
            // The memory store reverts to the default on every page refresh
            Store.store "memory-store" [
                Store.Attr.data "-1"
                Store.Attr.modifiedTimestamp 0L
            ]

            // The local store will take the initial data
            // only the first time the page is loaded
            // and keep it until it is cleared.
            Store.store "local-store" [
                Store.Attr.storageType PersistenceTypeOptions.Local
                Store.Attr.data null
                Store.Attr.modifiedTimestamp 0L
            ]

            // Same as the local store but will lose the data
            // when the browser/tab closes.
            Store.store "session-store" [
                Store.Attr.storageType PersistenceTypeOptions.Session
                Store.Attr.data null
                Store.Attr.modifiedTimestamp 0L
            ]

            Html.table [
                Attr.children [
                    Html.thead [
                        Attr.children [
                            Html.tr [
                                Attr.children [
                                    Html.th [
                                        Attr.colSpan 3
                                        Attr.children "Click to store in:"
                                    ]
                                ]
                            ]
                            Html.tr [
                                stores
                                |> List.map (fun store ->
                                    Html.th [
                                        Attr.children [
                                            Html.button [
                                                Attr.children store
                                                Attr.id (mkButtonId store)
                                                Attr.n_clicks 0
                                            ]
                                        ]
                                    ]
                                )
                                |> Attr.children
                            ]
                            Html.tr [
                                stores
                                |> List.map (fun store ->
                                    Html.th [ Attr.children (mkClickText store) ]
                                )
                                |> Attr.children
                            ]
                        ]
                    ]
                    Html.tbody [
                        Attr.children [
                            Html.tr [
                                stores
                                |> List.map (fun store ->
                                    Html.td [ Attr.id (mkOutputId store) ]
                                )
                                |> Attr.children
                            ]
                        ]
                    ]
                ]
            ]
        ]
    ]

let mkOnClick store =
    let storeKey = mkStoreId store
    let outputTarget = storeKey @. (CustomProperty "data")
    Callback.singleOut (
        (mkButtonId store) @. N_Clicks,
        outputTarget,
        (fun (_: int) (data: Nullable<int>) ->
            outputTarget => if data.HasValue then data.Value + 1 else 0),
        State = [ CallbackState.create(storeKey, "data") ],
        PreventInitialCall = false
    )

let mkOnStoreUpdated store =
    let storeKey = mkStoreId store
    let outputTarget = (mkOutputId store) @. Children
    Callback.singleOut (
        storeKey @. (CustomProperty "modified_timestamp"),
        outputTarget,
        (fun (_: int64) (data: Nullable<int>) -> outputTarget => data),
        State = [ CallbackState.create(storeKey, "data") ],
        PreventInitialCall = false
    )

[<EntryPoint>]
let main args =
      let dashApp =
        DashApp.initDefault()
        |> DashApp.withLayout layout
        |> DashApp.addCallbacks (stores |> List.map mkOnClick)
        |> DashApp.addCallbacks (stores |> List.map mkOnStoreUpdated)

      let config = 
        { HostName = "localhost"
          LogLevel = Microsoft.Extensions.Logging.LogLevel.Debug
          ErrorHandler = (fun ex -> Giraffe.Core.text ex.Message) }

      DashApp.run args config dashApp
