packages feed

github-actions-0.1.0.0: src/Language/Github/Actions/Workflow/Trigger.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- |
-- Module      : Language.Github.Actions.Workflow.Trigger
-- Description : GitHub Actions workflow trigger events and configurations
-- Copyright   : (c) 2025 Bellroy Pty Ltd
-- License     : BSD-3-Clause
-- Maintainer  : Bellroy Tech Team <haskell@bellroy.com>
--
-- This module provides comprehensive support for GitHub Actions workflow triggers,
-- which determine when workflows should run.
--
-- Workflow triggers respond to various GitHub events such as pushes, pull requests,
-- issue updates, scheduled times, and external repository dispatch events. Each trigger
-- type can be configured with specific activity types and filtering criteria.
--
-- The main types include:
--
-- * 'WorkflowTrigger' - The primary trigger type supporting all GitHub event types
-- * Activity type enums for each event (e.g., 'PullRequestActivityType', 'IssuesActivityType')
-- * Attribute types for triggers with complex configuration (e.g., 'PushTriggerAttributes')
--
-- Example usage:
--
-- @
-- import Language.Github.Actions.Workflow.Trigger
--
-- -- Trigger on push to main branch
-- pushTrigger :: WorkflowTrigger
-- pushTrigger = PushTrigger $ PushTriggerAttributes
--   { branches = Just ("main" :| [])
--   , branchesIgnore = Nothing
--   , paths = Nothing
--   , pathsIgnore = Nothing
--   , tags = Nothing
--   }
--
-- -- Trigger on pull request opened or synchronized
-- prTrigger :: WorkflowTrigger
-- prTrigger = PullRequestTrigger $ PullRequestTriggerAttributes
--   { activityTypes = Just (PullRequestOpened :| [PullRequestSynchronize])
--   , branches = Nothing
--   , branchesIgnore = Nothing
--   , paths = Nothing
--   , pathsIgnore = Nothing
--   }
-- @
--
-- For more information about GitHub Actions events and triggers, see:
-- <https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows>
module Language.Github.Actions.Workflow.Trigger
  ( BranchProtectionRuleActivityType (..),
    CheckRunActivityType (..),
    DiscussionActivityType (..),
    DiscussionCommentActivityType (..),
    IssueCommentActivityType (..),
    IssuesActivityType (..),
    LabelActivityType (..),
    MilestoneActivityType (..),
    PullRequestActivityType (..),
    PullRequestReviewActivityType (..),
    PullRequestReviewCommentActivityType (..),
    PullRequestTargetActivityType (..),
    PullRequestTargetTriggerAttributes (..),
    PullRequestTriggerAttributes (..),
    PushTriggerAttributes (..),
    RegistryPackageActivityType (..),
    ReleaseActivityType (..),
    WorkflowCallAttributes (..),
    WorkflowDispatchAttributes (..),
    WorkflowRunActivityType (..),
    WorkflowRunTriggerAttributes (..),
    WorkflowTrigger (..),
    gen,
  )
where

