module Comp.ItemDetail.EditMenu exposing
    ( Model
    , Msg
    , SaveNameState(..)
    , defaultViewConfig
    , init
    , loadModel
    , update
    , view
    )

import Api
import Api.Model.EquipmentList exposing (EquipmentList)
import Api.Model.FolderItem exposing (FolderItem)
import Api.Model.FolderList exposing (FolderList)
import Api.Model.IdName exposing (IdName)
import Api.Model.ItemProposals exposing (ItemProposals)
import Api.Model.PersonList exposing (PersonList)
import Api.Model.ReferenceList exposing (ReferenceList)
import Api.Model.Tag exposing (Tag)
import Api.Model.TagList exposing (TagList)
import Comp.CustomFieldMultiInput
import Comp.DatePicker
import Comp.DetailEdit
import Comp.Dropdown exposing (isDropdownChangeMsg)
import Comp.ItemDetail.FormChange exposing (FormChange(..))
import Data.CustomFieldChange exposing (CustomFieldChange(..))
import Data.Direction exposing (Direction)
import Data.Fields
import Data.Flags exposing (Flags)
import Data.Icons as Icons
import Data.UiSettings exposing (UiSettings)
import DatePicker exposing (DatePicker)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)
import Http
import Markdown
import Page exposing (Page(..))
import Task
import Throttle exposing (Throttle)
import Time
import Util.Folder exposing (mkFolderOption)
import Util.List
import Util.Maybe
import Util.Tag



--- Model


type SaveNameState
    = Saving
    | SaveSuccess
    | SaveFailed


type TagEditMode
    = AddTags
    | RemoveTags
    | ReplaceTags


type alias Model =
    { tagModel : Comp.Dropdown.Model Tag
    , nameModel : String
    , nameSaveThrottle : Throttle Msg
    , folderModel : Comp.Dropdown.Model IdName
    , allFolders : List FolderItem
    , directionModel : Comp.Dropdown.Model Direction
    , itemDatePicker : DatePicker
    , itemDate : Maybe Int
    , itemProposals : ItemProposals
    , dueDate : Maybe Int
    , dueDatePicker : DatePicker
    , corrOrgModel : Comp.Dropdown.Model IdName
    , corrPersonModel : Comp.Dropdown.Model IdName
    , concPersonModel : Comp.Dropdown.Model IdName
    , concEquipModel : Comp.Dropdown.Model IdName
    , modalEdit : Maybe Comp.DetailEdit.Model
    , tagEditMode : TagEditMode
    , customFieldModel : Comp.CustomFieldMultiInput.Model
    }


type Msg
    = ItemDatePickerMsg Comp.DatePicker.Msg
    | DueDatePickerMsg Comp.DatePicker.Msg
    | SetName String
    | SaveName
    | UpdateThrottle
    | RemoveDueDate
    | RemoveDate
    | ConfirmMsg Bool
    | ToggleTagEditMode
    | FolderDropdownMsg (Comp.Dropdown.Msg IdName)
    | TagDropdownMsg (Comp.Dropdown.Msg Tag)
    | DirDropdownMsg (Comp.Dropdown.Msg Direction)
    | OrgDropdownMsg (Comp.Dropdown.Msg IdName)
    | CorrPersonMsg (Comp.Dropdown.Msg IdName)
    | ConcPersonMsg (Comp.Dropdown.Msg IdName)
    | ConcEquipMsg (Comp.Dropdown.Msg IdName)
    | GetTagsResp (Result Http.Error TagList)
    | GetOrgResp (Result Http.Error ReferenceList)
    | GetPersonResp (Result Http.Error PersonList)
    | GetEquipResp (Result Http.Error EquipmentList)
    | GetFolderResp (Result Http.Error FolderList)
    | CustomFieldMsg Comp.CustomFieldMultiInput.Msg


