{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables  #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE TypeApplications     #-}
{-# LANGUAGE TypeOperators        #-}
{-# LANGUAGE DataKinds            #-}
{-# LANGUAGE PolyKinds            #-}

module Html.Reify
  ( R(..)
  ) where

import Html.Type.Internal
import Html.Convert
import Html.CPP

import GHC.TypeLits
import Data.Proxy
import Data.Semigroup ((<>))

class R a where
  render :: a -> Converted

instance
  KnownSymbol s
  => R (Proxy (s :: Symbol)) where
  {-# INLINE render #-}
  render = convert

instance
  R (Proxy ('[] :: [Symbol])) where
  {-# INLINE render #-}
  render _ = mempty

instance
  ( KnownSymbol x, R (Proxy xs)
  ) => R (Proxy ((x ': xs) :: [Symbol])) where
  {-# INLINE render #-}
  render _ = convert (Proxy @ x) <> render (Proxy @ xs)

instance {-# INCOHERENT #-}
  R (T '[] val) where
  {-# INLINE render #-}
  render _ = mempty

instance {-# INCOHERENT #-}
  ( Convert val
  ) => R (T '[ EmptySym ] val) where
  {-# INLINE render #-}
  render (T x) = convert x

instance {-# OVERLAPPING #-}
  R (T '[ EmptySym ] String) where
  {-# INLINE render #-}
  render (T x) = convert x

instance
  ( Convert b
  , R (Proxy s)
  ) => R (T '[s] (a := b)) where
  {-# INLINE render #-}
  render (T (AT x)) = render (Proxy @ s) <> convert x

instance {-# INCOHERENT #-}
  ( Convert val
  , R (Proxy s)
  ) => R (T '[s] val) where
  {-# INLINE render #-}
  render (T x) = render (Proxy @ s) <> convert x

instance {-# OVERLAPPING #-}
  ( R (Proxy s)
  ) => R (T '[s] String) where
  {-# INLINE render #-}
  render (T x) = render (Proxy @ s) <> convert x

instance {-# OVERLAPPING #-}
  ( R (T xs val)
  ) => R (T (NoTail xs) val) where
  {-# INLINE render #-}
  render (T t) = render (T t :: T xs val)

instance
  ( R (T xs val)
  , R (Proxy x)
  ) => R (T ('FingerTree xs x) val) where
  {-# INLINE render #-}
  render (T t) = render (T t :: T xs val) <> render (Proxy @ x)

instance
  ( R (T (Take (Length b) ps) b)
  , R (T (Drop (Length b) ps) c)
  ) => R (T ps ((a :@: b) c)) where
  {-# INLINE render #-}
  render (T ~(WithAttributes b c))
    = render (T b :: T (Take (Length b) ps) b)
   <> render (T c :: T (Drop (Length b) ps) c)

instance
  ( R (T (Take (Length a) ps) a)
  , R (T (Drop (Length a) ps) b)
  ) => R (T ps (a # b)) where
  {-# INLINE render #-}
  render (T ~(a :#: b))
    = render (T a :: T (Take (Length a) ps) a)
   <> render (T b :: T (Drop (Length a) ps) b)

instance
  ( R (T (ToList a) a)
  , R (Proxy s)
  ) => R (T (s ': ss) [a]) where
  {-# INLINE render #-}
  render (T xs)
    = render (Proxy @ s)
    <> foldMap (render . newT) xs

instance
  ( R (T (ToList a) a)
  , R (Proxy s)
  ) => R (T (s ': ss) (Maybe a)) where
  {-# INLINE render #-}
  render (T mx)
    = render (Proxy @ s)
    <> foldMap (render . newT) mx

instance
  ( R (T (ToList a) a)
  , R (T (ToList b) b)
  , R (Proxy s)
  ) => R (T (s ': ss) (Either a b)) where
  {-# INLINE render #-}
  render (T eab)
    = render (Proxy @ s)
    <> either (render . newT) (render . newT) eab

newT :: x -> T (ToList x) x
newT = T