{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_GHC -fno-warn-missing-signatures #-}

module Hero.Core where

import Alpha
import qualified Clay
import Data.Aeson
  ( FromJSON (..),
    ToJSON (..),
    defaultOptions,
    genericParseJSON,
    genericToJSON,
  )
import Data.Data (Data, Typeable)
import qualified Data.List as List
import qualified Data.List.Split as List
import Data.Proxy (Proxy (..))
import Data.String.Quote
import Data.Text (Text)
import GHC.Generics (Generic)
import qualified GHC.Show as Legacy
import Hero.Look as Look
import Hero.Look.Typography
import qualified Hero.Pack as Pack
import Miso
import qualified Miso (for_)
import Miso.Extend
import Miso.String
import Network.RemoteData
import Servant.API
  ( (:<|>) (..),
    (:>),
  )
import qualified Servant.API as Api
import Servant.Links (linkURI)

-- | The css id for controling music in the comic player.
audioId :: MisoString
audioId = "audioSource"

-- TODO: make ComicId a hashid
-- https://hackage.haskell.org/package/hashids-1.0.2.4/docs/Web-Hashids.html
newtype ComicId
  = ComicId String
  deriving
    ( Show,
      Eq,
      Ord,
      Data,
      Typeable,
      Generic,
      ToMisoString,
      IsString,
      Api.ToHttpApiData,
      Api.FromHttpApiData
    )

instance ToJSON ComicId where
  toJSON = genericToJSON Data.Aeson.defaultOptions

instance FromJSON ComicId where
  parseJSON = genericParseJSON Data.Aeson.defaultOptions

-- | Used for looking up images on S3, mostly
comicSlug :: Comic -> Text
comicSlug Comic {..} = snake comicName <> "-" <> comicIssue

-- * user

data User
  = User
      { userEmail :: Text,
        userName :: Text,
        userLibrary :: [Comic]
      }
  deriving (Show, Eq, Generic, Data, Ord)

instance Semigroup User where
  a <> b =
    User
      (userEmail a <> userEmail b)
      (userName a <> userName b)
      (userLibrary a <> userLibrary b)

instance Monoid User where
  mempty = User mempty mempty mempty

instance ToJSON User where
  toJSON = genericToJSON Data.Aeson.defaultOptions

instance FromJSON User where
  parseJSON = genericParseJSON Data.Aeson.defaultOptions

-- | Class for rendering media objects in different ways.
class IsMediaObject o where
  -- | Render a thumbnail for use in a shelf, or otherwise.
  thumbnail :: o -> View Move

  -- | Render a featured banner.
  feature :: o -> User -> View Move

  -- | Media info view
  info :: o -> User -> View Move

-- | How much to Zoom the comic image
type Magnification = Int

-- | All the buttons.
data Button
  = Watch Comic
  | Read Comic
  | Save Comic User
  | SaveIcon Comic User
  | ZoomIcon Magnification Comic PageNumber
  | PlayPause MisoString AudioState
  | Arrow Move

-- | Class for defining general, widely used elements in the heroverse.
class Elemental v where el :: v -> View Move

-- TODO: what if I just did this on all actions?
-- then I could e.g. `el $ ToggleAudio audioId audioState`
instance Elemental Button where
  el (PlayPause id form) =
    button_
      [ class_ "button is-large icon",
        onClick $ ToggleAudio id
      ]
      [i_ [class_ $ "fa " <> icon] []]
    where
      icon = case form of
        Paused -> "fa-play-circle"
        Playing -> "fa-pause-circle"
  el (Arrow act) =
    button_
      [class_ "button is-large turn-page", onClick act]
      [img_ [src_ $ ms $ Pack.demo <> image <> ".png"]]
    where
      image = case act of
        PrevPage -> "prev-page"
        NextPage -> "next-page"
        _ -> "prev-page"
  el (Save c u) =
    if c `elem` userLibrary u -- in library
      then
        a_
          [class_ "wrs-button saved", onClick $ ToggleInLibrary c]
          [ img_ [src_ $ ms $ Pack.icon <> "save.svg"],
            span_ [] [text "saved"]
          ]
      else-- not in library

        a_
          [class_ "wrs-button", onClick $ ToggleInLibrary c]
          [ img_ [src_ $ ms $ Pack.icon <> "save.svg"],
            span_ [] [text "save"]
          ]
  el (SaveIcon c u) =
    if c `elem` userLibrary u -- in library
      then
        button_
          [ class_ "button is-large has-background-black",
            onClick $ ToggleInLibrary c
          ]
          [img_ [src_ $ ms $ Pack.demo <> "library-add.png"]]
      else-- not in library

        button_
          [ class_ "button is-large has-background-black-bis",
            onClick $ ToggleInLibrary c
          ]
          [img_ [src_ $ ms $ Pack.demo <> "library-add.png"]]
  el (ZoomIcon zform comic page) =
    button_
      [ id_ "zoom-button",
        class_ "button is-large",
        onClick $ ToggleZoom comic page
      ]
      [ img_ [src_ $ ms $ Pack.demo <> "zoom.png"],
        input_
          [ type_ "range",
            min_ "0",
            max_ "100",
            disabled_ True,
            value_ $ ms (show zform :: String),
            class_ "ctrl",
            id_ "zoom"
          ],
        label_
          [class_ "ctrl", Miso.for_ "zoom"]
          [text $ ms $ (show zform :: String) ++ "%"]
      ]
  el (Read c) =
    a_
      [class_ "wrs-button", onClick $ SelectExperience c]
      [ img_ [src_ $ ms $ Pack.icon <> "read.svg"],
        span_ [] [text "read"]
      ]
  el (Watch c) =
    a_
      [class_ "wrs-button", onClick $ StartWatching c]
      [ img_ [src_ $ ms $ Pack.icon <> "watch.svg"],
        span_ [] [text "watch"]
      ]

data AudioState = Playing | Paused
  deriving (Show, Eq)

data ComicReaderState
  = NotReading
  | Cover ComicId
  | ChooseExperience ComicId PageNumber
  | Reading ComicReaderView ComicId PageNumber
  | Watching ComicId
  deriving (Show, Eq)

findComic :: ComicId -> [Comic] -> Maybe Comic
findComic id = List.find (\c -> comicId c == id)

-- | Main form for the app.
--
-- Try to prefix component-specific state with the component initials: 'd' for
-- discover, 'cp' for comic player.
data Form
  = Form
      { uri :: Api.URI,
        appComics :: RemoteData MisoString [Comic],
        user :: User,
        dMediaInfo :: Maybe Comic,
        cpState :: ComicReaderState,
        cpAudioState :: AudioState,
        magnification :: Magnification
      }
  deriving (Show, Eq)

initForm :: Api.URI -> Form
initForm uri_ =
  Form
    { uri = uri_,
      appComics = NotAsked,
      dMediaInfo = Nothing,
      user = mempty,
      cpState = detectPlayerState uri_,
      cpAudioState = Paused,
      magnification = 100
    }

-- | Hacky way to initialize the 'ComicReaderState' from the Api.URI.
detectPlayerState :: Api.URI -> ComicReaderState
detectPlayerState u = case List.splitOn "/" $ Api.uriPath u of
  ["", "comic", id, pg, "experience"] -> ChooseExperience (ComicId id) (toPage pg)
  ["", "comic", id, _, "video"] -> Watching $ ComicId id
  ["", "comic", id, pg, "full"] -> Reading Full (ComicId id) (toPage pg)
  ["", "comic", id, pg] -> Reading Spread (ComicId id) (toPage pg)
  ["", "comic", id] -> Cover $ ComicId id
  _ -> NotReading
  where
    toPage pg = fromMaybe 1 (readMaybe pg :: Maybe PageNumber)

type PageNumber = Int

data Move
  = NoOp
  | -- comic player stuff
    SelectExperience Comic
  | StartReading Comic
  | StartWatching Comic
  | NextPage
  | PrevPage
  | ToggleZoom Comic PageNumber
  | ToggleAudio MisoString
  | FetchComics
  | SetComics (RemoteData MisoString [Comic])
  | ToggleFullscreen
  | -- discover stuff
    SetMediaInfo (Maybe Comic)
  | ToggleInLibrary Comic
  | -- login
    ValidateUserPassword
  | -- app stuff
    ScrollIntoView MisoString
  | HandleURI Api.URI
  | ChangeURI Api.URI
  | Dumpform
  deriving (Show, Eq)

type AppRoutes =
  Home
    :<|> ComicCover
    :<|> ComicReaderSpread
    :<|> ComicReaderFull
    :<|> ComicVideo
    :<|> Discover
    :<|> ChooseExperience

handlers =
  home
    :<|> comicCover
    :<|> comicReader
    :<|> comicReader
    :<|> comicReader
    :<|> discover
    :<|> comicReader

routes :: Proxy AppRoutes
routes = Proxy

type PubRoutes =
  Home
    :<|> Login

pubRoutes :: Proxy PubRoutes
pubRoutes = Proxy

-- * pages
--
-- TODO: consider making a typeclass, something like:
--
-- class Page name where
--   type Route name :: View Move
--   proxy :: Proxy name
--   proxy = Proxy name
--   view :: form -> View Move
--   link :: Api.URI

-- ** home
--
-- this is the unauthenticated page that you see when you first visit

type Home =
  View Move

homeProxy :: Proxy Home
homeProxy = Proxy

homeLink :: Api.URI
homeLink = linkURI $ Api.safeLink front homeProxy
  where
    front = Proxy :: Proxy Home

home :: form -> View Move
home = login

-- ** login

data LoginForm = LoginForm {loginEmail :: String, loginPass :: String}
  deriving (Eq, Show, Read, Generic)

instance ToJSON LoginForm

instance FromJSON LoginForm

type Login =
  "login" :> View Move

loginProxy :: Proxy Login
loginProxy = Proxy

loginLink :: Api.URI
loginLink = linkURI $ Api.safeLink pubRoutes loginProxy

login :: form -> View Move
login _ =
  template
    "login"
    [ div_
        [id_ "login-inner"]
        [ img_
            [ class_ fadeIn,
              src_ $ ms $ Pack.cdnEdge <> "/old-assets/images/icons/hero-large.png"
            ],
          hr_ [class_ fadeIn],
          form_
            [class_ fadeIn]
            [ ctrl [id_ "user", class_ "input", type_ "email", placeholder_ "Email"],
              ctrl [id_ "pass", class_ "input", type_ "password", placeholder_ "Password"],
              div_
                [class_ "action", css euro]
                [ div_
                    [class_ "checkbox remember-me"]
                    [ input_ [type_ "checkbox"],
                      label_ [Miso.for_ "checkbox"] [text "Remember Me"]
                    ],
                  div_
                    [class_ "button is-black", onClick ValidateUserPassword]
                    [text "Login"]
                ]
            ],
          hr_ [class_ fadeIn],
          p_
            [class_ $ "help " <> fadeIn]
            [ a_ [href_ "#"] [text "Forgot your username or password?"],
              a_ [href_ "#"] [text "Don't have an account? Sign Up"]
            ],
          img_
            [ id_ "hero-logo",
              class_ "blur-out",
              src_ $ ms $ Pack.cdnEdge <> "/old-assets/images/icons/success-her-image.png"
            ]
        ]
    ]
  where
    fadeIn = "animated fadeIn delay-2s"
    ctrl x = div_ [class_ "control"] [input_ x]

-- ** discover

type Discover = "discover" :> View Move

discoverLink :: Api.URI
discoverLink = linkURI $ Api.safeLink routes discoverProxy

discoverProxy :: Proxy Discover
discoverProxy = Proxy

discover :: Form -> View Move
discover form@Form {user = u} =
  template
    "discover"
    [ topbar,
      main_ [id_ "app-body"] $ case appComics form of
        NotAsked -> [loading]
        Loading -> [loading]
        Failure _ -> [nocomics]
        Success [] -> [nocomics]
        Success (comic : rest) ->
          [ feature comic u,
            shelf "Recent Releases" (comic : rest),
            maybeView (`info` u) $ dMediaInfo form
          ],
      appmenu,
      discoverFooter
    ]

discoverFooter :: View Move
discoverFooter =
  footer_
    [ id_ "app-foot",
      class_ "is-black"
    ]
    [ div_
        [id_ "app-foot-social", css euro]
        [ div_
            [class_ "row is-marginless"]
            [ smallImg "facebook.png" $ Just "https://www.facebook.com/musicmeetscomics",
              smallImg "twitter.png" $ Just "https://twitter.com/musicmeetscomic",
              smallImg "instagram.png" $ Just "https://www.instagram.com/musicmeetscomics/",
              smallImg "spotify.png" $ Just "https://open.spotify.com/user/i4ntfg6ganjgxdsylinigcjlq?si=ymWsSkwsT9iaLw2LeAJNNg",
              smallImg "youtube.png" $ Just "https://www.youtube.com/channel/UCnNPLiuJ1ueo1KTPgHDE7lA/"
            ],
          div_ [class_ "row"] [text "Team | Contact Us | Privacy Policy"]
        ],
      div_
        [id_ "app-foot-quote", css euro]
        [ p_ [] [text "With great power comes great responsiblity."],
          p_ [] [text "-Stan Lee"]
        ],
      div_
        [css euro, id_ "app-foot-logo", onClick Dumpform]
        [ a_ [class_ "social-icon", href_ "#"] [img_ [src_ $ ms $ Pack.icon <> "hero-logo.svg"]],
          span_ [] [text "© Hero Records, Inc. All Rights Reserved"]
        ]
    ]
  where
    attrs Nothing = [class_ "social-icon"]
    attrs (Just lnk) = [class_ "social-icon", href_ lnk, target_ "_blank"]
    smallImg x lnk =
      a_
        (attrs lnk)
        [img_ [src_ $ ms $ Pack.cdnEdge <> "/old-assets/images/icons/" <> x]]

-- ** comic

data Comic
  = Comic
      { comicId :: ComicId,
        comicPages :: Integer,
        comicName :: Text,
        -- | Ideally this would be a dynamic number-like type
        comicIssue :: Text,
        comicDescription :: Text
      }
  deriving (Show, Eq, Generic, Data, Ord)

instance ToJSON Comic where
  toJSON = genericToJSON Data.Aeson.defaultOptions

instance FromJSON Comic where
  parseJSON = genericParseJSON Data.Aeson.defaultOptions

instance IsMediaObject Comic where
  thumbnail c@Comic {..} =
    li_
      []
      [ a_
          [ class_ "comic grow clickable",
            id_ $ "comic-" <> ms comicId,
            onClick $ SetMediaInfo $ Just c
          ]
          [ img_ [src_ $ ms $ Pack.demo <> comicSlug c <> ".png"],
            span_ [] [text $ "Issue #" <> ms comicIssue],
            span_ [] [text $ ms comicName]
          ]
      ]
  feature comic lib =
    div_
      [id_ "featured-comic"]
      [ img_
          [ id_ "featured-banner",
            src_ $ ms $ Pack.demo <> "feature-banner.png"
          ],
        div_
          [id_ "featured-content"]
          [ div_
              [class_ "hero-original", css wide]
              [ span_ [css thicc] [text "Herø"],
                span_ [css euro] [text " Original"]
              ],
            div_
              [class_ "comic-logo"]
              [ img_
                  [ src_
                      $ ms
                      $ Pack.demo <> comicSlug comic <> "-logo.png"
                  ]
              ],
            div_ [class_ "comic-action-menu"] $
              el <$> [Watch comic, Read comic, Save comic lib],
            p_
              [class_ "description"]
              [ text . ms $ comicDescription comic
              ]
          ]
      ]
  info c@Comic {..} lib =
    div_
      [class_ "media-info", css euro]
      [ div_
          [class_ "media-info-meta"]
          [ column [img_ [src_ $ ms $ Pack.demo <> "dmc-widethumb.png"]],
            column
              [ span_ [style_ title] [text $ ms comicName],
                span_ [style_ subtitle] [text $ "Issue #" <> ms comicIssue],
                span_ [] [text "Released: "],
                span_ [] [text $ "Pages: " <> ms (show comicPages :: String)]
              ]
          ],
        div_
          [class_ "media-info-summary"]
          [ p_
              [style_ $ uppercase <> bold <> Look.expanded <> "font-size" =: ".8rem"]
              [text "Summary"],
            p_ [] [text $ ms comicDescription]
          ],
        div_ [class_ "media-info-actions"] $ el <$> [Save c lib, Read c, Watch c]
        -- , row [ text "credits" ]
      ]
    where
      title =
        "color" =: "red" <> "font-size" =: "1.6rem" <> uppercase
          <> "line-height"
            =: "100%"
          <> Look.condensed
          <> bold
      subtitle = "color" =: "#fff" <> "font-size" =: "1.2rem" <> bold <> Look.condensed

type ComicCover =
  "comic"
    :> Api.Capture "comicId" ComicId
    :> View Move

comicProxy :: Proxy ComicCover
comicProxy = Proxy

comicCover :: ComicId -> Form -> View Move
comicCover comicId_ = comicReader comicId_ 1

comicLink :: ComicId -> Api.URI
comicLink comicId_ = linkURI $ Api.safeLink routes comicProxy comicId_

-- ** chooseExperience

type ChooseExperience =
  "comic"
    :> Api.Capture "id" ComicId
    :> Api.Capture "page" PageNumber
    :> "experience"
    :> View Move

chooseExperienceProxy :: Proxy ChooseExperience
chooseExperienceProxy = Proxy

chooseExperienceLink :: ComicId -> PageNumber -> Api.URI
chooseExperienceLink id page =
  linkURI $ Api.safeLink routes chooseExperienceProxy id page

chooseExperiencePage :: Comic -> PageNumber -> Form -> View Move
chooseExperiencePage comic page form =
  template
    "choose-experience"
    [ topbar,
      main_
        [id_ "app-body"]
        [ h2_ [] [text "Choose Your Musical Experience"],
          p_ [] [text experienceBlurb],
          ul_ [] $ li comic </ experiences
        ],
      appmenu,
      comicControls comic page form
    ]
  where
    li c (name, artist, track) =
      li_
        [onClick $ StartReading c]
        [ div_
            []
            [ img_ [src_ $ ms $ Pack.demo <> name <> ".png"],
              span_ [] [text $ ms name]
            ],
          span_ [css thicc] [text $ ms artist],
          span_ [] [text $ ms track]
        ]
    experiences :: [(Text, Text, Text)]
    experiences =
      [ ("comedic", "RxGF", "Soft Reveal"),
        ("dark", "Logan Henderson", "Speak of the Devil"),
        ("original", "Mehcad Brooks", "Stars"),
        ("energetic", "Skela", "What's wrong with me"),
        ("dramatic", "Josh Jacobson", "Sideline")
      ]

experienceBlurb :: MisoString
experienceBlurb =
  [s|
As you enter the world of Hero, you will find that music and visual art have a
symbiotic relationship that can only be experienced, not described. Here, choose
the tonality of the experience you wish to adventure on, whether it's a comedic,
dark, energetic or dramatic. Feeling indecisive? Let us navigate your journey
with the original curated music for this piece of visual art.
|]

-- ** comicReader

data ComicReaderView = Spread | Full
  deriving (Show, Eq)

comicReader :: ComicId -> PageNumber -> Form -> View Move
comicReader _ _ form = case appComics form of
  NotAsked -> loading
  Loading -> loading
  Failure _ -> nocomics
  Success comics -> case cpState form of
    NotReading -> template "comic-player" [text "error: not reading"]
    Cover id -> viewOr404 comics comicSpread id 1 form
    ChooseExperience id pg ->
      viewOr404 comics chooseExperiencePage id pg form
    Reading Spread id pg -> viewOr404 comics comicSpread id pg form
    Reading Full id pg -> viewOr404 comics zoomScreen id pg form
    Watching id -> viewOr404 comics comicVideo id 0 form

zoomScreen :: Comic -> PageNumber -> Form -> View Move
zoomScreen comic page form =
  template
    "comic-player"
    [ topbar,
      main_
        [id_ "app-body"]
        [ img_
            [ src_ comicImg,
              class_ "comic-page-full"
            ]
        ],
      comicControls comic page form
    ]
  where
    comicImg =
      ms Pack.demo
        <> ms (comicSlug comic)
        <> "-"
        <> padLeft page
        <> ".png"

-- ** comicReaderSpread

type ComicReaderSpread =
  "comic"
    :> Api.Capture "id" ComicId
    :> Api.Capture "page" PageNumber
    :> View Move

comicReaderSpreadProxy :: Proxy ComicReaderSpread
comicReaderSpreadProxy = Proxy

comicReaderSpreadLink :: ComicId -> PageNumber -> Api.URI
comicReaderSpreadLink id page =
  linkURI $ Api.safeLink routes comicReaderSpreadProxy id page

comicSpread :: Comic -> PageNumber -> Form -> View Move
comicSpread comic page form =
  template
    "comic-player"
    [ topbar,
      main_
        [id_ "app-body"]
        [ div_
            [class_ "comic-player"]
            [ img_ [src_ comicImgLeft, class_ "comic-page"],
              img_ [src_ comicImgRight, class_ "comic-page"]
            ],
          closeButton
        ],
      appmenu,
      comicControls comic page form
    ]
  where
    comicImgLeft, comicImgRight :: MisoString
    comicImgLeft =
      ms Pack.demo
        <> ms (comicSlug comic)
        <> "-"
        <> padLeft page
        <> ".png"
    comicImgRight =
      ms Pack.demo
        <> ms (comicSlug comic)
        <> "-"
        <> padLeft (1 + page)
        <> ".png"

closeButton :: View Move
closeButton =
  a_
    [id_ "close-button", onClick $ ChangeURI discoverLink]
    [text "x"]

-- * comicReaderFull

type ComicReaderFull =
  "comic"
    :> Api.Capture "id" ComicId
    :> Api.Capture "page" PageNumber
    :> "full"
    :> View Move

comicReaderFullProxy :: Proxy ComicReaderFull
comicReaderFullProxy = Proxy

comicReaderFullLink :: ComicId -> PageNumber -> Api.URI
comicReaderFullLink id page =
  linkURI $ Api.safeLink routes comicReaderFullProxy id page

-- * comicVideo

type ComicVideo =
  "comic"
    :> Api.Capture "id" ComicId
    :> Api.Capture "page" PageNumber
    :> "video"
    :> View Move

comicVideoProxy :: Proxy ComicVideo
comicVideoProxy = Proxy

comicVideoLink :: ComicId -> PageNumber -> Api.URI
comicVideoLink id page =
  linkURI $ Api.safeLink routes comicVideoProxy id page

frameborder_ :: MisoString -> Attribute action
frameborder_ = textProp "frameborder"

allowfullscreen_ :: Bool -> Attribute action
allowfullscreen_ = boolProp "allowfullscreen"

comicVideo :: Comic -> PageNumber -> Form -> View Move
comicVideo _ _ _ =
  template
    "comic-player"
    [ topbar,
      main_
        [id_ "app-body"]
        [ div_
            [class_ "comic-video"]
            [ iframe_
                [ src_ "//player.vimeo.com/video/325757560",
                  frameborder_ "0",
                  allowfullscreen_ True
                ]
                []
            ]
        ]
    ]

-- * general page components & utils

-- | If 'View' had a 'Monoid' instance, then '(text "")' could just be 'mempty'
maybeView :: (a -> View action) -> Maybe a -> View action
maybeView = maybe (text "")

mediaInfo :: Maybe Comic -> User -> View Move
mediaInfo Nothing _ = text ""
mediaInfo (Just comic) user =
  div_ [class_ "media-info"] [info comic user]

appmenu :: View Move
appmenu = aside_ [id_ "appmenu"] $ btn </ links
  where
    links =
      -- these extra 'discoverLink's are just dummies
      [ (discoverLink, "discover.svg", "discover"),
        (discoverLink, "save.svg", "library"),
        (discoverLink, "watch.svg", "videos"),
        (comicLink "1", "read.svg", "comics"),
        (discoverLink, "listen.svg", "music")
      ]
    btn (lnk, img, label) =
      a_
        [ class_ "button",
          onPreventClick $ ChangeURI lnk
        ]
        [ img_ [src_ $ ms $ Pack.icon <> img],
          span_ [] [text label]
        ]

-- TODO: make this a loading gif of some sort... maybe the hero icon filling
-- from white to red
loading :: View Move
loading = div_ [class_ "loading"] [text "Loading..."]

nocomics :: View Move
nocomics = div_ [class_ "loading"] [text "error: no comics found"]

shelf :: IsMediaObject o => MisoString -> [o] -> View Move
shelf title comics =
  div_
    [class_ "shelf"]
    [ div_ [class_ "shelf-head"] [text title],
      ul_ [class_ "shelf-body"] $ thumbnail </ comics
    ]

viewOr404 ::
  [Comic] ->
  (Comic -> PageNumber -> form -> View Move) ->
  ComicId ->
  PageNumber ->
  form ->
  View Move
viewOr404 comics f id pg form =
  case findComic id comics of
    Just c -> f c pg form
    Nothing -> the404 form

template :: MisoString -> [View Move] -> View Move
template id = div_ [id_ id, class_ "app is-black"]

padLeft :: Int -> MisoString
padLeft n
  | n < 10 = ms ("0" <> Legacy.show n)
  | otherwise = ms $ Legacy.show n

comicControls :: Comic -> PageNumber -> Form -> View Move
comicControls comic page form =
  footer_
    [id_ "app-foot", class_ "comic-controls"]
    [ div_
        [ class_ "comic-nav-audio",
          css flexCenter
        ]
        [ audio_
            [id_ audioId, loop_ True, crossorigin_ "anonymous"]
            [source_ [src_ $ ms $ Pack.demo <> "stars-instrumental.mp3"]],
          el $ PlayPause audioId $ cpAudioState form,
          span_
            [css $ euro <> thicc <> smol <> wide]
            [text "Experiencing: Original"]
        ],
      div_
        [class_ "comic-controls-pages", css euro]
        [ el $ Arrow PrevPage,
          span_ [] [text $ leftPage <> "-" <> rightPage <> " of " <> totalpages],
          el $ Arrow NextPage
        ],
      div_
        [class_ "comic-controls-share"]
        [ el $ SaveIcon comic $ user form,
          el $ ZoomIcon (magnification form) comic page,
          button_
            [class_ "button icon is-large", onClick ToggleFullscreen]
            [i_ [class_ "fa fa-expand"] []]
        ]
    ]
  where
    leftPage = ms . Legacy.show $ page
    rightPage = ms . Legacy.show $ 1 + page
    totalpages = ms . Legacy.show $ comicPages comic

topbar :: View Move
topbar =
  header_
    [id_ "app-head", class_ "is-black", css euro]
    [ a_
        [ class_ "button is-medium is-black",
          onClick $ ChangeURI discoverLink
        ]
        [img_ [src_ $ ms $ Pack.icon <> "hero-logo.svg"]],
      div_
        [id_ "app-head-right"]
        [ button_
            [class_ "button icon is-medium is-black"]
            [i_ [class_ "fas fa-search"] []],
          button_
            [ class_ "button is-medium is-black is-size-7",
              css $ euro <> wide <> thicc
            ]
            [text "News"],
          span_
            [class_ "icon is-large"]
            [ i_ [class_ "fas fa-user"] []
            ]
        ]
    ]

row :: [View Move] -> View Move
row = div_ [css $ Clay.display Clay.flex <> Clay.flexDirection Clay.row]

column :: [View Move] -> View Move
column = div_ [css $ Clay.display Clay.flex <> Clay.flexDirection Clay.column]

-- | Links
the404 :: form -> View Move
the404 _ = template "404" [p_ [] [text "Not found"]]