init : Model
init =
    { tagModel =
        Util.Tag.makeDropdownModel
    , directionModel =
        Comp.Dropdown.makeSingleList
            { makeOption =
                \entry ->
                    { value = Data.Direction.toString entry
                    , text = Data.Direction.toString entry
                    , additional = ""
                    }
            , options = Data.Direction.all
            , placeholder = "Choose a direction…"
            , selected = Nothing
            }
    , corrOrgModel =
        Comp.Dropdown.makeSingle
            { makeOption = \e -> { value = e.id, text = e.name, additional = "" }
            , placeholder = ""
            }
    , corrPersonModel =
        Comp.Dropdown.makeSingle
            { makeOption = \e -> { value = e.id, text = e.name, additional = "" }
            , placeholder = ""
            }
    , concPersonModel =
        Comp.Dropdown.makeSingle
            { makeOption = \e -> { value = e.id, text = e.name, additional = "" }
            , placeholder = ""
            }
    , concEquipModel =
        Comp.Dropdown.makeSingle
            { makeOption = \e -> { value = e.id, text = e.name, additional = "" }
            , placeholder = ""
            }
    , folderModel =
        Comp.Dropdown.makeSingle
            { makeOption = \e -> { value = e.id, text = e.name, additional = "" }
            , placeholder = ""
            }
    , allFolders = []
    , nameModel = ""
    , nameSaveThrottle = Throttle.create 1
    , itemDatePicker = Comp.DatePicker.emptyModel
    , itemDate = Nothing
    , itemProposals = Api.Model.ItemProposals.empty
    , dueDate = Nothing
    , dueDatePicker = Comp.DatePicker.emptyModel
    , modalEdit = Nothing
    , tagEditMode = AddTags
    , customFieldModel = Comp.CustomFieldMultiInput.initWith []
    }


loadModel : Flags -> Cmd Msg
loadModel flags =
    let
        ( _, dpc ) =
            Comp.DatePicker.init
    in
    Cmd.batch
        [ Api.getTags flags "" GetTagsResp
        , Api.getOrgLight flags GetOrgResp
        , Api.getPersons flags "" GetPersonResp
        , Api.getEquipments flags "" GetEquipResp
        , Api.getFolders flags "" False GetFolderResp
        , Cmd.map CustomFieldMsg (Comp.CustomFieldMultiInput.initCmd flags)
        , Cmd.map ItemDatePickerMsg dpc
        , Cmd.map DueDatePickerMsg dpc
        ]


isFolderMember : Model -> Bool
isFolderMember model =
    let
        selected =
            Comp.Dropdown.getSelected model.folderModel
                |> List.head
                |> Maybe.map .id
    in
    Util.Folder.isFolderMember model.allFolders selected



--- Update


type alias UpdateResult =
    { model : Model
    , cmd : Cmd Msg
    , sub : Sub Msg
    , change : FormChange
    }


resultNoCmd : FormChange -> Model -> UpdateResult
resultNoCmd change model =
    UpdateResult model Cmd.none Sub.none change


resultNone : Model -> UpdateResult
resultNone model =
    resultNoCmd NoFormChange model


