module Main exposing (..)

import Browser exposing (Document)
import Card exposing (CardData)
import Chart as C
import Chart.Attributes as CA
import Database
import Deck
import Draft exposing (Draft)
import Html exposing (Html, button, div, img, span, text)
import Html.Attributes exposing (alt, class, classList, disabled, src)
import Html.Events as Events exposing (onClick)
import Html.Keyed as Keyed
import List.Extra as List
import Round
import Signals
import Tuple exposing (first, second)
import Zipper


main =
    Browser.document
        { init = init
        , update = update
        , view = view
        , subscriptions = \_ -> Sub.none
        }


type Model
    = Ready ReadyModel
    | Error ErrorModel


type DeckProgress
    = EntireDeck
    | DeckUpToPick


type FocusStat
    = FocusALSA
    | FocusALPA
    | FocusGIHWR
    | FocusPickRate


type alias ReadyModel =
    { draft : Draft
    , database : Database.Database
    , highlighted : Maybe Draft.PickCard
    , flipHighlighted : Bool
    , focusStat : FocusStat
    , deckProgress : DeckProgress
    }


type alias ErrorModel =
    { error : String
    }


init : { setData : String, draftData : String } -> ( Model, Cmd Msg )
init flags =
    case ( Draft.decode flags.draftData, Database.decode flags.setData flags.draftData ) of
        ( Ok draftData, Ok database ) ->
            ( Ready
                { draft = draftData
                , database = database
                , highlighted = Nothing
                , flipHighlighted = False
                , focusStat = FocusPickRate
                , deckProgress = DeckUpToPick
                }
            , Cmd.none
            )

        ( Err err, Ok _ ) ->
            ( Error { error = "Error decoding draft data: " ++ err }, Cmd.none )

        ( Ok _, Err err ) ->
            ( Error { error = "Error decoding set data: " ++ err }, Cmd.none )

        ( Err draftError, Err databaseError ) ->
            ( Error { error = "Error decoding draft data: " ++ draftError ++ ", set data: " ++ databaseError }, Cmd.none )


type Msg
    = Increment
    | Decrement
    | Highlight Draft.PickCard
    | FlipHighlightedCard
    | SetFocusStat FocusStat
    | SetDeckProgress DeckProgress


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case model of
        Ready mdl ->
            case msg of
                Increment ->
                    ( Ready { mdl | draft = Zipper.moveRight mdl.draft }, Cmd.none )

                Decrement ->
                    ( Ready { mdl | draft = Zipper.moveLeft mdl.draft }, Cmd.none )

                Highlight card ->
                    ( Ready
                        { mdl
                            | highlighted = Just card
                            , flipHighlighted = False
                        }
                    , Cmd.none
                    )

                FlipHighlightedCard ->
                    ( Ready { mdl | flipHighlighted = not mdl.flipHighlighted }, Cmd.none )

                SetFocusStat stat ->
                    ( Ready { mdl | focusStat = stat }, Cmd.none )

                SetDeckProgress progress ->
                    ( Ready { mdl | deckProgress = progress }, Cmd.none )

        Error mdl ->
            ( Error mdl, Cmd.none )


view : Model -> Document Msg
view model =
    { title = "Drafter"
    , body =
        [ case model of
            Ready mdl ->
                viewReady mdl

            Error mdl ->
                viewError mdl
        ]
    }


viewReady : ReadyModel -> Html Msg
viewReady model =
    div [ class "grid grid-cols-12 gap-6 h-full bg-slate-100" ]
        [ viewSidebar model
        , viewDraft model
        , viewHighlightedCard model
        ]


viewSidebar : ReadyModel -> Html Msg
viewSidebar model =
    div [ class "col-span-2 p-6 bg-slate-600" ]
        [ div []
            [ button
                [ onClick Decrement
                , disabled (not (Zipper.hasLeft model.draft))
                , class "bg-slate-900 rounded text-white p-2 shadow disabled:opacity-50"
                ]
                [ text "Previous" ]
            , span [ class "text-white px-3 font-medium" ] [ text ("Pick " ++ String.fromInt (Zipper.position model.draft)) ]
            , button
                [ onClick Increment
                , class "bg-slate-900 rounded text-white p-2 shadow disabled:opacity-50"
                , disabled (not (Zipper.hasRight model.draft))
                ]
                [ text "Next" ]
            ]
        , viewDeck model
        ]


