module Main.Gen where

import qualified Data.HashSet as HashSet
import qualified Data.List as List
import qualified Data.Text as Text
import Hedgehog (Gen, MonadGen)
import Hedgehog.Gen
import qualified Hedgehog.Range as Range
import PostgresqlSyntax.Ast
import qualified PostgresqlSyntax.KeywordSet as KeywordSet
import qualified PostgresqlSyntax.Validation as Validation
import Prelude hiding (bit, bool, filter, fromList, maybe, sortBy)

-- * Generic

inSet _set = filter (flip HashSet.member _set)

notInSet _set = filter (not . flip HashSet.member _set)

-- * Statements

preparableStmt =
  choice
    [ SelectPreparableStmt <$> selectStmt,
      InsertPreparableStmt <$> insertStmt,
      UpdatePreparableStmt <$> updateStmt,
      DeletePreparableStmt <$> deleteStmt
    ]

-- * Insert

insertStmt = InsertStmt <$> maybe withClause <*> insertTarget <*> insertRest <*> maybe onConflict <*> maybe returningClause

insertTarget = InsertTarget <$> qualifiedName <*> maybe colId

insertRest =
  choice
    [ SelectInsertRest <$> maybe insertColumnList <*> maybe overrideKind <*> selectStmt,
      pure DefaultValuesInsertRest
    ]

overrideKind = enumBounded

insertColumnList = nonEmpty (Range.exponential 1 7) insertColumnItem

insertColumnItem = InsertColumnItem <$> colId <*> maybe indirection

onConflict = OnConflict <$> maybe confExpr <*> onConflictDo

onConflictDo =
  choice
    [ UpdateOnConflictDo <$> setClauseList <*> maybe whereClause,
      pure NothingOnConflictDo
    ]

confExpr =
  choice
    [ WhereConfExpr <$> indexParams <*> maybe whereClause,
      ConstraintConfExpr <$> name
    ]

returningClause = targetList

-- * Update

updateStmt = UpdateStmt <$> maybe withClause <*> relationExprOptAlias <*> setClauseList <*> maybe fromClause <*> maybe whereOrCurrentClause <*> maybe returningClause

setClauseList = nonEmpty (Range.exponential 1 10) setClause

setClause =
  choice
    [ TargetSetClause <$> setTarget <*> aExpr,
      TargetListSetClause <$> setTargetList <*> aExpr
    ]

setTarget = SetTarget <$> colId <*> maybe indirection

setTargetList = nonEmpty (Range.exponential 1 10) setTarget

-- * Delete

deleteStmt = DeleteStmt <$> maybe withClause <*> relationExprOptAlias <*> maybe usingClause <*> maybe whereOrCurrentClause <*> maybe returningClause

usingClause = fromList

-- * Select

selectStmt = Left <$> selectNoParens

-- ** selectNoParens

selectNoParens =
  frequency
    [ (90, SelectNoParens <$> maybe withClause <*> (Left <$> simpleSelect) <*> maybe sortClause <*> maybe selectLimit <*> maybe forLockingClause),
      (10, SelectNoParens <$> fmap Just withClause <*> selectClause <*> fmap Just sortClause <*> fmap Just selectLimit <*> fmap Just forLockingClause)
    ]

terminalSelectNoParens =
  SelectNoParens <$> pure Nothing <*> (Left <$> terminalSimpleSelect) <*> pure Nothing <*> pure Nothing <*> pure Nothing

-- ** selectWithParens

selectWithParens = sized $ \_size ->
  if _size <= 1
    then discard
    else
      frequency
        [ (95, NoParensSelectWithParens <$> selectNoParens),
          (5, WithParensSelectWithParens <$> selectWithParens)
        ]

terminalSelectWithParens = NoParensSelectWithParens <$> terminalSelectNoParens

-- ** selectClause

selectClause =
  choice
    [ Left <$> simpleSelect,
      Right <$> small selectWithParens
    ]

nonTrailingSelectClause = Left <$> nonTrailingSimpleSelect

-- ** simpleSelect

simpleSelect =
  choice
    [ normalSimpleSelect,
      tableSimpleSelect,
      valuesSimpleSelect,
      small nonTrailingSelectClause >>= binSimpleSelect
    ]

