content: -- | Types for postgres database
  module Guide.Database.Types
         (
         -- * Types
           DatabaseError(..)
         , CategoryRow (..)
         , ItemRow (..)
         , TraitRow (..)
         -- ** Lenses
         , CategoryRowLenses (..)
         , ItemRowLenses (..)
         , TraitRowLenses (..)
  
         -- * Type convertions
         , categoryRowToCategory
         , categoryToRowCategory
         , itemRowToItem
         , itemToRowItem
         , traitRowToTrait
         , traitToTraitRow
  
         ) where
  
  import Imports
  
  import Guide.Markdown (toMarkdownBlock, toMarkdownTree, toMarkdownInline, markdownBlockSource, markdownTreeSource, markdownInlineSource)
  import Guide.Types.Core
  import Guide.Uid
  import Guide.Utils (makeClassWithLenses, fields)
  import Guide.Database.Utils
  
  
  -- | Custom datatype errors for database
  data DatabaseError
    = ItemNotFound (Uid Item)
    | CategoryNotFound (Uid Category)
    | TraitNotFound (Uid Trait)
    | CategoryRowUpdateNotAllowed
        { deCategoryId :: Uid Category
        , deFieldName :: Text }
    | ItemRowUpdateNotAllowed
        { deItemId :: Uid Item
        , deFieldName :: Text }
    | TraitRowUpdateNotAllowed
        { deTraitId :: Uid Trait
        , deFieldName :: Text }
    deriving Show
  
  -- | Category intermediary type.
  data CategoryRow = CategoryRow
    { categoryRowUid             :: Uid Category
    , categoryRowTitle           :: Text
    , categoryRowCreated         :: UTCTime
    , categoryRowGroup           :: Text
    , categoryRowStatus          :: CategoryStatus
    , categoryRowNotes           :: Text
    , categoryRowEnabledSections :: Set ItemSection
    , categoryRowItemsOrder      :: [Uid Item]
    , categoryRowDeleted         :: Bool
    } deriving (Show, Generic)
  
  -- | Make CategoryRowLenses Class to use lenses with this type.
  makeClassWithLenses ''CategoryRow
  
  instance ToPostgresParams CategoryRow
  instance FromPostgresRow CategoryRow
  
  -- | Item intermediary type.
  data ItemRow = ItemRow
    { itemRowUid         :: Uid Item
    , itemRowName        :: Text
    , itemRowCreated     :: UTCTime
    , itemRowLink        :: Maybe Text
    , itemRowHackage     :: Maybe Text
    , itemRowSummary     :: Text
    , itemRowEcosystem   :: Text
    , itemRowNotes       :: Text
    , itemRowDeleted     :: Bool
    , itemRowCategoryUid :: Uid Category
    , itemRowProsOrder   :: [Uid Trait]
    , itemRowConsOrder   :: [Uid Trait]
    } deriving (Show, Generic)
  
  -- | Make ItemRowLenses Class to use lenses with this type.
  makeClassWithLenses ''ItemRow
  
  instance ToPostgresParams ItemRow
  instance FromPostgresRow ItemRow
  
  -- | Trait intermediary type.
  data TraitRow = TraitRow
    { traitRowUid     :: Uid Trait
    , traitRowContent :: Text
    , traitRowDeleted :: Bool
    , traitRowType    :: TraitType
    , traitRowItemUid :: Uid Item
    } deriving (Show, Generic)
  
  -- | Make TraitRowLenses Class to use lenses with this type.
  makeClassWithLenses ''TraitRow
  
  instance ToPostgresParams TraitRow
  instance FromPostgresRow TraitRow
  
  ----------------------------------------------------------------------------
  -- Convertions between types
  ----------------------------------------------------------------------------
  
  -- | Convert CategoryRow to Category.
  --
  -- To fetch items, use @selectItemRowsByCategory@ from
  -- "Guide.Database.Queries.Select". To fetch deleted items, use
  -- @selectDeletedItemRowsByCategory@.
  categoryRowToCategory
    :: "items" :! [Item]
    -> "itemsDeleted" :! [Item]
    -> CategoryRow
    -> Category
  categoryRowToCategory
    (arg #items -> items)
    (arg #itemsDeleted -> itemsDeleted)
    $(fields 'CategoryRow)
    =
    Category
      { categoryUid = categoryRowUid
      , categoryTitle = categoryRowTitle
      , categoryCreated = categoryRowCreated
      , categoryGroup = categoryRowGroup
      , categoryStatus = categoryRowStatus
      , categoryNotes = toMarkdownBlock categoryRowNotes
      , categoryItems = items
      , categoryItemsDeleted = itemsDeleted
      , categoryEnabledSections = categoryRowEnabledSections
      }
    where
      -- Ignored fields
      _ = categoryRowDeleted
      _ = categoryRowItemsOrder
  
  -- | Convert Category to CategoryRow.
  categoryToRowCategory
    :: Category
    -> "deleted" :! Bool
    -> CategoryRow
  categoryToRowCategory $(fields 'Category) (arg #deleted -> deleted) =
    CategoryRow
      { categoryRowUid = categoryUid
      , categoryRowTitle = categoryTitle
      , categoryRowCreated = categoryCreated
      , categoryRowGroup = categoryGroup
      , categoryRowStatus = categoryStatus
      , categoryRowNotes = markdownBlockSource categoryNotes
      , categoryRowEnabledSections = categoryEnabledSections
      , categoryRowItemsOrder = map itemUid categoryItems
      , categoryRowDeleted = deleted
      }
    where
      -- Ignored fields
      _ = categoryItemsDeleted
  
  -- | Convert ItemRow to Item.
  --
  -- To fetch traits, use @getTraitRowsByItem@ from
  -- "Guide.Database.Queries.Select". To fetch deleted traits, use
  -- @getDeletedTraitRowsByItem@.
  itemRowToItem
    :: "proTraits" :! [Trait]
    -> "proDeletedTraits" :! [Trait]
    -> "conTraits" :! [Trait]
    -> "conDeletedTraits" :! [Trait]
    -> ItemRow
    -> Item
  itemRowToItem
    (arg #proTraits -> proTraits)
    (arg #proDeletedTraits -> proDeletedTraits)
    (arg #conTraits -> conTraits)
    (arg #conDeletedTraits -> conDeletedTraits)
    $(fields 'ItemRow)
    =
    Item
      { itemUid = itemRowUid
      , itemName = itemRowName
      , itemCreated = itemRowCreated
      , itemHackage = itemRowHackage
      , itemSummary = toMarkdownBlock itemRowSummary
      , itemPros = proTraits
      , itemProsDeleted = proDeletedTraits
      , itemCons = conTraits
      , itemConsDeleted = conDeletedTraits
      , itemEcosystem = toMarkdownBlock itemRowEcosystem
      , itemNotes = toMarkdownTree prefix itemRowNotes
      , itemLink = itemRowLink
      }
    where
      prefix = "item-notes-" <> uidToText itemRowUid <> "-"
      -- Ignored fields
      _ = (itemRowConsOrder, itemRowProsOrder)
      _ = itemRowCategoryUid
      _ = itemRowDeleted
  
  -- | Convert Item to ItemRow.
  itemToRowItem :: Uid Category -> "deleted" :! Bool -> Item -> ItemRow
  itemToRowItem catId (arg #deleted -> deleted) $(fields 'Item) =
    ItemRow
      { itemRowUid = itemUid
      , itemRowName = itemName
      , itemRowCreated = itemCreated
      , itemRowLink = itemLink
      , itemRowHackage = itemHackage
      , itemRowSummary = markdownBlockSource itemSummary
      , itemRowEcosystem = markdownBlockSource itemEcosystem
      , itemRowNotes = markdownTreeSource itemNotes
      , itemRowDeleted = deleted
      , itemRowCategoryUid = catId
      , itemRowProsOrder = map traitUid itemPros
      , itemRowConsOrder = map traitUid itemCons
      }
    where
      -- Ignored fields
      _ = (itemConsDeleted, itemProsDeleted)
  
  -- | Convert TraitRow to Trait.
  traitRowToTrait :: TraitRow -> Trait
  traitRowToTrait $(fields 'TraitRow) =
    Trait
      { traitUid = traitRowUid
      , traitContent = toMarkdownInline traitRowContent
      }
    where
      -- Ignored fields
      _ = traitRowItemUid
      _ = traitRowType
      _ = traitRowDeleted
  
  -- | Convert Trait to TraitRow.
  traitToTraitRow
    :: Uid Item
    -> "deleted" :! Bool
    -> TraitType
    -> Trait
    -> TraitRow
  traitToTraitRow itemId (arg #deleted -> deleted) traitType $(fields 'Trait) =
    TraitRow
      { traitRowUid = traitUid
      , traitRowContent = markdownInlineSource traitContent
      , traitRowDeleted = deleted
      , traitRowType = traitType
      , traitRowItemUid = itemId
      }
  