module Page.Profile exposing (Model, Msg, init, subscriptions, toSession, update, view)

{-| An Author's profile.
-}

import Api
import Article exposing (Article, Preview)
import Article.Feed as Feed
import Article.FeedSources as FeedSources exposing (FeedSources, Source(..))
import Author exposing (Author(..), FollowedAuthor, UnfollowedAuthor)
import Avatar exposing (Avatar)
import Html exposing (..)
import Html.Attributes exposing (..)
import Http
import HttpBuilder exposing (RequestBuilder)
import Loading
import Log
import Page
import PaginatedList exposing (PaginatedList)
import Profile exposing (Profile)
import Route
import Session exposing (Session)
import Task exposing (Task)
import Time
import Username exposing (Username)
import Viewer exposing (Viewer)
import Viewer.Cred as Cred exposing (Cred)



-- MODEL


type alias Model =
    { session : Session
    , timeZone : Time.Zone
    , errors : List String
    , feedTab : FeedTab
    , feedPage : Int

    -- Loaded independently from server
    , author : Status Author
    , feed : Status Feed.Model
    }


type FeedTab
    = MyArticles
    | FavoritedArticles


type Status a
    = Loading Username
    | LoadingSlowly Username
    | Loaded a
    | Failed Username


init : Session -> Username -> ( Model, Cmd Msg )
init session username =
    let
        maybeCred =
            Session.cred session
    in
    ( { session = session
      , timeZone = Time.utc
      , errors = []
      , feedTab = defaultFeedTab
      , feedPage = 1
      , author = Loading username
      , feed = Loading username
      }
    , Cmd.batch
        [ Author.fetch username maybeCred
            |> Http.toTask
            |> Task.mapError (Tuple.pair username)
            |> Task.attempt CompletedAuthorLoad
        , fetchFeed session defaultFeedTab username 1
        , Task.perform GotTimeZone Time.here
        , Task.perform (\_ -> PassedSlowLoadThreshold) Loading.slowThreshold
        ]
    )


currentUsername : Model -> Username
currentUsername model =
    case model.author of
        Loading username ->
            username

        LoadingSlowly username ->
            username

        Loaded author ->
            Author.username author

        Failed username ->
            username


defaultFeedTab : FeedTab
defaultFeedTab =
    MyArticles



-- HTTP


fetchFeed : Session -> FeedTab -> Username -> Int -> Cmd Msg
fetchFeed session feedTabs username page =
    let
        maybeCred =
            Session.cred session

        ( extraParamName, extraParamVal ) =
            case feedTabs of
                MyArticles ->
                    ( "author", Username.toString username )

                FavoritedArticles ->
                    ( "favorited", Username.toString username )
    in
    Api.url [ "articles" ]
        |> HttpBuilder.get
        |> HttpBuilder.withExpect (Http.expectJson (Feed.decoder maybeCred articlesPerPage))
        |> HttpBuilder.withQueryParam extraParamName extraParamVal
        |> Cred.addHeaderIfAvailable maybeCred
        |> PaginatedList.fromRequestBuilder articlesPerPage page
        |> Task.map (Feed.init session)
        |> Task.mapError (Tuple.pair username)
        |> Task.attempt CompletedFeedLoad


articlesPerPage : Int
articlesPerPage =
    5



-- VIEW


view : Model -> { title : String, content : Html Msg }
view model =
    let
        title =
            case model.author of
                Loaded (IsViewer _ _) ->
                    myProfileTitle

                Loaded ((IsFollowing followedAuthor) as author) ->
                    titleForOther (Author.username author)

                Loaded ((IsNotFollowing unfollowedAuthor) as author) ->
                    titleForOther (Author.username author)

                Loading username ->
                    titleForMe (Session.cred model.session) username

                LoadingSlowly username ->
                    titleForMe (Session.cred model.session) username

                Failed username ->
                    titleForMe (Session.cred model.session) username
    in
    { title = title
    , content =
        case model.author of
            Loaded author ->
                let
                    profile =
                        Author.profile author

                    username =
                        Author.username author

                    followButton =
                        case Session.cred model.session of
                            Just cred ->
                                case author of
                                    IsViewer _ _ ->
                                        -- We can't follow ourselves!
                                        text ""

                                    IsFollowing followedAuthor ->
                                        Author.unfollowButton ClickedUnfollow cred followedAuthor

                                    IsNotFollowing unfollowedAuthor ->
                                        Author.followButton ClickedFollow cred unfollowedAuthor

                            Nothing ->
                                -- We can't follow if we're logged out
                                text ""
                in
                div [ class "profile-page" ]
                    [ Page.viewErrors ClickedDismissErrors model.errors
                    , div [ class "user-info" ]
                        [ div [ class "container" ]
                            [ div [ class "row" ]
                                [ div [ class "col-xs-12 col-md-10 offset-md-1" ]
                                    [ img [ class "user-img", Avatar.src (Profile.avatar profile) ] []
                                    , h4 [] [ Username.toHtml username ]
                                    , p [] [ text (Maybe.withDefault "" (Profile.bio profile)) ]
                                    , followButton
                                    ]
                                ]
                            ]
                        ]
                    , case model.feed of
                        Loaded feed ->
                            div [ class "container" ]
                                [ div [ class "row" ]
                                    [ div [ class "col-xs-12 col-md-10 offset-md-1" ]
                                        [ div [ class "articles-toggle" ] <|
                                            List.concat
                                                [ [ viewTabs model.feedTab ]
                                                , Feed.viewArticles model.timeZone feed
                                                    |> List.map (Html.map GotFeedMsg)
                                                , [ Feed.viewPagination ClickedFeedPage feed ]
                                                ]
                                        ]
                                    ]
                                ]

                        Loading _ ->
                            text ""

                        LoadingSlowly _ ->
                            Loading.icon

                        Failed _ ->
                            Loading.error "feed"
                    ]

            Loading _ ->
                text ""

            LoadingSlowly _ ->
                Loading.icon

            Failed _ ->
                Loading.error "profile"
    }