nonTrailingSimpleSelect = choice [normalSimpleSelect, valuesSimpleSelect, tableSimpleSelect]

normalSimpleSelect = NormalSimpleSelect <$> maybe targeting <*> maybe intoClause <*> maybe fromClause <*> maybe whereClause <*> maybe groupClause <*> maybe havingClause <*> maybe windowClause

tableSimpleSelect = TableSimpleSelect <$> relationExpr

valuesSimpleSelect = ValuesSimpleSelect <$> valuesClause

binSimpleSelect _leftSelect =
  BinSimpleSelect <$> selectBinOp <*> pure _leftSelect <*> maybe allOrDistinct <*> small selectClause

terminalSimpleSelect = pure (NormalSimpleSelect Nothing Nothing Nothing Nothing Nothing Nothing Nothing)

-- * Targeting

targeting =
  choice
    [ NormalTargeting <$> targetList,
      AllTargeting <$> maybe targetList,
      DistinctTargeting <$> maybe (nonEmpty (Range.exponential 1 8) aExpr) <*> targetList
    ]

targetList = nonEmpty (Range.exponential 1 8) targetEl

targetEl =
  choice
    [ pure AsteriskTargetEl,
      AliasedExprTargetEl <$> aExpr <*> colLabel,
      ImplicitlyAliasedExprTargetEl <$> prefixAExpr <*> ident,
      ExprTargetEl <$> aExpr
    ]

-- * BinSimpleSelect

selectBinOp = element [UnionSelectBinOp, IntersectSelectBinOp, ExceptSelectBinOp]

-- * With Clause

withClause = WithClause <$> bool <*> nonEmpty (Range.exponential 1 7) commonTableExpr

commonTableExpr = CommonTableExpr <$> name <*> maybe (nonEmpty (Range.exponential 1 8) name) <*> maybe bool <*> small preparableStmt

-- * Into Clause

intoClause = optTempTableName

optTempTableName =
  choice
    [ TemporaryOptTempTableName <$> bool <*> qualifiedName,
      TempOptTempTableName <$> bool <*> qualifiedName,
      LocalTemporaryOptTempTableName <$> bool <*> qualifiedName,
      LocalTempOptTempTableName <$> bool <*> qualifiedName,
      GlobalTemporaryOptTempTableName <$> bool <*> qualifiedName,
      GlobalTempOptTempTableName <$> bool <*> qualifiedName,
      UnloggedOptTempTableName <$> bool <*> qualifiedName,
      TableOptTempTableName <$> qualifiedName,
      QualifedOptTempTableName <$> qualifiedName
    ]

-- * From Clause

fromList = nonEmpty (Range.exponential 1 8) tableRef

fromClause = fromList

tableRef = choice [relationExprTableRef, selectTableRef, joinTableRef]

relationExprTableRef = RelationExprTableRef <$> relationExpr <*> maybe aliasClause <*> maybe tablesampleClause

funcTableRef = FuncTableRef <$> bool <*> funcTable <*> maybe funcAliasClause

selectTableRef = SelectTableRef <$> bool <*> small selectWithParens <*> maybe aliasClause

joinTableRef = JoinTableRef <$> joinedTable <*> maybe aliasClause

relationExpr =
  choice
    [ SimpleRelationExpr <$> qualifiedName <*> bool,
      OnlyRelationExpr <$> qualifiedName <*> bool
    ]

relationExprOptAlias = RelationExprOptAlias <$> relationExpr <*> maybe ((,) <$> bool <*> colId)

tablesampleClause = TablesampleClause <$> funcName <*> exprList <*> maybe repeatableClause

repeatableClause = aExpr

funcTable =
  choice
    [ FuncExprFuncTable <$> funcExprWindowless <*> optOrdinality,
      RowsFromFuncTable <$> rowsfromList <*> optOrdinality
    ]

rowsfromItem = RowsfromItem <$> funcExprWindowless <*> maybe colDefList

rowsfromList = nonEmpty (Range.exponential 1 8) rowsfromItem

colDefList = tableFuncElementList

optOrdinality = bool

tableFuncElementList = nonEmpty (Range.exponential 1 7) tableFuncElement

tableFuncElement = TableFuncElement <$> colId <*> typename <*> maybe collateClause

collateClause = anyName

