module PostgresqlSyntax.Rendering where

import PostgresqlSyntax.Prelude hiding (aExpr, try, option, many, sortBy, bit, fromList)
import PostgresqlSyntax.Ast
import Data.ByteString.FastBuilder
import qualified PostgresqlSyntax.Extras.NonEmpty as NonEmpty
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import qualified Data.ByteString.Builder as BsBuilder
import qualified Data.ByteString.Lazy as LazyBs


-- * Execution
-------------------------

toByteString :: Builder -> ByteString
toByteString = toStrictByteString

toText :: Builder -> Text
toText = Text.decodeUtf8 . toByteString


-- * Helpers
-------------------------

text :: Text -> Builder
text = stringUtf8 . Text.unpack

commaNonEmpty :: (a -> Builder) -> NonEmpty a -> Builder
commaNonEmpty = NonEmpty.intersperseFoldMap ", "

spaceNonEmpty :: (a -> Builder) -> NonEmpty a -> Builder
spaceNonEmpty = NonEmpty.intersperseFoldMap " "

lexemes :: [Builder] -> Builder
lexemes = mconcat . intersperse " "

optLexemes :: [Maybe Builder] -> Builder
optLexemes = lexemes . catMaybes

inParens :: Builder -> Builder
inParens a = "(" <> a <> ")"

inBrackets :: Builder -> Builder
inBrackets a = "[" <> a <> "]"

prefixMaybe :: (a -> Builder) -> Maybe a -> Builder
prefixMaybe a = foldMap (flip mappend " " . a)

suffixMaybe :: (a -> Builder) -> Maybe a -> Builder
suffixMaybe a = foldMap (mappend " " . a)


-- * Statements
-------------------------

preparableStmt = \ case
  SelectPreparableStmt a -> selectStmt a
  InsertPreparableStmt a -> insertStmt a
  UpdatePreparableStmt a -> updateStmt a
  DeletePreparableStmt a -> deleteStmt a


-- * Insert
-------------------------

insertStmt (InsertStmt a b c d e) =
  prefixMaybe withClause a <>
  "INSERT INTO " <>
  insertTarget b <> " " <> insertRest c <>
  suffixMaybe onConflict d <>
  suffixMaybe returningClause e

insertTarget (InsertTarget a b) =
  qualifiedName a <> foldMap (mappend " AS " . colId) b

insertRest = \ case
  SelectInsertRest a b c ->
    optLexemes [
        fmap (inParens . insertColumnList) a,
        fmap insertRestOverriding b,
        Just (selectStmt c)
      ]
  DefaultValuesInsertRest -> "DEFAULT VALUES"

insertRestOverriding a = "OVERRIDING " <> overrideKind a <> " VALUE"

overrideKind = \ case
  UserOverrideKind -> "USER"
  SystemOverrideKind -> "SYSTEM"

insertColumnList = commaNonEmpty insertColumnItem

insertColumnItem (InsertColumnItem a b) = colId a <> suffixMaybe indirection b

onConflict (OnConflict a b) = "ON CONFLICT" <> suffixMaybe confExpr a <> " DO " <> onConflictDo b

onConflictDo = \ case
  UpdateOnConflictDo a b -> "UPDATE SET " <> setClauseList a <> suffixMaybe whereClause b
  NothingOnConflictDo -> "NOTHING"

confExpr = \ case
  WhereConfExpr a b -> inParens (indexParams a) <> suffixMaybe whereClause b
  ConstraintConfExpr a -> "ON CONSTRAINT " <> name a

returningClause = mappend "RETURNING " . targetList


-- * Update
-------------------------

updateStmt (UpdateStmt a b c d e f) =
  prefixMaybe withClause a <>
  "UPDATE " <> relationExprOptAlias b <> " " <>
  "SET " <> setClauseList c <>
  suffixMaybe fromClause d <>
  suffixMaybe whereOrCurrentClause e <>
  suffixMaybe returningClause f

setClauseList = commaNonEmpty setClause

setClause = \ case
  TargetSetClause a b -> setTarget a <> " = " <> aExpr b
  TargetListSetClause a b -> inParens (setTargetList a) <> " = " <> aExpr b

setTarget (SetTarget a b) = colId a <> suffixMaybe indirection b

setTargetList = commaNonEmpty setTarget


-- * Delete
-------------------------

deleteStmt (DeleteStmt a b c d e) =
  prefixMaybe withClause a <>
  "DELETE FROM " <> relationExprOptAlias b <>
  suffixMaybe usingClause c <>
  suffixMaybe whereOrCurrentClause d <>
  suffixMaybe returningClause e

usingClause = mappend "USING " . fromList


-- * Select
-------------------------

selectStmt = \ case
  Left a -> selectNoParens a
  Right a -> selectWithParens a

selectNoParens (SelectNoParens a b c d e) =
  optLexemes
    [
      fmap withClause a,
      Just (selectClause b),
      fmap sortClause c,
      fmap selectLimit d,
      fmap forLockingClause e
    ]

