open Dash.NET
open Dash.NET.DCC
open Plotly.NET
open FSharp.Data

let df = CsvFile.Load("https://plotly.github.io/datasets/country_indicators.csv").Rows |> List.ofSeq

let available_indicators = df |> List.map (fun r -> r.Item "Indicator Name") |> List.distinct

let dslLayout = 
    Html.div [
        Attr.children [
            Html.div [
                Attr.children [
                    Html.div [
                        Attr.children [
                            Dropdown.dropdown "crossfilter-xaxis-column" [
                                Dropdown.Attr.options <| List.map (fun ai -> DropdownOption.init(label = ai, value = ai)) available_indicators
                                Dropdown.Attr.value "Fertility rate, total (births per woman)"
                            ]
                            RadioItems.radioItems "crossfilter-xaxis-type" [
                                RadioItems.Attr.options [
                                    RadioItemsOption.init(label = "Linear", value = "Linear")
                                    RadioItemsOption.init(label = "Log", value = "Log")
                                ]
                                RadioItems.Attr.value "Linear"
                                RadioItems.Attr.labelStyle [
                                    Css.displayInlineBlock
                                    Css.marginTop (Feliz.length.px 5)
                                ]
                            ]
                        ]
                        Attr.style [
                            Css.displayInlineBlock
                            Css.width (Feliz.length.perc 49)
                        ]
                    ]

                    Html.div [
                        Attr.children [
                            Dropdown.dropdown "crossfilter-yaxis-column" [
                                Dropdown.Attr.options <| List.map (fun ai -> DropdownOption.init(label = ai, value = ai)) available_indicators
                                Dropdown.Attr.value "Life expectancy at birth, total (years)"
                            ]
                            RadioItems.radioItems "crossfilter-yaxis-type" [
                                RadioItems.Attr.options [
                                    RadioItemsOption.init(label = "Linear", value = "Linear")
                                    RadioItemsOption.init(label = "Log", value = "Log")
                                ]
                                RadioItems.Attr.value "Linear"
                                RadioItems.Attr.labelStyle [
                                    Css.displayInlineBlock
                                    Css.marginTop (Feliz.length.px 5)
                                ]
                            ]
                        ]
                        Attr.style [
                            Css.displayInlineBlock
                            Css.width (Feliz.length.perc 49)
                        ]
                    ]
                ]
                Attr.style [
                    Css.padding (Feliz.length.px 10, Feliz.length.px 5)
                ]
            ]

            Html.div [
                Attr.children [
                    Graph.graph "crossfilter-indicator-scatter" [
                        Graph.Attr.hoverData {| points = [{| text = "Japan" |}] |}
                    ]
                ]
                Attr.style [
                    Css.width (Feliz.length.perc 49)
                    Css.displayInlineBlock
                    Css.padding (Feliz.length.px 0, Feliz.length.px 20)
                ]
            ]

            Html.div [
                Attr.children [
                    Graph.graph "x-time-series" []
                    Graph.graph "y-time-series" []
                ]
                Attr.style [
                    Css.width (Feliz.length.perc 49)
                    Css.displayInlineBlock
                ]
            ]

            Html.div [
                Attr.children [
                    Slider.slider "crossfilter-year-slider" [
                        let years = df |> List.map (fun r -> r.Item "Year")
                        yield Slider.Attr.min (years |> List.map int |> List.min)
                        yield Slider.Attr.max (years |> List.map int |> List.max)
                        yield Slider.Attr.value (years |> List.map int |> List.max)
                        yield Slider.Attr.marks ( 
                            years
                            |> List.map (fun y -> (y, Slider.MarkValue.String y))
                            |> Map.ofList
                            |> Slider.MarksType
                        )
                        yield Slider.Attr.step null
                    ]
                ]
                Attr.style [
                    Css.width (Feliz.length.perc 49)
                    Css.padding (Feliz.length.px 0, Feliz.length.px 20, Feliz.length.px 20, Feliz.length.px 20)
                ]
            ]
        ]
    ]