aliasClause = AliasClause <$> bool <*> name <*> maybe (nonEmpty (Range.exponential 1 8) name)

funcAliasClause =
  choice
    [ AliasFuncAliasClause <$> aliasClause,
      AsFuncAliasClause <$> tableFuncElementList,
      AsColIdFuncAliasClause <$> colId <*> tableFuncElementList,
      ColIdFuncAliasClause <$> colId <*> tableFuncElementList
    ]

joinedTable =
  frequency
    [ (5,) $ InParensJoinedTable <$> joinedTable,
      (95,) $ MethJoinedTable <$> joinMeth <*> tableRef <*> choice [relationExprTableRef, selectTableRef, funcTableRef]
    ]

joinMeth =
  choice
    [ pure CrossJoinMeth,
      QualJoinMeth <$> maybe joinType <*> joinQual,
      NaturalJoinMeth <$> maybe joinType
    ]

joinType =
  choice
    [ FullJoinType <$> bool,
      LeftJoinType <$> bool,
      RightJoinType <$> bool,
      pure InnerJoinType
    ]

joinQual =
  choice
    [ UsingJoinQual <$> nonEmpty (Range.exponential 1 8) name,
      OnJoinQual <$> aExpr
    ]

-- * Group Clause

groupClause = nonEmpty (Range.exponential 1 8) groupByItem

groupByItem =
  choice
    [ ExprGroupByItem <$> aExpr,
      pure EmptyGroupingSetGroupByItem,
      RollupGroupByItem <$> nonEmpty (Range.exponential 1 8) aExpr,
      CubeGroupByItem <$> nonEmpty (Range.exponential 1 8) aExpr,
      GroupingSetsGroupByItem <$> nonEmpty (Range.exponential 1 3) groupByItem
    ]

-- * Having Clause

havingClause = aExpr

-- * Where Clause

whereClause = aExpr

whereOrCurrentClause =
  choice
    [ ExprWhereOrCurrentClause <$> aExpr,
      CursorWhereOrCurrentClause <$> cursorName
    ]

-- * Window Clause

windowClause = nonEmpty (Range.exponential 1 8) windowDefinition

windowDefinition = WindowDefinition <$> name <*> windowSpecification

windowSpecification = WindowSpecification <$> maybe name <*> maybe (nonEmpty (Range.exponential 1 8) nonSuffixOpAExpr) <*> maybe sortClause <*> maybe frameClause

frameClause = FrameClause <$> frameClauseMode <*> frameExtent <*> maybe windowExclusionClause

frameClauseMode = element [RangeFrameClauseMode, RowsFrameClauseMode, GroupsFrameClauseMode]

frameExtent =
  choice
    [ SingularFrameExtent <$> frameBound,
      BetweenFrameExtent <$> frameBound <*> frameBound
    ]

frameBound =
  choice
    [ pure UnboundedPrecedingFrameBound,
      pure UnboundedFollowingFrameBound,
      pure CurrentRowFrameBound,
      PrecedingFrameBound <$> prefixAExpr,
      FollowingFrameBound <$> prefixAExpr
    ]

windowExclusionClause = element [CurrentRowWindowExclusionClause, GroupWindowExclusionClause, TiesWindowExclusionClause, NoOthersWindowExclusionClause]

-- * Values Clause

valuesClause = nonEmpty (Range.exponential 1 8) (nonEmpty (Range.exponential 1 8) aExpr)

-- * Sort Clause

sortClause = nonEmpty (Range.exponential 1 8) sortBy

sortBy =
  choice
    [ UsingSortBy <$> nonSuffixOpAExpr <*> qualAllOp <*> maybe nullsOrder,
      AscDescSortBy <$> nonSuffixOpAExpr <*> maybe ascDesc <*> maybe nullsOrder
    ]

-- * All or distinct

allOrDistinct = bool

-- * Limit

selectLimit =
  choice
    [ LimitOffsetSelectLimit <$> limitClause <*> offsetClause,
      OffsetLimitSelectLimit <$> offsetClause <*> limitClause,
      LimitSelectLimit <$> limitClause,
      OffsetSelectLimit <$> offsetClause
    ]

limitClause =
  choice
    [ LimitLimitClause <$> selectLimitValue <*> maybe aExpr,
      FetchOnlyLimitClause <$> bool <*> maybe selectFetchFirstValue <*> bool
    ]