selectWithParens = inParens . \ case
  NoParensSelectWithParens a -> selectNoParens a
  WithParensSelectWithParens a -> selectWithParens a

withClause (WithClause a b) =
  "WITH " <> bool "" "RECURSIVE " a <> commaNonEmpty commonTableExpr b

commonTableExpr (CommonTableExpr a b c d) =
  optLexemes
    [
      Just (ident a),
      fmap (inParens . commaNonEmpty ident) b,
      Just "AS",
      fmap materialization c,
      Just (inParens (preparableStmt d))
    ]

materialization = bool "NOT MATERIALIZED" "MATERIALIZED"

selectLimit = \ case
  LimitOffsetSelectLimit a b -> lexemes [limitClause a, offsetClause b]
  OffsetLimitSelectLimit a b -> lexemes [offsetClause a, limitClause b]
  LimitSelectLimit a -> limitClause a
  OffsetSelectLimit a -> offsetClause a

limitClause = \ case
  LimitLimitClause a b -> "LIMIT " <> selectLimitValue a <> foldMap (mappend ", " . aExpr) b
  FetchOnlyLimitClause a b c ->
    optLexemes
      [
        Just "FETCH",
        Just (firstOrNext a),
        fmap selectFetchFirstValue b,
        Just (rowOrRows c),
        Just "ONLY"
      ]

firstOrNext = bool "FIRST" "NEXT"

rowOrRows = bool "ROW" "ROWS"

selectFetchFirstValue = \ case
  ExprSelectFetchFirstValue a -> cExpr a
  NumSelectFetchFirstValue a b -> bool "+" "-" a <> intOrFloat b

intOrFloat = either int64Dec doubleDec

selectLimitValue = \ case
  ExprSelectLimitValue a -> aExpr a
  AllSelectLimitValue -> "ALL"

offsetClause = \ case
  ExprOffsetClause a -> "OFFSET " <> aExpr a
  FetchFirstOffsetClause a b -> "OFFSET " <> selectFetchFirstValue a <> " " <> rowOrRows b

forLockingClause = \ case
  ItemsForLockingClause a -> spaceNonEmpty forLockingItem a
  ReadOnlyForLockingClause -> "FOR READ ONLY"

forLockingItem (ForLockingItem a b c) =
  optLexemes
    [
      Just (forLockingStrength a),
      fmap lockedRelsList b,
      fmap nowaitOrSkip c
    ]

forLockingStrength = \ case
  UpdateForLockingStrength -> "FOR UPDATE"
  NoKeyUpdateForLockingStrength -> "FOR NO KEY UPDATE"
  ShareForLockingStrength -> "FOR SHARE"
  KeyForLockingStrength -> "FOR KEY SHARE"

lockedRelsList a = "OF " <> commaNonEmpty qualifiedName a

nowaitOrSkip = bool "NOWAIT" "SKIP LOCKED"

selectClause = either simpleSelect selectWithParens

simpleSelect = \ case
  NormalSimpleSelect a b c d e f g ->
    optLexemes
      [
        Just "SELECT",
        fmap targeting a,
        fmap intoClause b,
        fmap fromClause c,
        fmap whereClause d,
        fmap groupClause e,
        fmap havingClause f,
        fmap windowClause g
      ]
  ValuesSimpleSelect a -> valuesClause a
  TableSimpleSelect a -> "TABLE " <> relationExpr a
  BinSimpleSelect a b c d -> selectClause b <> " " <> selectBinOp a <> foldMap (mappend " ". allOrDistinct) c <> " " <> selectClause d

selectBinOp = \ case
  UnionSelectBinOp -> "UNION"
  IntersectSelectBinOp -> "INTERSECT"
  ExceptSelectBinOp -> "EXCEPT"

targeting = \ case
  NormalTargeting a -> targetList a
  AllTargeting a -> "ALL" <> suffixMaybe targetList a
  DistinctTargeting a b -> "DISTINCT" <> suffixMaybe onExpressionsClause a <> " " <> commaNonEmpty targetEl b

targetList = commaNonEmpty targetEl

onExpressionsClause a = "ON (" <> commaNonEmpty aExpr a <> ")"

targetEl = \ case
  AliasedExprTargetEl a b -> aExpr a <> " AS " <> ident b
  ImplicitlyAliasedExprTargetEl a b -> aExpr a <> " " <> ident b
  ExprTargetEl a -> aExpr a
  AsteriskTargetEl -> "*"


-- * Select Into
-------------------------

intoClause a = "INTO " <> optTempTableName a

