{-
   Copyright 2020 Eike K. & Contributors

   SPDX-License-Identifier: AGPL-3.0-or-later
-}


module Comp.UploadForm exposing (Model, Msg, init, reset, update, view)

import Api
import Api.Model.BasicResult exposing (BasicResult)
import Api.Model.ItemUploadMeta
import Comp.Dropzone
import Comp.FixedDropdown
import Comp.Progress
import Data.DropdownStyle as DS
import Data.Flags exposing (Flags)
import Data.Language exposing (Language)
import Data.UiSettings exposing (UiSettings)
import Dict exposing (Dict)
import File exposing (File)
import Html exposing (Html, a, div, h2, h3, i, input, label, p, span, text)
import Html.Attributes exposing (action, checked, class, classList, href, id, type_)
import Html.Events exposing (onCheck, onClick)
import Http
import Messages.Comp.UploadForm exposing (Texts)
import Page exposing (Page(..))
import Set exposing (Set)
import Styles
import Util.File exposing (makeFileId)
import Util.Maybe
import Util.Size


type alias Model =
    { incoming : Bool
    , singleItem : Bool
    , files : List File
    , completed : Set String
    , errored : Set String
    , loading : Dict String Int
    , dropzone : Comp.Dropzone.Model
    , skipDuplicates : Bool
    , languageModel : Comp.FixedDropdown.Model Language
    , language : Maybe Language
    , flattenArchives : Bool
    , uploadDone : Maybe Bool
    }


type Msg
    = SubmitUpload
    | SingleUploadResp String (Result Http.Error BasicResult)
    | GotProgress String Http.Progress
    | ToggleIncoming
    | ToggleSingleItem
    | Clear
    | DropzoneMsg Comp.Dropzone.Msg
    | ToggleSkipDuplicates
    | LanguageMsg (Comp.FixedDropdown.Msg Language)
    | ToggleFlattenArchives


init : Model
init =
    { incoming = True
    , singleItem = False
    , files = []
    , completed = Set.empty
    , errored = Set.empty
    , loading = Dict.empty
    , dropzone = Comp.Dropzone.init []
    , skipDuplicates = True
    , languageModel =
        Comp.FixedDropdown.init Data.Language.all
    , language = Nothing
    , flattenArchives = False
    , uploadDone = Nothing
    }


reset : Msg
reset =
    Clear


isLoading : Model -> File -> Bool
isLoading model file =
    Dict.member (makeFileId file) model.loading


isCompleted : Model -> File -> Bool
isCompleted model file =
    Set.member (makeFileId file) model.completed


isError : Model -> File -> Bool
isError model file =
    Set.member (makeFileId file) model.errored


isIdle : Model -> File -> Bool
isIdle model file =
    not (isLoading model file || isCompleted model file || isError model file)


uploadAllTracker : String
uploadAllTracker =
    "upload-all"


isDone : Model -> Bool
isDone model =
    List.map makeFileId model.files
        |> List.all (\id -> Set.member id model.completed || Set.member id model.errored)


isSuccessAll : Model -> Bool
isSuccessAll model =
    List.map makeFileId model.files
        |> List.all (\id -> Set.member id model.completed)


hasErrors : Model -> Bool
hasErrors model =
    not (Set.isEmpty model.errored)



--- Update