selectFetchFirstValue =
  choice
    [ ExprSelectFetchFirstValue <$> cExpr,
      NumSelectFetchFirstValue <$> bool <*> iconstOrFconst
    ]

selectLimitValue =
  choice
    [ ExprSelectLimitValue <$> aExpr,
      pure AllSelectLimitValue
    ]

offsetClause =
  choice
    [ ExprOffsetClause <$> aExpr,
      FetchFirstOffsetClause <$> selectFetchFirstValue <*> bool
    ]

-- * For Locking

forLockingClause =
  choice
    [ ItemsForLockingClause <$> nonEmpty (Range.exponential 1 8) forLockingItem,
      pure ReadOnlyForLockingClause
    ]

forLockingItem = ForLockingItem <$> forLockingStrength <*> maybe (nonEmpty (Range.exponential 1 8) qualifiedName) <*> maybe bool

forLockingStrength =
  element
    [ UpdateForLockingStrength,
      NoKeyUpdateForLockingStrength,
      ShareForLockingStrength,
      KeyForLockingStrength
    ]

-- * Expressions

exprList = nonEmpty (Range.exponential 1 7) aExpr

aExpr =
  recursive
    choice
    [ CExprAExpr <$> cExpr,
      pure DefaultAExpr
    ]
    [ TypecastAExpr <$> prefixAExpr <*> typename,
      CollateAExpr <$> prefixAExpr <*> anyName,
      AtTimeZoneAExpr <$> prefixAExpr <*> aExpr,
      PlusAExpr <$> aExpr,
      MinusAExpr <$> aExpr,
      SymbolicBinOpAExpr <$> prefixAExpr <*> symbolicExprBinOp <*> aExpr,
      PrefixQualOpAExpr <$> qualOp <*> aExpr,
      SuffixQualOpAExpr <$> prefixAExpr <*> qualOp,
      AndAExpr <$> prefixAExpr <*> aExpr,
      OrAExpr <$> prefixAExpr <*> aExpr,
      NotAExpr <$> aExpr,
      VerbalExprBinOpAExpr <$> prefixAExpr <*> bool <*> verbalExprBinOp <*> prefixAExpr <*> maybe aExpr,
      ReversableOpAExpr <$> prefixAExpr <*> bool <*> aExprReversableOp,
      IsnullAExpr <$> prefixAExpr,
      NotnullAExpr <$> prefixAExpr,
      OverlapsAExpr <$> row <*> row,
      SubqueryAExpr <$> prefixAExpr <*> subqueryOp <*> subType <*> choice [Left <$> selectWithParens, Right <$> nonSelectAExpr],
      UniqueAExpr <$> selectWithParens
    ]

prefixAExpr =
  choice
    [ CExprAExpr <$> cExpr,
      pure DefaultAExpr,
      UniqueAExpr <$> selectWithParens
    ]

nonSuffixOpAExpr =
  recursive
    choice
    [ CExprAExpr <$> cExpr,
      pure DefaultAExpr
    ]
    [ TypecastAExpr <$> prefixAExpr <*> typename,
      CollateAExpr <$> prefixAExpr <*> anyName,
      AtTimeZoneAExpr <$> prefixAExpr <*> nonSuffixOpAExpr,
      PlusAExpr <$> nonSuffixOpAExpr,
      MinusAExpr <$> nonSuffixOpAExpr,
      SymbolicBinOpAExpr <$> prefixAExpr <*> symbolicExprBinOp <*> nonSuffixOpAExpr,
      PrefixQualOpAExpr <$> qualOp <*> nonSuffixOpAExpr,
      AndAExpr <$> prefixAExpr <*> nonSuffixOpAExpr,
      OrAExpr <$> prefixAExpr <*> nonSuffixOpAExpr,
      NotAExpr <$> nonSuffixOpAExpr,
      VerbalExprBinOpAExpr <$> prefixAExpr <*> bool <*> verbalExprBinOp <*> prefixAExpr <*> maybe nonSuffixOpAExpr,
      IsnullAExpr <$> prefixAExpr,
      NotnullAExpr <$> prefixAExpr,
      UniqueAExpr <$> selectWithParens
    ]