let updateGraph =
    Callback.multiOut(
        [ "crossfilter-xaxis-column" @. Value
          "crossfilter-yaxis-column" @. Value
          "crossfilter-xaxis-type" @. Value
          "crossfilter-yaxis-type" @. Value
          "crossfilter-year-slider" @. Value ],
        [ "crossfilter-indicator-scatter" @. (CustomProperty "figure") ],
        fun xAxisColumnName yAxisColumnName xAxisType yAxisType yearValue -> 
            let dff = df |> List.filter (fun r -> r.Item "Year" = yearValue)

            let xRows = dff |> List.filter (fun r -> r.Item "Indicator Name" = xAxisColumnName)
            let yRows = dff |> List.filter (fun r -> r.Item "Indicator Name" = yAxisColumnName)

            let xData = xRows |> List.map (fun r -> r.Item "Value")
            let yData = yRows |> List.map (fun r -> r.Item "Value")

            let yLabels = yRows |> List.map (fun r -> r.Item "Country Name")

            let fig = 
                Chart.Scatter(xData, yData, StyleParam.Mode.Markers, Labels = yLabels)
                |> Chart.withXAxis(
                    LayoutObjects.LinearAxis.init(
                        AxisType = (if xAxisType = "Linear" then StyleParam.AxisType.Linear else StyleParam.AxisType.Log),
                        Title = Title.init xAxisColumnName
                    )
                )
                |> Chart.withYAxis(
                    LayoutObjects.LinearAxis.init(
                        AxisType = (if yAxisType = "Linear" then StyleParam.AxisType.Linear else StyleParam.AxisType.Log),
                        Title = Title.init yAxisColumnName
                    )
                )
                |> Chart.withLayout (
                    Layout.init(
                        Margin = LayoutObjects.Margin.init(Left = 40, Right = 0, Top = 10, Bottom = 40),
                        HoverMode = StyleParam.HoverMode.Closest
                    )
                )
                |> GenericChart.toFigure

            [ "crossfilter-indicator-scatter" @. (CustomProperty "figure") => fig ]
        , PreventInitialCall = false
    )

let createTimeSeries xData yearData axisType title =
    Chart.Scatter(xData, yearData, StyleParam.Mode.Lines_Markers, Name = title)
    |> Chart.withXAxis(
        LayoutObjects.LinearAxis.init(
            ShowGrid = false,
            Title = Title.init "Value"
        )
    )
    |> Chart.withYAxis(
        LayoutObjects.LinearAxis.init(
            AxisType = (if axisType = "Linear" then StyleParam.AxisType.Linear else StyleParam.AxisType.Log),
            Title = Title.init "Year"
        )
    )
    |> Chart.withLayout (
        Layout.init(
            Margin = LayoutObjects.Margin.init(Left = 40, Right = 0, Top = 10, Bottom = 40),
            HoverMode = StyleParam.HoverMode.Closest
        )
    )
    |> Chart.withAnnotations [
        LayoutObjects.Annotation.init(
            X = 0, 
            Y = 0.85, 
            XRef = "paper", 
            YRef = "paper", 
            ShowArrow = false, 
            XAnchor = StyleParam.XAnchorPosition.Left, 
            YAnchor = StyleParam.YAnchorPosition.Bottom
        )
    ]
    |> GenericChart.toFigure

type PointData =
    { text: string }
type HoverData = 
    { points: PointData list}

let updateXTimeseries =
    Callback.multiOut(
        [ "crossfilter-xaxis-column" @. Value
          "crossfilter-xaxis-type" @. Value
          "crossfilter-indicator-scatter" @. (CustomProperty "hoverData") ],
        [ "x-time-series" @. (CustomProperty "figure") ],
        fun xAxisColumnName xAxisType (hoverData: HoverData) ->
            match hoverData.points |> List.tryHead |> Option.map (fun h -> h.text) with
            | None -> []
            | Some countryName -> 
                let dff = df |> List.filter (fun r -> r.Item "Country Name" = countryName)
                let rows = dff |> List.filter (fun r -> r.Item "Indicator Name" = xAxisColumnName)

                let yData = rows |> List.map (fun r -> r.Item "Value")
                let xData = rows |> List.map (fun r -> r.Item "Year")

                let title = sprintf "<b>%s</b><br>%s" countryName xAxisColumnName

                [ "x-time-series" @. (CustomProperty "figure") => createTimeSeries xData yData xAxisType title ]
        , PreventInitialCall = false
    )

let updateYTimeseries =
    Callback.multiOut(
        [ "crossfilter-yaxis-column" @. Value
          "crossfilter-yaxis-type" @. Value
          "crossfilter-indicator-scatter" @. (CustomProperty "hoverData") ],
        [ "y-time-series" @. (CustomProperty "figure") ],
        fun yAxisColumnName yAxisType (hoverData: HoverData) ->
            match hoverData.points |> List.tryHead |> Option.map (fun h -> h.text) with
            | None -> []
            | Some countryName -> 
                let dff = df |> List.filter (fun r -> r.Item "Country Name" = countryName)
                let rows = dff |> List.filter (fun r -> r.Item "Indicator Name" = yAxisColumnName)

                let yData = rows |> List.map (fun r -> r.Item "Value")
                let xData = rows |> List.map (fun r -> r.Item "Year")

                let title = sprintf "<b>%s</b><br>%s" countryName yAxisColumnName

                [ "y-time-series" @. (CustomProperty "figure") => createTimeSeries xData yData yAxisType title ]
        , PreventInitialCall = false
    )

[<EntryPoint>]
let main args =
    Giraffe.DashApp.initDefault()
    |> Giraffe.DashApp.withLayout dslLayout
    |> Giraffe.DashApp.appendCSSLinks ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
    |> Giraffe.DashApp.addCallback updateGraph
    |> Giraffe.DashApp.addCallbacks [
        updateXTimeseries
        updateYTimeseries
    ]
    |> Giraffe.DashApp.run args (Giraffe.DashGiraffeConfig.initDebug "localhost")