-- PAGE TITLE


titleForOther : Username -> String
titleForOther otherUsername =
    "Profile — " ++ Username.toString otherUsername


titleForMe : Maybe Cred -> Username -> String
titleForMe maybeCred username =
    case maybeCred of
        Just cred ->
            if username == Cred.username cred then
                myProfileTitle

            else
                defaultTitle

        Nothing ->
            defaultTitle


myProfileTitle : String
myProfileTitle =
    "My Profile"


defaultTitle : String
defaultTitle =
    "Profile"



-- TABS


viewTabs : FeedTab -> Html Msg
viewTabs tab =
    case tab of
        MyArticles ->
            Feed.viewTabs [] myArticles [ favoritedArticles ]

        FavoritedArticles ->
            Feed.viewTabs [ myArticles ] favoritedArticles []


myArticles : ( String, Msg )
myArticles =
    ( "My Articles", ClickedTab MyArticles )


favoritedArticles : ( String, Msg )
favoritedArticles =
    ( "Favorited Articles", ClickedTab FavoritedArticles )



-- UPDATE


type Msg
    = ClickedDismissErrors
    | ClickedFollow Cred UnfollowedAuthor
    | ClickedUnfollow Cred FollowedAuthor
    | ClickedTab FeedTab
    | ClickedFeedPage Int
    | CompletedFollowChange (Result Http.Error Author)
    | CompletedAuthorLoad (Result ( Username, Http.Error ) Author)
    | CompletedFeedLoad (Result ( Username, Http.Error ) Feed.Model)
    | GotTimeZone Time.Zone
    | GotFeedMsg Feed.Msg
    | GotSession Session
    | PassedSlowLoadThreshold


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        ClickedDismissErrors ->
            ( { model | errors = [] }, Cmd.none )

        ClickedUnfollow cred followedAuthor ->
            ( model
            , Author.requestUnfollow followedAuthor cred
                |> Http.send CompletedFollowChange
            )

        ClickedFollow cred unfollowedAuthor ->
            ( model
            , Author.requestFollow unfollowedAuthor cred
                |> Http.send CompletedFollowChange
            )

        ClickedTab tab ->
            ( { model | feedTab = tab }
            , fetchFeed model.session tab (currentUsername model) 1
            )

        ClickedFeedPage page ->
            ( { model | feedPage = page }
            , fetchFeed model.session model.feedTab (currentUsername model) page
            )

        CompletedFollowChange (Ok newAuthor) ->
            ( { model | author = Loaded newAuthor }
            , Cmd.none
            )

        CompletedFollowChange (Err error) ->
            ( model
            , Log.error
            )

        CompletedAuthorLoad (Ok author) ->
            ( { model | author = Loaded author }, Cmd.none )

        CompletedAuthorLoad (Err ( username, err )) ->
            ( { model | author = Failed username }
            , Log.error
            )

        CompletedFeedLoad (Ok feed) ->
            ( { model | feed = Loaded feed }
            , Cmd.none
            )

        CompletedFeedLoad (Err ( username, err )) ->
            ( { model | feed = Failed username }
            , Log.error
            )

        GotFeedMsg subMsg ->
            case model.feed of
                Loaded feed ->
                    let
                        ( newFeed, subCmd ) =
                            Feed.update (Session.cred model.session) subMsg feed
                    in
                    ( { model | feed = Loaded newFeed }
                    , Cmd.map GotFeedMsg subCmd
                    )

                Loading _ ->
                    ( model, Log.error )

                LoadingSlowly _ ->
                    ( model, Log.error )

                Failed _ ->
                    ( model, Log.error )

        GotTimeZone tz ->
            ( { model | timeZone = tz }, Cmd.none )

        GotSession session ->
            ( { model | session = session }
            , Route.replaceUrl (Session.navKey session) Route.Home
            )

        PassedSlowLoadThreshold ->
            let
                -- If any data is still Loading, change it to LoadingSlowly
                -- so `view` knows to render a spinner.
                feed =
                    case model.feed of
                        Loading username ->
                            LoadingSlowly username

                        other ->
                            other
            in
            ( { model | feed = feed }, Cmd.none )



-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
    Session.changes GotSession (Session.navKey model.session)



-- EXPORT


toSession : Model -> Session
toSession model =
    model.session