viewDeck : ReadyModel -> Html Msg
viewDeck model =
    div [ class "mt-6" ]
        [ div [ class "w-full flex" ]
            [ button
                [ onClick (SetDeckProgress DeckUpToPick)
                , disabled (model.deckProgress == DeckUpToPick)
                , classList
                    [ ( "grow border-b-2 text-white pb-2", True )
                    , ( "border-slate-300 font-medium"
                      , model.deckProgress == DeckUpToPick
                      )
                    , ( "border-slate-900"
                      , model.deckProgress /= DeckUpToPick
                      )
                    ]
                ]
                [ text "Up to current pick" ]
            , button
                [ onClick (SetDeckProgress EntireDeck)
                , disabled (model.deckProgress == EntireDeck)
                , classList
                    [ ( "grow border-b-2 text-white pb-2", True )
                    , ( "border-slate-300 font-medium"
                      , model.deckProgress == EntireDeck
                      )
                    , ( "border-slate-900"
                      , model.deckProgress /= EntireDeck
                      )
                    ]
                ]
                [ text "Entire deck" ]
            ]
        , viewCmcChart model
        , viewSignalsChart model
        , viewSignalsDeltaChart model
        ]


viewCmcChart : ReadyModel -> Html Msg
viewCmcChart model =
    let
        deck =
            Deck.fromDraft (model.deckProgress == EntireDeck) model.database model.draft

        cmcs =
            Deck.cmcCurve deck

        bars =
            C.bars [] [ C.bar (second >> toFloat) [] ] cmcs
    in
    div [ class "m-4" ]
        [ C.chart
            [ CA.height 300
            ]
            [ C.xLabels []
            , C.yLabels [ CA.withGrid ]
            , bars
            ]
        ]


viewSignalsChart : ReadyModel -> Html Msg
viewSignalsChart model =
    let
        signals =
            Signals.inferSignalCounts (model.deckProgress == EntireDeck) model.database model.draft

        lines =
            C.series (first >> toFloat)
                [ C.interpolated (second >> .blue >> toFloat) [ CA.color CA.blue ] []
                , C.interpolated (second >> .red >> toFloat) [ CA.color CA.red ] []
                , C.interpolated (second >> .black >> toFloat) [ CA.color "black" ] []
                , C.interpolated (second >> .green >> toFloat) [ CA.color CA.green ] []
                , C.interpolated (second >> .white >> toFloat) [ CA.color "white" ] []
                ]
                signals
    in
    div [ class "m-4" ]
        [ C.chart
            [ CA.height 300
            ]
            [ C.xLabels []
            , C.yLabels [ CA.withGrid ]
            , lines
            ]
        ]


viewSignalsDeltaChart : ReadyModel -> Html Msg
viewSignalsDeltaChart model =
    let
        signals =
            Signals.inferSignalDelta (model.deckProgress == EntireDeck) model.database model.draft

        lines =
            C.series (first >> toFloat)
                [ C.interpolated (second >> .blue) [ CA.color CA.blue ] []
                , C.interpolated (second >> .red) [ CA.color CA.red ] []
                , C.interpolated (second >> .black) [ CA.color "black" ] []
                , C.interpolated (second >> .green) [ CA.color CA.green ] []
                , C.interpolated (second >> .white) [ CA.color "white" ] []
                ]
                signals
    in
    div [ class "m-4" ]
        [ C.chart
            [ CA.height 300
            ]
            [ C.xLabels []
            , C.yLabels [ CA.withGrid ]
            , lines
            ]
        ]


viewError : ErrorModel -> Html Msg
viewError model =
    div [ class "error" ]
        [ text model.error ]