update : Maybe String -> Flags -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
update sourceId flags msg model =
    case msg of
        ToggleIncoming ->
            ( { model | incoming = not model.incoming }, Cmd.none, Sub.none )

        ToggleSingleItem ->
            let
                newFlag =
                    not model.singleItem
            in
            ( { model
                | singleItem = newFlag
                , flattenArchives =
                    if newFlag then
                        False

                    else
                        model.flattenArchives
              }
            , Cmd.none
            , Sub.none
            )

        ToggleSkipDuplicates ->
            ( { model | skipDuplicates = not model.skipDuplicates }, Cmd.none, Sub.none )

        ToggleFlattenArchives ->
            let
                newFlag =
                    not model.flattenArchives
            in
            ( { model
                | flattenArchives = newFlag
                , singleItem =
                    if newFlag then
                        False

                    else
                        model.singleItem
              }
            , Cmd.none
            , Sub.none
            )

        SubmitUpload ->
            let
                emptyMeta =
                    Api.Model.ItemUploadMeta.empty

                meta =
                    { emptyMeta
                        | multiple = not model.singleItem
                        , skipDuplicates = Just model.skipDuplicates
                        , direction =
                            if model.incoming then
                                Just "incoming"

                            else
                                Just "outgoing"
                        , language = Maybe.map Data.Language.toIso3 model.language
                        , flattenArchives = Just model.flattenArchives
                    }

                fileids =
                    List.map makeFileId model.files

                uploads =
                    if model.singleItem then
                        Api.uploadSingle flags
                            sourceId
                            meta
                            uploadAllTracker
                            model.files
                            (SingleUploadResp uploadAllTracker)

                    else
                        Cmd.batch (Api.upload flags sourceId meta model.files SingleUploadResp)

                tracker =
                    if model.singleItem then
                        Http.track uploadAllTracker (GotProgress uploadAllTracker)

                    else
                        Sub.batch <| List.map (\id -> Http.track id (GotProgress id)) fileids

                ( cm2, _, _ ) =
                    Comp.Dropzone.update (Comp.Dropzone.setActive False) model.dropzone

                nowLoading =
                    List.map (\fid -> ( fid, 0 )) fileids
                        |> Dict.fromList
            in
            if List.isEmpty model.files then
                ( model, Cmd.none, Sub.none )

            else
                ( { model | loading = nowLoading, dropzone = cm2 }, uploads, tracker )

        SingleUploadResp fileid (Ok res) ->
            let
                compl =
                    if res.success then
                        setCompleted model fileid

                    else
                        model.completed

                errs =
                    if not res.success then
                        setErrored model fileid

                    else
                        model.errored

                load =
                    if fileid == uploadAllTracker then
                        Dict.empty

                    else
                        Dict.remove fileid model.loading

                nextModel =
                    { model | completed = compl, errored = errs, loading = load }

                uploadsDone =
                    if isDone nextModel && hasErrors nextModel then
                        Just False

                    else if isDone nextModel && isSuccessAll nextModel then
                        Just True

                    else
                        Nothing

                result =
                    case uploadsDone of
                        Just _ ->
                            { nextModel | uploadDone = uploadsDone, files = [], dropzone = Comp.Dropzone.init [] }

                        Nothing ->
                            { nextModel | uploadDone = uploadsDone }
            in
            ( result
            , Cmd.none
            , Sub.none
            )

        SingleUploadResp fileid (Err _) ->
            let
                errs =
                    setErrored model fileid

                load =
                    if fileid == uploadAllTracker then
                        Dict.empty

                    else
                        Dict.remove fileid model.loading
            in
            ( { model | errored = errs, loading = load }, Cmd.none, Sub.none )

        GotProgress fileid progress ->
            let
                percent =
                    case progress of
                        Http.Sending p ->
                            Http.fractionSent p
                                |> (*) 100
                                |> round

                        _ ->
                            0

                newLoading =
                    if model.singleItem then
                        Dict.insert uploadAllTracker percent model.loading

                    else
                        Dict.insert fileid percent model.loading
            in
            ( { model | loading = newLoading }
            , Cmd.none
            , Sub.none
            )

        Clear ->
            ( init, Cmd.none, Sub.none )

        DropzoneMsg m ->
            let
                ( m2, c2, files ) =
                    Comp.Dropzone.update m model.dropzone

                nextFiles =
                    List.append model.files files
            in
            ( { model | files = nextFiles, dropzone = m2, uploadDone = Nothing }, Cmd.map DropzoneMsg c2, Sub.none )

        LanguageMsg lm ->
            let
                ( dm, sel ) =
                    Comp.FixedDropdown.update lm model.languageModel
            in
            ( { model
                | languageModel = dm
                , language = Util.Maybe.or [ sel, model.language ]
              }
            , Cmd.none
            , Sub.none
            )


setCompleted : Model -> String -> Set String
setCompleted model fileid =
    if fileid == uploadAllTracker then
        List.map makeFileId model.files |> Set.fromList

    else
        Set.insert fileid model.completed


