packages feed

ClasshSS-0.1.0.0: src/Classh.hs~

{-# LANGUAGE OverloadedStrings #-}

module Classh where


import Common.Types (tshow)
import Reflex.Dom.Core

import Language.Haskell.TH
import Control.Monad (forM_)
import Control.Lens (makeLenses, Lens',lens, ASetter, over)
import Data.Default
import qualified Data.Text as T

{-
GOALS
-> Cut down the surface area of what is needed from CSS
-> Make an easy to integrate system through this limited CSS
-> Eliminate messing with typos and repeats
-> Encourage non-naive responsive code
-> Have more organized code for when an attribute is highly responsive
   Non eg. "py-2 ......... sm:py-4 ............... lg:pt-9"
-> Helper functions with a definite limited scope for the goal of self-documenting / easy to visualize DomBuilders

-}


-- | A future research item for this library is how to work with accessibility
  -- do we need prerender ?
  -- is this gonna be reading in some config?
  -- is this a native thing?
  -- aria?
  -- will this mean scaling from same base accessibility based value?
  -- what features does this affect directly? indirectly?


--instance ShowTW (WhenTW a)

-- | TODO:
  -- use template haskell to autogenerate types from a CSS file eg if classes blah and blah2 exist
  -- data UserCSS = Blah | Blah2 

intercalate :: DomBuilder t m => WhenTW TextSize -> T.Text -> [m ()] -> m ()
intercalate size inter (t:ts) = do
  t >> prependAll size inter ts
  where
    prependAll _ _ [] = pure () 
    prependAll s i (t:ts) = elClass "span" (renderWhenTW s showTW ) (text i) >> t >> prependAll s i ts

-- | Works by setting top padding by one consistent value. Does not add padding for first element
paragraphs :: DomBuilder t m => WhenTW TWSize -> [m ()] -> m () 
paragraphs spacing (r:rows) = do
  row [] $ r
  withTopPadding spacing rows
  where
    withTopPadding _ [] = pure ()
    withTopPadding p (r:rs) = row [t .~ p] r >> withTopPadding p rs 

-- | Works by setting top padding variably
paragraphs' :: DomBuilder t m => [(WhenTW TWSize, m ())] -> m () 
paragraphs' ((s,r):rows) = row [t .~ s] r >> paragraphs' rows

rows :: DomBuilder t m => [(WhenTW TWSize, m ())] -> m () 
rows = paragraphs' 

-- | TODO: should we have a variant which controls for padding needs when being a full row? What should that look like? Do we even need that??
responsiveRowCol :: DomBuilder t m => [Int] -> m a -> m a
responsiveRowCol colSpans m =
  let spandex = renderWhenTW (zipScreens colSpans) ((<>) "col-span-" . tshow) 
  in elClass "div" spandex m
           

 


-- | Not real: just ideating
-- | How can we model a discrete set of consistent brand options?
--
-- data Mytextsize = Heading | Subtext | Normal
-- domain = Map.fromList [ (Heading , XL7) , (Normal , XL3) , (Subtext , XS) ]
--
-- brandF :: k -> WhenTW k
-- brandF (domainType :: Ord k => k) =
--   let baseSize = fromJust $ Map.lookup domainType (domain :: Map (k :: Ord k => k) (v :: ShowTW x => x))
--   in
--     autoScale baseSize 
--
-- then this above code suggests that we should think of our domain as mobile first 
-- it is important to remember this is not meant to represent 100% of CSS but rather ~80%
-- and should be a way to communicate branding concisely such that a stoopid cud impl. it to be
-- consistent 
--
-- Now, it wont always be super complex: for example, color
--
-- def = TW.def & text_color .~ only aceBlue
--
-- But then again, a brand may have multiple colors
--
-- brandColors = Map.fromList [ (AceBlue, hex _), (AceNavyBlue, hex _), (AceBabyBlue, hex _) ]
-- and also:
       -- What about dark mode? \
       --
-- and note: lets say 90% of titles should have Bold as their weight, we can set this and 




-------------------thought bubble ----------------------------------------------------------


-- module MyTWConfig where

-- def = TW.def & text_color .~ only aceBlue

-- then

-- module MyModule

-- import MyTWConfig
-- f = elTW def blank

-- ---

-- we could also have a defDerivative which functions just like V1 tailwindlib like so:
--   BoxConfig def:[]

--   where def here should be ("def", def)

--   and note we can make TWstring which could have an instance of IsString so that it can be written as a string literal

--   which helps cuz then TWString can have a default of "def"


-- Also for conceptual understanding:

--   fmap (,X) (enum screenSizes)  == ("def", X)
--   fmap (,X) (enum allConditions) == ("def", X)
--   fmap (,X) (enum allEvents) : ("def",X) : [] == ("def", X)
--   fmap (,def) (enum allEvents) == []


------------------------------------------------------------------------------------------------------
-- TODO:

-- CSS and Tailwind semigroups for folding into one CSSConfig OR TailwindClassConfig
-- where both render to a string

  -- Each config is only allowed to be set once -> alerting the dev if they asked for something like "red" and "blue"
  -- for the background color or similar illogical configurations

  -- Would be extra ideal to have this made clear at compile time


-- def
--   & leftPadding .~ 3
--   & styling .~ (def
--                 &  bg_color .~ Color_Custom (Hex "")
--                )


-- data BoxConfig = BoxConfig
--   { leftPadding :: T.Text
--   , bottomPadding :: T.Text
--   }



-- | Idea: elDynTW where we can update using the TW lib based on events and prev state

-- | Idea: for align and justify -> we have them as one setting of the datatype Position = TR -- TopRight | BL -- Bottom Left

-- | THIS DOESNT WORK: Grid causes other issues/effects - maybe we can negate this through defaults (eg. two styled texts begin to stack
-- | instead of go beside each other























-- HELPFUL CONSTANTS
centeredOnly = only centered
centered = (J_Center, A_Center)

--------------------

defaultClasses = "" -- "grid"

elTW :: DomBuilder t m => T.Text -> BoxConfig -> m a -> m a
elTW tag cfg m = elClass tag (defaultClasses <> " " <> showTW cfg) m

elTW' :: DomBuilder t m => T.Text -> BoxConfig -> m a -> m (Element EventResult (DomBuilderSpace m) t, a)
elTW' tag cfg m = elClass' tag (defaultClasses <> " " <> showTW cfg) m

elDynTW :: (PostBuild t m, DomBuilder t m) => T.Text -> Dynamic t BoxConfig -> m a -> m a
elDynTW tag cfgDyn m = elDynClass tag ( (\cfg -> defaultClasses <> " " <> showTW cfg) <$> cfgDyn ) m

elDynTW' :: (PostBuild t m, DomBuilder t m) => T.Text -> Dynamic t BoxConfig -> m a -> m (Element EventResult (DomBuilderSpace m) t, a)
elDynTW' tag cfgDyn m = elDynClass' tag ( (\cfg -> defaultClasses <> " " <> showTW cfg) <$> cfgDyn ) m

-- textTW' :: (DomBuilder t m, ShowTW tw) => tw -> T.Text -> m ()
-- textTW' = styledText'

-- styledText' :: (DomBuilder t m, ShowTW tw) => tw -> T.Text -> m ()
-- styledText' cfg txt = elClass "span" (showTW cfg) $ text txt

-- dynTextTW' :: (DomBuilder t m, PostBuild t m, ShowTW tw) => tw -> Dynamic t T.Text -> m ()
-- dynTextTW' cfg txt = elClass "span" (showTW cfg) $ dynText txt

-- textDynTW' :: (DomBuilder t m, PostBuild t m, ShowTW tw) => Dynamic t tw -> T.Text -> m ()
-- textDynTW' dynCfg txt = elDynClass "span" ( (\cfg -> defaultClasses <> " " <> showTW cfg) <$> dynCfg ) $ text txt

-- styledTexts' :: (ShowTW tw, DomBuilder t m) => [(tw, T.Text)] -> m ()
-- styledTexts' styTexs = el "div" $ forM_ styTexs $ \(cfg, txt) -> styledText' cfg txt


-- | TODO: variants which do not get checked/compiled
textS :: DomBuilder t m => CompiledS -> T.Text -> m ()
textS s txt = elClass "span" s $ text txt

textDynS :: (PostBuild t m, DomBuilder t m) => Dynamic t CompiledS -> T.Text -> m ()
textDynS s txt = elDynClass "span" s $ text txt

dynTextS :: (PostBuild t m, DomBuilder t m) => CompiledS -> Dynamic t T.Text -> m ()
dynTextS s txt = elClass "span" s $ dynText txt

dynTextDynS :: (PostBuild t m, DomBuilder t m) => Dynamic t CompiledS -> Dynamic t T.Text -> m ()
dynTextDynS s txt = elDynClass "span" s $ dynText txt



-- buttonC :: Map.Map T.Text T.Text -> m a -> m (Event t a)
-- buttonC attrs inner = do
--   (e,x) <- elAttr' "button" attrs inner
--   pure $ x <$ domEvent Click e


-- | TODO: styledParagraphs :: [( BottomPadding, [(tw,T.Text)])] -> m ()
-- then apply to bannerFor in Landing.*

-- | Purposefully designed to take no other data besides ColInt in order to be very self-contained
gridCol :: DomBuilder t m => ColInt -> m a -> m a
gridCol cInt ma = elClass "div" ("grid grid-cols-" <> showTW cInt) ma

data RowDivisor = RD1 | RD2 | RD3 | RD4 | RD6 | RD12  

-- | TODO: this should have the ability to add rows somehow
-- | and how we can think of this is that if we start at a large->Col12 and scale down to
-- | lets say a 'sm' then Col12 / n where n is from config. So lets say n=2 then sm->Col6 which will show up as
-- | 2 rows of 6; similarly n=6 => 6 rows of 2 ; n=12 -> 12 rows of 1; n=3 => 3 rows of 4 ; n=4 => 4 rows of 3  
gridColWhen :: DomBuilder t m => WhenTW ColInt -> m a -> m a
gridColWhen cInts ma = elClass "div" (showCInts cInts) ma 
  where
    showCInts [] = ""
    showCInts (("def",cInt):cInts) = "grid grid-cols-" <> showTW cInt <&> showCInts cInts 
    showCInts ((w,cInt):cInts) = w <> ":" <> "grid" <&> w <> ":" <> "grid-cols-" <> showTW cInt
                                 <&> showCInts cInts 

-- | Denotes a normal row. Does not have inline-block 
-- row :: DomBuilder t m => BoxPadding -> m a -> m a 
-- row padding = elClass "div" ( "" <&> showTW padding )

gridColW :: DomBuilder t m => ColInt -> TWSizeOrFraction -> m a -> m a
gridColW cInt width ma = elClass "div" ("grid grid-cols-" <> showTW cInt <&> ("w-" <> showTW width)) ma



row :: DomBuilder t m => [BoxPadding -> BoxPadding] -> m a -> m a 
row paddingF = elClass "div" ( "" <&> showTW (applyFs def paddingF) )


applyFs :: a -> [a -> a] -> a 
applyFs b fs = foldl (\acc f -> f acc) b fs

type CompiledS = T.Text
-- newtype CompiledS a = CompiledS T.Text
-- newtype CompiledS = CompiledS T.Text
-- type CompiledS a = T.Text
-- rowC :: DomBuilder t m => CompiledS BoxPadding -> m a -> m a 
-- rowC padding = elClass "div" ( "" <&> padding )


------ Helpers
infixr 0 <&>
(<&>) :: T.Text -> T.Text -> T.Text
a <&> b
  | a == "" = b
  | b == "" = a
  | otherwise = a <> " " <> b


sm = "sm"
md = "md"
lg = "lg"
xl = "xl"
_2xl = "2xl"

-- data TWCondition_V2
--   = S_Mobile
--   | S_SM
--   | S_MD
--   | S_LG
--   | S_XL
--   | S_2XL
--   | On T.Text
--   deriving (Eq, Read, Ord, Show, Enum, Bounded)

-- zipScreens_v2 :: [a] -> WhenTW a
-- zipScreens_v2 xs =
--   let
--     sizes :: TWCondition_V2 
--     sizes = take 6 [minBound .. maxBound] -- ["def", "sm", "md", "lg", "xl", "2xl"]
--   in zip sizes xs 

sizes = ["def", "sm", "md", "lg", "xl", "2xl"]

zipScreens :: [a] -> WhenTW a
zipScreens xs = zip sizes xs 

zipScreensWith :: (a -> b) -> [a] -> WhenTW b
zipScreensWith f xs = zip sizes (f <$> xs)

-- (:.) :: a -> [a]
-- (:.) a = [a]




-- | it would be awesome to have a map like function that could be used like config for text size
-- | mappy = Map.fromList [(Heading, _), (Maintext, _) ... ]
-- |
-- | and effectively this is a model of the branding which will allow for consistency
  -- | AND!!! because the new model, models responsive design we can make scaling and brand design expressable as a Map 

autoScalePx :: Int -> WhenTW TWSize
autoScalePx pixels =
  [ ("def", scalePx pixels 1.0)
  , ("sm", scalePx pixels smFactor)
  , ("md", scalePx pixels mdFactor)
  , ("lg", scalePx pixels lgFactor)
  , ("xl", scalePx pixels xlFactor)
  , ("2xl", scalePx pixels s_2xlFactor)
  ]
  where
    mobileEstimatedWidth_px = 390
    -- Screen sizes per: https://tailwindcss.com/docs/responsive-design
    smFactor = 640 / mobileEstimatedWidth_px
    mdFactor = 768 / mobileEstimatedWidth_px
    lgFactor = 1024 / mobileEstimatedWidth_px
    xlFactor = 1280 / mobileEstimatedWidth_px
    s_2xlFactor = 1536 / mobileEstimatedWidth_px
    scalePx :: Int -> Float -> TWSize
    scalePx p factor = pix . round $ fromIntegral p * factor


-- | autoScalePx = autoScalePx' 1.0 
autoScalePx' :: Float -> Int -> WhenTW TWSize
autoScalePx' userOpt pixels =
  [ ("def", scalePx pixels 1.0)
  , ("sm", scalePx pixels smFactor)
  , ("md", scalePx pixels mdFactor)
  , ("lg", scalePx pixels lgFactor)
  , ("xl", scalePx pixels xlFactor)
  , ("2xl", scalePx pixels s_2xlFactor)
  ]
  where
    mobileEstimatedWidth_px = 390
    -- Screen sizes per: https://tailwindcss.com/docs/responsive-design
    smFactor = userOpt * 640 / mobileEstimatedWidth_px
    mdFactor = userOpt * 768 / mobileEstimatedWidth_px
    lgFactor = userOpt * 1024 / mobileEstimatedWidth_px
    xlFactor = userOpt * 1280 / mobileEstimatedWidth_px
    s_2xlFactor = userOpt * 1536 / mobileEstimatedWidth_px
    scalePx :: Int -> Float -> TWSize
    scalePx p factor = pix . round $ fromIntegral p * factor



-- | GOAL:
-- autoScalePx :: Int -> WhenTW TWSize
-- autoScalePx px =
--   [ (S_Mobl, px)
--   , (S_SM, scalePx px smFactor)
--   , (S_MD, scalePx px mdFactor)
--   , (S_LG, scalePx px lgFactor)
--   , (S_XL, scalePx px 1.4)
--   , (S_2XL, scalePx px 1.5)
--   ]
--   where
--     mobileEstimatedWidth_px = 390
--     -- Screen sizes per: https://tailwindcss.com/docs/responsive-design
--     smFactor = 640 / mobileEstimatedWidth_px
--     mdFactor = 768 / mobileEstimatedWidth_px
--     lgFactor = 1024 / mobileEstimatedWidth_px
--     xlFactor = 1280 / mobileEstimatedWidth_px
--     s_2xlFactor = 1536 / mobileEstimatedWidth_px
--
--     scalePx :: Int -> Float -> Int
--     scalePx p factor = round $ fromIntegral p * factor

-- autoScale :: TWSize -> WhenTW TWSize
-- autoScale mobile = [(S_Mobl, x), (S_SM, scaleSize x 1.1), (S_MD, scaleSize x 1.2), (S_LG, scaleSize x 1.3), (S_XL, scaleSize x 1.4), (S_2XL, scaleSize x 1.5)]
--   where
--     scaleSize :: TWSize -> Float -> TWSize
--     scaleSize (TWSize_Custom (Pixel p)) factor = TWSize_Custom (Pixel (round $ fromIntegral p * factor))
--     scaleSize (TWSize n) factor = TWSize (n * factor)
--     -- Add more cases for other CSSSize constructors if needed
--     scaleSize size _ = size  -- Fallback for non-scalable sizes or units


only :: a -> WhenTW a
only p = ("def", p):[]

onlyDef :: (Default a, ShowTW a) => WhenTW a
onlyDef = ("def", def):[]

infixr 4 .~+
(.~+) :: ASetter s t [a] [a] -> [a] -> s -> t
lens .~+ newVals = over lens (++ newVals)


--infixr 4 .~+
(.+) :: ASetter s t [a] [a] -> [a] -> s -> t
(.+) = (.~+)

renderWhenTW :: WhenTW a -> (a -> T.Text) -> T.Text
renderWhenTW tws construct = foldr (<&>) mempty $
  fmap (\(c,p) -> (if c == "def" then "" else (c <> ":")) <> construct p) $ tws

compileWhenTW :: WhenTW a -> (a -> T.Text) -> Either T.Text T.Text
compileWhenTW tws construct = case f $ fmap fst tws of
  Right () -> Right $ renderWhenTW tws construct
  Left e -> Left e
  where
    f [] = Right ()
    f (s:ss) =
      if elem s ss
      then Left $ s <> " exists twice"
      else f ss



--foldr ((<&>) . (\(c,p) -> (if c == "def" then "" else (c <> ":")) <> construct p)) mempty


-- | Checkers on the lib user's input to ensure no *unintended repetition
joinUnique :: WhenTW a -> WhenTW a -> WhenTW a
joinUnique = undefined

addUnique :: (TWCondition, a) -> WhenTW a -> WhenTW a
addUnique = undefined

-- checkStyle ::  -> Q Exp
-- checkStyle styleString =
--   let
--     xs = T.split styleString
--     f [] = False
--     f (s:ss) = elem s ss || f ss
--   in
--     if not $ f xs
--     then [| styleString |]
--     else fail
---------------

classh :: CompileStyle s => s -> [(s -> s)] -> Q Exp 
classh base muts = case compileS $ foldl (\acc f -> f acc) base muts of
  Left e -> fail $ T.unpack e
  Right styleString -> [| styleString |]

classh' :: (Default s, CompileStyle s) => [(s -> s)] -> Q Exp 
classh' muts = case compileS $ foldl (\acc f -> f acc) def muts of
  Left e -> fail $ T.unpack e
  Right styleString -> [| styleString |]

classhUnsafe :: (Default a, ShowTW a) => [a -> a] -> T.Text
classhUnsafe muts = showTW $ def `applyFs` muts


instance CompileStyle BoxConfig where
  compileS cfg = do
    pure . foldr (<&>) mempty =<< sequenceA
      [ compilePos (_position cfg)
      , compileWhenTW (_colStart cfg) ((<>) "col-start-" . tshow)      
      , compileWhenTW (_colSpan cfg) ((<>) "col-span-" . tshow)    
      , compileBorder (_border cfg)
      , compileSizingBand (_sizingBand cfg)
      , compilePadding (_padding cfg)
      , compileMargin (_margin cfg)
      , compileWhenTW (_bgColor cfg) ((<>) "bg-" . showTW)
      , compileWhenTW (_bgOpacity cfg) ((<>) "bg-opacity-" . tshow)
      , Right $ _box_custom cfg
      ]
      where
        compileBorder cfg = pure . foldr (<&>) mempty =<< sequenceA
          [ compileWhenTW (_bStyle cfg) ((<>) "border-" . showTW)
          , compileBorderRadius (_radius cfg)
          , compileBorderWidth (_bWidth cfg)
          , compileBorderColor (_bColor cfg)
          ]

        compileBorderRadius cfg = pure . foldr (<&>) mempty =<< sequenceA
          [ compileWhenTW (_borderRadius_tr cfg) ((<>) "rounded-tr" . showTW)
          , compileWhenTW (_borderRadius_tl cfg) ((<>) "rounded-tl" . showTW)
          , compileWhenTW (_borderRadius_br cfg) ((<>) "rounded-br" . showTW)
          , compileWhenTW (_borderRadius_bl cfg) ((<>) "rounded-bl" . showTW)
          ]

        compileBorderWidth cfg = pure . foldr (<&>) mempty =<< sequenceA
          [ compileWhenTW (_borderWidth_l cfg) ((<>) "border-l" . showTW)
          , compileWhenTW (_borderWidth_r cfg) ((<>) "border-r" . showTW)
          , compileWhenTW (_borderWidth_t cfg) ((<>) "border-t" . showTW)
          , compileWhenTW (_borderWidth_b cfg) ((<>) "border-b" . showTW)
          ]

        compileBorderColor cfg = pure . foldr (<&>) mempty =<< sequenceA
          [ compileWhenTW (_borderColor_l cfg) ((<>) "border-l-" . showTW)
          , compileWhenTW (_borderColor_r cfg) ((<>) "border-r-" . showTW)
          , compileWhenTW (_borderColor_t cfg) ((<>) "border-t-" . showTW)
          , compileWhenTW (_borderColor_b cfg) ((<>) "border-b-" . showTW)
          ]

        compileSizingBand cfg = pure . foldr (<&>) mempty =<< sequenceA
          [ compileWhenTW (_widthC . _maxSize $ cfg) ((<>) "max-w-" . showTW)
          , compileWhenTW (_heightC . _maxSize $ cfg) ((<>) "max-h-" . showTW)
          , compileWhenTW (_widthC . _minSize $ cfg) ((<>) "min-w-" . showTW)
          , compileWhenTW (_heightC . _minSize $ cfg) ((<>) "min-h-" . showTW)
          , compileWhenTW (_width . _size $ cfg) ((<>) "w-" . showTW)
          , compileWhenTW (_height . _size $ cfg) ((<>) "h-" . showTW)
          ]


        compileMargin cfg = pure . foldr (<&>) mempty =<< sequenceA
          [ compileWhenTW (_marginL cfg) ((<>) "ml-" . showTW)
          , compileWhenTW (_marginR cfg) ((<>) "mr-" . showTW)
          , compileWhenTW (_marginT cfg) ((<>) "mt-" . showTW)
          , compileWhenTW (_marginB cfg) ((<>) "mb-" . showTW)
          ]

        compilePos posCfg = case f $ fmap fst posCfg of
          Left e -> Left e
          Right () -> Right $ foldr (<&>) mempty $ fmap
            (\(c,(jus,align)) ->
               let pre = if c == "def" then "" else (c <> ":")
               in
                 pre <> "grid" <&> pre <> (showTW jus) <&> pre <> (showTW align)
            ) $ posCfg
          where
            f [] = Right ()
            f (s:ss) =
              if elem s ss
              then Left $ s <> " exists twice"
              else f ss

-- | For row func
instance CompileStyle BoxPadding where
  compileS = compilePadding

compilePadding :: BoxPadding -> Either T.Text T.Text
compilePadding cfg = pure . foldr (<&>) mempty =<< sequenceA
  [ compileWhenTW (_paddingL cfg) ((<>) "pl-" . showTW)
  , compileWhenTW (_paddingR cfg) ((<>) "pr-" . showTW)
  , compileWhenTW (_paddingT cfg) ((<>) "pt-" . showTW)
  , compileWhenTW (_paddingB cfg) ((<>) "pb-" . showTW)
  ]
          
          

instance CompileStyle TextConfigTW where
  compileS cfg = pure . foldr (<&>) mempty =<< sequenceA
    [ compileWhenTW (_text_size cfg) showTW
    , compileWhenTW (_text_weight cfg) showTW
    , compileWhenTW (_text_font cfg) showTW
    , compileWhenTW (_text_color cfg) ((<>) "text-" . showTW)
    , compileDecoration (_text_decoration cfg)
    , Right $ _text_custom cfg
    ]
    where
      compileDecoration cfg = pure . foldr (<&>) mempty =<< sequenceA
        [ compileWhenTW (_textDec_line cfg) showTW
        , compileWhenTW (_textDec_color cfg) ((<>) "decoration-" . showTW)
        , compileWhenTW (_textDec_style cfg) showTW
        , compileWhenTW (_textDec_thickness cfg) showTW
        , compileWhenTW (_textDec_offset cfg) showTW
        ]






class HasCSSSize tw where
  pix :: Int -> tw --Lens' tw Int 
  pct :: Int -> tw 
  vh :: Int -> tw
  vw :: Int -> tw
  rem :: Float -> tw

instance HasCSSSize TWSize where
  pix = TWSize_Custom . Pixel
  pct = TWSize_Custom . Percent
  vh = TWSize_Custom . Vh
  vw = TWSize_Custom . Vw
  rem = TWSize_Custom . Rem

instance HasCSSSize TWSizeOrFraction where
  pix = TWSize' . TWSize_Custom . Pixel --px
  pct = TWSize' . TWSize_Custom . Percent --pct
  vh = TWSize' . TWSize_Custom . Vh --vh
  vw = TWSize' . TWSize_Custom . Vw--vw
  rem = TWSize' . TWSize_Custom . Rem -- Classh.rem
  
class HasCustom tw where
  custom :: Lens' tw T.Text 

class CompileStyle tw where
  compileS :: tw -> Either T.Text T.Text


class IsCSS css where
  renderCSS :: css -> T.Text

class ShowTW tw where
  showTW :: tw -> T.Text

class SetSides tw a where
  x :: Lens' tw (WhenTW a)
  y :: Lens' tw (WhenTW a)
  xy :: Lens' tw (WhenTW a)
  allS :: Lens' tw (WhenTW a)
  allS = xy
  l :: Lens' tw (WhenTW a)
  r :: Lens' tw (WhenTW a)
  b :: Lens' tw (WhenTW a)
  t :: Lens' tw (WhenTW a)

type TWCondition = T.Text
type WhenTW a = [(TWCondition, a)]

data BoxConfig = BoxConfig
  { _colStart :: WhenTW Int
  , _colSpan :: WhenTW Int
  , _bgColor :: WhenTW Color
  , _bgOpacity :: WhenTW Int -- 1 5 10 .. 100 -- def == 519
  , _padding :: BoxPadding
  , _margin :: BoxMargin
  , _sizingBand :: BoxSizingBand
  , _border :: BorderConfig -- { rounded, thickness, etc .. }
  , _position :: WhenTW (Justify, Align)
  --, _text_align :: Align ... or should we set == position.align
  , _box_custom :: T.Text
  }
  deriving Show

data BoxPadding = BoxPadding
  { _paddingL :: WhenTW TWSize
  , _paddingR :: WhenTW TWSize
  , _paddingT :: WhenTW TWSize
  , _paddingB :: WhenTW TWSize
  } deriving Show

-- TODO: lenses for y,x and all
data BoxMargin = BoxMargin
  { _marginL :: WhenTW TWSize
  , _marginR :: WhenTW TWSize
  , _marginT :: WhenTW TWSize
  , _marginB :: WhenTW TWSize
  } deriving Show

data BoxSizingBand = BoxSizingBand
  { _maxSize :: BoxSizingConstraint --(DimensionConstraint, DimensionConstraint)
  , _minSize :: BoxSizingConstraint -- (DimensionConstraint, DimensionConstraint)
  , _size :: BoxSizing
  }
  deriving Show

data BoxSizing = BoxSizing
  { _width :: WhenTW TWSizeOrFraction
  , _height :: WhenTW TWSizeOrFraction
  }
  deriving Show

data BoxSizingConstraint = BoxSizingConstraint
  { _widthC :: WhenTW DimensionConstraint
  , _heightC :: WhenTW DimensionConstraint
  }
  deriving Show

data BorderConfig = BorderConfig
  { _bStyle :: WhenTW BorderStyle
  , _bColor :: BorderColorSides
  , _bWidth :: BorderWidthSides
  , _radius :: BorderRadiusCorners
  } deriving Show

data BorderColorSides = BorderColorSides
  { _borderColor_l :: WhenTW Color
  , _borderColor_r :: WhenTW Color
  , _borderColor_t :: WhenTW Color
  , _borderColor_b :: WhenTW Color
  } deriving Show

data BorderRadiusCorners = BorderRadiusCorners
  { _borderRadius_tr :: WhenTW BorderRadius'
  , _borderRadius_tl :: WhenTW BorderRadius'
  , _borderRadius_br :: WhenTW BorderRadius'
  , _borderRadius_bl :: WhenTW BorderRadius'
  } deriving Show

data BorderWidthSides = BorderWidthSides
  { _borderWidth_l :: WhenTW BorderWidth
  , _borderWidth_r :: WhenTW BorderWidth
  , _borderWidth_t :: WhenTW BorderWidth
  , _borderWidth_b :: WhenTW BorderWidth
  } deriving Show

data TextConfigTW = TextConfigTW
  { _text_size :: WhenTW TextSize
  , _text_weight :: WhenTW TextWeight
  , _text_font :: WhenTW Font -- many options -- EG. Sarabun -> font-[Sarabun]
  , _text_color :: WhenTW Color
  , _text_decoration :: TextDecorationTW
  , _text_custom :: T.Text
  }

data TextDecorationTW = TextDecorationTW
  { _textDec_line :: WhenTW TextDecLineType
  , _textDec_color :: WhenTW Color
  , _textDec_style :: WhenTW TextDecStyle
  , _textDec_thickness :: WhenTW TextDecThickness -- TODO: is this a standard system: Thickness?
  , _textDec_offset :: WhenTW TextDecOffset -- and is this a system? (the numbers chosen 0,1,2,4,8)
  }


------------  Defaults of Records

instance Default BoxConfig where
  def = BoxConfig def def def def def def def def def ""

instance Default BoxPadding where
  def = BoxPadding def def def def -- (TWSize 0) (TWSize 0) (TWSize 0) (TWSize 0)

instance Default BoxMargin where
  def = BoxMargin def def def def -- (TWSize 0) (TWSize 0) (TWSize 0) (TWSize 0)

instance Default TextConfigTW where
  def = TextConfigTW def def def def def ""

instance Default TextDecorationTW where
  def = TextDecorationTW def def def def def

instance Default BorderConfig where
  def = BorderConfig def def def def

instance Default BorderRadiusCorners where
  def = BorderRadiusCorners def def def def

instance Default BorderWidthSides where
  def = BorderWidthSides def def def def

instance Default BorderColorSides where
  def = BorderColorSides def def def def

instance Default BoxSizing where
  def = BoxSizing def def -- TWSize_Auto TWSize_Auto

instance Default BoxSizingBand where
  def = BoxSizingBand def def def

instance Default BoxSizingConstraint where
  def = BoxSizingConstraint def def -- DC_none DC_none

------------  END: Defaults of Records

------------  ShowTW of Records

--compileS


instance ShowTW TextConfigTW where
  showTW cfg = foldr (<&>) mempty
    [ renderWhenTW (_text_size cfg) showTW
    , renderWhenTW (_text_weight cfg) showTW
    , renderWhenTW (_text_font cfg) showTW
    , renderWhenTW (_text_color cfg) ((<>) "text-" . showTW)
    , showTW (_text_decoration cfg)
--    , "align-left"
    --, "text-" <> showTW align
    , _text_custom cfg
    ]




instance ShowTW TextDecorationTW where
  showTW cfg = foldr (<&>) mempty
    [ renderWhenTW (_textDec_line cfg) showTW
    , renderWhenTW (_textDec_color cfg) ((<>) "decoration-" . showTW)
    , renderWhenTW (_textDec_style cfg) showTW
    , renderWhenTW (_textDec_thickness cfg) showTW
    , renderWhenTW (_textDec_offset cfg) showTW
    ]

instance ShowTW BoxConfig where
  showTW cfg = foldr (<&>) mempty
   [ renderWhenTW (_colStart cfg) ((<>) "col-start-" . tshow)
   , renderWhenTW (_colSpan cfg) ((<>) "col-span-" . tshow)
   , renderWhenTW (_bgColor cfg) ((<>) "bg-" . showTW)
   , renderWhenTW (_bgOpacity cfg) ((<>) "bg-opacity-" . tshow)                              
   , showTW . _border $ cfg
   , showTW . _sizingBand $ cfg
   , showTW . _padding $ cfg
   , showTW . _margin $ cfg
   , foldr (<&>) mempty $ fmap
     (\(c,(jus,align)) ->
        let pre = if c == "def" then "" else (c <> ":")
        in pre <> "grid" <&> pre <> (showTW jus) <&> pre <> (showTW align)
     ) $ _position cfg 
   , renderWhenTW (_position cfg) $ \(j,a) -> "grid " <> showTW j <> " " <> showTW a
   , _box_custom cfg
   ]

instance ShowTW BorderConfig where
  showTW cfg = foldr (<&>) mempty
    [ showTW . _radius $ cfg
    , showTW . _bWidth $ cfg
    , showTW . _bColor $ cfg
    , renderWhenTW (_bStyle cfg) ((<>) "border-" . showTW)
    ]

instance ShowTW BoxSizing where
  showTW cfg = foldr (<&>) mempty
  --showTW (BoxSizing w h) = T.intercalate " " $
    [ renderWhenTW (_width cfg) ((<>) "w-" . showTW)
    , renderWhenTW (_height cfg) ((<>) "h-" . showTW)
    ]

instance ShowTW BoxSizingBand where
  showTW cfg = foldr (<&>) mempty
    [ renderWhenTW (_widthC . _maxSize $ cfg) ((<>) "max-w-" . showTW)
    , renderWhenTW (_heightC . _maxSize $ cfg) ((<>) "max-h-" . showTW)
    , renderWhenTW (_widthC . _minSize $ cfg) ((<>) "min-w-" . showTW)
    , renderWhenTW (_heightC . _minSize $ cfg) ((<>) "min-h-" . showTW)
    , showTW $ _size cfg
    ]

instance ShowTW BoxMargin where
  showTW cfg = foldr (<&>) mempty
    [ renderWhenTW (_marginL cfg) ((<>) "ml-" . showTW)
    , renderWhenTW (_marginR cfg) ((<>) "mr-" . showTW)
    , renderWhenTW (_marginT cfg) ((<>) "mt-" . showTW)
    , renderWhenTW (_marginB cfg) ((<>) "mb-" . showTW)
    ]


instance ShowTW BoxPadding where
  showTW cfg = foldr (<&>) mempty
    [ renderWhenTW (_paddingL cfg) ((<>) "pl-" . showTW)
    , renderWhenTW (_paddingR cfg) ((<>) "pr-" . showTW)
    , renderWhenTW (_paddingT cfg) ((<>) "pt-" . showTW)
    , renderWhenTW (_paddingB cfg) ((<>) "pb-" . showTW)
    ]

-- TODO: stop overlaps through conditionals
instance ShowTW BorderRadiusCorners where
  showTW cfg = foldr (<&>) mempty
    [ renderWhenTW (_borderRadius_tr cfg) ((<>) "rounded-tr" . showTW)
    , renderWhenTW (_borderRadius_tl cfg) ((<>) "rounded-tl" . showTW)
    , renderWhenTW (_borderRadius_br cfg) ((<>) "rounded-br" . showTW)
    , renderWhenTW (_borderRadius_bl cfg) ((<>) "rounded-bl" . showTW)
    ]

instance ShowTW BorderWidthSides where
  showTW cfg = foldr (<&>) mempty
    [ renderWhenTW (_borderWidth_l cfg) ((<>) "border-l-" . showTW)
    , renderWhenTW (_borderWidth_r cfg) ((<>) "border-r-" . showTW)
    , renderWhenTW (_borderWidth_t cfg) ((<>) "border-t-" . showTW)
    , renderWhenTW (_borderWidth_b cfg) ((<>) "border-b-" . showTW)
    ]

instance ShowTW BorderColorSides where
  showTW cfg = foldr (<&>) mempty
    [ renderWhenTW (_borderColor_l cfg) ((<>) "border-l-" . showTW)
    , renderWhenTW (_borderColor_r cfg) ((<>) "border-r-" . showTW)
    , renderWhenTW (_borderColor_t cfg) ((<>) "border-t-" . showTW)
    , renderWhenTW (_borderColor_b cfg) ((<>) "border-b-" . showTW)
    ]

------------  END: ShowTW of Records


------------  Lenses
-- | Lenses will need 2 extensions:
  -- `only`
  -- puts (WhenTW t)

borderRadius_l, borderRadius_r, borderRadius_t, borderRadius_b :: Lens' BorderRadiusCorners (WhenTW BorderRadius')
borderRadius_l = lens undefined $ \tw new -> tw { _borderRadius_tl = new, _borderRadius_bl = new }
borderRadius_r = lens undefined $ \tw new -> tw { _borderRadius_tr = new, _borderRadius_br = new }
borderRadius_t = lens undefined $ \tw new -> tw { _borderRadius_tl = new, _borderRadius_tr = new }
borderRadius_b = lens undefined $ \tw new -> tw { _borderRadius_bl = new, _borderRadius_br = new }


------------  END: Lenses

    -- the corners override the sides
    -- [ "rounded-l" <> showTW l
    -- , "rounded-r" <> showTW r
    -- , "rounded-t" <> showTW t
    -- , "rounded-b" <> showTW b
    -- ]

data BorderRadius'
  = R_None
  | R_SM
  | R_Normal
  | R_Md
  | R_Lg
  | R_Xl
  | R_2Xl
  | R_3Xl
  | R_Full
  | R_Custom CSSSize
  deriving Show

instance Default BorderRadius' where
  def = R_None

instance ShowTW BorderRadius' where
  showTW = \case
    R_Custom cssSize -> "-[" <> renderCSS cssSize <> "]"
    R_Normal -> ""
    other -> "-" <> (T.toLower . T.drop 2 . tshow $ other)


data BorderWidth
  = B0
  | B1
  | B2
  | B4
  | B8
  | BW_Custom CSSSize
  deriving Show

instance Default BorderWidth where
  def = B0

instance ShowTW BorderWidth where
  showTW = \case
    B1 -> ""
    BW_Custom cssSize -> "-[" <> renderCSS cssSize <> "]"
    other -> "-" <> (T.drop 1 . tshow $ other)


data BorderStyle
  = BSolid
  | BDashed
  | BDotted
  | BDouble
  | BHidden
  | BNone
  deriving Show

instance Default BorderStyle where
  def = BSolid

instance ShowTW BorderStyle where
  showTW = T.toLower . T.drop 1 . tshow

-- | TODO: Isolate in own module since only used for gridCol
data ColInt
  = Col1
  | Col2
  | Col3
  | Col4
  | Col5
  | Col6
  | Col7
  | Col8
  | Col9
  | Col10
  | Col11
  | Col12
  deriving Show

instance ShowTW ColInt where
  showTW = T.drop 3 . T.pack . show

instance Default ColInt where
  def = Col1

data Dimension
  = DNum Float
  | DFrac Int DivInt
  | Full
  | Screen
  | Min
  | Max
  | Fit
  | Dim_Custom T.Text
--  | None

-- there's TWSize and then TWSizeOrFraction
data TWSize
  = TWSize Float
  | TWSize_Custom CSSSize
  deriving Show

instance ShowTW TWSize where
  showTW = \case
    TWSize float ->
      if fromIntegral (truncate float) == float
      then tshow $ (truncate float :: Int)
      else tshow float
    TWSize_Custom c -> "[" <> renderCSS c <> "]"

data TWSizeOrFraction
  = TWSize' TWSize
  | TWFraction Int DivInt
  | TWSize_Full
  | TWSize_Screen
  | TWSize_Min
  | TWSize_Max
  | TWSize_Fit
  | TWSize_Auto
  deriving Show

instance ShowTW TWSizeOrFraction where
  showTW = \case
    TWSize' s -> showTW s
    TWFraction n d -> tshow n <> "/" <> showTW d
    class' -> T.toLower . T.drop 7 . tshow $ class'

instance Default TWSizeOrFraction where
  def = TWSize_Auto

data DivInt
  = D2
  | D3
  | D4
  | D5
  | D6
  | D12
  deriving Show

instance ShowTW DivInt where
  showTW = T.drop 1 . tshow

instance Default DivInt where
  def = D12 -- baby

-- | NOTE: although I cant do Start for either, I could create a class like HasStart
-- | Also NOTE: there is no plan to make text align, rather you put it in a span and align it via this mechanism
data Justify
  = J_Start
  | J_End
  | J_Center
  | J_Stretch
  deriving Show

data Align
  = A_Start
  | A_End
  | A_Center
  | A_Baseline
  deriving Show

instance Default Justify where
  def = J_Start
instance Default Align where
  def = A_Start


instance ShowTW Justify where
  showTW = (<>) "justify-items-" . T.toLower . T.drop 2 . T.pack . show
instance ShowTW Align where
  
  showTW = (<>) "content-" . T.toLower . T.drop 2 . T.pack . show

-- | TODO: this is technically wrong: there are different classes for width vs height but oh well for now
data DimensionConstraint
  = DC_0
  | DC_none
  | DC_xs
  | DC_sm
  | DC_md
  | DC_lg
  | DC_xl
  | DC_2xl
  | DC_3xl
  | DC_4xl
  | DC_5xl
  | DC_6xl
  | DC_7xl
  | DC_full
  | DC_min
  | DC_max
  | DC_fit
  | DC_prose
  | DC_screen_sm
  | DC_screen_md
  | DC_screen_lg
  | DC_screen_xl
  | DC_screen_2xl
  | DC_Custom T.Text
  deriving Show

instance Default DimensionConstraint where
  def = DC_none

instance ShowTW DimensionConstraint where
  showTW = \case
    DC_Custom t -> "[" <> t <> "]"
    x -> T.replace "_" "-" . T.drop 3 . tshow $ x




-- DEPRECATED: use SetSides model
-- | TODO: move to SetSides lens model (t,b,r,l) all valid
data BorderRadius
  = None
  | RoundedSM Corner
  | Rounded Corner
  | RoundedMd Corner
  | RoundedLg Corner
  | RoundedXl Corner
  | Rounded2Xl Corner
  | Rounded3Xl Corner
  | RoundedFull Corner
  deriving Show



data Corner
  = All -- -> ""
  | S
  | E
  | T
  | R
  | B
  | L
  | SS
  | SE
  | EE
  | ES
  | TL
  | TR
  | BR
  | BL
  deriving Show

tshowCorner :: Corner -> T.Text
tshowCorner = \case
  All -> ""
  x -> ((<>) "-") . T.toLower . tshow $ x

instance Default BorderRadius where
  def = None

-- DEPRECATED
instance ShowTW BorderRadius where
  showTW = ((<>) "rounded") . \case
    None -> "-none"
    Rounded c -> tshowCorner c --"-" <> (T.toLower . tshow $ c) <> "-" <> "sm"
    RoundedSM c -> tshowCorner c <> "-" <> "sm"
    RoundedMd c -> tshowCorner c <> "-" <> "md"
    RoundedLg c -> tshowCorner c <> "-" <> "lg"
    RoundedXl c -> tshowCorner c <> "-" <> "xl"
    Rounded2Xl c -> tshowCorner c <> "-" <> "2xl"
    Rounded3Xl c -> tshowCorner c <> "-" <> "3xl"
    RoundedFull c -> tshowCorner c <> "-" <> "full"


-- | todo: can we merge with gridCol Col1-12?
data IntGrid
  = IG1
  | IG2
  | IG3
  | IG4
  | IG5
  | IG6
  | IG7
  | IG8
  | IG9
  | IG10
  | IG11
  | IG12


-- -- | Could also just do variants where they are evenly spaced and such
-- boxInGrid :: BoxConfig -> (IntGrid, IntGrid) -> m ()
-- boxInGrid = undefined



-- data BoxConfig = BoxConfig
--   { leftPadding :: T.Text
--   , bottomPadding :: T.Text
--   }

-- type Test = TWConfig BoxConfig


-- | Right now this is just for custom uses in Tailwind
data CSSSize
  = Pixel Int
  | Percent Int
  | Vh Int
  | Vw Int
  | Rem Float
  deriving Show

instance IsCSS CSSSize where
  renderCSS = \case
    Pixel int -> tshow int <> "px"
    Percent int -> tshow int <> "%"
    Vh int -> tshow int <> "vh"
    Vw int -> tshow int <> "vw"
    Rem float -> tshow float <> "rem"


-- -- | TODO(galen): remove ShowTW a constraint
-- data TWConfig a = ShowTW a => TWConfig { unTWConfig :: [(Maybe TWCondition, a)] }

-- instance (ShowTW a, Default a) => Default (TWConfig a) where
--   def = TWConfig [(Nothing, def)]

-- instance ShowTW (TWConfig a) where
--   showTW (TWConfig ts) = f ts
--     where
--       f [] = ""
--       f ((c,cfg):cs) = case c of
--         Just c' -> showTWWhen c' cfg <> (f cs)
--         Nothing -> showTW cfg <> (f cs)

--   showTWWhen = \_ -> showTW

-- -- All of these should also take a custom except when it's datatype is T.Text
-- data TextConfigTW = TextConfigTW
--   { _text_size :: WhenTW TextSize
--   , _text_weight :: WhenTW TextWeight
--   , _text_font :: WhenTW Font -- many options -- EG. Sarabun -> font-[Sarabun]
--   , _text_color :: WhenTW Color
--   , _text_decoration :: TextDecorationTW
--   --, _text_align :: Align
--   , _text_custom :: T.Text
--   }

data Font
  = Sans
  | Serif
  | Mono
  | Font_Custom T.Text
  deriving Show

instance Default Font where
  def = Sans

instance ShowTW Font where
  showTW = \case
    Font_Custom t -> "font-[" <> t <> "]"
    x -> ((<>) "font-") . T.toLower . tshow $ x



--  showTWWhen c (TextConfigTW size weight font color dec custom) = T.intercalate " " $
--    [ showTWWhen c size
--    , showTWWhen c weight
--    , showTWWhen c font
--    , c <> ":text-" <> showTW color
--    , showTWWhen c dec
    --, c <> ":text-" <> showTW align
--    , custom
--    ]

  --showTWWhen c tw

-- | This is used for many things
-- | DEPRECATED
-- data Align
--   = Align_Left
--   | Align_Center
--   | Align_Right
--   | Align_Justify
--   | Align_Start
--   | Align_End
--   deriving Show

-- instance Default Align where
--   def = Align_Left

-- instance ShowTW Align where
--   showTW = T.drop 6 . T.toLower . tshow
--   showTWWhen c tw = c <> ":" <> (showTW tw)

-- data TextDecorationTW = TextDecorationTW
--   { _textDec_line :: WhenTW TextDecLineType
--   , _textDec_color :: WhenTW Color
--   , _textDec_style :: WhenTW TextDecStyle
--   , _textDec_thickness :: WhenTW TextDecThickness -- TODO: is this a standard system: Thickness?
--   , _textDec_offset :: WhenTW TextDecOffset -- and is this a system? (the numbers chosen 0,1,2,4,8)
--   }

--  showTWWhen c (TextDecorationTW line color style thickness offset) = T.intercalate " " $
--    [ showTWWhen c line
--    , c <> ":decoration-" <> showTW color
--    , showTWWhen c style
--    , showTWWhen c thickness
--    , showTWWhen c offset
--    ]

data TextDecLineType
  = Underline
  | Overline
  | LineThrough
  | NoUnderline
  deriving Show

instance Default TextDecLineType where
  def = NoUnderline

instance ShowTW TextDecLineType where
  showTW = \case
    LineThrough -> "line-through"
    NoUnderline -> "no-underline"
    x -> T.toLower . tshow $ x
--  showTWWhen c tw = c <> ":" <> (showTW tw)

data TextDecStyle
  = Solid
  | Double
  | Dotted
  | Dashed
  | Wavy
  deriving Show

-- Note that we may need to make this a Maybe TextDecStyle
instance Default TextDecStyle where
  def = Solid

instance ShowTW TextDecStyle where
  showTW = ((<>) "decoration-") . T.toLower . tshow
  --showTWWhen c tw = c <> ":" <> (showTW tw)

data TextDecThickness
  = FromFont
  | TextDecThickness TWNum
  | TextDecThickness_Custom CSSSize -- TODO: is this just size??
  deriving Show

instance Default TextDecThickness where
  def = TextDecThickness def

instance ShowTW TextDecThickness where
  showTW thk = "decoration-" <> ( case thk of
                                    FromFont -> "from-font"
                                    TextDecThickness twnum -> showTW twnum
                                    TextDecThickness_Custom cssSize -> "[" <> (renderCSS cssSize) <> "]"
                                )
--  showTWWhen c tw = c <> ":" <> (showTW tw)

data TWNum
  = Auto
  | TW0
  | TW1
  | TW2
  | TW4
  | TW8
  deriving Show

instance Default TWNum where
  def = TW1

-- | TODO: see note about color / showColor ; showTWNum
instance ShowTW TWNum where
  showTW Auto = "auto"
  showTW x = T.drop 2 $ tshow x


data TextDecOffset
  = TextDecOffset TWNum
  | TextDecOffset_Custom CSSSize
  deriving Show

instance Default TextDecOffset where
  def = TextDecOffset def

instance ShowTW TextDecOffset where
  showTW (TextDecOffset tnum) = "underline-offset-" <> (showTW tnum)
  --showTWWhen c tw = c <> ":" <> (showTW tw)

data TextSize
  = XS
  | SM
  | Base
  | LG
  | XL
  | XL2 -- According to testing this is the default size `\_o_/`
  | XL3
  | XL4
  | XL5
  | XL6
  | XL7
  | XL8
  | XL9
  | TextSize_Custom T.Text
  deriving Show

-- | TODO: when this inevitably becomes its own package, make it easy to not import Defaults and make your own
instance Default TextSize where
  def = XL2 -- According to testing this is the default size `\_o_/'

instance ShowTW TextSize where
  showTW (TextSize_Custom t) = "text-[" <> t <> "]"
  showTW XS = "text-xs"
  showTW SM = "text-sm"
  showTW Base = "text-base"
  showTW LG = "text-lg"
  showTW XL = "text-xl"
  showTW XL2 = "text-2xl"
  showTW XL3 = "text-3xl"
  showTW XL4 = "text-4xl"
  showTW XL5 = "text-5xl"
  showTW XL6 = "text-6xl"
  showTW XL7 = "text-7xl"
  showTW XL8 = "text-8xl"
  showTW XL9 = "text-9xl"

--  showTWWhen c tw = c <> ":" <> (showTW tw)

data TextWeight
  = Thin
  | Extralight
  | Light
  | Normal
  | Medium
  | Semibold
  | Bold
  | Extrabold
  | Black_TW
  | TextWeight_Custom T.Text
  deriving Show

instance Default TextWeight where
  def = Normal

instance ShowTW TextWeight where
  showTW (TextWeight_Custom t) = "font-[" <> t <> "]"
  showTW (Black_TW) = "font-black"
  showTW x = "font-" <> (T.toLower . tshow $ x )
  --showTWWhen c tw = c <> ":" <> (showTW tw)

-- | TODO: myColor :: MyColor -> Color where MyColor is a typified Design Choice
-- | Note that this can be used for either background or text
data Color
  = Inherit
  | Current
  | Transparent
  | Black
  | White
  | Slate ColorNum
  | Gray ColorNum
  | Zinc ColorNum
  | Neutral ColorNum
  | Stone ColorNum
  | Red ColorNum
  | Orange ColorNum
  | Amber ColorNum
  | Yellow ColorNum
  | Lime ColorNum
  | Green ColorNum
  | Emerald ColorNum
  | Teal ColorNum
  | Cyan ColorNum
  | Sky ColorNum
  | Blue ColorNum
  | Indigo ColorNum
  | Violet ColorNum
  | Purple ColorNum
  | Fuchsia ColorNum
  | Pink ColorNum
  | Rose ColorNum
  | Color_Custom Hex
  deriving (Show, Eq)

-- Sky (ColorNum_Custom x)

hex :: T.Text -> Color
hex = Color_Custom . Hex
newtype Hex = Hex { unHex :: T.Text } deriving (Eq, Show)

-- TODO: own defaults
instance Default Color where
  def = Black

data ColorNum
 = C50
 | C100
 | C200
 | C300
 | C400
 | C500
 | C600
 | C700
 | C800
 | C900
 | C950
 deriving (Show, Eq)

instance Default ColorNum where
  def = C950


-- | There is no showTW for ColorNum since this will never be without an encompassing Color
-- | TODO(galen): Should we just make this its own special function? and then its called by showTW; showColor
-- | Because this is never used on its own either
instance ShowTW Color where
  showTW (Color_Custom (Hex h)) = "[#" <> h <> "]"
  showTW Inherit = "inherit"
  showTW Current = "current"
  showTW Transparent = "transparent"
  showTW Black = "black"
  showTW White = "white"
  showTW color =
    let c:(mag):[] = T.words $ tshow color
    in (T.toLower c) <> "-" <> (T.drop 1 mag)

--  showTWWhen c tw = showTW tw
  --

type BottomPadding = TWSize

-- tshow :: Show a => a -> T.Text
-- tshow = T.pack . show

makeLenses ''TextConfigTW
makeLenses ''TextDecorationTW

makeLenses ''BoxConfig
makeLenses ''BoxPadding
makeLenses ''BoxMargin
makeLenses ''BoxSizingBand
makeLenses ''BoxSizing
makeLenses ''BoxSizingConstraint
makeLenses ''BorderConfig
makeLenses ''BorderRadiusCorners
makeLenses ''BorderWidthSides
makeLenses ''BorderColorSides


-- | This is technically an illegal lens however if you ran 2 setters which overlap so that a /= b
-- | where a and b are the fields associated with respective separate fields, then classh' will
-- | most likely catch the error. Additionally, there is a lens way to access any field anyways
instance SetSides BoxMargin TWSize where
  l = marginL
  r = marginR
  b = marginB
  t = marginT
  x = lens _marginL $ \tw new -> tw { _marginL = new, _marginR = new }
  y =  lens _marginT $ \tw new -> tw { _marginT = new, _marginB = new }
  xy = lens _marginT $ \tw new -> tw { _marginT = new
                                     , _marginB = new
                                     , _marginL = new
                                     , _marginR = new
                                     }


-- | This is technically an illegal lens however if you ran 2 setters which overlap so that a /= b
-- | where a and b are the fields associated with respective separate fields, then classh' will
-- | most likely catch the error. Additionally, there is a lens way to access any field anyways
instance SetSides BoxPadding TWSize where
  l = paddingL
  r = paddingR
  b = paddingB
  t = paddingT
  x = lens _paddingL $ \tw new -> tw { _paddingL = new, _paddingR = new }
  y = lens _paddingR $ \tw new -> tw { _paddingT = new, _paddingB = new }
  xy = lens _paddingT $ \tw new -> tw { _paddingT = new
                                      , _paddingB = new
                                      , _paddingL = new
                                      , _paddingR = new
                                      }
  
instance SetSides BorderRadiusCorners BorderRadius' where
  l = borderRadius_l
  r = borderRadius_r
  b = borderRadius_b
  t = borderRadius_t
  xy = lens _borderRadius_tl $ \tw new -> tw { _borderRadius_tl = new
                                             , _borderRadius_bl = new
                                             , _borderRadius_tr = new
                                             , _borderRadius_br = new
                                             }
  -- Effectively, due to corners;
  y = xy
  x = xy

instance SetSides BorderWidthSides BorderWidth where
  l = borderWidth_l
  r = borderWidth_r
  t = borderWidth_t
  b = borderWidth_b
  x = lens _borderWidth_l $ \tw new -> tw { _borderWidth_l = new, _borderWidth_r = new }
  y = lens _borderWidth_b $ \tw new -> tw { _borderWidth_b = new, _borderWidth_t = new }
  xy = lens _borderWidth_b $ \tw new -> tw { _borderWidth_b = new
                                           , _borderWidth_t = new
                                           , _borderWidth_l = new
                                           , _borderWidth_r = new
                                           }

instance SetSides BorderColorSides Color where
  l = borderColor_l
  r = borderColor_r
  t = borderColor_t
  b = borderColor_b
  x = lens _borderColor_l $ \tw new -> tw { _borderColor_l = new, _borderColor_r = new }
  y = lens _borderColor_t $ \tw new -> tw { _borderColor_t = new, _borderColor_b = new }
  xy = lens _borderColor_t $ \tw new -> tw { _borderColor_t = new
                                           , _borderColor_b = new
                                           , _borderColor_l = new
                                           , _borderColor_r = new
                                           }


instance HasCustom BoxConfig where
  custom = box_custom 

instance HasCustom TextConfigTW where
  custom = text_custom 


-- deprecated?
-- | TODO: make a setter that takes both the color and opacity as args
-- | We need this cuz the Opacity defaults to 0
-- bgColor' :: Lens' BoxConfig Color
-- bgColor' = lens undefined $ \tw new -> tw { _bgColor = new , _bgOpacity = 100 }


pt, pl, pr, pb, px, py, p :: Lens' BoxConfig (WhenTW TWSize)
pt = padding . t 
pb = padding . b
pl = padding . l
pr = padding . r
px = padding . x
py = padding . y 
p = padding . allS

mt, ml, mr, mb, mx, my, m :: Lens' BoxConfig (WhenTW TWSize)
mt = margin . t 
mb = margin . b
ml = margin . l
mr = margin . r
mx = margin . x
my = margin . y
m = margin . allS



pos :: Lens' BoxConfig (WhenTW (Justify, Align))
pos = lens undefined $ \cfg new -> cfg { _position =  new }
-- pos :: Lens' BoxConfig (Justify, Align)
-- pos = position . _Just
width', height', w, h :: Lens' BoxConfig (WhenTW TWSizeOrFraction) 
width' = sizingBand . size . width
height' = sizingBand . size . height
w = width'
h = height'


-- v1_box :: BoxConfig
-- v1_box = def
--      & colSpan .~ onlyDef
--      & colStart .~ onlyDef
--      & bgColor .~ onlyDef
--      & bgOpacity .~ onlyDef
--      & padding .~ (def
--                    & t .~ onlyDef
--                    & b .~ onlyDef
--                    & r .~ onlyDef
--                    & l .~ onlyDef
--                   )
--      & margin .~ (def
--                   & t .~ onlyDef
--                   & b .~ onlyDef
--                   & r .~ onlyDef
--                   & l .~ onlyDef
--                  )
--      & sizingBand .~ (def
--                       & maxSize .~ (def
--                                     & widthC .~ onlyDef
--                                     & heightC .~ onlyDef
--                                    )
--                        & minSize .~ (def
--                                     & widthC .~ onlyDef
--                                     & heightC .~ onlyDef
--                                    )
--                        & size .~ (def
--                                   & width .~ onlyDef
--                                   & height .~ onlyDef
--                                  )
--                      )
--      & border .~ (def
--                   & bStyle .~ onlyDef
--                   & bColor .~ (def & allS .~ onlyDef)
--                   & bWidth .~ (def & allS .~ onlyDef)
--                   & radius .~ ((def :: BorderRadiusCorners) & allS .~ onlyDef)
--                  )
--      & position .~ [("def", (def,def))]