nonSelectAExpr =
  choice
    [ TypecastAExpr <$> prefixAExpr <*> typename,
      CollateAExpr <$> prefixAExpr <*> anyName,
      AtTimeZoneAExpr <$> prefixAExpr <*> aExpr,
      PlusAExpr <$> aExpr,
      MinusAExpr <$> aExpr,
      SymbolicBinOpAExpr <$> prefixAExpr <*> symbolicExprBinOp <*> aExpr,
      PrefixQualOpAExpr <$> qualOp <*> aExpr,
      SuffixQualOpAExpr <$> prefixAExpr <*> qualOp,
      AndAExpr <$> prefixAExpr <*> aExpr,
      OrAExpr <$> prefixAExpr <*> aExpr,
      NotAExpr <$> aExpr,
      VerbalExprBinOpAExpr <$> prefixAExpr <*> bool <*> verbalExprBinOp <*> prefixAExpr <*> maybe aExpr,
      ReversableOpAExpr <$> prefixAExpr <*> bool <*> aExprReversableOp,
      IsnullAExpr <$> prefixAExpr,
      NotnullAExpr <$> prefixAExpr,
      OverlapsAExpr <$> row <*> row,
      SubqueryAExpr <$> prefixAExpr <*> subqueryOp <*> subType <*> choice [Left <$> selectWithParens, Right <$> nonSelectAExpr],
      UniqueAExpr <$> selectWithParens
    ]

bExpr =
  recursive
    choice
    [ CExprBExpr <$> cExpr
    ]
    [ TypecastBExpr <$> prefixBExpr <*> typename,
      PlusBExpr <$> bExpr,
      MinusBExpr <$> bExpr,
      SymbolicBinOpBExpr <$> prefixBExpr <*> symbolicExprBinOp <*> bExpr,
      QualOpBExpr <$> qualOp <*> bExpr,
      IsOpBExpr <$> prefixBExpr <*> bool <*> bExprIsOp
    ]

prefixBExpr =
  choice
    [ CExprBExpr <$> cExpr
    ]

cExpr =
  recursive
    choice
    [ ColumnrefCExpr <$> columnref
    ]
    [ AexprConstCExpr <$> aexprConst,
      ParamCExpr <$> integral (Range.linear 1 19) <*> maybe indirection,
      InParensCExpr <$> nonSelectAExpr <*> maybe indirection,
      CaseCExpr <$> caseExpr,
      FuncCExpr <$> funcExpr,
      SelectWithParensCExpr <$> selectWithParens <*> maybe indirection,
      ExistsCExpr <$> selectWithParens,
      ArrayCExpr <$> choice [Left <$> selectWithParens, Right <$> arrayExpr],
      ExplicitRowCExpr <$> explicitRow,
      ImplicitRowCExpr <$> implicitRow,
      GroupingCExpr <$> exprList
    ]

-- **

caseExpr = CaseExpr <$> maybe aExpr <*> whenClauseList <*> maybe aExpr

whenClauseList = nonEmpty (Range.exponential 1 7) whenClause

whenClause = WhenClause <$> small aExpr <*> small aExpr

inExpr =
  choice
    [ SelectInExpr <$> NoParensSelectWithParens <$> selectNoParens,
      ExprListInExpr <$> exprList
    ]

arrayExpr =
  small $
    choice
      [ ExprListArrayExpr <$> exprList,
        ArrayExprListArrayExpr <$> arrayExprList,
        pure EmptyArrayExpr
      ]

arrayExprList = nonEmpty (Range.exponential 1 4) arrayExpr

row =
  choice
    [ ExplicitRowRow <$> explicitRow,
      ImplicitRowRow <$> implicitRow
    ]

explicitRow = maybe exprList

implicitRow = ImplicitRow <$> exprList <*> aExpr

-- ** FuncExpr

funcExpr =
  choice
    [ ApplicationFuncExpr <$> funcApplication <*> maybe withinGroupClause <*> maybe filterClause <*> maybe overClause,
      SubexprFuncExpr <$> funcExprCommonSubexpr
    ]

funcExprWindowless =
  choice
    [ ApplicationFuncExprWindowless <$> funcApplication,
      CommonSubexprFuncExprWindowless <$> funcExprCommonSubexpr
    ]

