{-# 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