optTempTableName = \ case
  TemporaryOptTempTableName a b -> optLexemes [Just "TEMPORARY", bool Nothing (Just "TABLE") a, Just (qualifiedName b)]
  TempOptTempTableName a b -> optLexemes [Just "TEMP", bool Nothing (Just "TABLE") a, Just (qualifiedName b)]
  LocalTemporaryOptTempTableName a b -> optLexemes [Just "LOCAL TEMPORARY", bool Nothing (Just "TABLE") a, Just (qualifiedName b)]
  LocalTempOptTempTableName a b -> optLexemes [Just "LOCAL TEMP", bool Nothing (Just "TABLE") a, Just (qualifiedName b)]
  GlobalTemporaryOptTempTableName a b -> optLexemes [Just "GLOBAL TEMPORARY", bool Nothing (Just "TABLE") a, Just (qualifiedName b)]
  GlobalTempOptTempTableName a b -> optLexemes [Just "GLOBAL TEMP", bool Nothing (Just "TABLE") a, Just (qualifiedName b)]
  UnloggedOptTempTableName a b -> optLexemes [Just "UNLOGGED", bool Nothing (Just "TABLE") a, Just (qualifiedName b)]
  TableOptTempTableName a -> "TABLE " <> qualifiedName a
  QualifedOptTempTableName a -> qualifiedName a


-- * From
-------------------------

fromClause a = "FROM " <> fromList a

fromList = commaNonEmpty tableRef

tableRef = \ case
  RelationExprTableRef a b c ->
    optLexemes [
        Just (relationExpr a),
        fmap aliasClause b,
        fmap tablesampleClause c
      ]
  FuncTableRef a b c ->
    optLexemes [
        if a then Just "LATERAL" else Nothing,
        Just (funcTable b),
        fmap funcAliasClause c
      ]
  SelectTableRef a b c ->
    optLexemes [
        if a then Just "LATERAL" else Nothing,
        Just (selectWithParens b),
        fmap aliasClause c
      ]
  JoinTableRef a b -> case b of
    Just c -> inParens (joinedTable a) <> " " <> aliasClause c
    Nothing -> joinedTable a

relationExpr = \ case
  SimpleRelationExpr a b -> qualifiedName a <> bool "" " *" b
  OnlyRelationExpr a b -> "ONLY " <> bool qualifiedName (inParens . qualifiedName) b a

relationExprOptAlias (RelationExprOptAlias a b) = relationExpr a <> suffixMaybe optAlias b

optAlias (a, b) = bool "" "AS " a <> colId b

tablesampleClause (TablesampleClause a b c) =
  "TABLESAMPLE " <> funcName a <> " (" <> exprList b <> ")" <> suffixMaybe repeatableClause c

repeatableClause a = "REPEATABLE (" <> aExpr a <> ")"

funcTable = \ case
  FuncExprFuncTable a b -> funcExprWindownless a <> bool "" " WITH ORDINALITY" b
  RowsFromFuncTable a b -> "ROWS FROM (" <> rowsfromList a <> ")" <> bool "" " WITH ORDINALITY" b

rowsfromItem (RowsfromItem a b) = funcExprWindownless a <> suffixMaybe colDefList b

rowsfromList = commaNonEmpty rowsfromItem

colDefList a = "AS (" <> tableFuncElementList a <> ")"

tableFuncElementList = commaNonEmpty tableFuncElement

tableFuncElement (TableFuncElement a b c) = colId a <> " " <> typename b <> suffixMaybe collateClause c

collateClause a = "COLLATE " <> anyName a

aliasClause (AliasClause a b c) =
  optLexemes
    [
      if a then Just "AS" else Nothing,
      Just (ident b),
      fmap (inParens . commaNonEmpty ident) c
    ]

funcAliasClause = \ case
  AliasFuncAliasClause a -> aliasClause a
  AsFuncAliasClause a -> "AS (" <> tableFuncElementList a <> ")"
  AsColIdFuncAliasClause a b -> "AS " <> colId a <> " (" <> tableFuncElementList b <> ")"
  ColIdFuncAliasClause a b -> colId a <> " (" <> tableFuncElementList b <> ")"

joinedTable = \ case
  InParensJoinedTable a -> inParens (joinedTable a)
  MethJoinedTable a b c -> case a of
    CrossJoinMeth -> tableRef b <> " CROSS JOIN " <> tableRef c
    QualJoinMeth d e -> tableRef b <> suffixMaybe joinType d <> " JOIN " <> tableRef c <> " " <> joinQual e
    NaturalJoinMeth d -> tableRef b <> " NATURAL" <> suffixMaybe joinType d <> " JOIN " <> tableRef c

joinType = \ case
  FullJoinType a -> "FULL" <> if a then " OUTER" else ""
  LeftJoinType a -> "LEFT" <> if a then " OUTER" else ""
  RightJoinType a -> "RIGHT" <> if a then " OUTER" else ""
  InnerJoinType -> "INNER"

joinQual = \ case
  UsingJoinQual a -> "USING (" <> commaNonEmpty ident a <> ")"
  OnJoinQual a -> "ON " <> aExpr a


-- * Where
-------------------------

whereClause a = "WHERE " <> aExpr a

