package sqlserver

import (
	"github.com/doug-martin/goqu/v9"
	"github.com/doug-martin/goqu/v9/exp"
	"github.com/doug-martin/goqu/v9/sqlgen"
)

func DialectOptions() *goqu.SQLDialectOptions {
	opts := goqu.DefaultDialectOptions()

	opts.BooleanDataTypeSupported = false
	opts.UseLiteralIsBools = false

	opts.SupportsReturn = false
	opts.SupportsOrderByOnUpdate = false
	opts.SupportsLimitOnUpdate = true
	opts.SupportsLimitOnDelete = true
	// T-SQL has no ORDER BY on DELETE. Fail loudly instead of generating invalid SQL (or silently
	// deleting arbitrary rows if the clause were dropped); use a subquery such as
	// WHERE id IN (SELECT TOP (n) id ... ORDER BY ...) to delete an ordered subset.
	opts.SupportsOrderByOnDelete = false
	opts.ErrorOnUnsupportedOrderByOnDelete = true
	opts.SupportsConflictUpdateWhere = false
	opts.SupportsInsertIgnoreSyntax = false
	opts.SupportsConflictTarget = false
	opts.SupportsWithCTE = false
	opts.SupportsWithCTERecursive = false
	opts.SupportsDistinctOn = false
	opts.SupportsWindowFunction = false
	opts.SurroundLimitWithParentheses = true

	opts.PlaceHolderFragment = []byte("@p")
	opts.LimitFragment = []byte(" TOP ")
	opts.IncludePlaceholderNum = true
	opts.DefaultValuesFragment = []byte("")
	opts.BinaryLiteralPrefix = []byte("0x")
	opts.BinaryLiteralSuffix = []byte("")
	opts.BinaryLiteralBytes = []byte{0}
	opts.UnicodeStringPrefix = []byte("N")
	opts.True = []byte("1")
	opts.False = []byte("0")
	opts.TimeFormat = "2006-01-02 15:04:05"
	opts.BooleanOperatorLookup = map[exp.BooleanOperation][]byte{
		exp.EqOp:             []byte("="),
		exp.NeqOp:            []byte("!="),
		exp.GtOp:             []byte(">"),
		exp.GteOp:            []byte(">="),
		exp.LtOp:             []byte("<"),
		exp.LteOp:            []byte("<="),
		exp.InOp:             []byte("IN"),
		exp.NotInOp:          []byte("NOT IN"),
		exp.IsOp:             []byte("IS"),
		exp.IsNotOp:          []byte("IS NOT"),
		exp.LikeOp:           []byte("LIKE"),
		exp.NotLikeOp:        []byte("NOT LIKE"),
		exp.ILikeOp:          []byte("LIKE"),
		exp.NotILikeOp:       []byte("NOT LIKE"),
		exp.RegexpLikeOp:     []byte("REGEXP BINARY"),
		exp.RegexpNotLikeOp:  []byte("NOT REGEXP BINARY"),
		exp.RegexpILikeOp:    []byte("REGEXP"),
		exp.RegexpNotILikeOp: []byte("NOT REGEXP"),
	}
	opts.BitwiseOperatorLookup = map[exp.BitwiseOperation][]byte{
		exp.BitwiseInversionOp: []byte("~"),
		exp.BitwiseOrOp:        []byte("|"),
		exp.BitwiseAndOp:       []byte("&"),
		exp.BitwiseXorOp:       []byte("^"),
	}

	opts.FetchFragment = []byte(" FETCH FIRST ")

	// SQL Server spells a row-limited update "UPDATE TOP (n) <table> SET ...", so TOP has to be
	// emitted immediately after UPDATE, ahead of the table. The default UpdateSQLOrder puts
	// LimitSQLFragment after the WHERE clause, which would produce
	// "UPDATE [t] SET ... WHERE ... TOP (n)". Every other fragment keeps its default position.
	opts.UpdateSQLOrder = []sqlgen.SQLFragmentType{
		sqlgen.CommonTableSQLFragment,
		sqlgen.UpdateBeginSQLFragment,
		sqlgen.LimitSQLFragment,
		sqlgen.SourcesSQLFragment,
		sqlgen.UpdateSQLFragment,
		sqlgen.UpdateFromSQLFragment,
		sqlgen.WhereSQLFragment,
		sqlgen.OrderSQLFragment,
		sqlgen.ReturningSQLFragment,
	}

	// SQL Server spells a row-limited delete "DELETE TOP (n) FROM ...", so TOP has to be
	// emitted immediately after DELETE. The default DeleteSQLOrder puts LimitSQLFragment
	// last, which would produce "DELETE FROM [t] WHERE ... TOP (n)". Every other fragment
	// keeps its default position.
	opts.DeleteSQLOrder = []sqlgen.SQLFragmentType{
		sqlgen.CommonTableSQLFragment,
		sqlgen.DeleteBeginSQLFragment,
		sqlgen.LimitSQLFragment,
		sqlgen.FromSQLFragment,
		sqlgen.WhereSQLFragment,
		sqlgen.OrderSQLFragment,
		sqlgen.ReturningSQLFragment,
	}

	opts.SelectSQLOrder = []sqlgen.SQLFragmentType{
		sqlgen.CommonTableSQLFragment,
		sqlgen.SelectWithLimitSQLFragment,
		sqlgen.FromSQLFragment,
		sqlgen.JoinSQLFragment,
		sqlgen.WhereSQLFragment,
		sqlgen.GroupBySQLFragment,
		sqlgen.HavingSQLFragment,
		sqlgen.QualifySQLFragment,
		sqlgen.WindowSQLFragment,
		sqlgen.CompoundsSQLFragment,
		sqlgen.OrderWithOffsetFetchSQLFragment,
		sqlgen.ForSQLFragment,
	}

	// T-SQL string literals only need the single quote doubled. Backslash escapes are not understood by SQL Server.
	opts.EscapedRunes = map[rune][]byte{
		'\'': []byte("''"),
	}

	opts.OfFragment = []byte("")
	opts.ConflictFragment = []byte("")
	opts.ConflictDoUpdateFragment = []byte("")
	opts.ConflictDoNothingFragment = []byte("")

	return opts
}

func init() {
	goqu.RegisterDialect("sqlserver", DialectOptions())
}