update : Flags -> Msg -> Model -> UpdateResult
update flags msg model =
    case msg of
        ConfirmMsg flag ->
            resultNoCmd (ConfirmChange flag) model

        TagDropdownMsg m ->
            let
                ( m2, _ ) =
                    Comp.Dropdown.update m model.tagModel

                newModel =
                    { model | tagModel = m2 }

                mkChange list =
                    case model.tagEditMode of
                        AddTags ->
                            AddTagChange list

                        RemoveTags ->
                            RemoveTagChange list

                        ReplaceTags ->
                            ReplaceTagChange list

                change =
                    if isDropdownChangeMsg m then
                        Comp.Dropdown.getSelected newModel.tagModel
                            |> Util.List.distinct
                            |> List.map (\t -> IdName t.id t.name)
                            |> ReferenceList
                            |> mkChange

                    else
                        NoFormChange
            in
            resultNoCmd change newModel

        ToggleTagEditMode ->
            let
                ( m2, _ ) =
                    Comp.Dropdown.update (Comp.Dropdown.SetSelection []) model.tagModel

                newModel =
                    { model | tagModel = m2 }
            in
            case model.tagEditMode of
                AddTags ->
                    resultNone { newModel | tagEditMode = RemoveTags }

                RemoveTags ->
                    resultNone { newModel | tagEditMode = ReplaceTags }

                ReplaceTags ->
                    resultNone { newModel | tagEditMode = AddTags }

        GetTagsResp (Ok tags) ->
            let
                tagList =
                    Comp.Dropdown.SetOptions tags.items
            in
            update flags (TagDropdownMsg tagList) model

        GetTagsResp (Err _) ->
            resultNone model

        FolderDropdownMsg m ->
            let
                ( m2, _ ) =
                    Comp.Dropdown.update m model.folderModel

                newModel =
                    { model | folderModel = m2 }

                idref =
                    Comp.Dropdown.getSelected m2 |> List.head

                change =
                    if isDropdownChangeMsg m then
                        FolderChange idref

                    else
                        NoFormChange
            in
            resultNoCmd change newModel

        GetFolderResp (Ok fs) ->
            let
                model_ =
                    { model
                        | allFolders = fs.items
                        , folderModel =
                            Comp.Dropdown.setMkOption
                                (mkFolderOption flags fs.items)
                                model.folderModel
                    }

                mkIdName fitem =
                    IdName fitem.id fitem.name

                opts =
                    fs.items
                        |> List.map mkIdName
                        |> Comp.Dropdown.SetOptions
            in
            update flags (FolderDropdownMsg opts) model_

        GetFolderResp (Err _) ->
            resultNone model

        DirDropdownMsg m ->
            let
                ( m2, _ ) =
                    Comp.Dropdown.update m model.directionModel

                newModel =
                    { model | directionModel = m2 }

                change =
                    if isDropdownChangeMsg m then
                        let
                            dir =
                                Comp.Dropdown.getSelected m2 |> List.head
                        in
                        case dir of
                            Just d ->
                                DirectionChange d

                            Nothing ->
                                NoFormChange

                    else
                        NoFormChange
            in
            resultNoCmd change newModel

        OrgDropdownMsg m ->
            let
                ( m2, _ ) =
                    Comp.Dropdown.update m model.corrOrgModel

                newModel =
                    { model | corrOrgModel = m2 }

                idref =
                    Comp.Dropdown.getSelected m2 |> List.head

                change =
                    if isDropdownChangeMsg m then
                        OrgChange idref

                    else
                        NoFormChange
            in
            resultNoCmd change newModel

        GetOrgResp (Ok orgs) ->
            let
                opts =
                    Comp.Dropdown.SetOptions orgs.items
            in
            update flags (OrgDropdownMsg opts) model

        GetOrgResp (Err _) ->
            resultNone model

        CorrPersonMsg m ->
            let
                ( m2, _ ) =
                    Comp.Dropdown.update m model.corrPersonModel

                newModel =
                    { model | corrPersonModel = m2 }

                idref =
                    Comp.Dropdown.getSelected m2 |> List.head

                change =
                    if isDropdownChangeMsg m then
                        CorrPersonChange idref

                    else
                        NoFormChange
            in
            resultNoCmd change newModel

        ConcPersonMsg m ->
            let
                ( m2, _ ) =
                    Comp.Dropdown.update m model.concPersonModel

                newModel =
                    { model | concPersonModel = m2 }

                idref =
                    Comp.Dropdown.getSelected m2 |> List.head

                change =
                    if isDropdownChangeMsg m then
                        ConcPersonChange idref

                    else
                        NoFormChange
            in
            resultNoCmd change newModel

        GetPersonResp (Ok ps) ->
            let
                ( conc, corr ) =
                    List.partition .concerning ps.items

                concRefs =
                    List.map (\e -> IdName e.id e.name) conc

                corrRefs =
                    List.map (\e -> IdName e.id e.name) corr

                res1 =
                    update flags (CorrPersonMsg (Comp.Dropdown.SetOptions corrRefs)) model

                res2 =
                    update flags (ConcPersonMsg (Comp.Dropdown.SetOptions concRefs)) res1.model
            in
            res2

        GetPersonResp (Err _) ->
            resultNone model

        ConcEquipMsg m ->
            let
                ( m2, _ ) =
                    Comp.Dropdown.update m model.concEquipModel

                newModel =
                    { model | concEquipModel = m2 }

                idref =
                    Comp.Dropdown.getSelected m2 |> List.head

                change =
                    if isDropdownChangeMsg m then
                        EquipChange idref

                    else
                        NoFormChange
            in
            resultNoCmd change newModel

        GetEquipResp (Ok equips) ->
            let
                opts =
                    Comp.Dropdown.SetOptions
                        (List.map (\e -> IdName e.id e.name)
                            equips.items
                        )
            in
            update flags (ConcEquipMsg opts) model

        GetEquipResp (Err _) ->
            resultNone model

        ItemDatePickerMsg m ->
            let
                ( dp, event ) =
                    Comp.DatePicker.updateDefault m model.itemDatePicker
            in
            case event of
                DatePicker.Picked date ->
                    let
                        newModel =
                            { model | itemDatePicker = dp, itemDate = Just (Comp.DatePicker.midOfDay date) }
                    in
                    resultNoCmd (ItemDateChange newModel.itemDate) newModel

                _ ->
                    resultNone { model | itemDatePicker = dp }

        RemoveDate ->
            resultNoCmd (ItemDateChange Nothing) { model | itemDate = Nothing }

        DueDatePickerMsg m ->
            let
                ( dp, event ) =
                    Comp.DatePicker.updateDefault m model.dueDatePicker
            in
            case event of
                DatePicker.Picked date ->
                    let
                        newModel =
                            { model | dueDatePicker = dp, dueDate = Just (Comp.DatePicker.midOfDay date) }
                    in
                    resultNoCmd (DueDateChange newModel.dueDate) newModel

                _ ->
                    resultNone { model | dueDatePicker = dp }

        RemoveDueDate ->
            resultNoCmd (DueDateChange Nothing) { model | dueDate = Nothing }

        SetName str ->
            case Util.Maybe.fromString str of
                Just newName ->
                    let
                        cmd_ =
                            Task.succeed ()
                                |> Task.perform (\_ -> SaveName)

                        ( newThrottle, cmd ) =
                            Throttle.try cmd_ model.nameSaveThrottle

                        newModel =
                            { model
                                | nameSaveThrottle = newThrottle
                                , nameModel = newName
                            }

                        sub =
                            nameThrottleSub newModel
                    in
                    UpdateResult newModel cmd sub NoFormChange

                Nothing ->
                    resultNone { model | nameModel = str }

        SaveName ->
            case Util.Maybe.fromString model.nameModel of
                Just n ->
                    resultNoCmd (NameChange n) model

                Nothing ->
                    resultNone model

        UpdateThrottle ->
            let
                ( newThrottle, cmd ) =
                    Throttle.update model.nameSaveThrottle

                newModel =
                    { model | nameSaveThrottle = newThrottle }

                sub =
                    nameThrottleSub newModel
            in
            UpdateResult newModel cmd sub NoFormChange

        CustomFieldMsg lm ->
            let
                res =
                    Comp.CustomFieldMultiInput.update lm model.customFieldModel

                model_ =
                    { model | customFieldModel = res.model }

                cmd_ =
                    Cmd.map CustomFieldMsg res.cmd

                change =
                    case res.result of
                        NoFieldChange ->
                            NoFormChange

                        FieldValueRemove cf ->
                            RemoveCustomValue cf

                        FieldValueChange cf value ->
                            CustomValueChange cf value

                        FieldCreateNew ->
                            NoFormChange
            in
            UpdateResult model_ cmd_ Sub.none change