whereOrCurrentClause = \ case
  ExprWhereOrCurrentClause a -> "WHERE " <> aExpr a
  CursorWhereOrCurrentClause a -> "WHERE CURRENT OF " <> cursorName a


-- * Group By
-------------------------

groupClause a = "GROUP BY " <> commaNonEmpty groupByItem a

groupByItem = \ case
  ExprGroupByItem a -> aExpr a
  EmptyGroupingSetGroupByItem -> "()"
  RollupGroupByItem a -> "ROLLUP (" <> commaNonEmpty aExpr a <> ")"
  CubeGroupByItem a -> "CUBE (" <> commaNonEmpty aExpr a <> ")"
  GroupingSetsGroupByItem a -> "GROUPING SETS (" <> commaNonEmpty groupByItem a <> ")"


-- * Having
-------------------------

havingClause a = "HAVING " <> aExpr a


-- * Window
-------------------------

windowClause a = "WINDOW " <> commaNonEmpty windowDefinition a

windowDefinition (WindowDefinition a b) = ident a <> " AS " <> windowSpecification b

windowSpecification (WindowSpecification a b c d) =
  inParens $ optLexemes
    [
      fmap ident a,
      fmap partitionClause b,
      fmap sortClause c,
      fmap frameClause d
    ]

partitionClause a = "PARTITION BY " <> commaNonEmpty aExpr a

frameClause (FrameClause a b c) =
  optLexemes
    [
      Just (frameClauseMode a),
      Just (frameExtent b),
      fmap windowExclusionCause c
    ]

frameClauseMode = \ case
  RangeFrameClauseMode -> "RANGE"
  RowsFrameClauseMode -> "ROWS"
  GroupsFrameClauseMode -> "GROUPS"

frameExtent = \ case
  SingularFrameExtent a -> frameBound a
  BetweenFrameExtent a b -> "BETWEEN " <> frameBound a <> " AND " <> frameBound b

frameBound = \ case
  UnboundedPrecedingFrameBound -> "UNBOUNDED PRECEDING"
  UnboundedFollowingFrameBound -> "UNBOUNDED FOLLOWING"
  CurrentRowFrameBound -> "CURRENT ROW"
  PrecedingFrameBound a -> aExpr a <> " PRECEDING"
  FollowingFrameBound a -> aExpr a <> " FOLLOWING"

windowExclusionCause = \ case
  CurrentRowWindowExclusionClause -> "EXCLUDE CURRENT ROW"
  GroupWindowExclusionClause -> "EXCLUDE GROUP"
  TiesWindowExclusionClause -> "EXCLUDE TIES"
  NoOthersWindowExclusionClause -> "EXCLUDE NO OTHERS"


-- * Order By
-------------------------

sortClause a = "ORDER BY " <> commaNonEmpty sortBy a

sortBy = \ case
  UsingSortBy a b c -> aExpr a <> " USING " <> qualAllOp b <> suffixMaybe nullsOrder c
  AscDescSortBy a b c -> aExpr a <> suffixMaybe ascDesc b <> suffixMaybe nullsOrder c


-- * Values
-------------------------

valuesClause a = "VALUES " <> commaNonEmpty (inParens . commaNonEmpty aExpr) a


-- * Exprs
-------------------------

exprList = commaNonEmpty aExpr

aExpr = \ case
  CExprAExpr a -> cExpr a
  TypecastAExpr a b -> aExpr a <> " :: " <> typename b
  CollateAExpr a b -> aExpr a <> " COLLATE " <> anyName b
  AtTimeZoneAExpr a b -> aExpr a <> " AT TIME ZONE " <> aExpr b
  PlusAExpr a -> "+ " <> aExpr a
  MinusAExpr a -> "- " <> aExpr a
  SymbolicBinOpAExpr a b c -> aExpr a <> " " <> symbolicExprBinOp b <> " " <> aExpr c
  PrefixQualOpAExpr a b -> qualOp a <> " " <> aExpr b
  SuffixQualOpAExpr a b -> aExpr a <> " " <> qualOp b
  AndAExpr a b -> aExpr a <> " AND " <> aExpr b
  OrAExpr a b -> aExpr a <> " OR " <> aExpr b
  NotAExpr a -> "NOT " <> aExpr a
  VerbalExprBinOpAExpr a b c d e -> aExpr a <> " " <> verbalExprBinOp b c <> " " <> aExpr d <> foldMap (mappend " ESCAPE " . aExpr) e
  ReversableOpAExpr a b c -> aExpr a <> " " <> aExprReversableOp b c
  IsnullAExpr a -> aExpr a <> " ISNULL"
  NotnullAExpr a -> aExpr a <> " NOTNULL"
  OverlapsAExpr a b -> row a <> " OVERLAPS " <> row b
  SubqueryAExpr a b c d -> aExpr a <> " " <> subqueryOp b <> " " <> subType c <> " " <> either selectWithParens (inParens . aExpr) d
  UniqueAExpr a -> "UNIQUE " <> selectWithParens a
  DefaultAExpr -> "DEFAULT"

