{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module Lucid.HTMX.QuasiQuoters where

{-
{- Idea for QuasiQuoting syntax:

[htmx| hx-get="bar/foo/baz?id=34" hx-swap="delay:500" hx-trigger="click" |]

-}
import Data.Text as Text
import HTMX.Types
import Lucid.HTMX

toUrlPiece' = ("/" <>) . toUrlPiece

-- -- | Makes hx_boost_ a "boolean attribute" since the only valid value for hx-boost is "true".
-- hx_boost_ :: Attribute
-- hx_boost_ = Base.hx_boost_ "true"

-- hx_confirm_ :: Text -> Attribute
-- hx_confirm_ = Base.hx_confirm_

hxDeleteSafe_ :: Link -> Attribute
hxDeleteSafe_ = Base.hx_delete_ . toUrlPiece'

-- hx_disable_ :: Attribute
-- hx_disable_ = Base.hx_disable_

-- | Makes hx_encoding_ a "boolean attribute" since the only valid value for hx-encoding is "multipart/form-data".
-- hx_encoding_ :: Attribute
-- hx_encoding_ = Base.hx_encoding_ "multipart/form-data"

getHTMXExtName :: HTMXExtension -> Text
getHTMXExtName htmxExt = case htmxExt of
    JSONEnc -> "json-enc"
    MethodOverride -> "method-override"
    MorphdomSwap -> "morphdom-swap"
    ClientSideTemplates -> "client-side-templates"
    Debug -> "debug"
    PathDeps -> "path-deps"
    ClassTools -> "class-tools"
    RemoveMe -> "remove-me"
    IncludeVals -> "included-vals"
    AJAXHeader -> "ajax-header"
    EventHeader -> "event-header"
    Preload -> "preload"
    OtherHTMXExtension extName -> extName

hxExtSafe_ :: HXExt -> Attribute
hxExtSafe_ HXExt{..} =
   hxExt_
   . ((if hxExtIgnore then "ignore:" else "") <>)
   . Text.intercalate ","
   . Prelude.map getHTMXExtName
   . HashSet.toList
   $ hxExtExtensions
   -- HXExtVal htmxExtHashSet -> Base.hx_ext_ . Text.intercalate "," . Prelude.map getHTMXExtName . HashSet.toList $ htmxExtHashSet
   -- HXExtValIgnore htmxExtHashSet -> Base.hx_ext_ . ("ignore:" <>) . Text.intercalate "," . Prelude.map getHTMXExtName . HashSet.toList $ htmxExtHashSet

hxGetSafe_ :: Link -> Attribute
hxGetSafe_ = Base.hx_get_ . toUrlPiece'

-- | Value of hx_headers_ must be valid JSON
hxHeadersSafe_ :: HXHeaders -> Attribute
hxHeadersSafe_ HXHeaders{..} =
   Base.hx_headers_
   . Text.decodeUtf8
   . LBS.toStrict
   . Aeson.encode
   $ hxHeadersJSON

-- hx_history_elt_ :: Attribute
-- hx_history_elt_ = Base.hx_history_elt_

hxIncludeSafe_ :: HXInclude -> Attribute
hxIncludeSafe_ HXInclude{..} = Base.hx_include_ . toCssSelector

hxIndicatorSafe_ :: HXIndicator -> Attribute
hxIndicatorSafe_ HXIndicator{..} =
    hxIndicator_
    . ((if hxIndicatorClosest then "closest:" else "") <>)
    . toCssSelector
    $ hxIndicatorSelector

-- hx_indicator_ :: HXIndicatorVal -> Attribute 
-- hx_indicator_ val = case val of
--     HXIndicatorVal selector -> Base.hx_indicator_ . toCssSelector $ selector
--     HXIndicatorValClosest selector -> Base.hx_indicator_ . ("closest " <>) . toCssSelector $ selector

hxParamsSafe_ :: HXParams -> Attribute
hxParamsSafe_ hxParams = case hxParams of
    HXParams params -> hxParams_ . Text.intercalate "," $ params
    HXParamsNot params -> hxParams_ . ("not " <>) . Text.intercalate "," $ params
    HXParamsAll -> hxParams_ "*"
    HXParamsNone -> hxParams_ "none"

hx_patch_ :: Link -> Attribute
hx_patch_ = Base.hx_patch_ . toUrlPiece'

hx_post_ :: Link -> Attribute
hx_post_ = Base.hx_post_ . toUrlPiece'

-- For same reasons as hx_boost_
hx_preserve_ :: Attribute
hx_preserve_ = Base.hx_preserve_ "true"

hx_prompt_ :: Text -> Attribute
hx_prompt_ = Base.hx_prompt_

hx_push_url_ :: Link -> Attribute
hx_push_url_ = Base.hx_push_url_ . toUrlPiece'

hx_put_ :: Link -> Attribute
hx_put_ = Base.hx_put_ . toUrlPiece'

hx_request_ :: HXRequestVal -> Attribute
hx_request_ val = Base.hx_request_ $ case val of
    (HXRequestVal (JavaScript _) _ _) -> ("javascript:" <>) . Text.decodeUtf8 . LBS.toStrict . Aeson.encode $ val
    (HXRequestVal _ (JavaScript _) _) -> ("javascript:" <>) . Text.decodeUtf8 . LBS.toStrict . Aeson.encode $ val
    (HXRequestVal _ _ (JavaScript _)) -> ("javascript:" <>) . Text.decodeUtf8 . LBS.toStrict . Aeson.encode $ val
    _ -> Text.decodeUtf8 . LBS.toStrict . Aeson.encode $ val

hx_select_ :: ToCssSelector a => a -> Attribute
hx_select_ = Base.hx_select_ . toCssSelector

hx_sse_ :: HXSSEVal -> Attribute
hx_sse_ val = Base.hx_sse_ $ case val of
    (HXSSEVal Nothing Nothing) -> ""
    (HXSSEVal (Just link) Nothing) -> "connect:" <> (toUrlPiece' link)
    (HXSSEVal Nothing (Just eventName)) -> "swap:" <> eventName
    (HXSSEVal (Just link) (Just eventName)) -> "connect:" <> (toUrlPiece' link) <> " " <> "swap:" <> eventName

pos :: SwapPos -> Text
pos p = case p of
    SwapPosInner -> "innerHTML"
    SwapPosOuter -> "outerHTML"
    SwapPosBeforeBegin -> "beforebegin"
    SwapPosAfterBegin -> "afterbegin"
    SwapPosBeforeEnd -> "beforeend"
    SwapPosAfterEnd -> "afterend"
    SwapPosNone -> "none"

hx_swap_ :: HXSwapVal -> Attribute
hx_swap_ HXSwapVal{..} = Base.hx_swap_ $ (pos hxSwapValPos) <> (swap hxSwapValSwap) <> (settle hxSwapValSettle) <> (view hxSwapValView)
    where
        swap :: Maybe SwapModDelay -> Text
        swap s = case s of
            Nothing -> ""
            Just (SwapModDelay delay) -> " swap:" <> (Text.pack $ show delay) <> "s"

        settle :: Maybe SwapModSettle -> Text
        settle s = case s of
            Nothing -> ""
            Just (SwapModSettle delay) -> " settle:" <> (Text.pack $ show delay) <> "s"

        view :: Maybe SwapModView -> Text
        view v = case v of
            Nothing -> ""
            Just v' -> " " <> (viewPrefix v') <> (viewPostfix v')
            where
                viewPostfix :: SwapModView -> Text
                viewPostfix v' = case v' of
                    SwapModView _ sm ss -> case (sm, ss) of
                        (ScrollMoveTop, ss') -> (selectorPrefix ss') <> "top"
                        (ScrollMoveBottom, ss') -> (selectorPrefix ss') <> "bottom"

                selectorPrefix :: Maybe ScrollSelector -> Text
                selectorPrefix ss = case ss of
                    Nothing -> ""
                    Just ss' -> case ss' of
                        ScrollSelector q -> (toCssSelector q) <> ":"
                        ScrollSelectorWindow -> "window:"

                viewPrefix :: SwapModView -> Text
                viewPrefix v' = case v' of
                      SwapModView SwapModViewTypeScroll _ _ -> "scroll:"
                      SwapModView SwapModViewTypeShow _ _ -> "show:"

hx_swap_oob_ :: HXSwapOOBVal -> Attribute
hx_swap_oob_ val = Base.hx_swap_oob_ $ case val of
    HXSwapOOBVal -> "true"
    HXSwapOOBValSwap swapPos -> pos swapPos
    HXSwapOOBValSwapSelector swapPos selector -> (pos swapPos) <> ":" <> (toCssSelector selector)

hx_target_ :: HXTargetVal -> Attribute
hx_target_ val = Base.hx_target_ $ case val of
    HXTargetVal -> "this"
    HXTargetValSelector selector -> toCssSelector selector
    HXTargetValSelectorClosest selector -> "closest " <> (toCssSelector selector)
    HXTargetValSelectorFind selector -> "find " <> (toCssSelector selector) 

hx_vals_ :: ToJSON a => a -> Attribute
hx_vals_ = Base.hx_vals_ . Text.decodeUtf8 . LBS.toStrict . Aeson.encode

-- TODO: Study more. Basically all possible events plus event modifiers.

-- hx_trigger_ :: HXTriggerVal -> Attribute
-- hx_trigger_ = Base.hx_trigger_

-- BELOW EXPERIMENTAL!!

-- hx_ws_ :: HXWS -> Attribute
-- hx_ws_ = hx_ws_

-- TODO: Add QuasiQuoters for parsing and generating values that are checked at compile time for the various arguments to the HTMX attributes.
-- TODO: Write tests to check that the Val types are generating the correct Text for the HTMX attributes. Tests for HTMX tag functionality maybe?

-}