funcApplication = FuncApplication <$> funcName <*> maybe funcApplicationParams

funcApplicationParams =
  choice
    [ NormalFuncApplicationParams <$> maybe allOrDistinct <*> nonEmpty (Range.exponential 1 8) funcArgExpr <*> maybe sortClause,
      VariadicFuncApplicationParams <$> maybe (nonEmpty (Range.exponential 1 8) funcArgExpr) <*> funcArgExpr <*> maybe sortClause,
      pure StarFuncApplicationParams
    ]

funcArgExpr =
  choice
    [ ExprFuncArgExpr <$> small aExpr,
      ColonEqualsFuncArgExpr <$> name <*> small aExpr,
      EqualsGreaterFuncArgExpr <$> name <*> small aExpr
    ]

withinGroupClause = sortClause

filterClause = aExpr

overClause = choice [WindowOverClause <$> windowSpecification, ColIdOverClause <$> colId]

funcExprCommonSubexpr =
  choice
    [ CollationForFuncExprCommonSubexpr <$> aExpr,
      pure CurrentDateFuncExprCommonSubexpr,
      CurrentTimeFuncExprCommonSubexpr <$> maybe iconst,
      CurrentTimestampFuncExprCommonSubexpr <$> maybe iconst,
      LocalTimeFuncExprCommonSubexpr <$> maybe iconst,
      LocalTimestampFuncExprCommonSubexpr <$> maybe iconst,
      pure CurrentRoleFuncExprCommonSubexpr,
      pure CurrentUserFuncExprCommonSubexpr,
      pure SessionUserFuncExprCommonSubexpr,
      pure UserFuncExprCommonSubexpr,
      pure CurrentCatalogFuncExprCommonSubexpr,
      pure CurrentSchemaFuncExprCommonSubexpr,
      CastFuncExprCommonSubexpr <$> aExpr <*> typename,
      ExtractFuncExprCommonSubexpr <$> maybe extractList,
      OverlayFuncExprCommonSubexpr <$> overlayList,
      PositionFuncExprCommonSubexpr <$> maybe positionList,
      SubstringFuncExprCommonSubexpr <$> maybe substrList,
      TreatFuncExprCommonSubexpr <$> aExpr <*> typename,
      TrimFuncExprCommonSubexpr <$> maybe trimModifier <*> trimList,
      NullIfFuncExprCommonSubexpr <$> aExpr <*> aExpr,
      CoalesceFuncExprCommonSubexpr <$> exprList,
      GreatestFuncExprCommonSubexpr <$> exprList,
      LeastFuncExprCommonSubexpr <$> exprList
    ]

extractList = ExtractList <$> extractArg <*> aExpr

extractArg =
  choice
    [ IdentExtractArg <$> ident,
      pure YearExtractArg,
      pure MonthExtractArg,
      pure DayExtractArg,
      pure HourExtractArg,
      pure MinuteExtractArg,
      pure SecondExtractArg,
      SconstExtractArg <$> sconst
    ]

overlayList = OverlayList <$> aExpr <*> overlayPlacing <*> substrFrom <*> maybe substrFor

overlayPlacing = aExpr

positionList = PositionList <$> bExpr <*> bExpr

substrList =
  choice
    [ ExprSubstrList <$> aExpr <*> substrListFromFor,
      ExprListSubstrList <$> exprList
    ]

substrListFromFor =
  choice
    [ FromForSubstrListFromFor <$> substrFrom <*> substrFor,
      ForFromSubstrListFromFor <$> substrFor <*> substrFrom,
      FromSubstrListFromFor <$> substrFrom,
      ForSubstrListFromFor <$> substrFor
    ]

substrFrom = aExpr

substrFor = aExpr

trimModifier = enumBounded

trimList =
  choice
    [ ExprFromExprListTrimList <$> aExpr <*> exprList,
      FromExprListTrimList <$> exprList,
      ExprListTrimList <$> exprList
    ]

-- * Operators

qualOp = choice [OpQualOp <$> op, OperatorQualOp <$> anyOperator]

qualAllOp =
  choice
    [ AllQualAllOp <$> allOp,
      AnyQualAllOp <$> anyOperator
    ]