bExpr = \ case
  CExprBExpr a -> cExpr a
  TypecastBExpr a b -> bExpr a <> " :: " <> typename b
  PlusBExpr a -> "+ " <> bExpr a
  MinusBExpr a -> "- " <> bExpr a
  SymbolicBinOpBExpr a b c -> bExpr a <> " " <> symbolicExprBinOp b <> " " <> bExpr c
  QualOpBExpr a b -> qualOp a <> " " <> bExpr b
  IsOpBExpr a b c -> bExpr a <> " " <> bExprIsOp b c

cExpr = \ case
  ColumnrefCExpr a -> columnref a
  AexprConstCExpr a -> aexprConst a
  ParamCExpr a b -> "$" <> intDec a <> foldMap indirection b
  InParensCExpr a b -> inParens (aExpr a) <> foldMap indirection b
  CaseCExpr a -> caseExpr a
  FuncCExpr a -> funcExpr a
  SelectWithParensCExpr a b -> selectWithParens a <> foldMap indirection b
  ExistsCExpr a -> "EXISTS " <> selectWithParens a
  ArrayCExpr a -> "ARRAY " <> either selectWithParens arrayExpr a
  ExplicitRowCExpr a -> explicitRow a
  ImplicitRowCExpr a -> implicitRow a
  GroupingCExpr a -> "GROUPING " <> inParens (exprList a)


-- * Ops
-------------------------

aExprReversableOp a = \ case
  NullAExprReversableOp -> bool "IS " "IS NOT " a <> "NULL"
  TrueAExprReversableOp -> bool "IS " "IS NOT " a <> "TRUE"
  FalseAExprReversableOp -> bool "IS " "IS NOT " a <> "FALSE"
  UnknownAExprReversableOp -> bool "IS " "IS NOT " a <> "UNKNOWN"
  DistinctFromAExprReversableOp b -> bool "IS " "IS NOT " a <> "DISTINCT FROM " <> aExpr b
  OfAExprReversableOp b -> bool "IS " "IS NOT " a <> "OF " <> inParens (typeList b)
  BetweenAExprReversableOp b c d -> bool "" "NOT " a <> bool "BETWEEN " "BETWEEN ASYMMETRIC " b <> bExpr c <> " AND " <> aExpr d
  BetweenSymmetricAExprReversableOp b c -> bool "" "NOT " a <> "BETWEEN SYMMETRIC " <> bExpr b <> " AND " <> aExpr c
  InAExprReversableOp b -> bool "" "NOT " a <> "IN " <> inExpr b
  DocumentAExprReversableOp -> bool "IS " "IS NOT " a <> "DOCUMENT"

verbalExprBinOp a = mappend (bool "" "NOT " a) . \ case
  LikeVerbalExprBinOp -> "LIKE"
  IlikeVerbalExprBinOp -> "ILIKE"
  SimilarToVerbalExprBinOp -> "SIMILAR TO"

subqueryOp = \ case
  AllSubqueryOp a -> allOp a
  AnySubqueryOp a -> "OPERATOR " <> inParens (anyOperator a)
  LikeSubqueryOp a -> bool "" "NOT " a <> "LIKE"
  IlikeSubqueryOp a -> bool "" "NOT " a <> "ILIKE"

bExprIsOp a = mappend (bool "IS " "IS NOT " a) . \ case
  DistinctFromBExprIsOp b -> "DISTINCT FROM " <> bExpr b
  OfBExprIsOp a -> "OF " <> inParens (typeList a)
  DocumentBExprIsOp -> "DOCUMENT"

symbolicExprBinOp = \ case
  MathSymbolicExprBinOp a -> mathOp a
  QualSymbolicExprBinOp a -> qualOp a

qualOp = \ case
  OpQualOp a -> op a
  OperatorQualOp a -> "OPERATOR (" <> anyOperator a <> ")"

qualAllOp = \ case
  AllQualAllOp a -> allOp a
  AnyQualAllOp a -> "OPERATOR (" <> anyOperator a <> ")"

op = text

anyOperator = \ case
  AllOpAnyOperator a -> allOp a
  QualifiedAnyOperator a b -> colId a <> "." <> anyOperator b

allOp = \ case
  OpAllOp a -> op a
  MathAllOp a -> mathOp a

mathOp = \ case
  PlusMathOp -> char7 '+'
  MinusMathOp -> char7 '-'
  AsteriskMathOp -> char7 '*'
  SlashMathOp -> char7 '/'
  PercentMathOp -> char7 '%'
  ArrowUpMathOp -> char7 '^'
  ArrowLeftMathOp -> char7 '<'
  ArrowRightMathOp -> char7 '>'
  EqualsMathOp -> char7 '='
  LessEqualsMathOp -> "<="
  GreaterEqualsMathOp -> ">="
  ArrowLeftArrowRightMathOp -> "<>"
  ExclamationEqualsMathOp -> "!="