setErrored : Model -> String -> Set String
setErrored model fileid =
    if fileid == uploadAllTracker then
        List.map makeFileId model.files |> Set.fromList

    else
        Set.insert fileid model.errored



--- View


type alias ViewSettings =
    { showForm : Bool
    , sourceId : Maybe String
    , lightForm : Bool
    }


view : Texts -> ViewSettings -> Flags -> UiSettings -> Model -> Html Msg
view texts viewCfg _ _ model =
    let
        showForm =
            viewCfg.sourceId == Nothing && viewCfg.showForm

        dropzoneCfg =
            { light = viewCfg.lightForm
            }
    in
    div [ class "mx-auto" ]
        [ div [ class "px-0 flex flex-col" ]
            [ if showForm then
                div [ class "mb-4" ]
                    [ renderForm texts model
                    ]

              else
                span [ class "hidden" ] []
            , div [ class "py-0" ]
                [ Html.map DropzoneMsg
                    (Comp.Dropzone.view2 texts.dropzone dropzoneCfg model.dropzone)
                ]
            , div [ class "py-4" ]
                [ a
                    [ class Styles.primaryButton
                    , href "#"
                    , onClick SubmitUpload
                    ]
                    [ text texts.basics.submit
                    ]
                , a
                    [ class Styles.secondaryButton
                    , class "ml-2"
                    , href "#"
                    , onClick Clear
                    ]
                    [ text texts.reset
                    ]
                ]
            ]
        , renderErrorMsg texts model
        , renderSuccessMsg texts (Util.Maybe.nonEmpty viewCfg.sourceId) model
        , renderUploads texts model
        ]


renderForm : Texts -> Model -> Html Msg
renderForm texts model =
    let
        languageCfg =
            { display = texts.languageLabel
            , icon = \_ -> Nothing
            , style = DS.mainStyleWith "w-40"
            , selectPlaceholder = texts.basics.selectPlaceholder
            }
    in
    div [ class "row" ]
        [ Html.form [ action "#" ]
            [ div [ class "flex flex-col mb-3" ]
                [ label [ class "inline-flex items-center" ]
                    [ input
                        [ type_ "radio"
                        , checked model.incoming
                        , onCheck (\_ -> ToggleIncoming)
                        , class Styles.radioInput
                        ]
                        []
                    , span [ class "ml-2" ] [ text texts.basics.incoming ]
                    ]
                , label [ class "inline-flex items-center" ]
                    [ input
                        [ type_ "radio"
                        , checked (not model.incoming)
                        , onCheck (\_ -> ToggleIncoming)
                        , class Styles.radioInput
                        ]
                        []
                    , span [ class "ml-2" ] [ text texts.basics.outgoing ]
                    ]
                ]
            , div [ class "flex flex-col mb-3" ]
                [ label [ class "inline-flex items-center" ]
                    [ input
                        [ type_ "checkbox"
                        , checked model.singleItem
                        , onCheck (\_ -> ToggleSingleItem)
                        , class Styles.checkboxInput
                        ]
                        []
                    , span [ class "ml-2" ]
                        [ text texts.allFilesOneItem
                        ]
                    ]
                ]
            , div [ class "flex flex-col mb-3" ]
                [ label [ class "inline-flex items-center" ]
                    [ input
                        [ type_ "checkbox"
                        , checked model.flattenArchives
                        , onCheck (\_ -> ToggleFlattenArchives)
                        , class Styles.checkboxInput
                        ]
                        []
                    , span [ class "ml-2" ]
                        [ text texts.flattenArchives
                        ]
                    ]
                ]
            , div [ class "flex flex-col mb-3" ]
                [ label [ class "inline-flex items-center" ]
                    [ input
                        [ type_ "checkbox"
                        , checked model.skipDuplicates
                        , onCheck (\_ -> ToggleSkipDuplicates)
                        , class Styles.checkboxInput
                        ]
                        []
                    , span [ class "ml-2" ]
                        [ text texts.skipExistingFiles
                        ]
                    ]
                ]
            , div [ class "flex flex-col mb-3" ]
                [ label [ class "inline-flex items-center mb-2" ]
                    [ span [ class "mr-2" ] [ text (texts.language ++ ":") ]
                    , Html.map LanguageMsg
                        (Comp.FixedDropdown.viewStyled2
                            languageCfg
                            False
                            model.language
                            model.languageModel
                        )
                    ]
                , div [ class "text-gray-400 text-xs" ]
                    [ text texts.languageInfo
                    ]
                ]
            ]
        ]


