content: {-# LANGUAGE TemplateHaskell #-}
  {-# OPTIONS_GHC -Wno-partial-type-signatures #-}
  
  -- | Guild endpoints
  module Calamity.HTTP.Guild (
    GuildRequest (..),
    CreateGuildData (..),
    ModifyGuildData (..),
    ChannelCreateData (..),
    ChannelPosition (..),
    ListMembersOptions (..),
    AddGuildMemberData (..),
    ModifyGuildMemberData,
    modifyGuildMemberNick,
    modifyGuildMemberRoles,
    modifyGuildMemberMute,
    modifyGuildMemberDeaf,
    modifyGuildMemberChannelID,
    CreateGuildBanData (..),
    ModifyGuildRoleData,
    modifyGuildRoleName,
    modifyGuildRolePermissions,
    modifyGuildRoleColour,
    modifyGuildRoleHoist,
    modifyGuildRoleMentionable,
    ModifyGuildRolePositionsData (..),
  ) where
  
  import Calamity.HTTP.Internal.Request
  import Calamity.HTTP.Internal.Route
  import Calamity.Internal.IntColour
  import Calamity.Internal.Utils (CalamityToJSON (..), CalamityToJSON' (..), (.=), (.?=))
  import Calamity.Types.Model.Channel
  import Calamity.Types.Model.Guild
  import Calamity.Types.Model.User
  import Calamity.Types.Model.Voice
  import Calamity.Types.Snowflake
  import Data.Aeson qualified as Aeson
  import Data.Aeson.KeyMap qualified as K
  import Data.Colour (Colour)
  import Data.Default.Class
  import Data.Text (Text)
  import Network.HTTP.Req
  import Optics
  
  data CreateGuildData = CreateGuildData
    { name :: Text
    , region :: Text
    , icon :: Text
    , verificationLevel :: Integer -- TODO: enums for these
    , defaultMessageNotifications :: Integer
    , explicitContentFilter :: Integer
    , roles :: [Role]
    , channels :: [Partial Channel]
    }
    deriving (Show)
    deriving (Aeson.ToJSON) via CalamityToJSON CreateGuildData
  
  instance CalamityToJSON' CreateGuildData where
    toPairs CreateGuildData {..} =
      [ "name" .= name
      , "region" .= region
      , "icon" .= icon
      , "verification_level" .= verificationLevel
      , "default_message_notifications" .= defaultMessageNotifications
      , "explicit_content_filter" .= explicitContentFilter
      , "roles" .= roles
      , "channels" .= channels
      ]
  
  data ModifyGuildData = ModifyGuildData
    { name :: Maybe Text
    , region :: Maybe Text
    , icon :: Maybe Text
    , verificationLevel :: Maybe Integer -- TODO: enums for these
    , defaultMessageNotifications :: Maybe Integer
    , explicitContentFilter :: Maybe Integer
    , afkChannelID :: Maybe (Snowflake GuildChannel)
    , afkTimeout :: Maybe Integer
    , ownerID :: Maybe (Snowflake User)
    , splash :: Maybe Text
    , banner :: Maybe Text
    , systemChannelID :: Maybe (Snowflake GuildChannel)
    }
    deriving (Show)
    deriving (Aeson.ToJSON) via CalamityToJSON ModifyGuildData
  
  instance Default ModifyGuildData where
    def =
      ModifyGuildData
        Nothing
        Nothing
        Nothing
        Nothing
        Nothing
        Nothing
        Nothing
        Nothing
        Nothing
        Nothing
        Nothing
        Nothing
  
  -- TODO: Make this work properly, a la modifyguildmember
  instance CalamityToJSON' ModifyGuildData where
    toPairs ModifyGuildData {..} =
      [ "name" .?= name
      , "region" .?= region
      , "icon" .?= icon
      , "verification_level" .?= verificationLevel
      , "default_message_notifications" .?= defaultMessageNotifications
      , "explicit_content_filter" .?= explicitContentFilter
      , "afk_timeout" .?= afkTimeout
      , "owner_id" .?= ownerID
      , "splash" .?= splash
      , "banner" .?= banner
      , "system_channel_id" .?= systemChannelID
      ]
  
  data ChannelCreateData = ChannelCreateData
    { name :: Text
    , type_ :: Maybe ChannelType
    , topic :: Maybe Text
    , bitrate :: Maybe Integer
    , userLimit :: Maybe Integer
    , rateLimitPerUser :: Maybe Integer
    , position :: Maybe Integer
    , permissionOverwrites :: Maybe [Overwrite]
    , parentID :: Maybe (Snowflake Category)
    , nsfw :: Maybe Bool
    }
    deriving (Show)
    deriving (Aeson.ToJSON) via CalamityToJSON ChannelCreateData
  
  instance CalamityToJSON' ChannelCreateData where
    toPairs ChannelCreateData {..} =
      [ "name" .= name
      , "type" .?= type_
      , "topic" .?= topic
      , "bitrate" .?= bitrate
      , "user_limit" .?= userLimit
      , "rate_limit_per_user" .?= rateLimitPerUser
      , "position" .?= position
      , "permission_overwrites" .?= permissionOverwrites
      , "parent_id" .?= parentID
      , "nsfw" .?= nsfw
      ]
  
  data ChannelPosition = ChannelPosition
    { id :: Snowflake GuildChannel
    , position :: Maybe Integer
    , lockPermissions :: Maybe Bool
    , parentID :: Snowflake GuildChannel
    }
    deriving (Show)
    deriving (Aeson.ToJSON) via CalamityToJSON ChannelPosition
  
  instance CalamityToJSON' ChannelPosition where
    toPairs ChannelPosition {..} =
      [ "id" .= id
      , "position" .= position
      , "lock_permissions" .= lockPermissions
      , "parent_id" .= parentID
      ]
  
  data ListMembersOptions = ListMembersOptions
    { limit :: Maybe Integer
    , after :: Maybe (Snowflake User)
    }
    deriving (Show)
    deriving (Aeson.ToJSON) via CalamityToJSON ListMembersOptions
  
  instance Default ListMembersOptions where
    def = ListMembersOptions Nothing Nothing
  
  instance CalamityToJSON' ListMembersOptions where
    toPairs ListMembersOptions {..} =
      [ "limit" .?= limit
      , "after" .?= after
      ]
  
  data SearchMembersOptions = SearchMembersOptions
    { limit :: Maybe Integer
    , query :: Text
    }
    deriving (Show)
    deriving (Aeson.ToJSON) via CalamityToJSON SearchMembersOptions
  
  instance CalamityToJSON' SearchMembersOptions where
    toPairs SearchMembersOptions {..} =
      [ "limit" .?= limit
      , "query" .= query
      ]
  
  data AddGuildMemberData = AddGuildMemberData
    { accessToken :: Text
    , nick :: Maybe Text
    , roles :: Maybe [Snowflake Role]
    , mute :: Maybe Bool
    , deaf :: Maybe Bool
    }
    deriving (Show)
    deriving (Aeson.ToJSON) via CalamityToJSON AddGuildMemberData
  
  instance CalamityToJSON' AddGuildMemberData where
    toPairs AddGuildMemberData {..} =
      [ "access_token" .= accessToken
      , "nick" .?= nick
      , "roles" .?= roles
      , "mute" .?= mute
      , "deaf" .?= deaf
      ]
  
  {- | Parameters to the Modify Guild Member endpoint.
  
   Use the provided methods (@modifyGuildMemberX@) to create a value with the
   field set, use the Semigroup instance to combine modifications.
  
   ==== Examples
  
   >>> encode $ modifyGuildMemberNick (Just "test") <> modifyGuildMemberDeaf Nothing
   "{\"nick\":\"test\",\"deaf\":null}"
  -}
  newtype ModifyGuildMemberData = ModifyGuildMemberData Aeson.Object
    deriving stock (Show)
    deriving newtype (Aeson.ToJSON, Semigroup, Monoid)
  
  modifyGuildMemberNick :: Maybe Text -> ModifyGuildMemberData
  modifyGuildMemberNick v = ModifyGuildMemberData $ K.fromList [("nick", Aeson.toJSON v)]
  
  modifyGuildMemberRoles :: Maybe [Snowflake Role] -> ModifyGuildMemberData
  modifyGuildMemberRoles v = ModifyGuildMemberData $ K.fromList [("roles", Aeson.toJSON v)]
  
  modifyGuildMemberMute :: Maybe Bool -> ModifyGuildMemberData
  modifyGuildMemberMute v = ModifyGuildMemberData $ K.fromList [("mute", Aeson.toJSON v)]
  
  modifyGuildMemberDeaf :: Maybe Bool -> ModifyGuildMemberData
  modifyGuildMemberDeaf v = ModifyGuildMemberData $ K.fromList [("deaf", Aeson.toJSON v)]
  
  modifyGuildMemberChannelID :: Maybe (Snowflake VoiceChannel) -> ModifyGuildMemberData
  modifyGuildMemberChannelID v = ModifyGuildMemberData $ K.fromList [("channel_id", Aeson.toJSON v)]
  
  data GetGuildBansOptions = GetGuildBansOptions
    { limit :: Maybe Int
    , before :: Maybe Int
    , after :: Maybe Int
    }
    deriving (Show)
  
  instance Default GetGuildBansOptions where
    def = GetGuildBansOptions Nothing Nothing Nothing
  
  data CreateGuildBanData = CreateGuildBanData
    { deleteMessageDays :: Maybe Integer
    , reason :: Maybe Text
    }
    deriving (Show)
  
  instance Default CreateGuildBanData where
    def = CreateGuildBanData Nothing Nothing
  
  {- | Parameters to the Modify Guild Role endpoint.
  
   Use the provided methods (@modifyGuildRoleX@) to create a value with the
   field set, use the Semigroup instance to combine parameters.
  
   ==== Examples
  
   >>> encode $ modifyGuildRoleName (Just "test") <> modifyGuildRolePermissions Nothing
   "{\"name\":\"test\",\"permissions\":null}"
  -}
  newtype ModifyGuildRoleData = ModifyGuildRoleData Aeson.Object
    deriving stock (Show)
    deriving newtype (Aeson.ToJSON, Semigroup, Monoid)
  
  modifyGuildRoleName :: Maybe Text -> ModifyGuildRoleData
  modifyGuildRoleName v = ModifyGuildRoleData $ K.fromList [("name", Aeson.toJSON v)]
  
  modifyGuildRolePermissions :: Maybe Permissions -> ModifyGuildRoleData
  modifyGuildRolePermissions v = ModifyGuildRoleData $ K.fromList [("permissions", Aeson.toJSON v)]
  
  modifyGuildRoleColour :: Maybe (Colour Double) -> ModifyGuildRoleData
  modifyGuildRoleColour v = ModifyGuildRoleData $ K.fromList [("colour", Aeson.toJSON (IntColour <$> v))]
  
  modifyGuildRoleHoist :: Maybe Bool -> ModifyGuildRoleData
  modifyGuildRoleHoist v = ModifyGuildRoleData $ K.fromList [("hoist", Aeson.toJSON v)]
  
  modifyGuildRoleMentionable :: Maybe Bool -> ModifyGuildRoleData
  modifyGuildRoleMentionable v = ModifyGuildRoleData $ K.fromList [("mentionable", Aeson.toJSON v)]
  
  data ModifyGuildRolePositionsData = ModifyGuildRolePositionsData
    { id :: Snowflake Role
    , position :: Maybe Integer
    }
    deriving (Show)
    deriving (Aeson.ToJSON) via CalamityToJSON ModifyGuildRolePositionsData
  
  instance CalamityToJSON' ModifyGuildRolePositionsData where
    toPairs ModifyGuildRolePositionsData {..} =
      [ "id" .= id
      , "postition" .?= position
      ]
  
  data GuildRequest a where
    CreateGuild :: CreateGuildData -> GuildRequest Guild
    GetGuild :: (HasID Guild g) => g -> GuildRequest Guild
    ModifyGuild :: (HasID Guild g) => g -> ModifyGuildData -> GuildRequest Guild
    DeleteGuild :: (HasID Guild g) => g -> GuildRequest ()
    GetGuildChannels :: (HasID Guild g) => g -> GuildRequest [Channel]
    CreateGuildChannel :: (HasID Guild g) => g -> ChannelCreateData -> GuildRequest Channel
    ModifyGuildChannelPositions :: (HasID Guild g) => g -> [ChannelPosition] -> GuildRequest ()
    GetGuildMember :: (HasID Guild g, HasID User u) => g -> u -> GuildRequest Member
    ListGuildMembers :: (HasID Guild g) => g -> ListMembersOptions -> GuildRequest [Member]
    SearchGuildMembers :: (HasID Guild g) => g -> SearchMembersOptions -> GuildRequest [Member]
    AddGuildMember :: (HasID Guild g, HasID User u) => g -> u -> AddGuildMemberData -> GuildRequest (Maybe Member)
    ModifyGuildMember :: (HasID Guild g, HasID User u) => g -> u -> ModifyGuildMemberData -> GuildRequest ()
    ModifyCurrentUserNick :: (HasID Guild g) => g -> Maybe Text -> GuildRequest ()
    AddGuildMemberRole :: (HasID Guild g, HasID User u, HasID Role r) => g -> u -> r -> GuildRequest ()
    RemoveGuildMemberRole :: (HasID Guild g, HasID User u, HasID Role r) => g -> u -> r -> GuildRequest ()
    RemoveGuildMember :: (HasID Guild g, HasID User u) => g -> u -> GuildRequest ()
    GetGuildBans :: (HasID Guild g) => g -> GetGuildBansOptions -> GuildRequest [BanData]
    GetGuildBan :: (HasID Guild g, HasID User u) => g -> u -> GuildRequest BanData
    CreateGuildBan :: (HasID Guild g, HasID User u) => g -> u -> CreateGuildBanData -> GuildRequest ()
    RemoveGuildBan :: (HasID Guild g, HasID User u) => g -> u -> GuildRequest ()
    GetGuildRoles :: (HasID Guild g) => g -> GuildRequest [Role]
    CreateGuildRole :: (HasID Guild g) => g -> ModifyGuildRoleData -> GuildRequest Role
    ModifyGuildRolePositions :: (HasID Guild g) => g -> ModifyGuildRolePositionsData -> GuildRequest [Role]
    ModifyGuildRole :: (HasID Guild g, HasID Role r) => g -> r -> ModifyGuildRoleData -> GuildRequest Role
    DeleteGuildRole :: (HasID Guild g, HasID Role r) => g -> r -> GuildRequest ()
    GetGuildPruneCount :: (HasID Guild g) => g -> Integer -> GuildRequest Integer
    BeginGuildPrune :: (HasID Guild g) => g -> Integer -> Bool -> GuildRequest (Maybe Integer)
    GetGuildVoiceRegions :: (HasID Guild g) => g -> GuildRequest [VoiceRegion]
    GetGuildInvites :: (HasID Guild g) => g -> GuildRequest [Invite]
  
  baseRoute :: Snowflake Guild -> RouteBuilder _
  baseRoute id =
    mkRouteBuilder
      // S "guilds"
      // ID @Guild
        & giveID id
  
  instance Request (GuildRequest a) where
    type Result (GuildRequest a) = a
  
    route (CreateGuild _) =
      mkRouteBuilder
        // S "guilds"
          & buildRoute
    route (GetGuild (getID -> gid)) =
      baseRoute gid
        & buildRoute
    route (ModifyGuild (getID -> gid) _) =
      baseRoute gid
        & buildRoute
    route (DeleteGuild (getID -> gid)) =
      baseRoute gid
        & buildRoute
    route (GetGuildChannels (getID -> gid)) =
      baseRoute gid
        // S "channels"
          & buildRoute
    route (CreateGuildChannel (getID -> gid) _) =
      baseRoute gid
        // S "channels"
          & buildRoute
    route (ModifyGuildChannelPositions (getID -> gid) _) =
      baseRoute gid
        // S "channels"
          & buildRoute
    route (GetGuildMember (getID -> gid) (getID @User -> uid)) =
      baseRoute gid
        // S "members"
        // ID @User
          & giveID uid
          & buildRoute
    route (ListGuildMembers (getID -> gid) _) =
      baseRoute gid
        // S "members"
          & buildRoute
    route (SearchGuildMembers (getID -> gid) _) =
      baseRoute gid
        // S "members"
        // S "search"
          & buildRoute
    route (AddGuildMember (getID -> gid) (getID @User -> uid) _) =
      baseRoute gid
        // S "members"
        // ID @User
          & giveID uid
          & buildRoute
    route (ModifyGuildMember (getID -> gid) (getID @User -> uid) _) =
      baseRoute gid
        // S "members"
        // ID @User
          & giveID uid
          & buildRoute
    route (ModifyCurrentUserNick (getID -> gid) _) =
      baseRoute gid
        // S "members"
        // S "@me"
        // S "nick"
          & buildRoute
    route (AddGuildMemberRole (getID -> gid) (getID @User -> uid) (getID @Role -> rid)) =
      baseRoute gid
        // S "members"
        // ID @User
        // S "roles"
        // ID @Role
          & giveID uid
          & giveID rid
          & buildRoute
    route (RemoveGuildMemberRole (getID -> gid) (getID @User -> uid) (getID @Role -> rid)) =
      baseRoute gid
        // S "members"
        // ID @User
        // S "roles"
        // ID @Role
          & giveID uid
          & giveID rid
          & buildRoute
    route (RemoveGuildMember (getID -> gid) (getID @User -> uid)) =
      baseRoute gid
        // S "members"
        // ID @User
          & giveID uid
          & buildRoute
    route (GetGuildBans (getID -> gid) _) =
      baseRoute gid
        // S "bans"
          & buildRoute
    route (GetGuildBan (getID -> gid) (getID @User -> uid)) =
      baseRoute gid
        // S "bans"
        // ID @User
          & giveID uid
          & buildRoute
    route (CreateGuildBan (getID -> gid) (getID @User -> uid) _) =
      baseRoute gid
        // S "bans"
        // ID @User
          & giveID uid
          & buildRoute
    route (RemoveGuildBan (getID -> gid) (getID @User -> uid)) =
      baseRoute gid
        // S "bans"
        // ID @User
          & giveID uid
          & buildRoute
    route (GetGuildRoles (getID -> gid)) =
      baseRoute gid
        // S "roles"
          & buildRoute
    route (CreateGuildRole (getID -> gid) _) =
      baseRoute gid
        // S "roles"
          & buildRoute
    route (ModifyGuildRolePositions (getID -> gid) _) =
      baseRoute gid
        // S "roles"
          & buildRoute
    route (ModifyGuildRole (getID -> gid) (getID @Role -> rid) _) =
      baseRoute gid
        // S "roles"
        // ID @Role
          & giveID rid
          & buildRoute
    route (DeleteGuildRole (getID -> gid) (getID @Role -> rid)) =
      baseRoute gid
        // S "roles"
        // ID @Role
          & giveID rid
          & buildRoute
    route (GetGuildPruneCount (getID -> gid) _) =
      baseRoute gid
        // S "prune"
          & buildRoute
    route (BeginGuildPrune (getID -> gid) _ _) =
      baseRoute gid
        // S "prune"
          & buildRoute
    route (GetGuildVoiceRegions (getID -> gid)) =
      baseRoute gid
        // S "regions"
          & buildRoute
    route (GetGuildInvites (getID -> gid)) =
      baseRoute gid
        // S "invites"
          & buildRoute
  
    action (CreateGuild o) = postWith' (ReqBodyJson o)
    action (GetGuild _) = getWith
    action (ModifyGuild _ o) = patchWith' (ReqBodyJson o)
    action (DeleteGuild _) = deleteWith
    action (GetGuildChannels _) = getWith
    action (CreateGuildChannel _ o) = postWith' (ReqBodyJson o)
    action (ModifyGuildChannelPositions _ o) = postWith' (ReqBodyJson o)
    action (GetGuildMember _ _) = getWith
    action (ListGuildMembers _ ListMembersOptions {limit, after}) =
      getWithP
        ("limit" =:? limit <> "after" =:? (fromSnowflake <$> after))
    action (SearchGuildMembers _ SearchMembersOptions {limit, query}) =
      getWithP
        ("limit" =:? limit <> "query" =: query)
    action (AddGuildMember _ _ o) = putWith' (ReqBodyJson o)
    action (ModifyGuildMember _ _ o) = patchWith' (ReqBodyJson o)
    action (ModifyCurrentUserNick _ nick) = patchWith' (ReqBodyJson $ Aeson.object ["nick" Aeson..= nick])
    action AddGuildMemberRole {} = putEmpty
    action RemoveGuildMemberRole {} = deleteWith
    action (RemoveGuildMember _ _) = deleteWith
    action (GetGuildBans _ GetGuildBansOptions {limit, before, after}) =
      getWithP
        ("limit" =:? limit <> "before" =:? before <> "after" =:? after)
    action (GetGuildBan _ _) = getWith
    action (CreateGuildBan _ _ CreateGuildBanData {deleteMessageDays}) =
      putEmptyP
        ("delete_message_days" =:? deleteMessageDays)
    action (RemoveGuildBan _ _) = deleteWith
    action (GetGuildRoles _) = getWith
    action (CreateGuildRole _ o) = postWith' (ReqBodyJson o)
    action (ModifyGuildRolePositions _ o) = patchWith' (ReqBodyJson o)
    action (ModifyGuildRole _ _ o) = patchWith' (ReqBodyJson o)
    action (DeleteGuildRole _ _) = deleteWith
    action (GetGuildPruneCount _ d) = getWithP ("days" =: d)
    action (BeginGuildPrune _ d r) = postEmptyP ("days" =: d <> "compute_prune_count" =: r)
    action (GetGuildVoiceRegions _) = getWith
    action (GetGuildInvites _) = getWith
  
    -- this is a bit of a hack
    -- TODO: add something to allow for contextual parsing
    modifyResponse _ = Prelude.id
  
  $(makeFieldLabelsNoPrefix ''CreateGuildData)
  $(makeFieldLabelsNoPrefix ''ModifyGuildData)
  $(makeFieldLabelsNoPrefix ''ChannelCreateData)
  $(makeFieldLabelsNoPrefix ''ChannelPosition)
  $(makeFieldLabelsNoPrefix ''ListMembersOptions)
  $(makeFieldLabelsNoPrefix ''AddGuildMemberData)
  $(makeFieldLabelsNoPrefix ''ModifyGuildRolePositionsData)
  $(makeFieldLabelsNoPrefix ''CreateGuildBanData)
  