-- *
-------------------------

inExpr = \ case
  SelectInExpr a -> selectWithParens a
  ExprListInExpr a -> inParens (exprList a)

caseExpr (CaseExpr a b c) = optLexemes [
    Just "CASE",
    fmap aExpr a,
    Just (spaceNonEmpty whenClause b),
    fmap caseDefault c,
    Just "END"
  ]

whenClause (WhenClause a b) = "WHEN " <> aExpr a <> " THEN " <> aExpr b

caseDefault a = "ELSE " <> aExpr a

arrayExpr = inBrackets . \ case
  ExprListArrayExpr a -> exprList a
  ArrayExprListArrayExpr a -> arrayExprList a
  EmptyArrayExpr -> mempty

arrayExprList = commaNonEmpty arrayExpr

row = \ case
  ExplicitRowRow a -> explicitRow a
  ImplicitRowRow a -> implicitRow a

explicitRow a = "ROW " <> inParens (foldMap exprList a)

implicitRow (ImplicitRow a b) = inParens (exprList a <> ", " <> aExpr b)

funcApplication (FuncApplication a b) =
  funcName a <> "(" <> foldMap funcApplicationParams b <> ")"

funcApplicationParams = \ case
  NormalFuncApplicationParams a b c ->
    optLexemes
      [
        fmap allOrDistinct a,
        Just (commaNonEmpty funcArgExpr b),
        fmap sortClause c
      ]
  VariadicFuncApplicationParams a b c ->
    optLexemes
      [
        fmap (flip mappend "," . commaNonEmpty funcArgExpr) a,
        Just "VARIADIC",
        Just (funcArgExpr b),
        fmap sortClause c
      ]
  StarFuncApplicationParams -> "*"

allOrDistinct = \ case
  False -> "ALL"
  True -> "DISTINCT"

funcArgExpr = \ case
  ExprFuncArgExpr a -> aExpr a
  ColonEqualsFuncArgExpr a b -> ident a <> " := " <> aExpr b
  EqualsGreaterFuncArgExpr a b -> ident a <> " => " <> aExpr b

-- ** Func Expr
-------------------------

funcExpr = \ case
  ApplicationFuncExpr a b c d -> optLexemes [
      Just (funcApplication a),
      fmap withinGroupClause b,
      fmap filterClause c,
      fmap overClause d
    ]
  SubexprFuncExpr a -> funcExprCommonSubexpr a

funcExprWindownless = \ case
  ApplicationFuncExprWindowless a -> funcApplication a
  CommonSubexprFuncExprWindowless a -> funcExprCommonSubexpr a

withinGroupClause a = "WITHIN GROUP (" <> sortClause a <> ")"

filterClause a = "FILTER (WHERE " <> aExpr a <> ")"

overClause = \ case
  WindowOverClause a -> "OVER " <> windowSpecification a
  ColIdOverClause a -> "OVER " <> colId a

funcExprCommonSubexpr = \ case
  CollationForFuncExprCommonSubexpr a -> "COLLATION FOR (" <> aExpr a <> ")"
  CurrentDateFuncExprCommonSubexpr -> "CURRENT_DATE"
  CurrentTimeFuncExprCommonSubexpr a -> "CURRENT_TIME" <> suffixMaybe (inParens . iconst) a
  CurrentTimestampFuncExprCommonSubexpr a -> "CURRENT_TIMESTAMP" <> suffixMaybe (inParens . iconst) a
  LocalTimeFuncExprCommonSubexpr a -> "LOCALTIME" <> suffixMaybe (inParens . iconst) a
  LocalTimestampFuncExprCommonSubexpr a -> "LOCALTIMESTAMP" <> suffixMaybe (inParens . iconst) a
  CurrentRoleFuncExprCommonSubexpr -> "CURRENT_ROLE"
  CurrentUserFuncExprCommonSubexpr -> "CURRENT_USER"
  SessionUserFuncExprCommonSubexpr -> "SESSION_USER"
  UserFuncExprCommonSubexpr -> "USER"
  CurrentCatalogFuncExprCommonSubexpr -> "CURRENT_CATALOG"
  CurrentSchemaFuncExprCommonSubexpr -> "CURRENT_SCHEMA"
  CastFuncExprCommonSubexpr a b -> "CAST (" <> aExpr a <> " AS " <> typename b <> ")"
  ExtractFuncExprCommonSubexpr a -> "EXTRACT (" <> foldMap extractList a <> ")"
  OverlayFuncExprCommonSubexpr a -> "OVERLAY (" <> overlayList a <> ")"
  PositionFuncExprCommonSubexpr a -> "POSITION (" <> foldMap positionList a <> ")"
  SubstringFuncExprCommonSubexpr a -> "SUBSTRING (" <> foldMap substrList a <> ")"
  TreatFuncExprCommonSubexpr a b -> "TREAT (" <> aExpr a <> " AS " <> typename b <> ")"
  TrimFuncExprCommonSubexpr a b -> "TRIM (" <> prefixMaybe trimModifier a <> trimList b <> ")"
  NullIfFuncExprCommonSubexpr a b -> "NULLIF (" <> aExpr a <> ", " <> aExpr b <> ")"
  CoalesceFuncExprCommonSubexpr a -> "COALESCE (" <> exprList a <> ")"
  GreatestFuncExprCommonSubexpr a -> "GREATEST (" <> exprList a <> ")"
  LeastFuncExprCommonSubexpr a -> "LEAST (" <> exprList a <> ")"