renderErrorMsg : Texts -> Model -> Html Msg
renderErrorMsg texts model =
    div
        [ class "row"
        , classList [ ( "hidden", Maybe.withDefault True model.uploadDone ) ]
        ]
        [ div [ class "mt-4" ]
            [ div [ class Styles.errorMessage ]
                [ text texts.uploadErrorMessage
                ]
            ]
        ]


renderSuccessMsg : Texts -> Bool -> Model -> Html Msg
renderSuccessMsg texts public model =
    div
        [ class "row"
        , classList [ ( "hidden", Maybe.map not model.uploadDone |> Maybe.withDefault True ) ]
        ]
        [ div [ class "mt-4" ]
            [ div [ class Styles.successMessage ]
                [ h3 [ class Styles.header2, class "text-green-800 dark:text-lime-800" ]
                    [ i [ class "fa fa-smile font-thin" ] []
                    , span [ class "ml-2" ]
                        [ text texts.successBox.allFilesUploaded
                        ]
                    ]
                , p
                    [ classList [ ( "hidden", public ) ]
                    ]
                    [ text texts.successBox.line1
                    , a
                        [ class Styles.successMessageLink
                        , Page.href (SearchPage Nothing)
                        ]
                        [ text texts.successBox.itemsPage
                        ]
                    , text texts.successBox.line2
                    , a
                        [ class Styles.successMessageLink
                        , Page.href QueuePage
                        ]
                        [ text texts.successBox.processingPage
                        ]
                    , text texts.successBox.line3
                    ]
                , p []
                    [ text texts.successBox.resetLine1
                    , a
                        [ class Styles.successMessageLink
                        , href "#"
                        , onClick Clear
                        ]
                        [ text texts.successBox.reset
                        ]
                    , text texts.successBox.resetLine2
                    ]
                ]
            ]
        ]


renderUploads : Texts -> Model -> Html Msg
renderUploads texts model =
    div
        [ class "mt-4"
        , classList [ ( "hidden", List.isEmpty model.files || isSuccessAll model ) ]
        ]
        [ h2 [ class Styles.header2 ]
            [ text texts.selectedFiles
            , text (" (" ++ (List.length model.files |> String.fromInt) ++ ")")
            ]
        , div [] <|
            if model.singleItem then
                List.map (renderFileItem model (Just uploadAllTracker)) model.files

            else
                List.map (renderFileItem model Nothing) model.files
        ]


getProgress : Model -> File -> Int
getProgress model file =
    let
        key =
            if model.singleItem then
                uploadAllTracker

            else
                makeFileId file
    in
    Dict.get key model.loading
        |> Maybe.withDefault 0


renderFileItem : Model -> Maybe String -> File -> Html Msg
renderFileItem model _ file =
    let
        name =
            File.name file

        size =
            File.size file
                |> toFloat
                |> Util.Size.bytesReadable Util.Size.B
    in
    div [ class "flex flex-col w-full mb-4" ]
        [ div [ class "flex flex-row items-center" ]
            [ div [ class "inline-flex items-center" ]
                [ i
                    [ classList
                        [ ( "mr-2 text-lg", True )
                        , ( "fa fa-file font-thin", isIdle model file )
                        , ( "fa fa-spinner animate-spin ", isLoading model file )
                        , ( "fa fa-check ", isCompleted model file )
                        , ( "fa fa-bolt", isError model file )
                        ]
                    ]
                    []
                , div [ class "middle aligned content" ]
                    [ div [ class "header" ]
                        [ text name
                        ]
                    ]
                ]
            , div [ class "flex-grow inline-flex justify-end" ]
                [ text size
                ]
            ]
        , div [ class "h-4" ]
            [ Comp.Progress.progress2 (getProgress model file)
            ]
        ]
