{-# LANGUAGE AllowAmbiguousTypes        #-}
{-# LANGUAGE ConstraintKinds            #-}
{-# LANGUAGE DataKinds                  #-}
{-# LANGUAGE ExplicitNamespaces         #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE FunctionalDependencies     #-}
{-# LANGUAGE GADTs                      #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE PatternSynonyms            #-}
{-# LANGUAGE PolyKinds                  #-}
{-# LANGUAGE RankNTypes                 #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE StandaloneDeriving         #-}
{-# LANGUAGE TypeApplications           #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE TypeOperators              #-}
{-# LANGUAGE UndecidableInstances       #-}
{-# LANGUAGE ViewPatterns               #-}
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-}
{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
{-# OPTIONS_GHC -Wno-orphans #-}
{-# OPTIONS_GHC -Wno-redundant-constraints #-} 
module Data.DocRecord
  (
  
  
  
    module Data.Vinyl.Core
  , module Data.Vinyl.Lens
  , module Data.Vinyl.Derived
  , module Data.Vinyl.Curry
  , type (++)
  , type AllFst, type AllSnd
  
  , PathWithType(..)
  , FieldWithTag, fieldTag
  , Field(..)
  , Tagged(..)
  , WithDoc
  , PossiblyEmpty(..)
  , PossiblyEmptyField
  , pattern PEField
  , type DocField
  , pattern DocField
  , DocRec
  , FieldTypes
  , IdentityField
  , NamedField(..)
  , NamedFieldTag(..)
  , MissingValueReason(..)
  , IntermediaryLevel
  , FlattenedLevel
  , HasField, Includes, EquivalentTo
  , type Difference, type Intersection
  , ToJSONFields
  , RecBijection(..)
  , ShowPath(..)
  , ApplyRec(..)
  , MD(..)
  , type Fst
  , type Snd
  
  , removeDoc
  , withoutDef
  , getPossiblyEmpty
  , chooseHighestPriority
  , fld
  , runcurryF
  , runcurryAF
  , docField
  , itmLevel
  , fieldPath
  , fieldPathList
  , fieldFromDef
  , fieldNoDef
  , singleton
  , useDef
  , fromJSONAs
  , (^^.), (^^?), (^^?!), (%%~), (..~)
  , renamedAs
  , rsubset, rcast, rreplace
  , rcastAs, rsplit, rsplitFrom, rdifference, rintersection
  , PrefixPath(..), rinclude, (-.)
  , rdrill
  , rsplitDrill
  , rfoldSubset
  , funder
  , runder
  , (-/)
  , withSameFields
  , (&:)
  , recFrom
  , invertRecBij, (<<|>>), bijectField, bijectField', renameField, addConstField
  , bijectUnder
  , showDocumentation
  ) where
import           Control.Applicative
import qualified Control.Category     as Cat
import qualified Control.Lens         as L
import           Data.Aeson
import           Data.Default
import qualified Data.HashMap.Strict  as HM
import           Data.Maybe           (fromMaybe)
import           Data.Proxy
import qualified Data.Text            as T
import           Data.Typeable
import           Data.Vinyl.Core
import           Data.Vinyl.Curry
import           Data.Vinyl.Derived   hiding (HasField, rfield, (=:))
import qualified Data.Vinyl.Functor   as F
import           Data.Vinyl.Lens      (RElem, RSubset, rlens)
import qualified Data.Vinyl.Lens      as VL
import           Data.Vinyl.TypeLevel hiding (Fst, Snd)
import           GHC.Exts             (Constraint)
import           GHC.TypeLits         (ErrorMessage (..), KnownSymbol, Symbol,
                                       TypeError, symbolVal)
data MissingValueReason = NoDefault | MissingValueInJSON
  deriving (Show, Eq, Ord)
newtype PossiblyEmpty a = PE (Either MissingValueReason a)
  deriving (Functor, Applicative)
instance (Eq a) => Eq (PossiblyEmpty a) where
  PE (Right a) == PE (Right b) = a == b
  PE (Left _) == PE (Left _) = True
  _ == _ = False
instance Ord a => Ord (PossiblyEmpty a) where
  PE (Right a) `compare` PE (Right b) = a `compare` b
  PE (Left _) `compare` PE (Left _) = EQ
  _ `compare` _ = LT
instance (Show (f (s:|:a)), ShowPath s)
      => Show (PossiblyEmpty (f (s:|:a))) where
  show x = case x of
    PE (Right a) -> show a
    PE (Left r)  ->
      T.unpack (showPath (Proxy @s)) ++ " (empty: " ++ show r ++ ")\n"
instance (Semigroup a) => Semigroup (PossiblyEmpty a) where
  PE (Right x) <> PE (Right y) = PE $ Right $ x<>y
  PE (Left _) <> PE (Right x) = PE $ Right x
  PE (Right x) <> PE (Left _) = PE $ Right x
  PE (Left x) <> PE (Left y) = PE $ Left $ max x y
instance (Semigroup a) => Monoid (PossiblyEmpty a) where
  mempty = PE (Left NoDefault)
data Tagged tag a = Tagged { tagFromTagged   :: tag
                           , valueFromTagged :: a }
type WithDoc = Tagged T.Text
instance (Eq a) => Eq (Tagged tag a) where
  Tagged _ a == Tagged _ b = a == b
instance (Ord a) => Ord (Tagged tag a) where
  Tagged _ a `compare` Tagged _ b = a `compare` b
instance (Show a) => Show (Tagged tag a) where
  show (Tagged _ a) = show a
instance (Semigroup a) => Semigroup (Tagged tag a) where
  Tagged _ x <> Tagged tag y = Tagged tag (x<>y)
instance (Monoid tag, Monoid a) => Monoid (Tagged tag a) where
  mempty = Tagged mempty mempty
instance Functor (Tagged tag) where
  fmap f (Tagged d a) = Tagged d (f a)
instance (Monoid tag) => Applicative (Tagged tag) where
  pure = Tagged mempty
  Tagged t1 f <*> Tagged t2 x = Tagged (t1<>t2) (f x)
chooseHighestPriority
  :: Ord a
  => F.Compose (Tagged a) (F.Compose (Tagged T.Text) f) x
  -> F.Compose (Tagged a) (F.Compose (Tagged T.Text) f) x
  -> F.Compose (Tagged a) (F.Compose (Tagged T.Text) f) x
chooseHighestPriority (F.Compose (Tagged s1 (F.Compose (Tagged doc1 f1))))
                      (F.Compose (Tagged s2 (F.Compose (Tagged doc2 f2)))) =
  if s2 >= s1
  then F.Compose (Tagged s2 (F.Compose (Tagged doc f2)))
  else F.Compose (Tagged s1 (F.Compose (Tagged doc f1)))
  where  
         
    doc | doc1 == "" = doc2
        | otherwise = doc1
data PathWithType a b = a :|: b
data Field (pathAndType :: ( PathWithType [Symbol] * )) where
  Field :: (ShowPath s) => !t -> Field (s :|: t)
deriving instance (Eq a) => Eq (Field (s:|:a))
deriving instance (Ord a) => Ord (Field (s:|:a))
instance (Show t, ShowPath s) => Show (Field (s:|:t)) where
  show (Field x) =
    T.unpack (showPath (Proxy @s)) ++ " =: " ++ show x ++ "\n"
instance (Semigroup t) => Semigroup (Field (s :|: t)) where
  Field a <> Field b = Field $ a<>b
instance (ShowPath s, Monoid t) => Monoid (Field (s :|: t)) where
  mempty = Field mempty
type PossiblyEmptyField = F.Compose PossiblyEmpty Field
peToMb :: (NamedField field, ShowPath s) => Either r (field (s:|:a)) -> Maybe a
peToMb (Left _)  = Nothing
peToMb (Right f) = f L.^. rfield
peFromMb
  :: (ShowPath s, NamedField field)
  => Maybe a -> Either MissingValueReason (field (s ':|: a))
peFromMb Nothing  = Left NoDefault
peFromMb (Just x) = Right $ fromValue x
pattern PEField
  :: ( NamedField f, ShowPath s)
  => Maybe a -> F.Compose PossiblyEmpty f (s:|:a)
pattern PEField v <- F.Compose (PE (peToMb -> v)) where
  PEField v = F.Compose (PE (peFromMb v))
type DocField = F.Compose WithDoc PossiblyEmptyField
removeDoc :: F.Compose WithDoc f st -> f st
removeDoc (F.Compose (Tagged _ x)) = x
withoutDef :: F.Compose WithDoc (F.Compose PossiblyEmpty f) st
           -> F.Compose WithDoc (F.Compose PossiblyEmpty f) st
withoutDef (DocField doc _) = DocField doc (Left NoDefault)
getPossiblyEmpty
  :: F.Compose PossiblyEmpty f st -> Either MissingValueReason (f st)
getPossiblyEmpty (F.Compose (PE x)) = x
pattern DocField :: T.Text
                 -> Either MissingValueReason (g x)
                 -> F.Compose WithDoc (F.Compose PossiblyEmpty g) x
pattern DocField doc mbf = F.Compose (Tagged doc (F.Compose (PE mbf)))
type DocRec = Rec DocField
type family FieldTypes rs where
  FieldTypes '[] = '[]
  FieldTypes ((s:|:t) : rs) = t : FieldTypes rs
fieldPathList :: forall st p. (ShowPath (Fst st)) => p st -> [T.Text]
fieldPathList _ = showPathList (Proxy @(Fst st))
fieldPath :: forall st p. (ShowPath (Fst st)) => p st -> T.Text
fieldPath _ = showPath (Proxy @(Fst st))
class ShowPath p where
  showPathList :: proxy p -> [T.Text]
  showPath :: proxy p -> T.Text
  showPath p = T.intercalate (T.pack ".") (showPathList p)
instance ShowPath '[] where
  showPathList _ = []
instance (ShowPath ps, KnownSymbol p) => ShowPath (p ': ps) where
  showPathList _ = T.pack (symbolVal (Proxy @p)) : showPathList (Proxy @ps)
jsonAtPath :: [T.Text] -> L.Lens' (Maybe Value) (Maybe Value)
jsonAtPath [] f x = f x
jsonAtPath (p:ps) f val = rebuild <$> recur
  where
    (obj, recur) = case val of
      Just (Object o) -> (o,        jsonAtPath ps f $ HM.lookup p o)
      _               -> (HM.empty, jsonAtPath ps f $ Just $ Object HM.empty)
    rebuild Nothing  = Just $ Object $ HM.delete p obj
    rebuild (Just v) = Just $ Object $ HM.insert p v obj
instance FromJSON (Rec PossiblyEmptyField '[]) where
  parseJSON (Object _) = pure RNil
  parseJSON _          = mempty
instance (FromJSON t, FromJSON (Rec PossiblyEmptyField rs), ShowPath s)
  => FromJSON (Rec PossiblyEmptyField ((s:|:t) ': rs)) where
  parseJSON v = rebuild <$> parseField (L.view (jsonAtPath p) (Just v))
                        <*> parseJSON @(Rec PossiblyEmptyField rs) v
    where p = showPathList (Proxy @s)
          rebuild mbV rest = F.Compose (PE (Field <$> mbV)) :& rest
          parseField mbX = if x == Null  
                           then parsing <|> pure (Left MissingValueInJSON)
                           else parsing  
            where parsing = Right <$> parseJSON x
                  x = fromMaybe Null mbX
instance (RMap rs, FromJSON (Rec PossiblyEmptyField rs)) => FromJSON (Rec DocField rs) where
  parseJSON v = rmap toDoc <$> parseJSON v
    where toDoc = F.Compose . Tagged ""
fromJSONAs :: (FromJSON x) => x -> Value -> Result x
fromJSONAs _ = fromJSON
type family Fst a where
  Fst (a:|:b) = a
type family Snd a where
  Snd (a:|:b) = b
type family AllFst c p :: Constraint where
  AllFst c (r ': rs) = (c (Fst r), AllFst c rs)
  AllFst c '[] = ()
type family AllSnd c p :: Constraint where
  AllSnd c (r ': rs) = (c (Snd r), AllSnd c rs)
  AllSnd c '[] = ()
instance (ToJSON `AllSnd` rs, ShowPath `AllFst` rs)
      => ToJSON (Rec PossiblyEmptyField rs) where
  toJSON RNil = Object mempty
  toJSON x = into (Object mempty) x 
    where
      into :: forall rs'. (ToJSON `AllSnd` rs', ShowPath `AllFst` rs')
           => Value -> Rec PossiblyEmptyField rs' -> Value
      into o RNil = o
      into o (field@(F.Compose (PE innerField)) :& fs) = into
        (fromMaybe (error "Should not happen!") $
            L.set (jsonAtPath $ fieldPathList field)
                  (Just $ case innerField of
                     Right (Field v) -> toJSON v
                     Left _          -> Null)
                  (Just o))
        fs
instance (RMap rs, ToJSON `AllSnd` rs, ShowPath `AllFst` rs)
      => ToJSON (Rec DocField rs) where
  toJSON = toJSON . rmap removeDoc
type ToJSONFields fields =
  (ToJSON `AllSnd` fields, Typeable `AllSnd` fields, ShowPath `AllFst` fields)
showDocumentation
  :: forall rs field. (ShowPath `AllFst` rs, Typeable `AllSnd` rs)
  => Int  
  -> Rec (F.Compose WithDoc field) rs
  -> T.Text
showDocumentation charLimit (f :& fs) =
  showF f <> case fs of
               (_ :& _) -> "\n" <> showDocumentation charLimit fs
               RNil     -> ""
  where
    showF :: forall r. (ShowPath (Fst r), Typeable (Snd r))
          => F.Compose WithDoc field r -> T.Text
    showF (F.Compose (Tagged doc _)) =
      showPath (Proxy @(Fst r))
      <> " :: " <> T.pack (cap $ show $ typeRep $ Proxy @(Snd r))
      <> " : " <> doc
    cap x | length x >= charLimit = take charLimit x ++ "..."
          | otherwise = x
showDocumentation _ RNil = ""
class NamedField field where
  
  rfield :: (ShowPath s) => L.Lens (field (s :|: a)) (field (s :|: b)) (Maybe a) (Maybe b)
  
  fromValue :: (ShowPath s) => a -> field (s :|: a)
  
  mapField :: (ShowPath s)
           => (t -> t') -> field (s:|:t) -> field (s:|:t')
  mapField = L.over (rfield . L._Just)
  {-# INLINE mapField #-}
  
  
  (=:) :: (ShowPath s) => DocField (s :|: a) -> a -> Rec field '[ s :|: a ]
  infixl 7 =:
  _ =: x = fromValue x :& RNil
  {-# INLINE (=:) #-}
  changePath :: (ShowPath s') => field (s:|:a) -> field (s':|:a)
type family FieldDirectlyContainsTag tag field where
  FieldDirectlyContainsTag tag (F.Compose (Tagged tag) f) = True
  FieldDirectlyContainsTag _ _ = False
class (hasTag ~ FieldDirectlyContainsTag tag field) => FieldWithTag_ tag field hasTag where
  
  fieldTag :: L.Traversal' (field r) tag
type FieldWithTag tag field = FieldWithTag_ tag field (FieldDirectlyContainsTag tag field)
renamedAs :: (ShowPath s', NamedField f) => proxy (s':|:a) -> f (s:|:a) -> f (s':|:a)
renamedAs _ = changePath
instance NamedField Field where
  rfield f (Field v) = Field . fromM <$> f (Just v)
    where fromM (Just v') = v'
          fromM _         = error "Cannot remove a Field's value!"
  {-# INLINE rfield #-}
  fromValue = Field
  changePath (Field v) = Field v
instance (NamedField f) => NamedField (F.Compose PossiblyEmpty f) where
  rfield f (F.Compose (PE field)) = F.Compose . PE <$> inspect field
    where
      inspect (Right inner) = rebuild NoDefault (setInner inner) <$> f (L.view rfield inner)
      inspect (Left r)      = rebuild r fromValue <$> f Nothing
      setInner inner x = L.set rfield (Just x) inner
      rebuild reason _   Nothing  = Left reason
      rebuild _ updInner (Just x) = Right $ updInner x
  fromValue = F.Compose . PE . Right . fromValue
  changePath (F.Compose (PE x)) = F.Compose $ PE $ case x of
    Left r  -> Left r
    Right f -> Right $ changePath f
instance (FieldWithTag tag g)
  => FieldWithTag_ tag (F.Compose PossiblyEmpty g) False where
  fieldTag fn (F.Compose (PE (Right f))) =
    F.Compose . PE . Right <$> fieldTag fn f
  fieldTag _ field = pure field
class NamedFieldTag tag where
  
  defaultTag :: tag
  
  tagFromDoc :: T.Text -> tag
instance NamedFieldTag T.Text where
  defaultTag = ""
  tagFromDoc = id
instance (NamedField f, NamedFieldTag tag) => NamedField (F.Compose (Tagged tag) f) where
  rfield = (\f (F.Compose (Tagged d x)) ->
               F.Compose . Tagged d <$> f x) . rfield
  {-# INLINE rfield #-}
  fromValue = F.Compose . Tagged defaultTag . fromValue
  changePath (F.Compose (Tagged d x)) = F.Compose (Tagged d (changePath x))
  
  F.Compose (Tagged d _) =: v = F.Compose (Tagged (tagFromDoc d) (fromValue v)) :& RNil
instance FieldWithTag_ tag Field False where
  fieldTag _ f = pure f
instance FieldWithTag_ tag (F.Compose (Tagged tag) f) True where
  fieldTag fn (F.Compose (Tagged d x)) = rebuild <$> fn d
    where
      rebuild d' = F.Compose (Tagged d' x)
instance (FieldWithTag tag f, FieldDirectlyContainsTag tag (F.Compose (Tagged tag') f) ~ False)
      => FieldWithTag_ tag (F.Compose (Tagged tag') f) False where
  fieldTag fn (F.Compose (Tagged t f)) = F.Compose . Tagged t <$> fieldTag fn f
class OnFields ts f1 f2 | ts f1 -> f2 where
  onFields :: f1 -> f2
instance OnFields '[] a a where
  onFields x = x
instance (OnFields ts f1 f2) =>
     OnFields ((s:|:a) : ts) (a -> f1) (Field (s:|:a) -> f2) where
  onFields f (Field x) = onFields @ts (f x)
runcurryF :: forall ts f1 f a. (OnFields ts f1 (CurriedF f ts a))
          => f1 -> Rec f ts -> a
runcurryF = runcurry . onFields @ts
runcurryAF :: forall ts f1 f g a. (Applicative f, OnFields ts f1 (CurriedF g ts a))
           => f1 -> Rec (F.Compose f g) ts -> f a
runcurryAF = runcurryA . onFields @ts
type family RIndex' r1 rs1 (r :: k) (rs :: [k]) :: Nat where
  RIndex' r1 rs1 r (r ': rs) = 'Z
  RIndex' r1 rs1 r (notR ': rs) = 'S (RIndex' r1 rs1 r rs)
  RIndex' r1 rs1 r rs = TypeError
    (Text "Field " :<>: ShowType r1 :<>: Text " is not present in record " :<>: ShowType rs1)
class (RElem f rs (RIndex' f rs f rs)) => rs `HasField` f
instance (RElem f (r ': rs) (RIndex' f (r ': rs) f (r ': rs))) => (r ': rs) `HasField` f
type family RImage' rs1 ss1 (rs :: [k]) (ss :: [k]) :: [Nat] where
  RImage' rs1 ss1 '[] ss = '[]
  RImage' rs1 ss1 (r ': rs) ss = RIndex' r ss r ss ': RImage' rs1 ss1 rs ss
class (RSubset rs ss (RImage' rs ss rs ss)) => ss `Includes` rs
instance ss `Includes` '[]
instance (RSubset (r ': rs) ss (RImage' (r ': rs) ss (r ': rs) ss)) => ss `Includes` (r ': rs)
rsubset :: (Functor g, ss `Includes` rs) => (Rec f rs -> g (Rec f rs)) -> Rec f ss -> g (Rec f ss)
rsubset = VL.rsubset
rcast :: (ss `Includes` rs) => Rec f ss -> Rec f rs
rcast = VL.rcast
rreplace :: (ss `Includes` rs) => Rec f rs -> Rec f ss -> Rec f ss
rreplace = VL.rreplace
type rs `EquivalentTo` ss = (rs `Includes` ss, ss `Includes` rs)
fld :: forall s a rs field proxy.
       (NamedField field, rs `HasField` (s:|:a), ShowPath s)
    => proxy (s:|:a)
    -> L.Lens' (Rec field rs) (Maybe a)
fld _ = VL.rlens @(s:|:a) . rfield
(^^.) :: (NamedField field, rs `HasField` (s:|:t), ShowPath s, Monoid t)
      => Rec field rs -> proxy (s:|:t) -> t
record ^^. field = record L.^. fld field . L._Just
infixl 8 ^^.
(^^?) :: (NamedField field, rs `HasField` (s:|:t), ShowPath s)
      => Rec field rs -> proxy (s:|:t) -> (Maybe t)
record ^^? field = record L.^. fld field
infixl 8 ^^?
(^^?!) :: (NamedField field, rs `HasField` (s:|:t), ShowPath s)
       => Rec field rs -> proxy (s:|:t) -> t
record ^^?! field = record L.^?! fld field . L._Just
infixl 8 ^^?!
(%%~) :: (NamedField field, rs `HasField` (s:|:t), ShowPath s)
      => proxy (s:|:t) -> (t -> t) -> Rec field rs -> Rec field rs
field %%~ f = fld field . L._Just L.%~ f
infixr 4 %%~
(..~) :: (NamedField field, rs `HasField` (s:|:t), ShowPath s)
      => proxy (s:|:t) -> t -> Rec field rs -> Rec field rs
field ..~ v = fld field L..~ Just v
infixr 4 ..~
singleton :: (NamedField f) => t -> Rec f '[ ('[]:|:t ) ]
singleton x = fromValue x :& RNil
useDef :: (NamedField f, ShowPath s) => DocField (s:|:t) -> Rec f '[ (s:|:t) ]
useDef f = f =: (f L.^?! rfield . L._Just)
docField :: forall s t. (KnownSymbol s) => t -> T.Text -> DocField ('[s]:|:t)
docField defVal doc = DocField doc $ Right $ Field defVal
itmLevel
  :: forall s rs. (KnownSymbol s)
  => T.Text -> DocRec rs -> IntermediaryLevel '[s] rs
itmLevel doc content = ItmLvl $ DocField doc $ Right $ Field content
fieldFromDef
  :: forall s t. (KnownSymbol s, Default t)
  => T.Text -> DocField ('[s]:|:t)
fieldFromDef = docField def
fieldNoDef :: forall s t. T.Text -> DocField ('[s]:|:t)
fieldNoDef doc = DocField doc $ Left NoDefault
type family DeleteIn a b where
  DeleteIn t (t ': ts) = DeleteIn t ts
  DeleteIn t (t' ': ts) = t' : DeleteIn t ts
  DeleteIn t '[] = '[]
type family Difference a b where
  Difference ts' (t ': ts) = Difference (DeleteIn t ts') ts
  Difference ts' '[] = ts'
rcastAs
  :: (rs `Includes` selected)
  => p selected -> Rec f rs -> Rec f selected
rcastAs _ r = rcast r
rsplit
  :: (rs `Includes` selected, rs `Includes` (rs `Difference` selected))
  => Rec f rs -> (Rec f selected, Rec f (rs `Difference` selected))
rsplit r = (rcast r, rcast r)
rsplitFrom
  :: (rs `Includes` selected, rs `Includes` (rs `Difference` selected))
  => p selected -> Rec f rs -> (Rec f selected, Rec f (rs `Difference` selected))
rsplitFrom _ r = rsplit r
rdifference
  :: (rs `Includes` selected, rs `Includes` (rs `Difference` selected))
  => Rec f rs -> p selected -> Rec f (rs `Difference` selected)
rdifference r _ = rcast r
type a `Intersection` b = a `Difference` (a `Difference` b)
rintersection
  :: (a `Includes` (a `Difference` b)
     ,a `Includes` (a `Intersection` b)
     ,b `Includes` (b `Intersection` a)
     ,b `Includes` (b `Difference` a))
  => Rec f a -> Rec f b -> ( Rec f (a `Difference` b), Rec f (a `Intersection` b)
                           , Rec f (b `Intersection` a), Rec f (b `Difference` a) )
rintersection a b = (rcast a, rcast a, rcast b, rcast b)
class PrefixPath (s::[Symbol]) rs where
  type s `PrefixingAll` rs :: [PathWithType [Symbol] *]
  prefixPath :: (NamedField f) => Rec f rs -> Rec f (s `PrefixingAll` rs)
instance PrefixPath s '[] where
  type s `PrefixingAll` '[] = '[]
  prefixPath _ = RNil
instance (PrefixPath s ps, ShowPath (s++p1)) => PrefixPath s ( (p1:|:t) : ps) where
  type s `PrefixingAll` ( (p1:|:t) : ps) = ( (s++p1:|:t) : s `PrefixingAll` ps)
  prefixPath (f :& fs) = (changePath f) :& prefixPath @s fs
data MD = MD
instance Eq MD where
  _ == _ = True
instance Ord MD where
  compare _ _ = EQ
instance ToJSON MD where
  toJSON _ = Null
instance FromJSON MD where
  parseJSON _ = pure MD
instance Show MD where
  show _ = "<just_doc>"
newtype IntermediaryLevel_ a = ItmLvl (DocField a)
type IntermediaryLevel s rs = IntermediaryLevel_ (s:|:DocRec rs)
type FlattenedLevel s rs = s `PrefixingAll` rs
rinclude
  :: forall s rs. (PrefixPath s rs, ShowPath s)
  => IntermediaryLevel s rs -> DocRec (FlattenedLevel s rs)
rinclude (ItmLvl (DocField _ (Left r))) =
  error $ "rinclude: Trying to flatten an empty field (" ++ show r ++ ")!"
rinclude (ItmLvl (DocField _doc (Right (Field r)))) = prefixPath @s r
class ComposableNesting f lvl2 where
  
  
  
  (-.) :: (NestedLvlConstraints rs f p lvl2, ShowPath (s++p))
       => IntermediaryLevel s rs -> f (p:|:lvl2) -> NestedLvl s f p lvl2
  infixr 9 -.
type family NestedLvl s f p lvl2 where
  NestedLvl s IntermediaryLevel_ p (DocRec rs') = IntermediaryLevel (s++p) rs'
  NestedLvl s f p t = f ((s++p):|:t)
type family NestedLvlConstraints rs f p lvl2 :: Constraint where
  NestedLvlConstraints rs IntermediaryLevel_ p (DocRec rs') =
    ( rs `Includes` (p `PrefixingAll` rs') )
  NestedLvlConstraints rs f p t =
    ( rs `HasField` (p:|:t) )
instance ComposableNesting IntermediaryLevel_ (DocRec rs') where
  _ -. ItmLvl f = ItmLvl $ changePath f
instance (NamedField f) => ComposableNesting f t where
  _ -. f = changePath f
funder :: forall s p t. ( ShowPath (s ': p) )
     => DocField (p:|:t) -> DocField ((s ': p) :|: t)
funder = changePath
(-/) :: forall s rs selected f.
        (rs `Includes` selected, PrefixPath s selected, NamedField f)
     => IntermediaryLevel s rs -> Rec f selected -> Rec f (s `PrefixingAll` selected)
_ -/ r = prefixPath @s r
infixr 6 -/
runder :: forall s selected f. (PrefixPath '[s] selected, NamedField f)
      => Rec f selected -> Rec f ('[s] `PrefixingAll` selected)
runder r = prefixPath @'[s] r
type family Strip s src where
  Strip (a ': as) (a ': bs) = Strip as bs
  Strip (a ': as) (b ': bs) = b ': bs
  Strip as bs = bs
class UnprefixPath (s::[Symbol]) rs where
  type s `UnprefixingAll` rs :: [PathWithType [Symbol] *]
  unprefixPath :: (NamedField f) => Rec f rs -> Rec f (s `UnprefixingAll` rs)
instance UnprefixPath s '[] where
  type s `UnprefixingAll` '[] = '[]
  unprefixPath _ = RNil
instance (UnprefixPath s ps, ShowPath (Strip s p1))
       => UnprefixPath s ( (p1:|:t) : ps) where
  type s `UnprefixingAll` ( (p1:|:t) : ps) =
          ( (Strip s p1 :|: t) : s `UnprefixingAll` ps)
  unprefixPath (f :& fs) = changePath f :& unprefixPath @s fs
rdrill :: forall s inner outer f.
          ( inner ~ (s `UnprefixingAll` (s `PrefixingAll` inner))
              
          , UnprefixPath s (s `PrefixingAll` inner)
          , outer `Includes` (s `PrefixingAll` inner)
          , NamedField f )
       => IntermediaryLevel s inner
       -> Rec f outer
       -> Rec f inner
rdrill _ outer = unprefixPath @s (rcast outer :: Rec f (s `PrefixingAll` inner))
rsplitDrill :: forall s inner outer f.
          ( inner ~ (s `UnprefixingAll` (s `PrefixingAll` inner))
              
          , UnprefixPath s (s `PrefixingAll` inner)
          , outer `Includes` (s `PrefixingAll` inner)
          , outer `Includes` (outer `Difference` (FlattenedLevel s inner))
          , NamedField f )
       => IntermediaryLevel s inner
       -> Rec f outer
       -> (Rec f inner, Rec f (outer `Difference` (FlattenedLevel s inner)))
rsplitDrill il outer = (rdrill il outer, rcast outer)
rfoldSubset
  :: forall outer' inner outer p t proxy f.
  ( outer `Includes` inner
  , ((p:|:t) ': outer) `Includes` outer')
  => proxy inner  
  -> (Rec f inner -> f (p:|:t))
  -> Rec f outer
  -> Rec f outer'
rfoldSubset _ f r = rcast $ f (rcast r) :& r
withSameFields :: Rec f rs -> Rec g rs -> t -> t
withSameFields _ _ x = x
type IdentityField = F.Identity
(&:) :: t -> Rec IdentityField ts -> Rec IdentityField (t ': ts)
x &: r = F.Identity x :& r
infixr 5 &:
class ApplyRec fns fields results | fns fields -> results where
  appRec :: (NamedField f) => Rec F.Identity fns -> Rec f fields -> Rec f results
instance ApplyRec '[] a a where
  appRec RNil r = r
instance (ApplyRec fns fields results, ShowPath s)
  => ApplyRec ( (a -> b) : fns ) ((s:|:a) : fields) ((s:|:b) : results) where
  appRec (F.Identity f :& fns) (field :& fields) =
    L.over (rfield . L._Just) f field :& appRec fns fields
type family FirstFieldSkipped rs where
  FirstFieldSkipped ((s:|:MD) : rs) = 'True
  FirstFieldSkipped a  = 'False
class (skipFirst ~ FirstFieldSkipped rs)
   => BuildRecFrom f rs (acc::[PathWithType [Symbol] *]) skipFirst where
  type RecCtor f rs acc skipFirst
  buildRecFrom_ :: Rec f acc -> DocRec rs -> RecCtor f rs acc skipFirst
instance BuildRecFrom f '[] acc 'False where
  type RecCtor f '[] acc 'False = Rec f acc
  buildRecFrom_ acc RNil = acc
  {-# INLINE buildRecFrom_ #-}
instance ( BuildRecFrom f rs (acc ++ '[s:|:a]) (FirstFieldSkipped rs)
         , FirstFieldSkipped ((s:|:a):rs) ~ 'False
         , NamedField f, ShowPath s )
      => BuildRecFrom f ((s:|:a) : rs) acc 'False where
  type RecCtor f ((s:|:a) : rs) acc 'False =
         a -> RecCtor f rs (acc ++ '[s:|:a]) (FirstFieldSkipped rs)
  buildRecFrom_ acc (r :& rs) a = buildRecFrom_ (acc <+> r =: a) rs
   
   
  {-# INLINE buildRecFrom_ #-}
instance ( BuildRecFrom f rs (acc++'[s:|:MD]) (FirstFieldSkipped rs)
         , NamedField f, ShowPath s )
      => BuildRecFrom f ((s:|:MD) : rs) acc 'True where
  type RecCtor f ((s:|:MD) : rs) acc 'True =
         RecCtor f rs (acc ++ '[s:|:MD]) (FirstFieldSkipped rs)
  buildRecFrom_ acc (_ :& rs) = buildRecFrom_ (acc <+> fromValue @f @s MD :& RNil) rs
  {-# INLINE buildRecFrom_ #-}
recFrom :: forall f rs. (BuildRecFrom f rs '[] (FirstFieldSkipped rs))
        => DocRec rs -> RecCtor f rs '[] (FirstFieldSkipped rs)
recFrom = buildRecFrom_ @f RNil
{-# INLINE recFrom #-}
data RecBijection f as bs = RecBijection
  { applyRecBij    :: Rec f as -> Rec f bs
  , applyRecBijInv :: Rec f bs -> Rec f as
  }
instance Cat.Category (RecBijection f) where
  id = RecBijection id id
  RecBijection f fi . RecBijection g gi = RecBijection (f . g) (gi . fi)
invertRecBij :: RecBijection f as bs -> RecBijection f bs as
invertRecBij (RecBijection f g) = RecBijection g f
(<<|>>)
  :: ( as `Intersection` as' ~ '[]
     , bs `Intersection` bs' ~ '[]
     , (as ++ as') `Includes` as, (as ++ as') `Includes` as'
     , (bs ++ bs') `Includes` bs, (bs ++ bs') `Includes` bs')
  => RecBijection f as bs
  -> RecBijection f as' bs'
  -> RecBijection f (as++as') (bs++bs')
RecBijection f fi <<|>> RecBijection g gi =
  RecBijection (\r -> f (rcast r) <+> g (rcast r))
               (\r -> fi (rcast r) <+> gi (rcast r))
bijectField :: forall s f a b. (ShowPath s, NamedField f)
               => (a -> b) -> (b -> a) -> RecBijection f '[s:|:a] '[s:|:b]
bijectField f g = RecBijection (\(fl :& RNil) -> mapField f fl :& RNil)
                                    (\(fl :& RNil) -> mapField g fl :& RNil)
{-# INLINE bijectField #-}
bijectField' :: forall s s' f a b. (ShowPath s, ShowPath s', NamedField f)
               => (a -> b) -> (b -> a) -> RecBijection f '[s:|:a] '[s':|:b]
bijectField' f g = RecBijection (\(fl :& RNil) -> changePath (mapField f fl) :& RNil)
                                    (\(fl :& RNil) -> changePath (mapField g fl) :& RNil)
{-# INLINE bijectField' #-}
renameField :: forall s s' f a. (ShowPath s, ShowPath s', NamedField f)
              => RecBijection f '[s:|:a] '[s':|:a]
renameField = RecBijection (\(fl :& RNil) -> changePath fl :& RNil)
                             (\(fl :& RNil) -> changePath fl :& RNil)
{-# INLINE renameField #-}
addConstField :: forall s f a. f (s:|:a) -> RecBijection f '[] '[s:|:a]
addConstField x = RecBijection (\_ -> x :& RNil) (const RNil)
{-# INLINE addConstField #-}
bijectUnder
  :: forall s f as bs.
     ( as ~ UnprefixingAll s (PrefixingAll s as)
     , bs ~ UnprefixingAll s (PrefixingAll s bs)
     , PrefixPath s as, PrefixPath s bs, NamedField f
     , UnprefixPath s (PrefixingAll s as)
     , UnprefixPath s (PrefixingAll s bs) )
  => RecBijection f as bs
  -> RecBijection f (s `PrefixingAll` as) (s `PrefixingAll` bs)
bijectUnder (RecBijection f fi) =
  RecBijection (prefixPath @s . f . unprefixPath @s)
               (prefixPath @s . fi . unprefixPath @s)