extractList (ExtractList a b) = extractArg a <> " FROM " <> aExpr b

extractArg = \ case
  IdentExtractArg a -> ident a
  YearExtractArg -> "YEAR"
  MonthExtractArg -> "MONTH"
  DayExtractArg -> "DAY"
  HourExtractArg -> "HOUR"
  MinuteExtractArg -> "MINUTE"
  SecondExtractArg -> "SECOND"
  SconstExtractArg a -> sconst a

overlayList (OverlayList a b c d) = aExpr a <> " " <> overlayPlacing b <> " " <> substrFrom c <> suffixMaybe substrFor d

overlayPlacing a = "PLACING " <> aExpr a

positionList (PositionList a b) = bExpr a <> " IN " <> bExpr b

substrList = \ case
  ExprSubstrList a b -> aExpr a <> " " <> substrListFromFor b
  ExprListSubstrList a -> exprList a

substrListFromFor = \ case
  FromForSubstrListFromFor a b -> substrFrom a <> " " <> substrFor b
  ForFromSubstrListFromFor a b -> substrFor a <> " " <> substrFrom b
  FromSubstrListFromFor a -> substrFrom a
  ForSubstrListFromFor a -> substrFor a

substrFrom a = "FROM " <> aExpr a

substrFor a = "FOR " <> aExpr a

trimModifier = \ case
  BothTrimModifier -> "BOTH"
  LeadingTrimModifier -> "LEADING"
  TrailingTrimModifier -> "TRAILING"

trimList = \ case
  ExprFromExprListTrimList a b -> aExpr a <> " FROM " <> exprList b
  FromExprListTrimList a -> "FROM " <> exprList a
  ExprListTrimList a -> exprList a


-- * AexprConsts
-------------------------

aexprConst = \ case
  IAexprConst a -> iconst a
  FAexprConst a -> fconst a
  SAexprConst a -> sconst a
  BAexprConst a -> "B'" <> text a <> "'"
  XAexprConst a -> "X'" <> text a <> "'"
  FuncAexprConst a b c -> funcName a <> foldMap (inParens . funcAexprConstArgList) b <> " " <> sconst c
  ConstTypenameAexprConst a b -> constTypename a <> " " <> sconst b
  StringIntervalAexprConst a b -> "INTERVAL " <> sconst a <> suffixMaybe interval b
  IntIntervalAexprConst a b -> "INTERVAL " <> inParens (int64Dec a) <> " " <> sconst b
  BoolAexprConst a -> if a then "TRUE" else "FALSE"
  NullAexprConst -> "NULL"

iconst = int64Dec

fconst = doubleDec

sconst a = "'" <> text (Text.replace "'" "''" a) <> "'"

funcAexprConstArgList (FuncConstArgs a b) = commaNonEmpty funcArgExpr a <> suffixMaybe sortClause b

constTypename = \ case
  NumericConstTypename a -> numeric a
  ConstBitConstTypename a -> constBit a
  ConstCharacterConstTypename a -> constCharacter a
  ConstDatetimeConstTypename a -> constDatetime a

numeric = \ case
  IntNumeric -> "INT"
  IntegerNumeric -> "INTEGER"
  SmallintNumeric -> "SMALLINT"
  BigintNumeric -> "BIGINT"
  RealNumeric -> "REAL"
  FloatNumeric a -> "FLOAT" <> suffixMaybe (inParens . int64Dec) a
  DoublePrecisionNumeric -> "DOUBLE PRECISION"
  DecimalNumeric a -> "DECIMAL" <> suffixMaybe (inParens . commaNonEmpty aExpr) a
  DecNumeric a -> "DEC" <> suffixMaybe (inParens . commaNonEmpty aExpr )a
  NumericNumeric a -> "NUMERIC" <> suffixMaybe (inParens . commaNonEmpty aExpr) a
  BooleanNumeric -> "BOOLEAN"

bit (Bit a b) = optLexemes [
    Just "BIT",
    bool Nothing (Just "VARYING") a,
    fmap (inParens . commaNonEmpty aExpr) b
  ]

constBit = bit

constCharacter (ConstCharacter a b) = character a <> suffixMaybe (inParens . int64Dec) b

