{-
   Copyright 2020 Eike K. & Contributors

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


module Comp.DueItemsTaskForm exposing
    ( Action(..)
    , Model
    , Msg
    , init
    , initWith
    , update
    , view2
    )

import Api
import Api.Model.PeriodicDueItemsSettings exposing (PeriodicDueItemsSettings)
import Api.Model.TagList exposing (TagList)
import Comp.Basic as B
import Comp.CalEventInput
import Comp.ChannelRefInput
import Comp.IntField
import Comp.MenuBar as MB
import Comp.TagDropdown
import Comp.YesNoDimmer
import Data.CalEvent exposing (CalEvent)
import Data.DropdownStyle as DS
import Data.Flags exposing (Flags)
import Data.TagOrder
import Data.TimeZone exposing (TimeZone)
import Data.UiSettings exposing (UiSettings)
import Data.Validated exposing (Validated(..))
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
import Http
import Markdown
import Messages.Comp.DueItemsTaskForm exposing (Texts)
import Styles as S
import Util.Maybe


type alias Model =
    { settings : PeriodicDueItemsSettings
    , channelModel : Comp.ChannelRefInput.Model
    , tagInclModel : Comp.TagDropdown.Model
    , tagExclModel : Comp.TagDropdown.Model
    , remindDays : Maybe Int
    , remindDaysModel : Comp.IntField.Model
    , capOverdue : Bool
    , enabled : Bool
    , schedule : Maybe CalEvent
    , scheduleModel : Comp.CalEventInput.Model
    , formState : FormState
    , loading : Int
    , yesNoDelete : Comp.YesNoDimmer.Model
    , summary : Maybe String
    }


type FormState
    = FormStateInitial
    | FormStateHttpError Http.Error
    | FormStateInvalid ValidateError


type ValidateError
    = ValidateRemindDaysRequired
    | ValidateCalEventInvalid
    | ValidateChannelRequired


type Action
    = SubmitAction PeriodicDueItemsSettings
    | StartOnceAction PeriodicDueItemsSettings
    | CancelAction
    | DeleteAction String
    | NoAction


type Msg
    = Submit
    | TagIncMsg Comp.TagDropdown.Msg
    | TagExcMsg Comp.TagDropdown.Msg
    | GetTagsResp (Result Http.Error TagList)
    | RemindDaysMsg Comp.IntField.Msg
    | ToggleEnabled
    | ToggleCapOverdue
    | CalEventMsg Comp.CalEventInput.Msg
    | StartOnce
    | Cancel
    | RequestDelete
    | YesNoDeleteMsg Comp.YesNoDimmer.Msg
    | SetSummary String
    | ChannelMsg Comp.ChannelRefInput.Msg


initWith : Flags -> PeriodicDueItemsSettings -> ( Model, Cmd Msg )
initWith flags s =
    let
        ( im, ic ) =
            init flags

        newSchedule =
            Data.CalEvent.fromEvent s.schedule
                |> Maybe.withDefault Data.CalEvent.everyMonth

        ( sm, sc ) =
            Comp.CalEventInput.init flags newSchedule

        ( cfm, cfc ) =
            Comp.ChannelRefInput.initSelected flags s.channels
    in
    ( { im
        | settings = s
        , channelModel = cfm
        , tagExclModel = Comp.TagDropdown.initWith [] s.tagsExclude
        , tagInclModel = Comp.TagDropdown.initWith [] s.tagsInclude
        , remindDays = Just s.remindDays
        , enabled = s.enabled
        , capOverdue = s.capOverdue
        , schedule = Just newSchedule
        , scheduleModel = sm
        , formState = FormStateInitial
        , loading = im.loading
        , yesNoDelete = Comp.YesNoDimmer.emptyModel
        , summary = s.summary
      }
    , Cmd.batch
        [ ic
        , Cmd.map CalEventMsg sc
        , Cmd.map ChannelMsg cfc
        ]
    )


init : Flags -> ( Model, Cmd Msg )
init flags =
    let
        initialSchedule =
            Data.CalEvent.everyMonth

        ( sm, scmd ) =
            Comp.CalEventInput.init flags initialSchedule

        ( cfm, cfc ) =
            Comp.ChannelRefInput.init flags
    in
    ( { settings = Api.Model.PeriodicDueItemsSettings.empty
      , channelModel = cfm
      , tagInclModel = Comp.TagDropdown.initWith [] []
      , tagExclModel = Comp.TagDropdown.initWith [] []
      , remindDays = Just 1
      , remindDaysModel = Comp.IntField.init (Just 1) Nothing True
      , enabled = False
      , capOverdue = False
      , schedule = Just initialSchedule
      , scheduleModel = sm
      , formState = FormStateInitial
      , loading = 1
      , yesNoDelete = Comp.YesNoDimmer.emptyModel
      , summary = Nothing
      }
    , Cmd.batch
        [ Api.getTags flags "" Data.TagOrder.NameAsc GetTagsResp
        , Cmd.map CalEventMsg scmd
        , Cmd.map ChannelMsg cfc
        ]
    )



--- Update


makeSettings : Model -> Result ValidateError PeriodicDueItemsSettings
makeSettings model =
    let
        prev =
            model.settings

        rmdays =
            Maybe.map Ok model.remindDays
                |> Maybe.withDefault (Err ValidateRemindDaysRequired)

        schedule_ =
            case model.schedule of
                Just s ->
                    Ok s

                Nothing ->
                    Err ValidateCalEventInvalid

        channelM =
            let
                list =
                    Comp.ChannelRefInput.getSelected model.channelModel
            in
            if list == [] then
                Err ValidateChannelRequired

            else
                Ok list

        make days timer channels =
            { prev
                | tagsInclude = Comp.TagDropdown.getSelected model.tagInclModel
                , tagsExclude = Comp.TagDropdown.getSelected model.tagExclModel
                , remindDays = days
                , capOverdue = model.capOverdue
                , enabled = model.enabled
                , schedule = Data.CalEvent.makeEvent timer
                , summary = model.summary
                , channels = channels
            }
    in
    Result.map3 make
        rmdays
        schedule_
        channelM


withValidSettings : (PeriodicDueItemsSettings -> Action) -> Model -> ( Model, Action, Cmd Msg )
withValidSettings mkcmd model =
    case makeSettings model of
        Ok set ->
            ( { model | formState = FormStateInitial }
            , mkcmd set
            , Cmd.none
            )

        Err errs ->
            ( { model | formState = FormStateInvalid errs }
            , NoAction
            , Cmd.none
            )


update : Flags -> TimeZone -> Msg -> Model -> ( Model, Action, Cmd Msg )
update flags tz msg model =
    case msg of
        ChannelMsg lm ->
            let
                ( cfm, cfc ) =
                    Comp.ChannelRefInput.update lm model.channelModel
            in
            ( { model | channelModel = cfm }
            , NoAction
            , Cmd.map ChannelMsg cfc
            )

        CalEventMsg lmsg ->
            let
                ( cm, cc, cs ) =
                    Comp.CalEventInput.update flags
                        tz
                        model.schedule
                        lmsg
                        model.scheduleModel
            in
            ( { model
                | schedule = cs
                , scheduleModel = cm
                , formState = FormStateInitial
              }
            , NoAction
            , Cmd.map CalEventMsg cc
            )

        TagIncMsg m ->
            let
                ( m2, c2 ) =
                    Comp.TagDropdown.update m model.tagInclModel
            in
            ( { model
                | tagInclModel = m2
                , formState = FormStateInitial
              }
            , NoAction
            , Cmd.map TagIncMsg c2
            )

        TagExcMsg m ->
            let
                ( m2, c2 ) =
                    Comp.TagDropdown.update m model.tagExclModel
            in
            ( { model
                | tagExclModel = m2
                , formState = FormStateInitial
              }
            , NoAction
            , Cmd.map TagExcMsg c2
            )

        GetTagsResp (Ok tags) ->
            let
                incModel =
                    Comp.TagDropdown.initWith tags.items model.settings.tagsInclude

                excModel =
                    Comp.TagDropdown.initWith tags.items model.settings.tagsExclude

                newModel =
                    { model
                        | loading = model.loading - 1
                        , tagExclModel = excModel
                        , tagInclModel = incModel
                    }
            in
            ( newModel, NoAction, Cmd.none )

        GetTagsResp (Err err) ->
            ( { model
                | loading = model.loading - 1
                , formState = FormStateHttpError err
              }
            , NoAction
            , Cmd.none
            )

        RemindDaysMsg m ->
            let
                ( pm, val ) =
                    Comp.IntField.update m model.remindDaysModel
            in
            ( { model
                | remindDaysModel = pm
                , remindDays = val
                , formState = FormStateInitial
              }
            , NoAction
            , Cmd.none
            )

        ToggleEnabled ->
            ( { model
                | enabled = not model.enabled
                , formState = FormStateInitial
              }
            , NoAction
            , Cmd.none
            )

        ToggleCapOverdue ->
            ( { model
                | capOverdue = not model.capOverdue
                , formState = FormStateInitial
              }
            , NoAction
            , Cmd.none
            )

        Submit ->
            withValidSettings
                SubmitAction
                model

        StartOnce ->
            withValidSettings
                StartOnceAction
                model

        Cancel ->
            ( model, CancelAction, Cmd.none )

        RequestDelete ->
            let
                ( ym, _ ) =
                    Comp.YesNoDimmer.update
                        Comp.YesNoDimmer.activate
                        model.yesNoDelete
            in
            ( { model | yesNoDelete = ym }
            , NoAction
            , Cmd.none
            )

        YesNoDeleteMsg lm ->
            let
                ( ym, flag ) =
                    Comp.YesNoDimmer.update lm model.yesNoDelete

                act =
                    if flag then
                        DeleteAction model.settings.id

                    else
                        NoAction
            in
            ( { model | yesNoDelete = ym }
            , act
            , Cmd.none
            )

        SetSummary str ->
            ( { model | summary = Util.Maybe.fromString str }
            , NoAction
            , Cmd.none
            )



--- View2


isFormError : Model -> Bool
isFormError model =
    case model.formState of
        FormStateInitial ->
            False

        _ ->
            True


isFormSuccess : Model -> Bool
isFormSuccess model =
    not (isFormError model)


view2 : Texts -> String -> UiSettings -> Model -> Html Msg
view2 texts extraClasses settings model =
    let
        dimmerSettings =
            Comp.YesNoDimmer.defaultSettings texts.reallyDeleteTask
                texts.basics.yes
                texts.basics.no

        startOnceBtn =
            MB.SecondaryButton
                { tagger = StartOnce
                , label = texts.startOnce
                , title = texts.startTaskNow
                , icon = Just "fa fa-play"
                }

        formHeader txt =
            h2 [ class S.formHeader, class "mt-2" ]
                [ text txt
                ]
    in
    div
        [ class "flex flex-col md:relative"
        , class extraClasses
        ]
        [ Html.map YesNoDeleteMsg
            (Comp.YesNoDimmer.viewN True
                dimmerSettings
                model.yesNoDelete
            )
        , B.loadingDimmer
            { active = model.loading > 0
            , label = texts.basics.loading
            }
        , MB.view
            { start =
                [ MB.PrimaryButton
                    { tagger = Submit
                    , label = texts.basics.submit
                    , title = texts.basics.submitThisForm
                    , icon = Just "fa fa-save"
                    }
                , MB.SecondaryButton
                    { tagger = Cancel
                    , label = texts.basics.backToList
                    , title = texts.basics.backToList
                    , icon = Just "fa fa-arrow-left"
                    }
                ]
            , end =
                if model.settings.id /= "" then
                    [ startOnceBtn
                    , MB.DeleteButton
                        { tagger = RequestDelete
                        , label = texts.basics.delete
                        , title = texts.deleteThisTask
                        , icon = Just "fa fa-trash"
                        }
                    ]

                else
                    [ startOnceBtn
                    ]
            , rootClasses = "mb-4"
            , sticky = True
            }
        , div
            [ classList
                [ ( S.successMessage, isFormSuccess model )
                , ( S.errorMessage, isFormError model )
                , ( "hidden", model.formState == FormStateInitial )
                ]
            , class "mb-4"
            ]
            [ case model.formState of
                FormStateInitial ->
                    text ""

                FormStateHttpError err ->
                    text (texts.httpError err)

                FormStateInvalid ValidateCalEventInvalid ->
                    text texts.invalidCalEvent

                FormStateInvalid ValidateRemindDaysRequired ->
                    text texts.remindDaysRequired

                FormStateInvalid ValidateChannelRequired ->
                    text texts.channelRequired
            ]
        , div [ class "mb-4" ]
            [ MB.viewItem <|
                MB.Checkbox
                    { tagger = \_ -> ToggleEnabled
                    , label = texts.enableDisable
                    , value = model.enabled
                    , id = "notify-enabled"
                    }
            ]
        , div [ class "mb-4" ]
            [ label [ class S.inputLabel ]
                [ text texts.summary
                ]
            , input
                [ type_ "text"
                , onInput SetSummary
                , class S.textInput
                , Maybe.withDefault "" model.summary
                    |> value
                ]
                []
            , span [ class "opacity-50 text-sm" ]
                [ text texts.summaryInfo
                ]
            ]
        , div [ class "mb-4" ]
            [ formHeader texts.channelHeader
            , Html.map ChannelMsg
                (Comp.ChannelRefInput.view texts.channelRef settings model.channelModel)
            ]
        , formHeader texts.queryLabel
        , div [ class "mb-4" ]
            [ label [ class S.inputLabel ]
                [ text texts.tagsInclude ]
            , Html.map TagIncMsg
                (Comp.TagDropdown.view
                    texts.tagDropdown
                    settings
                    DS.mainStyle
                    model.tagInclModel
                )
            , span [ class "opacity-50 text-sm" ]
                [ text texts.tagsIncludeInfo
                ]
            ]
        , div [ class "mb-4" ]
            [ label [ class S.inputLabel ]
                [ text texts.tagsExclude ]
            , Html.map TagExcMsg
                (Comp.TagDropdown.view
                    texts.tagDropdown
                    settings
                    DS.mainStyle
                    model.tagExclModel
                )
            , span [ class "opacity-50 text-sm" ]
                [ text texts.tagsExcludeInfo
                ]
            ]
        , Html.map RemindDaysMsg
            (Comp.IntField.view
                { label = texts.remindDaysLabel
                , info = texts.remindDaysInfo
                , number = model.remindDays
                , classes = "mb-4"
                }
                model.remindDaysModel
            )
        , div [ class "mb-4" ]
            [ MB.viewItem <|
                MB.Checkbox
                    { tagger = \_ -> ToggleCapOverdue
                    , id = "notify-toggle-cap-overdue"
                    , value = model.capOverdue
                    , label = texts.capOverdue
                    }
            , div [ class "opacity-50 text-sm" ]
                [ Markdown.toHtml [] texts.capOverdueInfo
                ]
            ]
        , div [ class "mb-4" ]
            [ formHeader texts.schedule
            , label [ class S.inputLabel ]
                [ text texts.schedule
                , a
                    [ class "float-right"
                    , class S.link
                    , href "https://github.com/eikek/calev#what-are-calendar-events"
                    , target "_blank"
                    ]
                    [ i [ class "fa fa-question" ] []
                    , span [ class "pl-2" ]
                        [ text texts.scheduleClickForHelp
                        ]
                    ]
                ]
            , Html.map CalEventMsg
                (Comp.CalEventInput.view2
                    texts.calEventInput
                    ""
                    model.schedule
                    model.scheduleModel
                )
            , span [ class "opacity-50 text-sm" ]
                [ text texts.scheduleInfo
                ]
            ]
        ]
