content: {-# language DeriveGeneric        #-}
  {-# language OverloadedStrings    #-}
  {-# language TypeSynonymInstances #-}
  {-# language FlexibleInstances    #-}
  -- | Simple interface for using AutoType inference
  --   in other code generators.
  --
  --   Simply takes a list of Aeson values,
  --   and returns a type description.
  --
  --   For this type description,
  --   we can use function to generate an entire new module.
  --
  --   Note that while we can put more code in the module,
  --   it is recommended to avoid multiple automatically
  --   generated types in order to avoid name conflicts.
  --
  --   NOTE: this interface is yet unstable
  module Data.Aeson.AutoType.Nested(
      defaultImportedModules
    , generateModuleImports
    , inferType
    , CodeFrag(..)
    , TypeName
    , TypeFrag
    , ModuleImport
    , PackageName
    ) where
  
  import Data.Aeson
  import Data.Aeson.AutoType.CodeGen.Haskell(generateModuleImports, requiredPackages, importedModules, ModuleImport)
  import Data.Aeson.AutoType.CodeGen.HaskellFormat(displaySplitTypes)
  import Data.Aeson.AutoType.Extract(extractType, unifyTypes)
  import Data.Aeson.AutoType.Split(splitTypeByLabel)
  import Data.Default
  import Data.Typeable
  import Data.Text(Text)
  import GHC.Generics
  
  -- FIXME: general type to compose generated types
  -- move to JSON Autotype as library interface?
  -- * API Response Structures
  type Code           = Text
  type TypeName       = Text
  type PackageName    = Text
  
  -- | Generated code reference and its requirements
  --   Content to embed in an autogenerated module:
  --
  --   * name of the reference
  --   * declarations to describe it
  --   * module imports necessary for declarations
  --     to work
  data CodeFrag a = CodeFrag
    {
      -- | Code fragment to be inserted in generated module
      codeFragCode     ::  Code
      -- | Toplevel type name to refer to
    , codeFragName     ::  a
      -- | List of clauses to add to imports list
    , codeFragImports  :: [ModuleImport]
      -- | List of packages to add to generated package dependencies
    , codeFragPackages :: [PackageName]
    } deriving
        ( Eq
        , Show
        , Generic
        , Typeable
        )
  
  type TypeFrag = CodeFrag TypeName
  
  instance Default TypeFrag where
    -- Minimal placeholder to use in case we cannot infer proper type
    def = CodeFrag {
              codeFragCode     =  ""
            , codeFragName     =  "Data.Aeson.Value"
            , codeFragImports  = ["qualified Data.Aeson"]
            , codeFragPackages = ["aeson"]
            }
  
  -- | List of modules imported for Autotyped declarations
  defaultImportedModules = importedModules
  
  -- | Given intended type name, and a list of
  --   text fields with JSON, return
  --   either an error, or an `EndpointResponse`
  --   that allows to declare and use this type
  --   in generated module.
  inferType :: Text -> [Value] -> TypeFrag
  inferType typeName []         = def
  inferType typeName jsonValues =
      CodeFrag {
            codeFragImports  = defaultImportedModules
          , codeFragCode     = displaySplitTypes splitTypeDescriptors
          , codeFragName     = typeName
          , codeFragPackages = requiredPackages
          }
    where
      valueTypes           = map extractType jsonValues
      -- FIXME: should be <> in Typelike?
      unifiedType          = foldr1 unifyTypes valueTypes
      splitTypeDescriptors = splitTypeByLabel typeName unifiedType
  
  