character = \ case
  CharacterCharacter a -> "CHARACTER" <> bool "" " VARYING" a
  CharCharacter a -> "CHAR" <> bool "" " VARYING" a
  VarcharCharacter -> "VARCHAR"
  NationalCharacterCharacter a -> "NATIONAL CHARACTER" <> bool "" " VARYING" a
  NationalCharCharacter a -> "NATIONAL CHAR" <> bool "" " VARYING" a
  NcharCharacter a -> "NCHAR" <> bool "" " VARYING" a

constDatetime = \ case
  TimestampConstDatetime a b -> optLexemes [
      Just "TIMESTAMP",
      fmap (inParens . int64Dec) a,
      fmap timezone b
    ]
  TimeConstDatetime a b -> optLexemes [
      Just "TIME",
      fmap (inParens . int64Dec) a,
      fmap timezone b
    ]

timezone = \ case
  False -> "WITH TIME ZONE"
  True -> "WITHOUT TIME ZONE"

interval = \ case
  YearInterval -> "YEAR"
  MonthInterval -> "MONTH"
  DayInterval -> "DAY"
  HourInterval -> "HOUR"
  MinuteInterval -> "MINUTE"
  SecondInterval a -> intervalSecond a
  YearToMonthInterval -> "YEAR TO MONTH"
  DayToHourInterval -> "DAY TO HOUR"
  DayToMinuteInterval -> "DAY TO MINUTE"
  DayToSecondInterval a -> "DAY TO " <> intervalSecond a
  HourToMinuteInterval -> "HOUR TO MINUTE"
  HourToSecondInterval a -> "HOUR TO " <> intervalSecond a
  MinuteToSecondInterval a -> "MINUTE TO " <> intervalSecond a

intervalSecond = \ case
  Nothing -> "SECOND"
  Just a -> "SECOND " <> inParens (int64Dec a)


-- * Names and refs
-------------------------

columnref (Columnref a b) = colId a <> foldMap indirection b

ident = \ case
  QuotedIdent a -> char7 '"' <> text (Text.replace "\"" "\"\"" a) <> char7 '"'
  UnquotedIdent a -> text a

qualifiedName = \ case
  SimpleQualifiedName a -> ident a
  IndirectedQualifiedName a b -> ident a <> indirection b

indirection = foldMap indirectionEl

indirectionEl = \ case
  AttrNameIndirectionEl a -> "." <> ident a
  AllIndirectionEl -> ".*"
  ExprIndirectionEl a -> "[" <> aExpr a <> "]"
  SliceIndirectionEl a b -> "[" <> foldMap aExpr a <> ":" <> foldMap aExpr b <> "]"

colId = ident

name = colId

cursorName = name

colLabel = ident

attrName = colLabel

typeFunctionName = ident

funcName = \ case
  TypeFuncName a -> typeFunctionName a
  IndirectedFuncName a b -> colId a <> indirection b

anyName (AnyName a b) = colId a <> foldMap attrs b


-- * Types
-------------------------

typename (Typename a b c d) =
  bool "" "SETOF " a <> simpleTypename b <> foldMap typenameArrayDimensionsWithQuestionMark d

typenameArrayDimensionsWithQuestionMark (a, b) =
  typenameArrayDimensions a

typenameArrayDimensions = \ case
  BoundsTypenameArrayDimensions a -> arrayBounds a
  ExplicitTypenameArrayDimensions a -> " ARRAY" <> foldMap (inBrackets . iconst) a

arrayBounds = spaceNonEmpty (inBrackets . foldMap iconst)

simpleTypename = \ case
  GenericTypeSimpleTypename a -> genericType a
  NumericSimpleTypename a -> numeric a
  BitSimpleTypename a -> bit a
  CharacterSimpleTypename a -> character a
  ConstDatetimeSimpleTypename a -> constDatetime a
  ConstIntervalSimpleTypename a -> "INTERVAL" <> either (suffixMaybe interval) (mappend " " . inParens . iconst) a

genericType (GenericType a b c) = typeFunctionName a <> foldMap attrs b <> suffixMaybe typeModifiers c

attrs = foldMap (mappend "." . attrName)

typeModifiers = inParens . exprList

typeList = commaNonEmpty typename

subType = \ case
  AnySubType -> "ANY"
  SomeSubType -> "SOME"
  AllSubType -> "ALL"


-- * Indexes
-------------------------

indexParams = commaNonEmpty indexElem

indexElem (IndexElem a b c d e) =
  indexElemDef a <>
  suffixMaybe collate b <>
  suffixMaybe class_ c <>
  suffixMaybe ascDesc d <>
  suffixMaybe nullsOrder e

indexElemDef = \ case
  IdIndexElemDef a -> colId a
  FuncIndexElemDef a -> funcExprWindownless a
  ExprIndexElemDef a -> inParens (aExpr a)

collate = mappend "COLLATE " . anyName

class_ = anyName

ascDesc = \ case
  AscAscDesc -> "ASC"
  DescAscDesc -> "DESC"

nullsOrder = \ case
  FirstNullsOrder -> "NULLS FIRST"
  LastNullsOrder -> "NULLS LAST"