op = do
  a <- text (Range.exponential 1 7) (element "+-*/<>=~!@#%^&|`?")
  case Validation.op a of
    Nothing -> return a
    _ -> discard

anyOperator =
  recursive
    choice
    [ AllOpAnyOperator <$> allOp
    ]
    [ QualifiedAnyOperator <$> colId <*> anyOperator
    ]

allOp = choice [OpAllOp <$> op, MathAllOp <$> mathOp]

mathOp = enumBounded

symbolicExprBinOp =
  choice
    [ MathSymbolicExprBinOp <$> mathOp,
      QualSymbolicExprBinOp <$> qualOp
    ]

binOp = element (toList KeywordSet.symbolicBinOp <> ["AND", "OR", "IS DISTINCT FROM", "IS NOT DISTINCT FROM"])

verbalExprBinOp = enumBounded

aExprReversableOp =
  choice
    [ pure NullAExprReversableOp,
      pure TrueAExprReversableOp,
      pure FalseAExprReversableOp,
      pure UnknownAExprReversableOp,
      DistinctFromAExprReversableOp <$> aExpr,
      OfAExprReversableOp <$> typeList,
      BetweenAExprReversableOp <$> bool <*> bExpr <*> aExpr,
      BetweenSymmetricAExprReversableOp <$> bExpr <*> aExpr,
      InAExprReversableOp <$> inExpr,
      pure DocumentAExprReversableOp
    ]

bExprIsOp =
  choice
    [ DistinctFromBExprIsOp <$> bExpr,
      OfBExprIsOp <$> typeList,
      pure DocumentBExprIsOp
    ]

subqueryOp =
  choice
    [ AllSubqueryOp <$> allOp,
      AnySubqueryOp <$> anyOperator,
      LikeSubqueryOp <$> bool,
      IlikeSubqueryOp <$> bool
    ]

-- * Constants

aexprConst =
  choice
    [ IAexprConst <$> iconst,
      FAexprConst <$> fconst,
      SAexprConst <$> sconst,
      BAexprConst <$> text (Range.exponential 1 100) (element "01"),
      XAexprConst <$> text (Range.exponential 1 100) (element "0123456789abcdefABCDEF"),
      FuncAexprConst <$> funcName <*> maybe funcConstArgs <*> sconst,
      ConstTypenameAexprConst <$> constTypename <*> sconst,
      StringIntervalAexprConst <$> sconst <*> maybe interval,
      IntIntervalAexprConst <$> integral (Range.exponential 0 2309482309483029) <*> sconst,
      BoolAexprConst <$> bool,
      pure NullAexprConst
    ]

funcConstArgs = FuncConstArgs <$> nonEmpty (Range.exponential 1 7) funcArgExpr <*> maybe sortClause

constTypename =
  choice
    [ NumericConstTypename <$> numeric,
      ConstBitConstTypename <$> constBit,
      ConstCharacterConstTypename <$> constCharacter,
      ConstDatetimeConstTypename <$> constDatetime
    ]

numeric =
  choice
    [ pure IntNumeric,
      pure IntegerNumeric,
      pure SmallintNumeric,
      pure BigintNumeric,
      pure RealNumeric,
      FloatNumeric <$> maybe iconst,
      pure DoublePrecisionNumeric,
      DecimalNumeric <$> maybe (nonEmpty (Range.exponential 1 7) (small aExpr)),
      DecNumeric <$> maybe (nonEmpty (Range.exponential 1 7) (small aExpr)),
      NumericNumeric <$> maybe (nonEmpty (Range.exponential 1 7) (small aExpr)),
      pure BooleanNumeric
    ]

bit = Bit <$> bool <*> maybe (nonEmpty (Range.exponential 1 7) (small aExpr))

constBit = bit

constCharacter = ConstCharacter <$> character <*> maybe iconst

character =
  choice
    [ CharacterCharacter <$> bool,
      CharCharacter <$> bool,
      pure VarcharCharacter,
      NationalCharacterCharacter <$> bool,
      NationalCharCharacter <$> bool,
      NcharCharacter <$> bool
    ]

constDatetime =
  choice
    [ TimestampConstDatetime <$> maybe iconst <*> maybe bool,
      TimeConstDatetime <$> maybe iconst <*> maybe bool
    ]