viewDraft : ReadyModel -> Html Msg
viewDraft model =
    let
        chosen =
            (Zipper.focus model.draft).chosen

        picks =
            List.map (\c -> viewKeyedCard model (chosen.name == c.name) c)
                (Zipper.focus model.draft).available

        viewFocusStatButton s =
            button
                [ onClick (SetFocusStat s)
                , classList [ ( "bg-slate-900 rounded text-white p-2 shadow mr-2 mb-2", True ), ( "bg-slate-500 shadow-inset", model.focusStat == s ) ]
                ]
                [ text <|
                    case s of
                        FocusPickRate ->
                            "Pick rate"

                        FocusALPA ->
                            "ALPA"

                        FocusGIHWR ->
                            "GIHWR"

                        FocusALSA ->
                            "ALSA"
                ]
    in
    -- Making this a keyed node forces Elm to recreate each card when the card name changes,
    -- so the old image doesn't hang around during loading of the new card image
    div [ class "flex flex-col space-between col-span-8 " ]
        [ Keyed.node "div"
            [ class "flex-grow grid grid-cols-8 auto-rows-min gap-6 p-6" ]
            picks
        , div []
            [ viewFocusStatButton FocusPickRate
            , viewFocusStatButton FocusALPA
            , viewFocusStatButton FocusGIHWR
            , viewFocusStatButton FocusALSA
            ]
        ]


viewHighlightedCard : ReadyModel -> Html Msg
viewHighlightedCard model =
    div [ class "col-span-2 shadow-xl bg-slate-600 p-6" ]
        [ case model.highlighted of
            Just highlighted ->
                let
                    url =
                        case ( model.flipHighlighted, highlighted.backImage ) of
                            ( True, Just backUrl ) ->
                                backUrl

                            _ ->
                                highlighted.frontImage
                in
                div
                    []
                    [ img
                        [ src url
                        , alt highlighted.name
                        , onClick FlipHighlightedCard
                        ]
                        []
                    , viewCardDetailedPerformance model.database highlighted
                    ]

            Nothing ->
                div [] []
        ]


formatPercentage : Float -> String
formatPercentage value =
    Round.round 2 (value * 100) ++ "%"


viewCardDetailedPerformance : Database.Database -> Draft.PickCard -> Html Msg
viewCardDetailedPerformance db { name } =
    let
        makeCardFacts : CardData -> List ( String, String )
        makeCardFacts cardData =
            [ ( "Pick rate"
              , Just <|
                    formatPercentage (Card.pickRate cardData)
              )
            , ( "GIHWR"
              , Maybe.map formatPercentage (Card.gihwr cardData)
              )
            , ( "ALPA"
              , Maybe.map (Round.round 2) (Card.alpa cardData)
              )
            , ( "ALSA"
              , Maybe.map (Round.round 2) (Card.alsa cardData)
              )
            ]
                |> List.filterMap (\( label, value ) -> Maybe.map (\v -> ( label, v )) value)

        viewFact : ( String, String ) -> Html Msg
        viewFact ( label, value ) =
            div
                [ class "" ]
                [ div [] [ text label ]
                , div [] [ text value ]
                ]
    in
    case Database.get name db of
        Just cardData ->
            div
                [ class "" ]
                (List.map viewFact (makeCardFacts cardData))

        Nothing ->
            div [] [ text "Could not find card in database" ]


viewKeyedCard : ReadyModel -> Bool -> Draft.PickCard -> ( String, Html Msg )
viewKeyedCard model wasChosen { name, frontImage, backImage } =
    let
        stats =
            Database.get name model.database

        focusStat =
            case model.focusStat of
                FocusALSA ->
                    Maybe.map (Round.round 2) (Maybe.andThen Card.alsa stats)

                FocusALPA ->
                    Maybe.map (Round.round 2) (Maybe.andThen Card.alpa stats)

                FocusGIHWR ->
                    Maybe.map formatPercentage (Maybe.andThen Card.gihwr stats)

                FocusPickRate ->
                    Maybe.map (formatPercentage << Card.pickRate) stats
    in
    ( name
    , div
        [ classList
            [ ( "relative", True )
            , ( "border-4 border-green-500", wasChosen )
            ]
        , Events.onMouseEnter (Highlight { name = name, frontImage = frontImage, backImage = backImage })
        ]
        [ img [ src frontImage, alt name ] []
        , span [ class "absolute top-0 left-0 bg-green-100" ] [ text <| Maybe.withDefault "?" focusStat ]
        ]
    )
