open System
open Plotly.NET
open Dash.NET
open Dash.NET.DCC
open Dash.NET.DashTable
open Dash.NET.Giraffe
open FSharp.Data

let [<Literal>] Csv = "https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv"
type CsvData = CsvProvider<Csv>
let csv = CsvData.Load(Csv).Cache()

let countries = csv.Rows |> Seq.map (fun r -> r.Country) |> Seq.distinct |> Seq.toList

let mkOption label value =
    DropdownOption.create label value false label

let tryField field (r: CsvProvider<Csv>.Row) =
    match field with
    | "lifeExp" -> Some r.LifeExp
    | "gdpPercap" -> Some r.GdpPercap
    | _ -> None

let filterCountries = function
    | [] -> csv.Rows
    | countries -> csv.Rows |> Seq.filter (fun r -> countries |> List.contains r.Country)

let mkTableData =
    Seq.map (fun (r:CsvProvider<Csv>.Row) ->
        box {|
            country = r.Country
            year = r.Year
            pop = r.Pop
            continent = r.Continent
            lifeExp = r.LifeExp
            gdpPercap = r.GdpPercap
        |}
    )

let mkLineGraphs (selectedField: string) =
    Seq.groupBy (fun (r:CsvProvider<Csv>.Row) -> r.Country)
    >> Seq.map (fun (country, data) ->
        let xy =
            if isNull selectedField then Seq.empty
            else
                data
                |> Seq.choose (fun r ->
                    r
                    |> tryField selectedField
                    |> Option.map (fun f -> f, int r.Year)
                )
        Chart.Line(xy, Name=country, ShowMarkers=true)
    )
    >> Chart.Combine
    >> GenericChart.toFigure

let layout =
    Html.div [
        Attr.children [

            Store.store "memory-output" [ Store.Attr.data "" ]

            Dropdown.dropdown "memory-countries" [
                Dropdown.Attr.options <| Seq.map (fun c -> mkOption c c) countries
                Dropdown.Attr.multi true
                Dropdown.Attr.value ([ "Canada"; "United States" ] |> List.map (fun c -> c :> IConvertible))
            ]

            Dropdown.dropdown "memory-field" [
                Dropdown.Attr.options [
                    mkOption "Life expectancy" "lifeExp"
                    mkOption "GDP per capita" "gdpPercap"
                ]
                Dropdown.Attr.value ("lifeExp" :> IConvertible)
            ]

            Html.div [
                Attr.children [
                    Graph.graph "memory-graph" []
                    DataTable.dataTable "memory-table" [
                        csv.Headers
                        |> Option.map (Array.map (fun h -> DataTable.Column.create h h))
                        |> Option.defaultValue (Array.empty)
                        |> DataTable.Attr.columns
                    ]
                ]
            ]
        ]
    ]

let onCountriesSelected =
    let outputTarget = "memory-output" @. CustomProperty "data"
    Callback.singleOut (
        [ "memory-countries" @. Value ],
        outputTarget,
        (fun selectedCountries -> outputTarget => filterCountries selectedCountries),
        PreventInitialCall = false
    )

let onStoreUpdated =
    let outputTarget = "memory-table" @. CustomProperty "data"
    Callback.singleOut (
        [ "memory-output" @. CustomProperty "data" ],
        outputTarget,
        (fun (data: seq<_>) -> outputTarget => mkTableData data),
        PreventInitialCall = false
    )

let onSelectionChanged =
    let outputTarget = "memory-graph" @. CustomProperty "figure"
    Callback.singleOut (
        [ "memory-output" @. CustomProperty "data"
          "memory-field" @. Value ],
        outputTarget,
        (fun (data: seq<_>) field -> outputTarget => mkLineGraphs field data),
        PreventInitialCall = false
    )

[<EntryPoint>]
let main args =
      let dashApp =
        DashApp.initDefault()
        |> DashApp.withLayout layout
        |> DashApp.addCallback onCountriesSelected
        |> DashApp.addCallback onStoreUpdated
        |> DashApp.addCallback onSelectionChanged

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

      DashApp.run args config dashApp
