{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE InstanceSigs, UndecidableInstances, AllowAmbiguousTypes, ScopedTypeVariables, IncoherentInstances  #-}

module IHP.View.Types
( FormField (..)
, SubmitButton (..)
, FormContext (..)
, InputType (..)
, CSSFramework (..)
, BreadcrumbsView(..)
, PaginationView(..)
, HtmlWithContext
, Layout
)
where

import Prelude
import Data.Text (Text)
import Data.ByteString (ByteString)
import IHP.HaskellSupport (SetField(..))
import qualified Text.Blaze.Html5 as Blaze
import Network.Wai.Middleware.FlashMessages (FlashMessage (..))
import IHP.ModelSupport.Types (Violation)
import IHP.Breadcrumb.Types
import IHP.Pagination.Types
import Network.Wai (Request)


type HtmlWithContext context = (?context :: context, ?request :: Request) => Blaze.Html

-- | A layout is just a function taking a view and returning a new view.
--
-- __Example:__ A very basic html layout.
--
-- > myLayout :: Layout
-- > myLayout view = [hsx|
-- >     <html>
-- >         <body>
-- >             {view}
-- >         </body>
-- >     </html>
-- > |]
type Layout = Blaze.Html -> Blaze.Html

data FormField = FormField
    { FormField -> InputType
fieldType :: !InputType
    , FormField -> Text
fieldName :: !Text
    , FormField -> Text
fieldLabel :: !Text
    , FormField -> Text
fieldValue :: !Text
    , FormField -> Text
fieldInputId :: !Text
    , FormField -> Maybe Violation
validatorResult :: !(Maybe Violation)
    , FormField -> [(Text, Text)]
additionalAttributes :: [(Text, Text)]
    , FormField -> Text
fieldClass :: !Text
    , FormField -> Text
labelClass :: !Text
    , FormField -> Bool
disabled :: !Bool
    , FormField -> Bool
disableLabel :: !Bool
    , FormField -> Bool
disableGroup :: !Bool
    , FormField -> Bool
disableValidationResult :: !Bool
    , FormField -> CSSFramework
cssFramework :: CSSFramework
    , FormField -> Text
helpText :: !Text
    , FormField -> Text
placeholder :: !Text
    , FormField -> Bool
required :: Bool
    , FormField -> Bool
autofocus :: Bool
    }

data SubmitButton = SubmitButton
    { SubmitButton -> Html
label :: Blaze.Html
    , SubmitButton -> Text
buttonClass :: Text
    , SubmitButton -> Bool
buttonDisabled :: Bool
    , SubmitButton -> CSSFramework
cssFramework :: CSSFramework
    }

data FormContext model = FormContext
    { forall model. FormContext model -> model
model :: model -- ^ The record this form is based on
    , forall model. FormContext model -> Text
formAction :: !Text -- ^ Url where the form is submitted to
    , forall model. FormContext model -> Text
formMethod :: !Text -- ^ Usually "POST", sometimes set to "GET"
    , forall model. FormContext model -> CSSFramework
cssFramework :: !CSSFramework
    , forall model. FormContext model -> Text
formClass :: !Text -- ^ In the generated HTML, the @class@  attribute will be set to this value
    , forall model. FormContext model -> Text
formId :: !Text -- ^ In the generated HTML, the @id@ attribute will be set to this value
    , forall model. FormContext model -> Bool
disableJavascriptSubmission :: !Bool -- ^ When set to True, the IHP helpers.js will not submit the form using ajax
    , forall model. FormContext model -> [(Text, Text)]
customFormAttributes :: ![(Text, Text)] -- ^ Attach custom HTML attributes here
    , forall model. FormContext model -> Text
fieldNamePrefix :: !Text -- ^ Used by nested forms to preprend the nested field name to the field name
    }
instance SetField "model" (FormContext record) record where setField :: record -> FormContext record -> FormContext record
setField record
value FormContext record
record = FormContext record
record { model = value }
instance SetField "formAction" (FormContext record) Text where setField :: Text -> FormContext record -> FormContext record
setField Text
value FormContext record
record = FormContext record
record { formAction = value }
instance SetField "formMethod" (FormContext record) Text where setField :: Text -> FormContext record -> FormContext record
setField Text
value FormContext record
record = FormContext record
record { formMethod = value }
instance SetField "cssFramework" (FormContext record) CSSFramework where setField :: CSSFramework -> FormContext record -> FormContext record
setField CSSFramework
value FormContext record
record = FormContext record
record { cssFramework = value }
instance SetField "formClass" (FormContext record) Text where setField :: Text -> FormContext record -> FormContext record
setField Text
value FormContext record
record = FormContext record
record { formClass = value }
instance SetField "formId" (FormContext record) Text where setField :: Text -> FormContext record -> FormContext record
setField Text
value FormContext record
record = FormContext record
record { formId = value }
instance SetField "disableJavascriptSubmission" (FormContext record) Bool where setField :: Bool -> FormContext record -> FormContext record
setField Bool
value FormContext record
record = FormContext record
record { disableJavascriptSubmission = value }
instance SetField "customFormAttributes" (FormContext record) [(Text, Text)] where setField :: [(Text, Text)] -> FormContext record -> FormContext record
setField [(Text, Text)]
value FormContext record
record = FormContext record
record { customFormAttributes = value }

data InputType
    = TextInput
    | NumberInput
    | UrlInput
    | CheckboxInput
    | ColorInput
    | EmailInput
    | HiddenInput
    | TextareaInput
    | DateInput
    | DateTimeInput
    | PasswordInput
    | SelectInput { InputType -> [(Text, Text)]
options :: ![(Text, Text)] }
    | RadioInput { options :: ![(Text, Text)] }
    | FileInput


data BreadcrumbsView = BreadcrumbsView { BreadcrumbsView -> Html
breadcrumbItems :: !Blaze.Html }

data PaginationView =
    PaginationView
    { PaginationView -> CSSFramework
cssFramework :: !CSSFramework
    , PaginationView -> Pagination
pagination :: !Pagination
    -- Function used to get the page URL.
    , PaginationView -> Int -> ByteString
pageUrl :: Int -> ByteString
    -- Previous page link.
    , PaginationView -> Html
linkPrevious :: !Blaze.Html
    -- Next page link.
    , PaginationView -> Html
linkNext :: !Blaze.Html
    -- The page and dot dot as rendered by `styledPaginationPageLink` and `styledPaginationDotDot`.
    , PaginationView -> Html
pageDotDotItems :: !Blaze.Html
    -- Selector changing the number of allowed items per page.
    , PaginationView -> Html
itemsPerPageSelector :: !Blaze.Html
    }

-- | Render functions to render with Bootstrap, Tailwind CSS etc.
-- We call this functions with the cssFramework passed to have late binding (like from OOP languages).
-- Here's an explanation breaking it down, step by step
--
-- > renderedHtml = styledPagination theCSSFramework theCSSFramework paginationView
--
-- Can also be written using get:
--
-- > renderedHtml = (theCSSFramework.styledPagination) theCSSFramework paginationView
--
-- That's important to understand here. We get a 'styledPagination' function using 'theCSSFramework.styledPagination'.
-- Next, we apply 'theCSSFramework' to that function. We do that so because the 'styledPagination' function internally
-- might want to call other functions of the CSSFramework type. But we might want to override some functions of the CSSFramework.
--
-- Here's an example of how it would look if we don't pass this a second time, and it's shortcomings.
-- Let's assume 'styledPagination' is calling a 'styledButton':
--
-- > data CSSFramework = CSSFramework { styledPagination :: Html, styledButton :: Html }
-- >
-- > bootstrapCSS = CSSFramework { styledPagination, styledButton }
-- >    where
-- >        styledPagination = [hsx|<div>{styledButton}</div>|]
-- >        styledButton = [hsx|<button style="color: red">button</button>|]]
-- >
-- > myPage = [hsx|{styledPagination bootstrapCSS}|]
--
-- So far all seems fine. But now let's say we would like to override the button styling, and change the button to green instead of red:
--
-- > customCSS = bootstrapCSS { styledButton = [hsx|<button style="color: green">button</button>|]]  }
-- >
-- > myPage = [hsx|{styledPagination customCSS}|]
--
-- Now, when we render the 'myPage' we will get '<div><button style="color: red">button</button></div>' (a red button, while our customCSS specified it should be green).
--
-- Our way to fix this is to "late-bind" the calls, by manually passing around a CSSFramework. Here we added that second 'CSSFramework' to all functions.
-- Notice how 'styledPagination' is getting the correct 'styledButton' by calling 'cssFramework.styledButton':
--
-- > data CSSFramework = CSSFramework { styledPagination :: CSSFramework -> Html, styledButton :: CSSFramework -> Html }
-- >
-- > bootstrapCSS = CSSFramework { styledPagination, styledButton }
-- >    where
-- >        styledPagination cssFramework = [hsx|<div>{cssFramework.styledButton}</div>|]
-- >        styledButton cssFramework = [hsx|<button style="color: red">button</button>|]]
-- >
-- > myPage = [hsx|{styledPagination bootstrapCSS bootstrapCSS}|]
--
-- Now, with this second 'CSSFramework' in place we can customize it again:
--
-- > customCSS = bootstrapCSS { styledButton = \cssFramework -> [hsx|<button style="color: green">button</button>|]]  }
-- >
-- > myPage = [hsx|{styledPagination customCSS customCSS}|]
data CSSFramework = CSSFramework
    { CSSFramework -> CSSFramework -> FlashMessage -> Html
styledFlashMessage :: CSSFramework -> FlashMessage -> Blaze.Html
    , CSSFramework -> CSSFramework -> [FlashMessage] -> Html
styledFlashMessages :: CSSFramework -> [FlashMessage] -> Blaze.Html
    -- | Renders the full form field calling other functions below
    , CSSFramework -> CSSFramework -> FormField -> Html
styledFormField :: CSSFramework -> FormField -> Blaze.Html
    , CSSFramework -> CSSFramework -> Text -> FormField -> Html -> Html
styledTextFormField :: CSSFramework -> Text -> FormField -> Blaze.Html -> Blaze.Html
    , CSSFramework -> CSSFramework -> FormField -> Html -> Html
styledTextareaFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html
    , CSSFramework -> CSSFramework -> FormField -> Html -> Html
styledCheckboxFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html
    , CSSFramework -> CSSFramework -> FormField -> Html -> Html
styledSelectFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html
    , CSSFramework -> CSSFramework -> FormField -> Html -> Html
styledRadioFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html
    , CSSFramework -> CSSFramework -> Text -> Html -> Html
styledFormGroup :: CSSFramework -> Text -> Blaze.Html -> Blaze.Html
    -- | The primary form submit button
    , CSSFramework -> CSSFramework -> SubmitButton -> Html
styledSubmitButton :: CSSFramework -> SubmitButton -> Blaze.Html
    -- | Class for the primary form submit button
    , CSSFramework -> Text
styledSubmitButtonClass :: Text
    -- | Renders the help text below an input field. Used with @[hsx|{(textField #firstname) { helpText = "Your first name" } }|]@
    , CSSFramework -> CSSFramework -> FormField -> Html
styledFormFieldHelp :: CSSFramework -> FormField -> Blaze.Html
    -- | First class attached to @<input/>@ elements, e.g. @<input class="form-control"/>@
    , CSSFramework -> CSSFramework -> FormField -> Text
styledInputClass :: CSSFramework -> FormField -> Text
    -- | When the form validation failed, invalid inputs will have this class
    , CSSFramework -> CSSFramework -> FormField -> Text
styledInputInvalidClass :: CSSFramework -> FormField -> Text
    -- | Class applied to the div wrapping the label and input, e.g. @"form-group"@
    , CSSFramework -> Text
styledFormGroupClass :: Text
    -- | Elements that containers the validation error message for a invalid form field
    , CSSFramework -> CSSFramework -> FormField -> Html
styledValidationResult :: CSSFramework -> FormField -> Blaze.Html
    -- | Class name for container of validation error message
    , CSSFramework -> Text
styledValidationResultClass :: Text
    -- | Renders a the entire pager, with all its elements.
    , CSSFramework -> CSSFramework -> PaginationView -> Html
styledPagination :: CSSFramework -> PaginationView -> Blaze.Html
    -- | The pagination's previous link
    , CSSFramework -> CSSFramework -> Pagination -> ByteString -> Html
styledPaginationLinkPrevious :: CSSFramework -> Pagination -> ByteString -> Blaze.Html
    -- | The pagination's next link
    , CSSFramework -> CSSFramework -> Pagination -> ByteString -> Html
styledPaginationLinkNext :: CSSFramework -> Pagination -> ByteString -> Blaze.Html
    -- | Render the pagination links
    , CSSFramework
-> CSSFramework -> Pagination -> ByteString -> Int -> Html
styledPaginationPageLink :: CSSFramework -> Pagination -> ByteString -> Int -> Blaze.Html
    -- | Render the dots between pagination numbers (e.g. 5 6 ... 7 8)
    , CSSFramework -> CSSFramework -> Pagination -> Html
styledPaginationDotDot :: CSSFramework -> Pagination -> Blaze.Html
    -- | Render the items per page selector for pagination.
    -- Note the (Int -> ByteString), we are passing the pageUrl function, so anyone that would like to override
    -- it the selector with different items per page could still use the pageUrl function to get the correct URL.
    , CSSFramework
-> CSSFramework -> Pagination -> (Int -> ByteString) -> Html
styledPaginationItemsPerPageSelector :: CSSFramework -> Pagination -> (Int -> ByteString) -> Blaze.Html
    -- | Renders an entire breadcrumbs element.
    , CSSFramework
-> CSSFramework -> [BreadcrumbItem] -> BreadcrumbsView -> Html
styledBreadcrumb :: CSSFramework -> [BreadcrumbItem] -> BreadcrumbsView -> Blaze.Html
    -- | Render a single breadcrumb item. We pass the entire list of breadcrumbs, in case an item may change based on that list.
    -- The 'Bool' indicates if item is the last one.
    , CSSFramework
-> CSSFramework
-> [BreadcrumbItem]
-> BreadcrumbItem
-> Bool
-> Html
styledBreadcrumbItem :: CSSFramework -> [BreadcrumbItem]-> BreadcrumbItem -> Bool -> Blaze.Html
    }