module Page.Article exposing (Model, Msg, init, update, view)

{-| Viewing an individual article.
-}

import Data.Article as Article exposing (Article, Body)
import Data.Article.Author exposing (Author)
import Data.Article.Comment exposing (Comment, CommentId)
import Data.Session as Session exposing (Session)
import Data.User as User exposing (User)
import Data.UserPhoto as UserPhoto
import Date exposing (Date)
import Date.Format
import Html exposing (..)
import Html.Attributes exposing (attribute, class, disabled, href, id, placeholder)
import Html.Events exposing (onClick, onInput, onSubmit)
import Http
import Page.Errored exposing (PageLoadError, pageLoadError)
import Request.Article
import Request.Article.Comments
import Request.Profile
import Route
import Task exposing (Task)
import Util exposing (pair, viewIf)
import Views.Article
import Views.Article.Favorite as Favorite
import Views.Author
import Views.Errors
import Views.Page as Page
import Views.User.Follow as Follow


-- MODEL --


type alias Model =
    { errors : List String
    , commentText : String
    , commentInFlight : Bool
    , article : Article Body
    , comments : List Comment
    }


init : Session -> Article.Slug -> Task PageLoadError Model
init session slug =
    let
        maybeAuthToken =
            Maybe.map .token session.user

        loadArticle =
            Request.Article.get maybeAuthToken slug
                |> Http.toTask

        loadComments =
            Request.Article.Comments.list maybeAuthToken slug
                |> Http.toTask

        handleLoadError _ =
            pageLoadError Page.Other "Article is currently unavailable."
    in
    Task.map2 (Model [] "" False) loadArticle loadComments
        |> Task.mapError handleLoadError



-- VIEW --


view : Session -> Model -> Html Msg
view session model =
    let
        article =
            model.article

        author =
            article.author

        buttons =
            viewButtons article author session.user

        postingDisabled =
            model.commentInFlight
    in
    div [ class "article-page" ]
        [ viewBanner model.errors article author session.user
        , div [ class "container page" ]
            [ div [ class "row article-content" ]
                [ div [ class "col-md-12" ]
                    [ Article.bodyToHtml article.body [] ]
                ]
            , hr [] []
            , div [ class "article-actions" ]
                [ div [ class "article-meta" ] <|
                    [ a [ Route.href (Route.Profile author.username) ]
                        [ img [ UserPhoto.src author.image ] [] ]
                    , div [ class "info" ]
                        [ Views.Author.view author.username
                        , Views.Article.viewTimestamp article
                        ]
                    ]
                        ++ buttons
                ]
            , div [ class "row" ]
                [ div [ class "col-xs-12 col-md-8 offset-md-2" ] <|
                    viewAddComment postingDisabled session.user
                        :: List.map (viewComment session.user) model.comments
                ]
            ]
        ]


viewBanner : List String -> Article a -> Author -> Maybe User -> Html Msg
viewBanner errors article author maybeUser =
    let
        buttons =
            viewButtons article author maybeUser
    in
    div [ class "banner" ]
        [ div [ class "container" ]
            [ h1 [] [ text article.title ]
            , div [ class "article-meta" ] <|
                [ a [ Route.href (Route.Profile author.username) ]
                    [ img [ UserPhoto.src author.image ] [] ]
                , div [ class "info" ]
                    [ Views.Author.view author.username
                    , Views.Article.viewTimestamp article
                    ]
                ]
                    ++ buttons
            , Views.Errors.view DismissErrors errors
            ]
        ]


viewAddComment : Bool -> Maybe User -> Html Msg
viewAddComment postingDisabled maybeUser =
    case maybeUser of
        Nothing ->
            p []
                [ a [ Route.href Route.Login ] [ text "Sign in" ]
                , text " or "
                , a [ Route.href Route.Register ] [ text "sign up" ]
                , text " to add comments on this article."
                ]

        Just user ->
            Html.form [ class "card comment-form", onSubmit PostComment ]
                [ div [ class "card-block" ]
                    [ textarea
                        [ class "form-control"
                        , placeholder "Write a comment..."
                        , attribute "rows" "3"
                        , onInput SetCommentText
                        ]
                        []
                    ]
                , div [ class "card-footer" ]
                    [ img [ class "comment-author-img", UserPhoto.src user.image ] []
                    , button
                        [ class "btn btn-sm btn-primary"
                        , disabled postingDisabled
                        ]
                        [ text "Post Comment" ]
                    ]
                ]


viewButtons : Article a -> Author -> Maybe User -> List (Html Msg)
viewButtons article author maybeUser =
    let
        isMyArticle =
            Maybe.map .username maybeUser == Just author.username
    in
    if isMyArticle then
        [ editButton article
        , text " "
        , deleteButton article
        ]
    else
        [ followButton author
        , text " "
        , favoriteButton article
        ]


viewComment : Maybe User -> Comment -> Html Msg
viewComment user comment =
    let
        author =
            comment.author

        isAuthor =
            Maybe.map .username user == Just comment.author.username
    in
    div [ class "card" ]
        [ div [ class "card-block" ]
            [ p [ class "card-text" ] [ text comment.body ] ]
        , div [ class "card-footer" ]
            [ a [ class "comment-author", href "" ]
                [ img [ class "comment-author-img", UserPhoto.src author.image ] []
                , text " "
                ]
            , text " "
            , a [ class "comment-author", Route.href (Route.Profile author.username) ]
                [ text (User.usernameToString comment.author.username) ]
            , span [ class "date-posted" ] [ text (formatCommentTimestamp comment.createdAt) ]
            , viewIf isAuthor <|
                span
                    [ class "mod-options"
                    , onClick (DeleteComment comment.id)
                    ]
                    [ i [ class "ion-trash-a" ] [] ]
            ]
        ]


