{-# 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 IHP.Prelude hiding (div)
import qualified Text.Blaze.Html5 as Blaze
import IHP.FlashMessages.Types
import IHP.ModelSupport (Violation)
import IHP.Breadcrumb.Types
import IHP.Pagination.Types


type HtmlWithContext context = (?context :: context) => 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 -> 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 { $sel:model:FormContext :: record
model = record
value }
instance SetField "formAction" (FormContext record) Text where setField :: Text -> FormContext record -> FormContext record
setField Text
value FormContext record
record = FormContext record
record { $sel:formAction:FormContext :: Text
formAction = Text
value }
instance SetField "formMethod" (FormContext record) Text where setField :: Text -> FormContext record -> FormContext record
setField Text
value FormContext record
record = FormContext record
record { $sel:formMethod:FormContext :: Text
formMethod = Text
value }
instance SetField "cssFramework" (FormContext record) CSSFramework where setField :: CSSFramework -> FormContext record -> FormContext record
setField CSSFramework
value FormContext record
record = FormContext record
record { $sel:cssFramework:FormContext :: CSSFramework
cssFramework = CSSFramework
value }
instance SetField "formClass" (FormContext record) Text where setField :: Text -> FormContext record -> FormContext record
setField Text
value FormContext record
record = FormContext record
record { $sel:formClass:FormContext :: Text
formClass = Text
value }
instance SetField "formId" (FormContext record) Text where setField :: Text -> FormContext record -> FormContext record
setField Text
value FormContext record
record = FormContext record
record { $sel:formId:FormContext :: Text
formId = Text
value }
instance SetField "disableJavascriptSubmission" (FormContext record) Bool where setField :: Bool -> FormContext record -> FormContext record
setField Bool
value FormContext record
record = FormContext record
record { $sel:disableJavascriptSubmission:FormContext :: Bool
disableJavascriptSubmission = Bool
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 { $sel:customFormAttributes:FormContext :: [(Text, Text)]
customFormAttributes = [(Text, Text)]
value }

data InputType
    = TextInput
    | NumberInput
    | 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
    }