import Control.Applicative ((<|>))
import Control.Monad (guard)
import Control.Monad.Fail.Hoist (hoistFail')
import Data.Aeson (FromJSON, ToJSON, ToJSONKey, (.:), (.:?), (.=))
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.KeyMap as AesonKeyMap
import qualified Data.Aeson.Types as Aeson
import Data.List.NonEmpty (NonEmpty (..), nonEmpty)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (catMaybes, fromMaybe, isJust)
import Data.String.Interpolate (i)
import Data.Text (Text)
import Data.Traversable (for)
import qualified Data.Vector as Vector
import GHC.Generics (Generic)
import Hedgehog (MonadGen)
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import Language.Github.Actions.Internal (inverseMap)

data BranchProtectionRuleActivityType
  = BranchProtectionRuleCreated
  | BranchProtectionRuleDeleted
  | BranchProtectionRuleEdited
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON BranchProtectionRuleActivityType where
  parseJSON =
    Aeson.withText "BranchProtectionRuleActivityType" $
      hoistFail' . parseBranchProtectionRuleActivityType

instance ToJSON BranchProtectionRuleActivityType where
  toJSON = Aeson.String . renderBranchProtectionRuleActivityType

renderBranchProtectionRuleActivityType :: BranchProtectionRuleActivityType -> Text
renderBranchProtectionRuleActivityType = \case
  BranchProtectionRuleCreated -> "created"
  BranchProtectionRuleDeleted -> "deleted"
  BranchProtectionRuleEdited -> "edited"

parseBranchProtectionRuleActivityType :: Text -> Either String BranchProtectionRuleActivityType
parseBranchProtectionRuleActivityType t =
  maybe (Left [i|Unknown BranchProtectionRuleActivityType: #{t}|]) Right $
    inverseMap renderBranchProtectionRuleActivityType t

data CheckRunActivityType
  = CheckRunCompleted
  | CheckRunCreated
  | CheckRunRequestedAction
  | CheckRunRerequested
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON CheckRunActivityType where
  parseJSON =
    Aeson.withText "CheckRunActivityType" $
      hoistFail' . parseCheckRunActivityType

instance ToJSON CheckRunActivityType where
  toJSON = Aeson.String . renderCheckRunActivityType

renderCheckRunActivityType :: CheckRunActivityType -> Text
renderCheckRunActivityType = \case
  CheckRunCompleted -> "completed"
  CheckRunCreated -> "created"
  CheckRunRequestedAction -> "requested_action"
  CheckRunRerequested -> "rerequested"

parseCheckRunActivityType :: Text -> Either String CheckRunActivityType
parseCheckRunActivityType t =
  maybe (Left [i|Unknown CheckRunActivityType: #{t}|]) Right $
    inverseMap renderCheckRunActivityType t

data DiscussionActivityType
  = DiscussionAnswered
  | DiscussionCategoryChanged
  | DiscussionCreated
  | DiscussionDeleted
  | DiscussionEdited
  | DiscussionLabeled
  | DiscussionLocked
  | DiscussionPinned
  | DiscussionTransferred
  | DiscussionUnanswered
  | DiscussionUnlabeled
  | DiscussionUnlocked
  | DiscussionUnpinned
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON DiscussionActivityType where
  parseJSON =
    Aeson.withText "DiscussionActivityType" $
      hoistFail' . parseDiscussionActivityType

instance ToJSON DiscussionActivityType where
  toJSON = Aeson.String . renderDiscussionActivityType

renderDiscussionActivityType :: DiscussionActivityType -> Text
renderDiscussionActivityType = \case
  DiscussionAnswered -> "answered"
  DiscussionCategoryChanged -> "category_changed"
  DiscussionCreated -> "created"
  DiscussionDeleted -> "deleted"
  DiscussionEdited -> "edited"
  DiscussionLabeled -> "labeled"
  DiscussionLocked -> "locked"
  DiscussionPinned -> "pinned"
  DiscussionTransferred -> "transferred"
  DiscussionUnanswered -> "unanswered"
  DiscussionUnlabeled -> "unlabeled"
  DiscussionUnlocked -> "unlocked"
  DiscussionUnpinned -> "unpinned"

parseDiscussionActivityType :: Text -> Either String DiscussionActivityType
parseDiscussionActivityType t =
  maybe (Left [i|Unknown DiscussionActivityType: #{t}|]) Right $
    inverseMap renderDiscussionActivityType t

data DiscussionCommentActivityType
  = DiscussionCommentCreated
  | DiscussionCommentDeleted
  | DiscussionCommentEdited
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON DiscussionCommentActivityType where
  parseJSON =
    Aeson.withText "DiscussionCommentActivityType" $
      hoistFail' . parseDiscussionCommentActivityType

instance ToJSON DiscussionCommentActivityType where
  toJSON = Aeson.String . renderDiscussionCommentActivityType

renderDiscussionCommentActivityType :: DiscussionCommentActivityType -> Text
renderDiscussionCommentActivityType = \case
  DiscussionCommentCreated -> "created"
  DiscussionCommentDeleted -> "deleted"
  DiscussionCommentEdited -> "edited"

parseDiscussionCommentActivityType :: Text -> Either String DiscussionCommentActivityType
parseDiscussionCommentActivityType t =
  maybe (Left [i|Unknown DiscussionCommentActivityType: #{t}|]) Right $
    inverseMap renderDiscussionCommentActivityType t

data IssueCommentActivityType
  = IssueCommentCreated
  | IssueCommentDeleted
  | IssueCommentEdited
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON IssueCommentActivityType where
  parseJSON =
    Aeson.withText "IssueCommentActivityType" $
      hoistFail' . parseIssueCommentActivityType

instance ToJSON IssueCommentActivityType where
  toJSON = Aeson.String . renderIssueCommentActivityType

renderIssueCommentActivityType :: IssueCommentActivityType -> Text
renderIssueCommentActivityType = \case
  IssueCommentCreated -> "created"
  IssueCommentDeleted -> "deleted"
  IssueCommentEdited -> "edited"

parseIssueCommentActivityType :: Text -> Either String IssueCommentActivityType
parseIssueCommentActivityType t =
  maybe (Left [i|Unknown IssueCommentActivityType: #{t}|]) Right $
    inverseMap renderIssueCommentActivityType t

data IssuesActivityType
  = IssuesAssigned
  | IssuesClosed
  | IssuesDeleted
  | IssuesDemilestoned
  | IssuesEdited
  | IssuesLabeled
  | IssuesLocked
  | IssuesMilestoned
  | IssuesOpened
  | IssuesPinned
  | IssuesReopened
  | IssuesTransferred
  | IssuesUnassigned
  | IssuesUnlabeled
  | IssuesUnlocked
  | IssuesUnpinned
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON IssuesActivityType where
  parseJSON =
    Aeson.withText "IssuesActivityType" $
      hoistFail' . parseIssuesActivityType

instance ToJSON IssuesActivityType where
  toJSON = Aeson.String . renderIssuesActivityType

renderIssuesActivityType :: IssuesActivityType -> Text
renderIssuesActivityType = \case
  IssuesAssigned -> "assigned"
  IssuesClosed -> "closed"
  IssuesDeleted -> "deleted"
  IssuesDemilestoned -> "demilestoned"
  IssuesEdited -> "edited"
  IssuesLabeled -> "labeled"
  IssuesLocked -> "locked"
  IssuesMilestoned -> "milestoned"
  IssuesOpened -> "opened"
  IssuesPinned -> "pinned"
  IssuesReopened -> "reopened"
  IssuesTransferred -> "transferred"
  IssuesUnassigned -> "unassigned"
  IssuesUnlabeled -> "unlabeled"
  IssuesUnlocked -> "unlocked"
  IssuesUnpinned -> "unpinned"

parseIssuesActivityType :: Text -> Either String IssuesActivityType
parseIssuesActivityType t =
  maybe (Left [i|Unknown IssuesActivityType: #{t}|]) Right $
    inverseMap renderIssuesActivityType t

data LabelActivityType
  = LabelCreated
  | LabelDeleted
  | LabelEdited
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON LabelActivityType where
  parseJSON =
    Aeson.withText "LabelActivityType" $
      hoistFail' . parseLabelActivityType

instance ToJSON LabelActivityType where
  toJSON = Aeson.String . renderLabelActivityType

renderLabelActivityType :: LabelActivityType -> Text
renderLabelActivityType = \case
  LabelCreated -> "created"
  LabelDeleted -> "deleted"
  LabelEdited -> "edited"

parseLabelActivityType :: Text -> Either String LabelActivityType
parseLabelActivityType t =
  maybe (Left [i|Unknown LabelActivityType: #{t}|]) Right $
    inverseMap renderLabelActivityType t

data MilestoneActivityType
  = MilestoneCreated
  | MilestoneClosed
  | MilestoneDeleted
  | MilestoneEdited
  | MilestoneOpened
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON MilestoneActivityType where
  parseJSON =
    Aeson.withText "MilestoneActivityType" $
      hoistFail' . parseMilestoneActivityType

instance ToJSON MilestoneActivityType where
  toJSON = Aeson.String . renderMilestoneActivityType

renderMilestoneActivityType :: MilestoneActivityType -> Text
renderMilestoneActivityType = \case
  MilestoneCreated -> "created"
  MilestoneClosed -> "closed"
  MilestoneDeleted -> "deleted"
  MilestoneEdited -> "edited"
  MilestoneOpened -> "opened"

parseMilestoneActivityType :: Text -> Either String MilestoneActivityType
parseMilestoneActivityType t =
  maybe (Left [i|Unknown MilestoneActivityType: #{t}|]) Right $
    inverseMap renderMilestoneActivityType t

-- | Activity types for pull request trigger events.
--
-- These specify which pull request activities should trigger the workflow.
-- Common combinations include opened/synchronized for CI workflows, or
-- closed for deployment workflows.
--
-- Example usage:
--
-- @
-- -- Trigger on pull request creation and updates
-- ciTrigger :: PullRequestActivityType
-- ciTrigger = PullRequestTrigger $ PullRequestTriggerAttributes
--  { activityTypes = Just (PullRequestOpened :| [PullRequestSynchronize])
--  , branches = Nothing
--  , branchesIgnore = Nothing
--  , paths = Nothing
--  , pathsIgnore = Nothing
--  }
-- @
--
-- For more details, see: <https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request>
data PullRequestActivityType
  = -- | Pull request was assigned to someone
    PullRequestAssigned
  | -- | Auto-merge was disabled
    PullRequestAutoMergeDisabled
  | -- | Auto-merge was enabled
    PullRequestAutoMergeEnabled
  | -- | Pull request was closed
    PullRequestClosed
  | -- | Pull request was converted to draft
    PullRequestConvertedToDraft
  | -- | Milestone was removed from pull request
    PullRequestDemilestoned
  | -- | Pull request was removed from merge queue
    PullRequestDequeued
  | -- | Pull request title or body was edited
    PullRequestEdited
  | -- | Pull request was added to merge queue
    PullRequestEnqueued
  | -- | Label was added to pull request
    PullRequestLabeled
  | -- | Pull request conversation was locked
    PullRequestLocked
  | -- | Milestone was added to pull request
    PullRequestMilestoned
  | -- | Pull request was opened
    PullRequestOpened
  | -- | Draft pull request was marked ready for review
    PullRequestReadyForReview
  | -- | Pull request was reopened
    PullRequestReopened
  | -- | Review request was removed
    PullRequestReviewRequestRemoved
  | -- | Review was requested
    PullRequestReviewRequested
  | -- | Pull request's head branch was updated
    PullRequestSynchronize
  | -- | Pull request was unassigned
    PullRequestUnassigned
  | -- | Label was removed from pull request
    PullRequestUnlabeled
  | -- | Pull request conversation was unlocked
    PullRequestUnlocked
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON PullRequestActivityType where
  parseJSON =
    Aeson.withText "PullRequestActivityType" $
      hoistFail' . parsePullRequestActivityType

instance ToJSON PullRequestActivityType where
  toJSON = Aeson.String . renderPullRequestActivityType

renderPullRequestActivityType :: PullRequestActivityType -> Text
renderPullRequestActivityType = \case
  PullRequestAssigned -> "assigned"
  PullRequestAutoMergeDisabled -> "auto_merge_disabled"
  PullRequestAutoMergeEnabled -> "auto_merge_enabled"
  PullRequestClosed -> "closed"
  PullRequestConvertedToDraft -> "converted_to_draft"
  PullRequestDemilestoned -> "demilestoned"
  PullRequestDequeued -> "dequeued"
  PullRequestEdited -> "edited"
  PullRequestEnqueued -> "enqueued"
  PullRequestLabeled -> "labeled"
  PullRequestLocked -> "locked"
  PullRequestMilestoned -> "milestoned"
  PullRequestOpened -> "opened"
  PullRequestReadyForReview -> "ready_for_review"
  PullRequestReopened -> "reopened"
  PullRequestReviewRequestRemoved -> "review_request_removed"
  PullRequestReviewRequested -> "review_requested"
  PullRequestSynchronize -> "synchronize"
  PullRequestUnassigned -> "unassigned"
  PullRequestUnlabeled -> "unlabeled"
  PullRequestUnlocked -> "unlocked"

parsePullRequestActivityType :: Text -> Either String PullRequestActivityType
parsePullRequestActivityType t =
  maybe (Left [i|Unknown PullRequestActivityType: #{t}|]) Right $
    inverseMap renderPullRequestActivityType t

data PullRequestTriggerAttributes = PullRequestTriggerAttributes
  { activityTypes :: Maybe (NonEmpty PullRequestActivityType),
    branches :: Maybe (NonEmpty Text),
    branchesIgnore :: Maybe (NonEmpty Text),
    paths :: Maybe (NonEmpty Text),
    pathsIgnore :: Maybe (NonEmpty Text)
  }
  deriving stock (Eq, Generic, Ord, Show)

instance FromJSON PullRequestTriggerAttributes where
  parseJSON = Aeson.withObject "PullRequestTriggerAttributes" $ \o -> do
    activityTypes <- o .:? "types"
    branches <- o .:? "branches"
    branchesIgnore <- o .:? "branches-ignore"
    paths <- o .:? "paths"
    pathsIgnore <- o .:? "paths-ignore"
    pure PullRequestTriggerAttributes {..}

instance ToJSON PullRequestTriggerAttributes where
  toJSON PullRequestTriggerAttributes {..} =
    Aeson.object $
      catMaybes
        [ ("branches" .=) <$> branches,
          ("branches-ignore" .=) <$> branchesIgnore,
          ("paths" .=) <$> paths,
          ("paths-ignore" .=) <$> pathsIgnore,
          ("types" .=) <$> activityTypes
        ]

data PullRequestReviewActivityType
  = PullRequestReviewDismissed
  | PullRequestReviewEdited
  | PullRequestReviewSubmitted
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON PullRequestReviewActivityType where
  parseJSON =
    Aeson.withText "PullRequestReviewActivityType" $
      hoistFail' . parsePullRequestReviewActivityType

instance ToJSON PullRequestReviewActivityType where
  toJSON = Aeson.String . renderPullRequestReviewActivityType

renderPullRequestReviewActivityType :: PullRequestReviewActivityType -> Text
renderPullRequestReviewActivityType = \case
  PullRequestReviewDismissed -> "dismissed"
  PullRequestReviewEdited -> "edited"
  PullRequestReviewSubmitted -> "submitted"

parsePullRequestReviewActivityType :: Text -> Either String PullRequestReviewActivityType
parsePullRequestReviewActivityType t =
  maybe (Left [i|Unknown PullRequestReviewActivityType: #{t}|]) Right $
    inverseMap renderPullRequestReviewActivityType t

data PullRequestReviewCommentActivityType
  = PullRequestReviewCommentCreated
  | PullRequestReviewCommentDeleted
  | PullRequestReviewCommentEdited
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON PullRequestReviewCommentActivityType where
  parseJSON =
    Aeson.withText "PullRequestReviewCommentActivityType" $
      hoistFail' . parsePullRequestReviewCommentActivityType

instance ToJSON PullRequestReviewCommentActivityType where
  toJSON = Aeson.String . renderPullRequestReviewCommentActivityType

renderPullRequestReviewCommentActivityType :: PullRequestReviewCommentActivityType -> Text
renderPullRequestReviewCommentActivityType = \case
  PullRequestReviewCommentCreated -> "created"
  PullRequestReviewCommentDeleted -> "deleted"
  PullRequestReviewCommentEdited -> "edited"

parsePullRequestReviewCommentActivityType :: Text -> Either String PullRequestReviewCommentActivityType
parsePullRequestReviewCommentActivityType t =
  maybe (Left [i|Unknown PullRequestReviewCommentActivityType: #{t}|]) Right $
    inverseMap renderPullRequestReviewCommentActivityType t

data PullRequestTargetActivityType
  = PullRequestTargetAssigned
  | PullRequestTargetAutoMergeDisabled
  | PullRequestTargetAutoMergeEnabled
  | PullRequestTargetClosed
  | PullRequestTargetConvertedToDraft
  | PullRequestTargetEdited
  | PullRequestTargetLabeled
  | PullRequestTargetLocked
  | PullRequestTargetOpened
  | PullRequestTargetReadyForReview
  | PullRequestTargetReopened
  | PullRequestTargetReviewRequestRemoved
  | PullRequestTargetReviewRequested
  | PullRequestTargetSynchronize
  | PullRequestTargetUnassigned
  | PullRequestTargetUnlabeled
  | PullRequestTargetUnlocked
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON PullRequestTargetActivityType where
  parseJSON =
    Aeson.withText "PullRequestTargetActivityType" $
      hoistFail' . parsePullRequestTargetActivityType

instance ToJSON PullRequestTargetActivityType where
  toJSON = Aeson.String . renderPullRequestTargetActivityType

renderPullRequestTargetActivityType :: PullRequestTargetActivityType -> Text
renderPullRequestTargetActivityType = \case
  PullRequestTargetAssigned -> "assigned"
  PullRequestTargetAutoMergeDisabled -> "auto_merge_disabled"
  PullRequestTargetAutoMergeEnabled -> "auto_merge_enabled"
  PullRequestTargetClosed -> "closed"
  PullRequestTargetConvertedToDraft -> "converted_to_draft"
  PullRequestTargetEdited -> "edited"
  PullRequestTargetLabeled -> "labeled"
  PullRequestTargetLocked -> "locked"
  PullRequestTargetOpened -> "opened"
  PullRequestTargetReadyForReview -> "ready_for_review"
  PullRequestTargetReopened -> "reopened"
  PullRequestTargetReviewRequestRemoved -> "review_request_removed"
  PullRequestTargetReviewRequested -> "review_requested"
  PullRequestTargetSynchronize -> "synchronize"
  PullRequestTargetUnassigned -> "unassigned"
  PullRequestTargetUnlabeled -> "unlabeled"
  PullRequestTargetUnlocked -> "unlocked"

parsePullRequestTargetActivityType :: Text -> Either String PullRequestTargetActivityType
parsePullRequestTargetActivityType t =
  maybe (Left [i|Unknown PullRequestTargetActivityType: #{t}|]) Right $
    inverseMap renderPullRequestTargetActivityType t

data PullRequestTargetTriggerAttributes = PullRequestTargetTriggerAttributes
  { activityTypes :: Maybe (NonEmpty PullRequestTargetActivityType),
    branches :: Maybe (NonEmpty Text),
    branchesIgnore :: Maybe (NonEmpty Text),
    paths :: Maybe (NonEmpty Text),
    pathsIgnore :: Maybe (NonEmpty Text)
  }
  deriving stock (Eq, Generic, Ord, Show)

instance FromJSON PullRequestTargetTriggerAttributes where
  parseJSON = Aeson.withObject "PullRequestTargetTriggerAttributes" $ \o -> do
    activityTypes <- o .:? "types"
    branches <- o .:? "branches"
    branchesIgnore <- o .:? "branches-ignore"
    paths <- o .:? "paths"
    pathsIgnore <- o .:? "paths-ignore"
    pure PullRequestTargetTriggerAttributes {..}

instance ToJSON PullRequestTargetTriggerAttributes where
  toJSON PullRequestTargetTriggerAttributes {..} =
    Aeson.object $
      catMaybes
        [ ("branches" .=) <$> branches,
          ("branches-ignore" .=) <$> branchesIgnore,
          ("paths" .=) <$> paths,
          ("paths-ignore" .=) <$> pathsIgnore,
          ("types" .=) <$> activityTypes
        ]

-- | Configuration attributes for push trigger events.
--
-- Push triggers can be filtered by branches, paths, and tags to control exactly
-- when the workflow should run. This allows for fine-grained control over which
-- repository changes should initiate workflow execution.
--
-- Example usage:
--
-- @
-- -- Trigger on pushes to main or develop branches
-- pushMainDevelop :: PushTriggerAttributes
-- pushMainDevelop = PushTriggerAttributes
--  { branches = Just ("main" :| ["develop"])
--  , branchesIgnore = Nothing
--  , paths = Nothing
--  , pathsIgnore = Nothing
--  , tags = Nothing
--  }
--
-- -- Trigger on pushes to docs directory, ignoring gh-pages
-- pushDocsOnly :: PushTriggerAttributes
-- pushDocsOnly = PushTriggerAttributes
--  { branches = Nothing
--  , branchesIgnore = Just ("gh-pages" :| [])
--  , paths = Just ("docs\/**" :| [])
--  , pathsIgnore = Nothing
--  , tags = Nothing
--  }
-- @
--
-- For more details, see: <https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#push>
data PushTriggerAttributes = PushTriggerAttributes
  { -- | Branches to include (default: all)
    branches :: Maybe (NonEmpty Text),
    -- | Branches to exclude
    branchesIgnore :: Maybe (NonEmpty Text),
    -- | File paths to include (default: all)
    paths :: Maybe (NonEmpty Text),
    -- | File paths to exclude
    pathsIgnore :: Maybe (NonEmpty Text),
    -- | Tags to include
    tags :: Maybe (NonEmpty Text)
  }
  deriving stock (Eq, Generic, Ord, Show)

instance FromJSON PushTriggerAttributes where
  parseJSON = Aeson.withObject "PushTriggerAttributes" $ \o -> do
    branches <- o .:? "branches"
    branchesIgnore <- o .:? "branches-ignore"
    paths <- o .:? "paths"
    pathsIgnore <- o .:? "paths-ignore"
    tags <- o .:? "tags"
    pure PushTriggerAttributes {..}

instance ToJSON PushTriggerAttributes where
  toJSON PushTriggerAttributes {..} =
    Aeson.object $
      catMaybes
        [ ("branches" .=) <$> branches,
          ("branches-ignore" .=) <$> branchesIgnore,
          ("paths" .=) <$> paths,
          ("paths-ignore" .=) <$> pathsIgnore,
          ("tags" .=) <$> tags
        ]

data RegistryPackageActivityType
  = RegistryPackagePublished
  | RegistryPackageUpdated
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON RegistryPackageActivityType where
  parseJSON =
    Aeson.withText "RegistryPackageActivityType" $
      hoistFail' . parseRegistryPackageActivityType

instance ToJSON RegistryPackageActivityType where
  toJSON = Aeson.String . renderRegistryPackageActivityType

renderRegistryPackageActivityType :: RegistryPackageActivityType -> Text
renderRegistryPackageActivityType = \case
  RegistryPackagePublished -> "published"
  RegistryPackageUpdated -> "updated"

parseRegistryPackageActivityType :: Text -> Either String RegistryPackageActivityType
parseRegistryPackageActivityType t =
  maybe (Left [i|Unknown RegistryPackageActivityType: #{t}|]) Right $
    inverseMap renderRegistryPackageActivityType t

data ReleaseActivityType
  = ReleaseCreated
  | ReleaseDeleted
  | ReleaseEdited
  | ReleasePrereleased
  | ReleasePublished
  | ReleaseReleased
  | ReleaseUnpublished
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON ReleaseActivityType where
  parseJSON =
    Aeson.withText "ReleaseActivityType" $ hoistFail' . parseReleaseActivityType

instance ToJSON ReleaseActivityType where
  toJSON = Aeson.String . renderReleaseActivityType

renderReleaseActivityType :: ReleaseActivityType -> Text
renderReleaseActivityType = \case
  ReleaseCreated -> "created"
  ReleaseDeleted -> "deleted"
  ReleaseEdited -> "edited"
  ReleasePrereleased -> "prereleased"
  ReleasePublished -> "published"
  ReleaseReleased -> "released"
  ReleaseUnpublished -> "unpublished"

parseReleaseActivityType :: Text -> Either String ReleaseActivityType
parseReleaseActivityType t =
  maybe (Left [i|Unknown ReleaseActivityType: #{t}|]) Right $
    inverseMap renderReleaseActivityType t

data WorkflowCallInputType
  = WorkflowCallInputTypeBoolean
  | WorkflowCallInputTypeNumber
  | WorkflowCallInputTypeString
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON WorkflowCallInputType where
  parseJSON =
    Aeson.withText "WorkflowCallInputType" $ hoistFail' . parseWorkflowCallInputType

instance ToJSON WorkflowCallInputType where
  toJSON = Aeson.String . renderWorkflowCallInputType

renderWorkflowCallInputType :: WorkflowCallInputType -> Text
renderWorkflowCallInputType = \case
  WorkflowCallInputTypeBoolean -> "boolean"
  WorkflowCallInputTypeNumber -> "number"
  WorkflowCallInputTypeString -> "string"

parseWorkflowCallInputType :: Text -> Either String WorkflowCallInputType
parseWorkflowCallInputType t =
  maybe (Left [i|Unknown WorkflowCallInputType: #{t}|]) Right $
    inverseMap renderWorkflowCallInputType t

data WorkflowCallInput = WorkflowCallInput
  { description :: Maybe Text,
    default_ :: Maybe Aeson.Value,
    required :: Maybe Bool,
    type_ :: WorkflowCallInputType
  }
  deriving stock (Eq, Generic, Ord, Show)

instance FromJSON WorkflowCallInput where
  parseJSON = Aeson.withObject "WorkflowCallInput" $ \o -> do
    description <- o .:? "description"
    default_ <- o .:? "default"
    required <- o .:? "required"
    type_ <- o .: "type"
    pure WorkflowCallInput {..}

instance ToJSON WorkflowCallInput where
  toJSON WorkflowCallInput {..} =
    Aeson.object $
      catMaybes
        [ ("description" .=) <$> description,
          ("default" .=) <$> default_,
          ("required" .=) <$> required,
          Just $ "type" .= type_
        ]

data WorkflowCallOutput = WorkflowCallOutput
  { description :: Maybe Text,
    value :: Text
  }
  deriving stock (Eq, Generic, Ord, Show)

instance FromJSON WorkflowCallOutput where
  parseJSON = Aeson.withObject "WorkflowCallOutput" $ \o -> do
    description <- o .:? "description"
    value <- o .: "value"
    pure WorkflowCallOutput {..}

instance ToJSON WorkflowCallOutput where
  toJSON WorkflowCallOutput {..} =
    Aeson.object $
      catMaybes
        [ ("description" .=) <$> description,
          Just $ "value" .= value
        ]

data WorkflowCallSecret = WorkflowCallSecret
  { description :: Maybe Text,
    required :: Maybe Bool
  }
  deriving stock (Eq, Generic, Ord, Show)

instance FromJSON WorkflowCallSecret where
  parseJSON = Aeson.withObject "WorkflowCallSecret" $ \o -> do
    description <- o .:? "description"
    required <- o .:? "required"
    pure WorkflowCallSecret {..}

instance ToJSON WorkflowCallSecret where
  toJSON WorkflowCallSecret {..} =
    Aeson.object $
      catMaybes
        [ ("description" .=) <$> description,
          ("required" .=) <$> required
        ]

data WorkflowCallAttributes = WorkflowCallAttributes
  { inputs :: Maybe (Map Text WorkflowCallInput),
    outputs :: Maybe (Map Text WorkflowCallOutput),
    secrets :: Maybe (Map Text WorkflowCallSecret)
  }
  deriving stock (Eq, Generic, Ord, Show)

instance FromJSON WorkflowCallAttributes where
  parseJSON = Aeson.withObject "WorkflowCallAttributes" $ \o -> do
    inputs <- o .:? "inputs"
    outputs <- o .:? "outputs"
    secrets <- o .:? "secrets"
    pure WorkflowCallAttributes {..}

instance ToJSON WorkflowCallAttributes where
  toJSON WorkflowCallAttributes {..} =
    Aeson.object $
      catMaybes
        [ ("inputs" .=) <$> inputs,
          ("outputs" .=) <$> outputs,
          ("secrets" .=) <$> secrets
        ]

data WorkflowDispatchInputType
  = WorkflowDispatchInputTypeBoolean
  | WorkflowDispatchInputTypeChoice (NonEmpty Text)
  | WorkflowDispatchInputTypeEnvironment
  | WorkflowDispatchInputTypeNumber
  | WorkflowDispatchInputTypeString
  deriving stock (Eq, Generic, Ord, Show)

instance FromJSON WorkflowDispatchInputType where
  parseJSON = Aeson.withObject "WorkflowDispatchInputType" $ \o -> do
    t :: Text <- o .: "type"
    maybeOptions <- o .:? "options"
    case t of
      "boolean" -> pure WorkflowDispatchInputTypeBoolean
      "choice" -> case maybeOptions of
        Just (op :| ops) -> pure $ WorkflowDispatchInputTypeChoice $ op :| ops
        Nothing -> fail "Expected a non-empty list of options"
      "environment" -> pure WorkflowDispatchInputTypeEnvironment
      "number" -> pure WorkflowDispatchInputTypeNumber
      "string" -> pure WorkflowDispatchInputTypeString
      _ -> fail [i|Unknown WorkflowDispatchInputType: #{t}|]

data WorkflowDispatchInput = WorkflowDispatchInput
  { description :: Maybe Text,
    default_ :: Maybe Aeson.Value,
    required :: Maybe Bool,
    type_ :: Maybe WorkflowDispatchInputType
  }
  deriving stock (Eq, Generic, Ord, Show)

instance FromJSON WorkflowDispatchInput where
  parseJSON = Aeson.withObject "WorkflowDispatchInput" $ \o -> do
    description <- o .:? "description"
    default_ <- o .:? "default"
    required <- o .:? "required"
    workflowDispatchInputTypeText :: Maybe Text <- o .:? "type"
    type_ <-
      maybe
        (pure Nothing)
        (const . Aeson.parseJSON $ Aeson.Object o)
        workflowDispatchInputTypeText
    pure WorkflowDispatchInput {..}

instance ToJSON WorkflowDispatchInput where
  toJSON WorkflowDispatchInput {..} =
    Aeson.object $
      catMaybes
        [ ("description" .=) <$> description,
          ("default" .=) <$> default_,
          ("required" .=) <$> required,
          ("type" .=)
            . ( \case
                  WorkflowDispatchInputTypeBoolean ->
                    "boolean" :: Text
                  WorkflowDispatchInputTypeChoice _ ->
                    "choice"
                  WorkflowDispatchInputTypeEnvironment ->
                    "environment"
                  WorkflowDispatchInputTypeNumber ->
                    "number"
                  WorkflowDispatchInputTypeString ->
                    "string"
              )
            <$> type_,
          type_
            >>= ( \case
                    WorkflowDispatchInputTypeChoice ops ->
                      Just $ "options" .= ops
                    _ ->
                      Nothing
                )
        ]

newtype WorkflowDispatchAttributes = WorkflowDispatchAttributes
  { inputs :: Maybe (Map Text WorkflowDispatchInput)
  }
  deriving stock (Eq, Generic, Ord, Show)

instance FromJSON WorkflowDispatchAttributes where
  parseJSON = Aeson.withObject "WorkflowDispatchAttributes" $ \o -> do
    inputs <- o .:? "inputs"
    pure WorkflowDispatchAttributes {..}

instance ToJSON WorkflowDispatchAttributes where
  toJSON WorkflowDispatchAttributes {..} =
    Aeson.object $
      catMaybes
        [ ("inputs" .=) <$> inputs
        ]

data WorkflowRunActivityType
  = WorkflowRunCompleted
  | WorkflowRunInProgress
  | WorkflowRunRequested
  deriving stock (Bounded, Enum, Eq, Generic, Ord, Show)

instance FromJSON WorkflowRunActivityType where
  parseJSON =
    Aeson.withText "WorkflowRunActivityType" $ hoistFail' . parseWorkflowRunActivityType

instance ToJSON WorkflowRunActivityType where
  toJSON = Aeson.String . renderWorkflowRunActivityType

renderWorkflowRunActivityType :: WorkflowRunActivityType -> Text
renderWorkflowRunActivityType = \case
  WorkflowRunCompleted -> "completed"
  WorkflowRunInProgress -> "in_progress"
  WorkflowRunRequested -> "requested"

parseWorkflowRunActivityType :: Text -> Either String WorkflowRunActivityType
parseWorkflowRunActivityType t =
  maybe (Left [i|Unknown WorkflowRunActivityType: #{t}|]) Right $
    inverseMap renderWorkflowRunActivityType t

data WorkflowRunTriggerAttributes = WorkflowRunTriggerAttributes
  { activityTypes :: NonEmpty WorkflowRunActivityType,
    workflows :: Maybe (NonEmpty Text),
    branches :: Maybe (NonEmpty Text),
    branchesIgnore :: Maybe (NonEmpty Text)
  }
  deriving stock (Eq, Generic, Ord, Show)

instance FromJSON WorkflowRunTriggerAttributes where
  parseJSON = Aeson.withObject "WorkflowRunTriggerAttributes" $ \o -> do
    activityTypes <- o .: "types"
    workflows <- o .:? "workflows"
    branches <- o .:? "branches"
    branchesIgnore <- o .:? "branches-ignore"
    pure WorkflowRunTriggerAttributes {..}

instance ToJSON WorkflowRunTriggerAttributes where
  toJSON WorkflowRunTriggerAttributes {..} =
    Aeson.object $
      catMaybes
        [ Just $ "types" .= activityTypes,
          ("workflows" .=) <$> workflows,
          ("branches" .=) <$> branches,
          ("branches-ignore" .=) <$> branchesIgnore
        ]

-- | Comprehensive enumeration of all GitHub Actions workflow trigger events.
--
-- Each trigger corresponds to a specific GitHub event that can initiate a workflow run.
-- Many triggers can be configured with activity types to specify exactly which sub-events
-- should cause the workflow to run.
--
-- Common trigger examples:
--
-- * 'PushTrigger' - Runs on pushes to repository branches or tags
-- * 'PullRequestTrigger' - Runs on pull request events (open, sync, close, etc.)
-- * 'ScheduleTrigger' - Runs on a schedule using cron syntax
-- * 'WorkflowDispatchTrigger' - Allows manual workflow execution
-- * 'IssuesTrigger' - Runs on issue events (open, close, label, etc.)
--
-- For triggers with complex configuration, attribute types provide filtering options:
--
-- @
-- -- Push trigger with branch filtering
-- pushToMain :: WorkflowTrigger
-- pushToMain = PushTrigger $ PushTriggerAttributes
--  { branches = Just ("main" :| [])
--  , branchesIgnore = Nothing
--  , paths = Nothing
--  , pathsIgnore = Nothing
--  , tags = Nothing
--  }
--
-- -- Issue trigger for specific activity types
-- issueEvents :: WorkflowTrigger
-- issueEvents = IssuesTrigger (IssuesOpened :| [IssuesClosed, IssuesLabeled])
-- @
--
-- For complete event documentation, see: <https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows>
data WorkflowTrigger
  = -- | Branch protection rule events
    BranchProtectionRuleTrigger (NonEmpty BranchProtectionRuleActivityType)
  | -- | Check run events
    CheckRunTrigger (NonEmpty CheckRunActivityType)
  | -- | Check suite completion
    CheckSuiteCompletedTrigger
  | -- | Branch or tag creation
    CreateTrigger
  | -- | Branch or tag deletion
    DeleteTrigger
  | -- | Deployment events
    DeploymentTrigger
  | -- | Deployment status changes
    DeploymentStatusTrigger
  | -- | Discussion events
    DiscussionTrigger (NonEmpty DiscussionActivityType)
  | -- | Discussion comment events
    DiscussionCommentTrigger (NonEmpty DiscussionCommentActivityType)
  | -- | Repository fork events
    ForkTrigger
  | -- | Wiki page events
    GollumTrigger
  | -- | Issue comment events
    IssueCommentTrigger (NonEmpty IssueCommentActivityType)
  | -- | Issue events
    IssuesTrigger (NonEmpty IssuesActivityType)
  | -- | Label events
    LabelTrigger (NonEmpty LabelActivityType)
  | -- | Merge group check requests
    MergeGroupChecksRequestedTrigger
  | -- | Milestone events
    MilestoneTrigger (NonEmpty MilestoneActivityType)
  | -- | GitHub Pages build events
    PageBuildTrigger
  | -- | Repository publicity changes
    PublicTrigger
  | -- | Pull request events with filtering
    PullRequestTrigger PullRequestTriggerAttributes
  | -- | Pull request review events
    PullRequestReviewTrigger (NonEmpty PullRequestReviewActivityType)
  | -- | PR review comment events
    PullRequestReviewCommentTrigger (NonEmpty PullRequestReviewCommentActivityType)
  | -- | Pull request target events with filtering
    PullRequestTargetTrigger PullRequestTargetTriggerAttributes
  | -- | Push events with filtering
    PushTrigger PushTriggerAttributes
  | -- | Package registry events
    RegistryPackageTrigger (NonEmpty RegistryPackageActivityType)
  | -- | Release events
    ReleaseTrigger (NonEmpty ReleaseActivityType)
  | -- | External repository dispatch events
    RepositoryDispatchTrigger (NonEmpty Text)
  | -- | Scheduled events (cron expressions)
    ScheduleTrigger (NonEmpty Text)
  | -- | Commit status events
    StatusTrigger
  | -- | Repository watch events
    WatchStartedTrigger
  | -- | Reusable workflow calls
    WorkflowCallTrigger WorkflowCallAttributes
  | -- | Manual workflow dispatch
    WorkflowDispatchTrigger WorkflowDispatchAttributes
  | -- | Workflow run events with filtering
    WorkflowRunTrigger WorkflowRunTriggerAttributes
  deriving stock (Eq, Generic, Ord, Show)

instance FromJSON WorkflowTrigger where
  parseJSON = Aeson.withObject "WorkflowTrigger" $ \o -> do
    maybeBranchProtectionRuleActivityTypes <-
      maybeActivityTypesInNestedObject o "branch_protection_rule"
    maybeCheckRunActivityTypes <- maybeActivityTypesInNestedObject o "check_run"
    maybeCheckSuiteActivityTypes :: Maybe (NonEmpty Text) <-
      maybeActivityTypesInNestedObject o "check_suite"
    maybeCreate :: Maybe Aeson.Value <- o .:? "create"
    maybeDelete :: Maybe Aeson.Value <- o .:? "delete"
    maybeDeployment :: Maybe Aeson.Value <- o .:? "deployment"
    maybeDeploymentStatus :: Maybe Aeson.Value <- o .:? "deployment_status"
    maybeDiscussionActivityTypes <-
      maybeActivityTypesInNestedObject o "discussion"
    maybeDiscussionCommentActivityTypes <-
      maybeActivityTypesInNestedObject o "discussion_comment"
    maybeFork :: Maybe Aeson.Value <- o .:? "fork"
    maybeGollum :: Maybe Aeson.Value <- o .:? "gollum"
    maybeIssueCommentActivityTypes <-
      maybeActivityTypesInNestedObject o "issue_comment"
    maybeIssuesActivityTypes <- maybeActivityTypesInNestedObject o "issues"
    maybeLabelActivityTypes <- maybeActivityTypesInNestedObject o "label"
    maybeMergeGroupChecksRequestedActivityTypes :: Maybe (NonEmpty Text) <-
      maybeActivityTypesInNestedObject o "merge_group"
    maybeMilestoneActivityTypes <- maybeActivityTypesInNestedObject o "milestone"
    maybePageBuild :: Maybe Aeson.Value <- o .:? "page_build"
    maybePublic :: Maybe Aeson.Value <- o .:? "public"
    maybePullRequestTriggerAttributes <- o .:? "pull_request"
    maybePullRequestReviewActivityTypes <-
      maybeActivityTypesInNestedObject o "pull_request_review"
    maybePullRequestReviewCommentActivityTypes <-
      maybeActivityTypesInNestedObject o "pull_request_review_comment"
    maybePullRequestTargetTriggerAttributes <- o .:? "pull_request_target"
    maybePushTriggerAttributes <- o .:? "push"
    maybeRegistryPackageActivityTypes <- maybeActivityTypesInNestedObject o "registry_package"
    maybeReleaseActivityTypes <- maybeActivityTypesInNestedObject o "release"
    maybeRepositoryDispatchActivityTypes :: Maybe (NonEmpty Text) <-
      maybeActivityTypesInNestedObject o "repository_dispatch"
    maybeScheduleCrons <- maybeScheduleCronsParser o
    maybeStatus :: Maybe Aeson.Value <- o .:? "status"
    maybeWatchStartedActivityTypes :: Maybe (NonEmpty Text) <-
      maybeActivityTypesInNestedObject o "watch"
    maybeWorkflowCall <- o .:? "workflow_call"
    maybeWorkflowDispatch <- o .:? "workflow_dispatch"
    maybeWorkflowRun <- o .:? "workflow_run"
    hoistFail'
      . fromMaybe (Left [i|Invalid workflow trigger (`on` property):\n#{AesonKeyMap.keys o}|])
      $ (Right . BranchProtectionRuleTrigger <$> maybeBranchProtectionRuleActivityTypes)
        <|> (Right . CheckRunTrigger <$> maybeCheckRunActivityTypes)
        <|> ( Right CheckSuiteCompletedTrigger
                <$ guard
                  (maybeCheckSuiteActivityTypes == Just ("completed" :| []))
            )
        <|> (Right CreateTrigger <$ guard (isJust maybeCreate))
        <|> (Right DeleteTrigger <$ guard (isJust maybeDelete))
        <|> (Right DeploymentTrigger <$ guard (isJust maybeDeployment))
        <|> (Right DeploymentStatusTrigger <$ guard (isJust maybeDeploymentStatus))
        <|> (Right . DiscussionTrigger <$> maybeDiscussionActivityTypes)
        <|> (Right . DiscussionCommentTrigger <$> maybeDiscussionCommentActivityTypes)
        <|> (Right ForkTrigger <$ guard (isJust maybeFork))
        <|> (Right GollumTrigger <$ guard (isJust maybeGollum))
        <|> (Right . IssueCommentTrigger <$> maybeIssueCommentActivityTypes)
        <|> (Right . IssuesTrigger <$> maybeIssuesActivityTypes)
        <|> (Right . LabelTrigger <$> maybeLabelActivityTypes)
        <|> ( Right MergeGroupChecksRequestedTrigger
                <$ guard
                  (maybeMergeGroupChecksRequestedActivityTypes == Just ("checks_requested" :| []))
            )
        <|> (Right . MilestoneTrigger <$> maybeMilestoneActivityTypes)
        <|> (Right PageBuildTrigger <$ guard (isJust maybePageBuild))
        <|> (Right PublicTrigger <$ guard (isJust maybePublic))
        <|> (Right . PullRequestTrigger <$> maybePullRequestTriggerAttributes)
        <|> (Right . PullRequestReviewTrigger <$> maybePullRequestReviewActivityTypes)
        <|> (Right . PullRequestReviewCommentTrigger <$> maybePullRequestReviewCommentActivityTypes)
        <|> (Right . PullRequestTargetTrigger <$> maybePullRequestTargetTriggerAttributes)
        <|> (Right . PushTrigger <$> maybePushTriggerAttributes)
        <|> (Right . RegistryPackageTrigger <$> maybeRegistryPackageActivityTypes)
        <|> (Right . ReleaseTrigger <$> maybeReleaseActivityTypes)
        <|> (Right . RepositoryDispatchTrigger <$> maybeRepositoryDispatchActivityTypes)
        <|> (Right . ScheduleTrigger <$> maybeScheduleCrons)
        <|> (Right StatusTrigger <$ guard (isJust maybeStatus))
        <|> ( Right WatchStartedTrigger
                <$ guard
                  (maybeWatchStartedActivityTypes == Just ("started" :| []))
            )
        <|> (Right . WorkflowCallTrigger <$> maybeWorkflowCall)
        <|> (Right . WorkflowDispatchTrigger <$> maybeWorkflowDispatch)
        <|> (Right . WorkflowRunTrigger <$> maybeWorkflowRun)
    where
      maybeActivityTypesInNestedObject ::
        (FromJSON a) =>
        Aeson.Object ->
        Aeson.Key ->
        Aeson.Parser (Maybe (NonEmpty a))
      maybeActivityTypesInNestedObject o attributeName =
        o
          .:? attributeName
          >>= maybe
            (pure Nothing)
            (Aeson.withObject "ActivityTypes" (.: "types"))

      maybeScheduleCronsParser :: Aeson.Object -> Aeson.Parser (Maybe (NonEmpty Text))
      maybeScheduleCronsParser o =
        o
          .:? "schedule"
          >>= maybe
            (pure Nothing)
            ( Aeson.withArray "Crons" $ \a ->
                fmap nonEmpty . for (Vector.toList a) $
                  Aeson.withObject "Cron" (.: "cron")
            )

instance ToJSONKey WorkflowTrigger where
  toJSONKey =
    Aeson.toJSONKeyText $ \case
      BranchProtectionRuleTrigger _ -> "branch_protection_rule"
      CheckRunTrigger _ -> "check_run"
      CheckSuiteCompletedTrigger -> "check_suite"
      CreateTrigger -> "create"
      DeleteTrigger -> "delete"
      DeploymentTrigger -> "deployment"
      DeploymentStatusTrigger -> "deployment_status"
      DiscussionTrigger _ -> "discussion"
      DiscussionCommentTrigger _ -> "discussion_comment"
      ForkTrigger -> "fork"
      GollumTrigger -> "gollum"
      IssueCommentTrigger _ -> "issue_comment"
      IssuesTrigger _ -> "issues"
      LabelTrigger _ -> "label"
      MergeGroupChecksRequestedTrigger -> "merge_group"
      MilestoneTrigger _ -> "milestone"
      PageBuildTrigger -> "page_build"
      PublicTrigger -> "public"
      PullRequestTrigger _ -> "pull_request"
      PullRequestReviewTrigger _ -> "pull_request_review"
      PullRequestReviewCommentTrigger _ -> "pull_request_review_comment"
      PullRequestTargetTrigger _ -> "pull_request_target"
      PushTrigger _ -> "push"
      RegistryPackageTrigger _ -> "registry_package"
      ReleaseTrigger _ -> "release"
      RepositoryDispatchTrigger _ -> "repository_dispatch"
      ScheduleTrigger _ -> "schedule"
      StatusTrigger -> "status"
      WatchStartedTrigger -> "watch"
      WorkflowCallTrigger _ -> "workflow_call"
      WorkflowDispatchTrigger _ -> "workflow_dispatch"
      WorkflowRunTrigger _ -> "workflow_run"

instance ToJSON WorkflowTrigger where
  toJSON trigger =
    Aeson.toJSON . Map.singleton trigger $ case trigger of
      BranchProtectionRuleTrigger activityTypes ->
        Aeson.object ["types" .= activityTypes]
      CheckRunTrigger activityTypes ->
        Aeson.object ["types" .= activityTypes]
      CheckSuiteCompletedTrigger ->
        Aeson.object ["types" .= [Aeson.String "completed"]]
      CreateTrigger ->
        Aeson.object []
      DeleteTrigger ->
        Aeson.object []
      DeploymentTrigger ->
        Aeson.object []
      DeploymentStatusTrigger ->
        Aeson.object []
      DiscussionTrigger activityTypes ->
        Aeson.object ["types" .= activityTypes]
      DiscussionCommentTrigger activityTypes ->
        Aeson.object ["types" .= activityTypes]
      ForkTrigger ->
        Aeson.object []
      GollumTrigger ->
        Aeson.object []
      IssueCommentTrigger activityTypes ->
        Aeson.object ["types" .= activityTypes]
      IssuesTrigger activityTypes ->
        Aeson.object ["types" .= activityTypes]
      LabelTrigger activityTypes ->
        Aeson.object ["types" .= activityTypes]
      MergeGroupChecksRequestedTrigger ->
        Aeson.object ["types" .= [Aeson.String "checks_requested"]]
      MilestoneTrigger activityTypes ->
        Aeson.object ["types" .= activityTypes]
      PageBuildTrigger ->
        Aeson.object []
      PublicTrigger ->
        Aeson.object []
      PullRequestTrigger attrs ->
        Aeson.toJSON attrs
      PullRequestReviewTrigger activityTypes ->
        Aeson.object ["types" .= activityTypes]
      PullRequestReviewCommentTrigger activityTypes ->
        Aeson.object ["types" .= activityTypes]
      PullRequestTargetTrigger attrs ->
        Aeson.toJSON attrs
      PushTrigger attrs ->
        Aeson.toJSON attrs
      RegistryPackageTrigger activityTypes ->
        Aeson.object ["types" .= activityTypes]
      ReleaseTrigger activityTypes ->
        Aeson.object ["types" .= activityTypes]
      RepositoryDispatchTrigger activityTypes ->
        Aeson.object ["types" .= activityTypes]
      ScheduleTrigger crons ->
        Aeson.toJSON $ (\cron -> Aeson.object ["cron" .= cron]) <$> crons
      StatusTrigger ->
        Aeson.object []
      WatchStartedTrigger ->
        Aeson.object ["types" .= [Aeson.String "started"]]
      WorkflowCallTrigger attrs ->
        Aeson.toJSON attrs
      WorkflowDispatchTrigger attrs ->
        Aeson.toJSON attrs
      WorkflowRunTrigger attrs ->
        Aeson.toJSON attrs

gen :: (MonadGen m) => m WorkflowTrigger
gen =
  Gen.choice
    [ BranchProtectionRuleTrigger <$> Gen.nonEmpty (Range.linear 1 3) Gen.enumBounded,
      CheckRunTrigger <$> Gen.nonEmpty (Range.linear 1 3) Gen.enumBounded,
      pure CheckSuiteCompletedTrigger,
      pure CreateTrigger,
      pure DeleteTrigger,
      pure DeploymentTrigger,
      pure DeploymentStatusTrigger,
      DiscussionTrigger <$> Gen.nonEmpty (Range.linear 1 3) Gen.enumBounded,
      DiscussionCommentTrigger <$> Gen.nonEmpty (Range.linear 1 3) Gen.enumBounded,
      pure ForkTrigger,
      pure GollumTrigger,
      IssueCommentTrigger <$> Gen.nonEmpty (Range.linear 1 3) Gen.enumBounded,
      IssuesTrigger <$> Gen.nonEmpty (Range.linear 1 3) Gen.enumBounded,
      LabelTrigger <$> Gen.nonEmpty (Range.linear 1 3) Gen.enumBounded,
      pure MergeGroupChecksRequestedTrigger,
      MilestoneTrigger <$> Gen.nonEmpty (Range.linear 1 3) Gen.enumBounded,
      pure PageBuildTrigger,
      pure PublicTrigger,
      PullRequestTrigger <$> genPullRequestTriggerAttributes,
      PullRequestReviewTrigger <$> Gen.nonEmpty (Range.linear 1 3) Gen.enumBounded,
      PullRequestReviewCommentTrigger <$> Gen.nonEmpty (Range.linear 1 3) Gen.enumBounded,
      PullRequestTargetTrigger <$> genPullRequestTargetAttributes,
      PushTrigger <$> genPushTriggerAttributes,
      RegistryPackageTrigger <$> Gen.nonEmpty (Range.linear 1 3) Gen.enumBounded,
      ReleaseTrigger <$> Gen.nonEmpty (Range.linear 1 3) Gen.enumBounded,
      RepositoryDispatchTrigger <$> Gen.nonEmpty (Range.linear 1 3) genText,
      ScheduleTrigger <$> Gen.nonEmpty (Range.linear 1 5) genText,
      pure StatusTrigger,
      pure WatchStartedTrigger,
      WorkflowCallTrigger <$> genWorkflowCallAttributes,
      WorkflowDispatchTrigger <$> genWorkflowDispatchAttributes,
      WorkflowRunTrigger <$> genWorkflowRunTriggerAttributes
    ]

genText :: (MonadGen m) => m Text
genText = Gen.text (Range.linear 1 5) Gen.alphaNum

genMap :: (MonadGen m) => m a -> m (Map Text a)
genMap ga = Gen.map (Range.linear 1 5) $ liftA2 (,) genText ga

genPullRequestTargetAttributes :: (MonadGen m) => m PullRequestTargetTriggerAttributes
genPullRequestTargetAttributes = do
  activityTypes <-
    Gen.maybe $
      Gen.nonEmpty (Range.linear 1 3) Gen.enumBounded
  branches <-
    Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  branchesIgnore <-
    Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  paths <-
    Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  pathsIgnore <-
    Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  pure PullRequestTargetTriggerAttributes {..}

genPullRequestTriggerAttributes :: (MonadGen m) => m PullRequestTriggerAttributes
genPullRequestTriggerAttributes = do
  activityTypes <-
    Gen.maybe $
      Gen.nonEmpty (Range.linear 1 3) Gen.enumBounded
  branches <-
    Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  branchesIgnore <-
    Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  paths <-
    Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  pathsIgnore <-
    Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  pure PullRequestTriggerAttributes {..}

genPushTriggerAttributes :: (MonadGen m) => m PushTriggerAttributes
genPushTriggerAttributes = do
  branches <-
    Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  branchesIgnore <-
    Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  paths <-
    Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  pathsIgnore <-
    Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  tags <-
    Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  pure PushTriggerAttributes {..}

genWorkflowCallAttributes :: (MonadGen m) => m WorkflowCallAttributes
genWorkflowCallAttributes = do
  inputs <- Gen.maybe $ genMap genWorkflowCallInput
  outputs <- Gen.maybe $ genMap genWorkflowCallOutput
  secrets <- Gen.maybe $ genMap genWorkflowCallSecret
  pure WorkflowCallAttributes {..}

genWorkflowCallInput :: (MonadGen m) => m WorkflowCallInput
genWorkflowCallInput = do
  description <- Gen.maybe genText
  default_ <-
    Gen.maybe $
      Aeson.String
        <$> Gen.text (Range.linear 3 20) Gen.alphaNum
  required <- Gen.maybe Gen.bool
  type_ <- Gen.enumBounded
  pure WorkflowCallInput {..}

genWorkflowCallOutput :: (MonadGen m) => m WorkflowCallOutput
genWorkflowCallOutput = do
  description <- Gen.maybe genText
  value <- genText
  pure WorkflowCallOutput {..}

genWorkflowCallSecret :: (MonadGen m) => m WorkflowCallSecret
genWorkflowCallSecret = do
  description <- Gen.maybe genText
  required <- Gen.maybe Gen.bool
  pure WorkflowCallSecret {..}

genWorkflowDispatchAttributes :: (MonadGen m) => m WorkflowDispatchAttributes
genWorkflowDispatchAttributes = do
  inputs <- Gen.maybe $ genMap genWorkflowDispatchInput
  pure WorkflowDispatchAttributes {..}

genWorkflowDispatchInput :: (MonadGen m) => m WorkflowDispatchInput
genWorkflowDispatchInput = do
  description <- Gen.maybe genText
  default_ <-
    Gen.maybe $
      Aeson.String
        <$> Gen.text (Range.linear 3 20) Gen.alphaNum
  required <- Gen.maybe Gen.bool
  type_ <- Gen.maybe genWorkflowDispatchInputType
  pure WorkflowDispatchInput {..}

genWorkflowDispatchInputType :: (MonadGen m) => m WorkflowDispatchInputType
genWorkflowDispatchInputType = do
  Gen.choice
    [ pure WorkflowDispatchInputTypeBoolean,
      WorkflowDispatchInputTypeChoice <$> Gen.nonEmpty (Range.linear 1 5) genText,
      pure WorkflowDispatchInputTypeEnvironment,
      pure WorkflowDispatchInputTypeNumber,
      pure WorkflowDispatchInputTypeString
    ]

genWorkflowRunTriggerAttributes :: (MonadGen m) => m WorkflowRunTriggerAttributes
genWorkflowRunTriggerAttributes = do
  activityTypes <- Gen.nonEmpty (Range.linear 1 3) Gen.enumBounded
  workflows <- Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  branches <- Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  branchesIgnore <- Gen.maybe $ Gen.nonEmpty (Range.linear 1 5) genText
  pure WorkflowRunTriggerAttributes {..}