nameThrottleSub : Model -> Sub Msg
nameThrottleSub model =
    Throttle.ifNeeded
        (Time.every 400 (\_ -> UpdateThrottle))
        model.nameSaveThrottle



--- View


type alias ViewConfig =
    { menuClass : String
    , nameState : SaveNameState
    , customFieldState : String -> SaveNameState
    }


defaultViewConfig : ViewConfig
defaultViewConfig =
    { menuClass = "ui vertical segment"
    , nameState = SaveSuccess
    , customFieldState = \_ -> SaveSuccess
    }


view : ViewConfig -> UiSettings -> Model -> Html Msg
view =
    renderEditForm


renderEditForm : ViewConfig -> UiSettings -> Model -> Html Msg
renderEditForm cfg settings model =
    let
        fieldVisible field =
            Data.UiSettings.fieldVisible settings field

        optional fields html =
            if
                List.map fieldVisible fields
                    |> List.foldl (||) False
            then
                html

            else
                span [ class "invisible hidden" ] []

        tagModeIcon =
            case model.tagEditMode of
                AddTags ->
                    i [ class "grey plus link icon" ] []

                RemoveTags ->
                    i [ class "grey eraser link icon" ] []

                ReplaceTags ->
                    i [ class "grey redo alternate link icon" ] []

        tagModeMsg =
            case model.tagEditMode of
                AddTags ->
                    "Tags chosen here are *added* to all selected items."

                RemoveTags ->
                    "Tags chosen here are *removed* from all selected items."

                ReplaceTags ->
                    "Tags chosen here *replace* those on selected items."

        customFieldIcon field =
            case cfg.customFieldState field.id of
                SaveSuccess ->
                    Nothing

                SaveFailed ->
                    Just "red exclamation triangle icon"

                Saving ->
                    Just "refresh loading icon"

        customFieldSettings =
            Comp.CustomFieldMultiInput.ViewSettings
                False
                "field"
                customFieldIcon
    in
    div [ class cfg.menuClass ]
        [ div [ class "ui form warning" ]
            [ div [ class "field" ]
                [ div
                    [ class "ui fluid buttons"
                    ]
                    [ button
                        [ class "ui primary button"
                        , onClick (ConfirmMsg True)
                        ]
                        [ text "Confirm"
                        ]
                    , div [ class "or" ] []
                    , button
                        [ class "ui secondary button"
                        , onClick (ConfirmMsg False)
                        ]
                        [ text "Unconfirm"
                        ]
                    ]
                ]
            , optional [ Data.Fields.Tag ] <|
                div [ class "field" ]
                    [ label []
                        [ Icons.tagsIcon "grey"
                        , text "Tags"
                        , a
                            [ class "right-float"
                            , href "#"
                            , title "Change tag edit mode"
                            , onClick ToggleTagEditMode
                            ]
                            [ tagModeIcon
                            ]
                        ]
                    , Html.map TagDropdownMsg (Comp.Dropdown.view settings model.tagModel)
                    , Markdown.toHtml [ class "small-info" ] tagModeMsg
                    ]
            , div [ class " field" ]
                [ label [] [ text "Name" ]
                , div [ class "ui icon input" ]
                    [ input [ type_ "text", value model.nameModel, onInput SetName ] []
                    , i
                        [ classList
                            [ ( "green check icon", cfg.nameState == SaveSuccess )
                            , ( "red exclamation triangle icon", cfg.nameState == SaveFailed )
                            , ( "sync loading icon", cfg.nameState == Saving )
                            ]
                        ]
                        []
                    ]
                ]
            , optional [ Data.Fields.Folder ] <|
                div [ class "field" ]
                    [ label []
                        [ Icons.folderIcon "grey"
                        , text "Folder"
                        ]
                    , Html.map FolderDropdownMsg (Comp.Dropdown.view settings model.folderModel)
                    , div
                        [ classList
                            [ ( "ui warning message", True )
                            , ( "hidden", isFolderMember model )
                            ]
                        ]
                        [ Markdown.toHtml [] """
You are **not a member** of this folder. This item will be **hidden**
from any search now. Use a folder where you are a member of to make this
item visible. This message will disappear then.
                      """
                        ]
                    ]
            , optional [ Data.Fields.CustomFields ] <|
                h4 [ class "ui dividing header" ]
                    [ Icons.customFieldIcon ""
                    , text "Custom Fields"
                    ]
            , optional [ Data.Fields.CustomFields ] <|
                Html.map CustomFieldMsg
                    (Comp.CustomFieldMultiInput.view customFieldSettings model.customFieldModel)
            , optional [ Data.Fields.Date, Data.Fields.DueDate ] <|
                h4 [ class "ui dividing header" ]
                    [ Icons.itemDatesIcon ""
                    , text "Item Dates"
                    ]
            , optional [ Data.Fields.Date ] <|
                div [ class "field" ]
                    [ label []
                        [ Icons.dateIcon "grey"
                        , text "Date"
                        ]
                    , div [ class "ui left icon action input" ]
                        [ Html.map ItemDatePickerMsg
                            (Comp.DatePicker.viewTime
                                model.itemDate
                                actionInputDatePicker
                                model.itemDatePicker
                            )
                        , a [ class "ui icon button", href "", onClick RemoveDate ]
                            [ i [ class "trash alternate outline icon" ] []
                            ]
                        , Icons.dateIcon ""
                        ]
                    ]
            , optional [ Data.Fields.DueDate ] <|
                div [ class " field" ]
                    [ label []
                        [ Icons.dueDateIcon "grey"
                        , text "Due Date"
                        ]
                    , div [ class "ui left icon action input" ]
                        [ Html.map DueDatePickerMsg
                            (Comp.DatePicker.viewTime
                                model.dueDate
                                actionInputDatePicker
                                model.dueDatePicker
                            )
                        , a [ class "ui icon button", href "", onClick RemoveDueDate ]
                            [ i [ class "trash alternate outline icon" ] [] ]
                        , Icons.dueDateIcon ""
                        ]
                    ]
            , optional [ Data.Fields.CorrOrg, Data.Fields.CorrPerson ] <|
                h4 [ class "ui dividing header" ]
                    [ Icons.correspondentIcon ""
                    , text "Correspondent"
                    ]
            , optional [ Data.Fields.CorrOrg ] <|
                div [ class "field" ]
                    [ label []
                        [ Icons.organizationIcon "grey"
                        , text "Organization"
                        ]
                    , Html.map OrgDropdownMsg (Comp.Dropdown.view settings model.corrOrgModel)
                    ]
            , optional [ Data.Fields.CorrPerson ] <|
                div [ class "field" ]
                    [ label []
                        [ Icons.personIcon "grey"
                        , text "Person"
                        ]
                    , Html.map CorrPersonMsg (Comp.Dropdown.view settings model.corrPersonModel)
                    ]
            , optional [ Data.Fields.ConcPerson, Data.Fields.ConcEquip ] <|
                h4 [ class "ui dividing header" ]
                    [ Icons.concernedIcon
                    , text "Concerning"
                    ]
            , optional [ Data.Fields.ConcPerson ] <|
                div [ class "field" ]
                    [ label []
                        [ Icons.personIcon "grey"
                        , text "Person"
                        ]
                    , Html.map ConcPersonMsg (Comp.Dropdown.view settings model.concPersonModel)
                    ]
            , optional [ Data.Fields.ConcEquip ] <|
                div [ class "field" ]
                    [ label []
                        [ Icons.equipmentIcon "grey"
                        , text "Equipment"
                        ]
                    , Html.map ConcEquipMsg (Comp.Dropdown.view settings model.concEquipModel)
                    ]
            , optional [ Data.Fields.Direction ] <|
                div [ class "field" ]
                    [ label []
                        [ Icons.directionIcon "grey"
                        , text "Direction"
                        ]
                    , Html.map DirDropdownMsg (Comp.Dropdown.view settings model.directionModel)
                    ]
            ]
        ]


actionInputDatePicker : DatePicker.Settings
actionInputDatePicker =
    let
        ds =
            Comp.DatePicker.defaultSettings
    in
    { ds | containerClassList = [ ( "ui action input", True ) ] }