formatCommentTimestamp : Date -> String
formatCommentTimestamp =
    Date.Format.format "%B %e, %Y"



-- UPDATE --


type Msg
    = DismissErrors
    | ToggleFavorite
    | FavoriteCompleted (Result Http.Error (Article Body))
    | ToggleFollow
    | FollowCompleted (Result Http.Error Author)
    | SetCommentText String
    | DeleteComment CommentId
    | CommentDeleted CommentId (Result Http.Error ())
    | PostComment
    | CommentPosted (Result Http.Error Comment)
    | DeleteArticle
    | ArticleDeleted (Result Http.Error ())


update : Session -> Msg -> Model -> ( Model, Cmd Msg )
update session msg model =
    let
        article =
            model.article

        author =
            article.author
    in
    case msg of
        DismissErrors ->
            ( { model | errors = [] }, Cmd.none )

        ToggleFavorite ->
            let
                cmdFromAuth authToken =
                    Request.Article.toggleFavorite model.article authToken
                        |> Http.toTask
                        |> Task.map (\newArticle -> { newArticle | body = article.body })
                        |> Task.attempt FavoriteCompleted
            in
            session
                |> Session.attempt "favorite" cmdFromAuth
                |> Tuple.mapFirst (Util.appendErrors model)

        FavoriteCompleted (Ok newArticle) ->
            ( { model | article = newArticle }, Cmd.none )

        FavoriteCompleted (Err error) ->
            -- In a serious production application, we would log the error to
            -- a logging service so we could investigate later.
            ( [ "There was a server error trying to record your Favorite. Sorry!" ]
                |> Util.appendErrors model
            , Cmd.none
            )

        ToggleFollow ->
            let
                cmdFromAuth authToken =
                    authToken
                        |> Request.Profile.toggleFollow author.username author.following
                        |> Http.send FollowCompleted
            in
            session
                |> Session.attempt "follow" cmdFromAuth
                |> Tuple.mapFirst (Util.appendErrors model)

        FollowCompleted (Ok { following }) ->
            let
                newArticle =
                    { article | author = { author | following = following } }
            in
            ( { model | article = newArticle }, Cmd.none )

        FollowCompleted (Err error) ->
            ( { model | errors = "Unable to follow user." :: model.errors }, Cmd.none )

        SetCommentText commentText ->
            ( { model | commentText = commentText }, Cmd.none )

        PostComment ->
            let
                comment =
                    model.commentText
            in
            if model.commentInFlight || String.isEmpty comment then
                ( model, Cmd.none )
            else
                let
                    cmdFromAuth authToken =
                        authToken
                            |> Request.Article.Comments.post model.article.slug comment
                            |> Http.send CommentPosted
                in
                session
                    |> Session.attempt "post a comment" cmdFromAuth
                    |> Tuple.mapFirst (Util.appendErrors { model | commentInFlight = True })

        CommentPosted (Ok comment) ->
            ( { model
                | commentInFlight = False
                , comments = comment :: model.comments
              }
            , Cmd.none
            )

        CommentPosted (Err error) ->
            ( { model | errors = model.errors ++ [ "Server error while trying to post comment." ] }
            , Cmd.none
            )

        DeleteComment id ->
            let
                cmdFromAuth authToken =
                    authToken
                        |> Request.Article.Comments.delete model.article.slug id
                        |> Http.send (CommentDeleted id)
            in
            session
                |> Session.attempt "delete comments" cmdFromAuth
                |> Tuple.mapFirst (Util.appendErrors model)

        CommentDeleted id (Ok ()) ->
            ( { model | comments = withoutComment id model.comments }
            , Cmd.none
            )

        CommentDeleted id (Err error) ->
            ( { model | errors = model.errors ++ [ "Server error while trying to delete comment." ] }
            , Cmd.none
            )

        DeleteArticle ->
            let
                cmdFromAuth authToken =
                    authToken
                        |> Request.Article.delete model.article.slug
                        |> Http.send ArticleDeleted
            in
            session
                |> Session.attempt "delete articles" cmdFromAuth
                |> Tuple.mapFirst (Util.appendErrors model)

        ArticleDeleted (Ok ()) ->
            ( model, Route.modifyUrl Route.Home )

        ArticleDeleted (Err error) ->
            ( { model | errors = model.errors ++ [ "Server error while trying to delete article." ] }
            , Cmd.none
            )



-- INTERNAL --


withoutComment : CommentId -> List Comment -> List Comment
withoutComment id =
    List.filter (\comment -> comment.id /= id)


favoriteButton : Article a -> Html Msg
favoriteButton article =
    let
        favoriteText =
            " Favorite Article (" ++ toString article.favoritesCount ++ ")"
    in
    Favorite.button (\_ -> ToggleFavorite) article [] [ text favoriteText ]


deleteButton : Article a -> Html Msg
deleteButton article =
    button [ class "btn btn-outline-danger btn-sm", onClick DeleteArticle ]
        [ i [ class "ion-trash-a" ] [], text " Delete Article" ]


editButton : Article a -> Html Msg
editButton article =
    a [ class "btn btn-outline-secondary btn-sm", Route.href (Route.EditArticle article.slug) ]
        [ i [ class "ion-edit" ] [], text " Edit Article" ]


followButton : Follow.State record -> Html Msg
followButton =
    Follow.button (\_ -> ToggleFollow)