interval =
  choice
    [ pure YearInterval,
      pure MonthInterval,
      pure DayInterval,
      pure HourInterval,
      pure MinuteInterval,
      SecondInterval <$> intervalSecond,
      pure YearToMonthInterval,
      pure DayToHourInterval,
      pure DayToMinuteInterval,
      DayToSecondInterval <$> intervalSecond,
      pure HourToMinuteInterval,
      HourToSecondInterval <$> intervalSecond,
      MinuteToSecondInterval <$> intervalSecond
    ]

intervalSecond = maybe iconst

sconst = text (Range.exponential 0 1000) unicode

iconstOrFconst = choice [Left <$> iconst <|> Right <$> fconst]

fconst =
  filter (\a -> fromIntegral (round a) /= a) $
    realFrac_ (Range.exponentialFloat 0 309457394857984375983475943)

iconst = integral (Range.exponential 0 maxBound)

-- * Types

nullable = pure False

arrayDimensionsAmount = int (Range.exponential 0 4)

-- ** Typename

typename = Typename <$> bool <*> simpleTypename <*> pure False <*> maybe ((,) <$> typenameArrayDimensions <*> pure False)

typenameArrayDimensions =
  choice
    [ BoundsTypenameArrayDimensions <$> arrayBounds,
      ExplicitTypenameArrayDimensions <$> maybe iconst
    ]

arrayBounds = nonEmpty (Range.exponential 1 4) (maybe iconst)

simpleTypename =
  choice
    [ GenericTypeSimpleTypename <$> genericType,
      NumericSimpleTypename <$> numeric,
      BitSimpleTypename <$> bit,
      CharacterSimpleTypename <$> character,
      ConstDatetimeSimpleTypename <$> constDatetime,
      ConstIntervalSimpleTypename <$> choice [Left <$> maybe interval, Right <$> iconst]
    ]

genericType = GenericType <$> typeFunctionName <*> maybe attrs <*> maybe typeModifiers

attrs = nonEmpty (Range.exponential 1 10) attrName

typeModifiers = exprList

typeList = nonEmpty (Range.exponential 1 7) typename

subType = enumBounded

-- * Names

columnref = Columnref <$> colId <*> maybe indirection

keywordNotInSet = \set -> notInSet set $ do
  a <- element startList
  b <- text (Range.linear 1 29) (element contList)
  return (Text.cons a b)
  where
    startList = "abcdefghijklmnopqrstuvwxyz_" <> List.filter isLower (enumFromTo '\200' '\377')
    contList = startList <> "0123456789$"

ident = identWithSet mempty

typeName = identWithSet KeywordSet.typeFunctionName

name = identWithSet KeywordSet.colId

cursorName = name

identWithSet set =
  frequency
    [ (95,) $ UnquotedIdent <$> (keywordNotInSet . HashSet.difference KeywordSet.keyword) set,
      (5,) $ QuotedIdent <$> text (Range.linear 1 30) quotedChar
    ]

qualifiedName =
  choice
    [ SimpleQualifiedName <$> name,
      IndirectedQualifiedName <$> name <*> indirection
    ]

indirection = nonEmpty (Range.linear 1 3) indirectionEl

indirectionEl =
  choice
    [ AttrNameIndirectionEl <$> name,
      pure AllIndirectionEl,
      ExprIndirectionEl <$> (small aExpr),
      SliceIndirectionEl <$> maybe (small aExpr) <*> maybe (small aExpr)
    ]

quotedChar = filter (not . isControl) unicode

colId = name

colLabel = name

attrName = colLabel

typeFunctionName = name

funcName =
  choice
    [ TypeFuncName <$> typeFunctionName,
      IndirectedFuncName <$> colId <*> indirection
    ]

anyName = AnyName <$> colId <*> maybe attrs

-- * Indexes

indexParams = nonEmpty (Range.exponential 1 5) indexElem

indexElem = IndexElem <$> indexElemDef <*> maybe collate <*> maybe class_ <*> maybe ascDesc <*> maybe nullsOrder

indexElemDef =
  choice
    [ IdIndexElemDef <$> colId,
      FuncIndexElemDef <$> funcExprWindowless,
      ExprIndexElemDef <$> aExpr
    ]

collate = anyName

class_ = anyName

ascDesc = enumBounded

nullsOrder = enumBounded
