from __future__ import annotations

import re
import typing as t

from sqlglot import exp
from sqlglot.dialects.dialect import (
    json_extract_segments,
    json_path_key_only_name,
    rename_func,
    bool_xor_sql,
    count_if_to_sum,
    timestamptrunc_sql,
    date_add_interval_sql,
    timestampdiff_sql,
    remove_ts_or_ds_to_date,
)
from sqlglot.expressions import DataType
from sqlglot import generator
from sqlglot.generator import unsupported_args
from sqlglot.generators.mysql import MySQLGenerator, date_add_sql


def _unicode_substitute(m: re.Match[str]) -> str:
    return chr(int(m.group(1), 16))


class SingleStoreGenerator(MySQLGenerator):
    SUPPORTS_UESCAPE = False
    NULL_ORDERING_SUPPORTED: bool | None = True
    MATCH_AGAINST_TABLE_PREFIX: str | None = "TABLE "
    STRUCT_DELIMITER = ("(", ")")

    UNICODE_SUBSTITUTE: t.ClassVar[t.Any] = staticmethod(_unicode_substitute)

    SUPPORTED_JSON_PATH_PARTS = {
        exp.JSONPathKey,
        exp.JSONPathRoot,
        exp.JSONPathSubscript,
    }

    TRANSFORMS = {
        **{
            k: v
            for k, v in MySQLGenerator.TRANSFORMS.items()
            if k not in (exp.JSONExtractScalar, exp.CurrentDate)
        },
        exp.TsOrDsToDate: lambda self, e: (
            self.func("TO_DATE", e.this, self.format_time(e))
            if e.args.get("format")
            else self.func("DATE", e.this)
        ),
        exp.StrToTime: lambda self, e: self.func("TO_TIMESTAMP", e.this, self.format_time(e)),
        exp.ToChar: lambda self, e: self.func("TO_CHAR", e.this, self.format_time(e)),
        exp.StrToDate: lambda self, e: self.func(
            "STR_TO_DATE",
            e.this,
            self.format_time(
                e,
                inverse_time_mapping=self.dialect.MYSQL_INVERSE_TIME_MAPPING,
                inverse_time_trie=self.dialect.MYSQL_INVERSE_TIME_TRIE,
            ),
        ),
        exp.TimeToStr: lambda self, e: self.func(
            "DATE_FORMAT",
            e.this,
            self.format_time(
                e,
                inverse_time_mapping=self.dialect.MYSQL_INVERSE_TIME_MAPPING,
                inverse_time_trie=self.dialect.MYSQL_INVERSE_TIME_TRIE,
            ),
        ),
        exp.Date: unsupported_args("zone", "expressions")(rename_func("DATE")),
        exp.Cast: unsupported_args("format", "action", "default")(
            lambda self, e: f"{self.sql(e, 'this')} :> {self.sql(e, 'to')}"
        ),
        exp.TryCast: unsupported_args("format", "action", "default")(
            lambda self, e: f"{self.sql(e, 'this')} !:> {self.sql(e, 'to')}"
        ),
        exp.CastToStrType: lambda self, e: self.sql(
            exp.cast(e.this, DataType.from_str(e.args["to"].name))
        ),
        exp.StrToUnix: unsupported_args("format")(rename_func("UNIX_TIMESTAMP")),
        exp.TimeToUnix: rename_func("UNIX_TIMESTAMP"),
        exp.TimeStrToUnix: rename_func("UNIX_TIMESTAMP"),
        exp.UnixSeconds: rename_func("UNIX_TIMESTAMP"),
        exp.UnixToStr: lambda self, e: self.func(
            "FROM_UNIXTIME",
            e.this,
            self.format_time(
                e,
                inverse_time_mapping=self.dialect.MYSQL_INVERSE_TIME_MAPPING,
                inverse_time_trie=self.dialect.MYSQL_INVERSE_TIME_TRIE,
            ),
        ),
        exp.UnixToTime: unsupported_args("scale", "zone", "hours", "minutes")(
            lambda self, e: self.func(
                "FROM_UNIXTIME",
                e.this,
                self.format_time(
                    e,
                    inverse_time_mapping=self.dialect.MYSQL_INVERSE_TIME_MAPPING,
                    inverse_time_trie=self.dialect.MYSQL_INVERSE_TIME_TRIE,
                ),
            ),
        ),
        exp.UnixToTimeStr: lambda self, e: f"FROM_UNIXTIME({self.sql(e, 'this')}) :> TEXT",
        exp.DateBin: unsupported_args("unit", "zone")(
            lambda self, e: self.func("TIME_BUCKET", e.this, e.expression, e.args.get("origin"))
        ),
        exp.TimeStrToDate: lambda self, e: self.sql(exp.cast(e.this, exp.DType.DATE)),
        exp.FromTimeZone: lambda self, e: self.func(
            "CONVERT_TZ", e.this, e.args.get("zone"), "'UTC'"
        ),
        exp.DiToDate: lambda self, e: (
            f"STR_TO_DATE({self.sql(e, 'this')}, {self.dialect.DATEINT_FORMAT})"
        ),
        exp.DateToDi: lambda self, e: (
            f"(DATE_FORMAT({self.sql(e, 'this')}, {self.dialect.DATEINT_FORMAT}) :> INT)"
        ),
        exp.TsOrDiToDi: lambda self, e: (
            f"(DATE_FORMAT({self.sql(e, 'this')}, {self.dialect.DATEINT_FORMAT}) :> INT)"
        ),
        exp.Time: unsupported_args("zone")(lambda self, e: f"{self.sql(e, 'this')} :> TIME"),
        exp.DatetimeAdd: remove_ts_or_ds_to_date(date_add_sql("ADD")),
        exp.DatetimeTrunc: unsupported_args("zone")(timestamptrunc_sql()),
        exp.DatetimeSub: date_add_interval_sql("DATE", "SUB"),
        exp.DatetimeDiff: timestampdiff_sql,
        exp.DateTrunc: unsupported_args("zone")(timestamptrunc_sql()),
        exp.DateDiff: unsupported_args("zone")(
            lambda self, e: (
                timestampdiff_sql(self, e)
                if e.unit is not None
                else self.func("DATEDIFF", e.this, e.expression)
            )
        ),
        exp.TsOrDsDiff: lambda self, e: (
            timestampdiff_sql(self, e)
            if e.unit is not None
            else self.func("DATEDIFF", e.this, e.expression)
        ),
        exp.TimestampTrunc: unsupported_args("zone")(timestamptrunc_sql()),
        exp.CurrentDatetime: lambda self, e: self.sql(
            self.dialect.CAST_TO_TIME6(
                exp.CurrentTimestamp(this=exp.Literal.number(6)), exp.DType.DATETIME
            )
        ),
        exp.JSONExtract: unsupported_args(
            "only_json_types",
            "expressions",
            "variant_extract",
            "json_query",
            "option",
            "quote",
            "on_condition",
            "requires_json",
        )(json_extract_segments("JSON_EXTRACT_JSON")),
        exp.JSONBExtract: json_extract_segments("BSON_EXTRACT_BSON"),
        exp.JSONPathKey: json_path_key_only_name,
        exp.JSONPathSubscript: lambda self, e: self.json_path_part(e.this),
        exp.JSONPathRoot: lambda *_: "",
        exp.JSONFormat: unsupported_args("options", "is_json")(rename_func("JSON_PRETTY")),
        exp.JSONArrayAgg: unsupported_args("null_handling", "return_type", "strict")(
            lambda self, e: self.func("JSON_AGG", e.this, suffix=f"{self.sql(e, 'order')})")
        ),
        exp.JSONArray: unsupported_args("null_handling", "return_type", "strict")(
            rename_func("JSON_BUILD_ARRAY")
        ),
        exp.JSONBExists: lambda self, e: self.func(
            "BSON_MATCH_ANY_EXISTS", e.this, e.args.get("path")
        ),
        exp.JSONExists: lambda self, e: (
            f"{self.sql(e.this)}::?{self.sql(e.args.get('path'))}"
            if e.args.get("from_dcolonqmark")
            else self.func("JSON_MATCH_ANY_EXISTS", e.this, e.args.get("path"))
        ),
        exp.JSONObject: unsupported_args("null_handling", "unique_keys", "return_type", "encoding")(
            rename_func("JSON_BUILD_OBJECT")
        ),
        exp.DayOfWeekIso: lambda self, e: f"(({self.func('DAYOFWEEK', e.this)} % 7) + 1)",
        exp.DayOfMonth: rename_func("DAY"),
        exp.Hll: rename_func("APPROX_COUNT_DISTINCT"),
        exp.ApproxDistinct: rename_func("APPROX_COUNT_DISTINCT"),
        exp.CountIf: count_if_to_sum,
        exp.LogicalOr: lambda self, e: f"MAX(ABS({self.sql(e, 'this')}))",
        exp.LogicalAnd: lambda self, e: f"MIN(ABS({self.sql(e, 'this')}))",
        exp.ApproxQuantile: unsupported_args("accuracy", "weight")(
            lambda self, e: self.func(
                "APPROX_PERCENTILE",
                e.this,
                e.args.get("quantile"),
                e.args.get("error_tolerance"),
            )
        ),
        exp.Variance: rename_func("VAR_SAMP"),
        exp.VariancePop: rename_func("VAR_POP"),
        exp.Xor: bool_xor_sql,
        exp.Cbrt: lambda self, e: self.sql(
            exp.Pow(this=e.this, expression=exp.Literal.number(1) / exp.Literal.number(3))
        ),
        exp.RegexpLike: lambda self, e: self.binary(e, "RLIKE"),
        exp.Repeat: lambda self, e: self.func(
            "LPAD",
            exp.Literal.string(""),
            exp.Mul(this=self.func("LENGTH", e.this), expression=e.args.get("times")),
            e.this,
        ),
        exp.IsAscii: lambda self, e: f"({self.sql(e, 'this')} RLIKE '^[\x00-\x7f]*$')",
        exp.MD5Digest: lambda self, e: self.func("UNHEX", self.func("MD5", e.this)),
        exp.Contains: rename_func("INSTR"),
        exp.RegexpExtractAll: unsupported_args("position", "occurrence", "group")(
            lambda self, e: self.func(
                "REGEXP_MATCH",
                e.this,
                e.expression,
                e.args.get("parameters"),
            )
        ),
        exp.RegexpExtract: unsupported_args("group")(
            lambda self, e: self.func(
                "REGEXP_SUBSTR",
                e.this,
                e.expression,
                e.args.get("position"),
                e.args.get("occurrence"),
                e.args.get("parameters"),
            )
        ),
        exp.StartsWith: lambda self, e: self.func(
            "REGEXP_INSTR", e.this, self.func("CONCAT", exp.Literal.string("^"), e.expression)
        ),
        exp.FromBase: lambda self, e: self.func(
            "CONV", e.this, e.expression, exp.Literal.number(10)
        ),
        exp.RegexpILike: lambda self, e: self.binary(
            exp.RegexpLike(
                this=exp.Lower(this=e.this),
                expression=exp.Lower(this=e.expression),
            ),
            "RLIKE",
        ),
        exp.Stuff: lambda self, e: self.func(
            "CONCAT",
            self.func("SUBSTRING", e.this, exp.Literal.number(1), e.args.get("start") - 1),
            e.expression,
            self.func("SUBSTRING", e.this, e.args.get("start") + e.args.get("length")),
        ),
        exp.National: lambda self, e: self.national_sql(e, prefix=""),
        exp.Reduce: unsupported_args("finish")(
            lambda self, e: self.func("REDUCE", e.args.get("initial"), e.this, e.args.get("merge"))
        ),
        exp.MatchAgainst: unsupported_args("modifier")(
            lambda self, e: generator.Generator.matchagainst_sql(self, e)
        ),
        exp.Show: unsupported_args(
            "history",
            "terse",
            "offset",
            "starts_with",
            "limit",
            "from_",
            "scope",
            "scope_kind",
            "mutex",
            "query",
            "channel",
            "log",
            "types",
            "privileges",
        )(lambda self, e: MySQLGenerator.show_sql(self, e)),
        exp.Describe: unsupported_args(
            "style",
            "kind",
            "expressions",
            "partition",
            "format",
        )(lambda self, e: generator.Generator.describe_sql(self, e)),
    }

    UNSUPPORTED_TYPES = {
        exp.DType.ARRAY,
        exp.DType.AGGREGATEFUNCTION,
        exp.DType.SIMPLEAGGREGATEFUNCTION,
        exp.DType.BIGSERIAL,
        exp.DType.BPCHAR,
        exp.DType.DATEMULTIRANGE,
        exp.DType.DATERANGE,
        exp.DType.DYNAMIC,
        exp.DType.HLLSKETCH,
        exp.DType.HSTORE,
        exp.DType.IMAGE,
        exp.DType.INET,
        exp.DType.INT128,
        exp.DType.INT256,
        exp.DType.INT4MULTIRANGE,
        exp.DType.INT4RANGE,
        exp.DType.INT8MULTIRANGE,
        exp.DType.INT8RANGE,
        exp.DType.INTERVAL,
        exp.DType.IPADDRESS,
        exp.DType.IPPREFIX,
        exp.DType.IPV4,
        exp.DType.IPV6,
        exp.DType.LIST,
        exp.DType.MAP,
        exp.DType.LOWCARDINALITY,
        exp.DType.MONEY,
        exp.DType.MULTILINESTRING,
        exp.DType.NAME,
        exp.DType.NESTED,
        exp.DType.NOTHING,
        exp.DType.NULL,
        exp.DType.NUMMULTIRANGE,
        exp.DType.NUMRANGE,
        exp.DType.OBJECT,
        exp.DType.RANGE,
        exp.DType.ROWVERSION,
        exp.DType.SERIAL,
        exp.DType.SMALLSERIAL,
        exp.DType.SMALLMONEY,
        exp.DType.SUPER,
        exp.DType.TIMETZ,
        exp.DType.TIMESTAMPNTZ,
        exp.DType.TIMESTAMPLTZ,
        exp.DType.TIMESTAMPTZ,
        exp.DType.TIMESTAMP_NS,
        exp.DType.TSMULTIRANGE,
        exp.DType.TSRANGE,
        exp.DType.TSTZMULTIRANGE,
        exp.DType.TSTZRANGE,
        exp.DType.UINT128,
        exp.DType.UINT256,
        exp.DType.UNION,
        exp.DType.UNKNOWN,
        exp.DType.USERDEFINED,
        exp.DType.UUID,
        exp.DType.VARIANT,
        exp.DType.XML,
        exp.DType.TDIGEST,
    }

    TYPE_MAPPING = {
        **MySQLGenerator.TYPE_MAPPING,
        exp.DType.BIGDECIMAL: "DECIMAL",
        exp.DType.BIT: "BOOLEAN",
        exp.DType.DATE32: "DATE",
        exp.DType.DATETIME64: "DATETIME",
        exp.DType.DECIMAL32: "DECIMAL",
        exp.DType.DECIMAL64: "DECIMAL",
        exp.DType.DECIMAL128: "DECIMAL",
        exp.DType.DECIMAL256: "DECIMAL",
        exp.DType.ENUM8: "ENUM",
        exp.DType.ENUM16: "ENUM",
        exp.DType.FIXEDSTRING: "TEXT",
        exp.DType.GEOMETRY: "GEOGRAPHY",
        exp.DType.POINT: "GEOGRAPHYPOINT",
        exp.DType.RING: "GEOGRAPHY",
        exp.DType.LINESTRING: "GEOGRAPHY",
        exp.DType.POLYGON: "GEOGRAPHY",
        exp.DType.MULTIPOLYGON: "GEOGRAPHY",
        exp.DType.STRUCT: "RECORD",
        exp.DType.JSONB: "BSON",
        exp.DType.TIMESTAMP: "TIMESTAMP",
        exp.DType.TIMESTAMP_S: "TIMESTAMP",
        exp.DType.TIMESTAMP_MS: "TIMESTAMP",
    }

    TYPE_PARAM_SETTINGS = {
        **MySQLGenerator.TYPE_PARAM_SETTINGS,
        exp.DType.TIMESTAMP_MS: ((6,), ()),
    }

    # https://docs.singlestore.com/cloud/reference/sql-reference/restricted-keywords/list-of-restricted-keywords/
    RESERVED_KEYWORDS = {
        "abs",
        "absolute",
        "access",
        "account",
        "acos",
        "action",
        "add",
        "adddate",
        "addtime",
        "admin",
        "aes_decrypt",
        "aes_encrypt",
        "after",
        "against",
        "aggregate",
        "aggregates",
        "aggregator",
        "aggregator_id",
        "aggregator_plan_hash",
        "aggregators",
        "algorithm",
        "all",
        "also",
        "alter",
        "always",
        "analyse",
        "analyze",
        "and",
        "anti_join",
        "any",
        "any_value",
        "approx_count_distinct",
        "approx_count_distinct_accumulate",
        "approx_count_distinct_combine",
        "approx_count_distinct_estimate",
        "approx_geography_intersects",
        "approx_percentile",
        "arghistory",
        "arrange",
        "arrangement",
        "array",
        "as",
        "asc",
        "ascii",
        "asensitive",
        "asin",
        "asm",
        "assertion",
        "assignment",
        "ast",
        "asymmetric",
        "async",
        "at",
        "atan",
        "atan2",
        "attach",
        "attribute",
        "authorization",
        "auto",
        "auto_increment",
        "auto_reprovision",
        "autostats",
        "autostats_cardinality_mode",
        "autostats_enabled",
        "autostats_histogram_mode",
        "autostats_sampling",
        "availability",
        "avg",
        "avg_row_length",
        "avro",
        "azure",
        "background",
        "_background_threads_for_cleanup",
        "backup",
        "backup_history",
        "backup_id",
        "backward",
        "batch",
        "batches",
        "batch_interval",
        "_batch_size_limit",
        "before",
        "begin",
        "between",
        "bigint",
        "bin",
        "binary",
        "_binary",
        "bit",
        "bit_and",
        "bit_count",
        "bit_or",
        "bit_xor",
        "blob",
        "bool",
        "boolean",
        "bootstrap",
        "both",
        "_bt",
        "btree",
        "bucket_count",
        "by",
        "byte",
        "byte_length",
        "cache",
        "call",
        "call_for_pipeline",
        "called",
        "capture",
        "cascade",
        "cascaded",
        "case",
        "cast",
        "catalog",
        "ceil",
        "ceiling",
        "chain",
        "change",
        "char",
        "character",
        "characteristics",
        "character_length",
        "char_length",
        "charset",
        "check",
        "checkpoint",
        "_check_can_connect",
        "_check_consistency",
        "checksum",
        "_checksum",
        "class",
        "clear",
        "client",
        "client_found_rows",
        "close",
        "cluster",
        "clustered",
        "cnf",
        "coalesce",
        "coercibility",
        "collate",
        "collation",
        "collect",
        "column",
        "columnar",
        "columns",
        "columnstore",
        "columnstore_segment_rows",
        "comment",
        "comments",
        "commit",
        "committed",
        "_commit_log_tail",
        "committed",
        "compact",
        "compile",
        "compressed",
        "compression",
        "concat",
        "concat_ws",
        "concurrent",
        "concurrently",
        "condition",
        "configuration",
        "connection",
        "connection_id",
        "connections",
        "config",
        "constraint",
        "constraints",
        "content",
        "continue",
        "_continue_replay",
        "conv",
        "conversion",
        "convert",
        "convert_tz",
        "copy",
        "_core",
        "cos",
        "cost",
        "cot",
        "count",
        "create",
        "credentials",
        "cross",
        "cube",
        "csv",
        "cume_dist",
        "curdate",
        "current",
        "current_catalog",
        "current_date",
        "current_role",
        "current_schema",
        "current_security_groups",
        "current_security_roles",
        "current_time",
        "current_timestamp",
        "current_user",
        "cursor",
        "curtime",
        "cycle",
        "data",
        "database",
        "databases",
        "date",
        "date_add",
        "datediff",
        "date_format",
        "date_sub",
        "date_trunc",
        "datetime",
        "day",
        "day_hour",
        "day_microsecond",
        "day_minute",
        "dayname",
        "dayofmonth",
        "dayofweek",
        "dayofyear",
        "day_second",
        "deallocate",
        "dec",
        "decimal",
        "declare",
        "decode",
        "default",
        "defaults",
        "deferrable",
        "deferred",
        "defined",
        "definer",
        "degrees",
        "delayed",
        "delay_key_write",
        "delete",
        "delimiter",
        "delimiters",
        "dense_rank",
        "desc",
        "describe",
        "detach",
        "deterministic",
        "dictionary",
        "differential",
        "directory",
        "disable",
        "discard",
        "_disconnect",
        "disk",
        "distinct",
        "distinctrow",
        "distributed_joins",
        "div",
        "do",
        "document",
        "domain",
        "dot_product",
        "double",
        "drop",
        "_drop_profile",
        "dual",
        "dump",
        "duplicate",
        "dynamic",
        "earliest",
        "each",
        "echo",
        "election",
        "else",
        "elseif",
        "elt",
        "enable",
        "enclosed",
        "encoding",
        "encrypted",
        "end",
        "engine",
        "engines",
        "enum",
        "errors",
        "escape",
        "escaped",
        "estimate",
        "euclidean_distance",
        "event",
        "events",
        "except",
        "exclude",
        "excluding",
        "exclusive",
        "execute",
        "exists",
        "exit",
        "exp",
        "explain",
        "extended",
        "extension",
        "external",
        "external_host",
        "external_port",
        "extract",
        "extractor",
        "extractors",
        "extra_join",
        "_failover",
        "failed_login_attempts",
        "failure",
        "false",
        "family",
        "fault",
        "fetch",
        "field",
        "fields",
        "file",
        "files",
        "fill",
        "first",
        "first_value",
        "fix_alter",
        "fixed",
        "float",
        "float4",
        "float8",
        "floor",
        "flush",
        "following",
        "for",
        "force",
        "force_compiled_mode",
        "force_interpreter_mode",
        "foreground",
        "foreign",
        "format",
        "forward",
        "found_rows",
        "freeze",
        "from",
        "from_base64",
        "from_days",
        "from_unixtime",
        "fs",
        "_fsync",
        "full",
        "fulltext",
        "function",
        "functions",
        "gc",
        "gcs",
        "get_format",
        "_gc",
        "_gcx",
        "generate",
        "geography",
        "geography_area",
        "geography_contains",
        "geography_distance",
        "geography_intersects",
        "geography_latitude",
        "geography_length",
        "geography_longitude",
        "geographypoint",
        "geography_point",
        "geography_within_distance",
        "geometry",
        "geometry_area",
        "geometry_contains",
        "geometry_distance",
        "geometry_filter",
        "geometry_intersects",
        "geometry_length",
        "geometrypoint",
        "geometry_point",
        "geometry_within_distance",
        "geometry_x",
        "geometry_y",
        "global",
        "_global_version_timestamp",
        "grant",
        "granted",
        "grants",
        "greatest",
        "group",
        "grouping",
        "groups",
        "group_concat",
        "gzip",
        "handle",
        "handler",
        "hard_cpu_limit_percentage",
        "hash",
        "has_temp_tables",
        "having",
        "hdfs",
        "header",
        "heartbeat_no_logging",
        "hex",
        "highlight",
        "high_priority",
        "hold",
        "holding",
        "host",
        "hosts",
        "hour",
        "hour_microsecond",
        "hour_minute",
        "hour_second",
        "identified",
        "identity",
        "if",
        "ifnull",
        "ignore",
        "ilike",
        "immediate",
        "immutable",
        "implicit",
        "import",
        "in",
        "including",
        "increment",
        "incremental",
        "index",
        "indexes",
        "inet_aton",
        "inet_ntoa",
        "inet6_aton",
        "inet6_ntoa",
        "infile",
        "inherit",
        "inherits",
        "_init_profile",
        "init",
        "initcap",
        "initialize",
        "initially",
        "inject",
        "inline",
        "inner",
        "inout",
        "input",
        "insensitive",
        "insert",
        "insert_method",
        "instance",
        "instead",
        "instr",
        "int",
        "int1",
        "int2",
        "int3",
        "int4",
        "int8",
        "integer",
        "_internal_dynamic_typecast",
        "interpreter_mode",
        "intersect",
        "interval",
        "into",
        "invoker",
        "is",
        "isnull",
        "isolation",
        "iterate",
        "join",
        "json",
        "json_agg",
        "json_array_contains_double",
        "json_array_contains_json",
        "json_array_contains_string",
        "json_array_push_double",
        "json_array_push_json",
        "json_array_push_string",
        "json_delete_key",
        "json_extract_double",
        "json_extract_json",
        "json_extract_string",
        "json_extract_bigint",
        "json_get_type",
        "json_length",
        "json_set_double",
        "json_set_json",
        "json_set_string",
        "json_splice_double",
        "json_splice_json",
        "json_splice_string",
        "kafka",
        "key",
        "key_block_size",
        "keys",
        "kill",
        "killall",
        "label",
        "lag",
        "language",
        "large",
        "last",
        "last_day",
        "last_insert_id",
        "last_value",
        "lateral",
        "latest",
        "lc_collate",
        "lc_ctype",
        "lcase",
        "lead",
        "leading",
        "leaf",
        "leakproof",
        "least",
        "leave",
        "leaves",
        "left",
        "length",
        "level",
        "license",
        "like",
        "limit",
        "lines",
        "listen",
        "llvm",
        "ln",
        "load",
        "loaddata_where",
        "_load",
        "local",
        "localtime",
        "localtimestamp",
        "locate",
        "location",
        "lock",
        "log",
        "log10",
        "log2",
        "long",
        "longblob",
        "longtext",
        "loop",
        "lower",
        "low_priority",
        "lpad",
        "_ls",
        "ltrim",
        "lz4",
        "management",
        "_management_thread",
        "mapping",
        "master",
        "match",
        "materialized",
        "max",
        "maxvalue",
        "max_concurrency",
        "max_errors",
        "max_partitions_per_batch",
        "max_queue_depth",
        "max_retries_per_batch_partition",
        "max_rows",
        "mbc",
        "md5",
        "mpl",
        "median",
        "mediumblob",
        "mediumint",
        "mediumtext",
        "member",
        "memory",
        "memory_percentage",
        "_memsql_table_id_lookup",
        "memsql",
        "memsql_deserialize",
        "memsql_imitating_kafka",
        "memsql_serialize",
        "merge",
        "metadata",
        "microsecond",
        "middleint",
        "min",
        "min_rows",
        "minus",
        "minute",
        "minute_microsecond",
        "minute_second",
        "minvalue",
        "mod",
        "mode",
        "model",
        "modifies",
        "modify",
        "month",
        "monthname",
        "months_between",
        "move",
        "mpl",
        "names",
        "named",
        "namespace",
        "national",
        "natural",
        "nchar",
        "next",
        "no",
        "node",
        "none",
        "no_query_rewrite",
        "noparam",
        "not",
        "nothing",
        "notify",
        "now",
        "nowait",
        "no_write_to_binlog",
        "no_query_rewrite",
        "norely",
        "nth_value",
        "ntile",
        "null",
        "nullcols",
        "nullif",
        "nulls",
        "numeric",
        "nvarchar",
        "object",
        "octet_length",
        "of",
        "off",
        "offline",
        "offset",
        "offsets",
        "oids",
        "on",
        "online",
        "only",
        "open",
        "operator",
        "optimization",
        "optimize",
        "optimizer",
        "optimizer_state",
        "option",
        "options",
        "optionally",
        "or",
        "order",
        "ordered_serialize",
        "orphan",
        "out",
        "out_of_order",
        "outer",
        "outfile",
        "over",
        "overlaps",
        "overlay",
        "owned",
        "owner",
        "pack_keys",
        "paired",
        "parser",
        "parquet",
        "partial",
        "partition",
        "partition_id",
        "partitioning",
        "partitions",
        "passing",
        "password",
        "password_lock_time",
        "parser",
        "pause",
        "_pause_replay",
        "percent_rank",
        "percentile_cont",
        "percentile_disc",
        "periodic",
        "persisted",
        "pi",
        "pipeline",
        "pipelines",
        "pivot",
        "placing",
        "plan",
        "plans",
        "plancache",
        "plugins",
        "pool",
        "pools",
        "port",
        "position",
        "pow",
        "power",
        "preceding",
        "precision",
        "prepare",
        "prepared",
        "preserve",
        "primary",
        "prior",
        "privileges",
        "procedural",
        "procedure",
        "procedures",
        "process",
        "processlist",
        "profile",
        "profiles",
        "program",
        "promote",
        "proxy",
        "purge",
        "quarter",
        "queries",
        "query",
        "query_timeout",
        "queue",
        "quote",
        "radians",
        "rand",
        "range",
        "rank",
        "read",
        "_read",
        "reads",
        "real",
        "reassign",
        "rebalance",
        "recheck",
        "record",
        "recursive",
        "redundancy",
        "redundant",
        "ref",
        "reference",
        "references",
        "refresh",
        "regexp",
        "reindex",
        "relative",
        "release",
        "reload",
        "rely",
        "remote",
        "remove",
        "rename",
        "repair",
        "_repair_table",
        "repeat",
        "repeatable",
        "_repl",
        "_reprovisioning",
        "replace",
        "replica",
        "replicate",
        "replicating",
        "replication",
        "durability",
        "require",
        "resource",
        "resource_pool",
        "reset",
        "restart",
        "restore",
        "restrict",
        "result",
        "_resurrect",
        "retry",
        "return",
        "returning",
        "returns",
        "reverse",
        "revoke",
        "rg_pool",
        "right",
        "right_anti_join",
        "right_semi_join",
        "right_straight_join",
        "rlike",
        "role",
        "roles",
        "rollback",
        "rollup",
        "round",
        "routine",
        "row",
        "row_count",
        "row_format",
        "row_number",
        "rows",
        "rowstore",
        "rule",
        "rpad",
        "_rpc",
        "rtrim",
        "running",
        "s3",
        "safe",
        "save",
        "savepoint",
        "scalar",
        "schema",
        "schemas",
        "schema_binding",
        "scroll",
        "search",
        "second",
        "second_microsecond",
        "sec_to_time",
        "security",
        "select",
        "semi_join",
        "_send_threads",
        "sensitive",
        "separator",
        "sequence",
        "sequences",
        "serial",
        "serializable",
        "series",
        "service_user",
        "server",
        "session",
        "session_user",
        "set",
        "setof",
        "security_lists_intersect",
        "sha",
        "sha1",
        "sha2",
        "shard",
        "sharded",
        "sharded_id",
        "share",
        "show",
        "shutdown",
        "sigmoid",
        "sign",
        "signal",
        "similar",
        "simple",
        "site",
        "signed",
        "sin",
        "skip",
        "skipped_batches",
        "sleep",
        "_sleep",
        "smallint",
        "snapshot",
        "_snapshot",
        "_snapshots",
        "soft_cpu_limit_percentage",
        "some",
        "soname",
        "sparse",
        "spatial",
        "spatial_check_index",
        "specific",
        "split",
        "sql",
        "sql_big_result",
        "sql_buffer_result",
        "sql_cache",
        "sql_calc_found_rows",
        "sqlexception",
        "sql_mode",
        "sql_no_cache",
        "sql_no_logging",
        "sql_small_result",
        "sqlstate",
        "sqlwarning",
        "sqrt",
        "ssl",
        "stable",
        "standalone",
        "start",
        "starting",
        "state",
        "statement",
        "statistics",
        "stats",
        "status",
        "std",
        "stddev",
        "stddev_pop",
        "stddev_samp",
        "stdin",
        "stdout",
        "stop",
        "storage",
        "str_to_date",
        "straight_join",
        "strict",
        "string",
        "strip",
        "subdate",
        "substr",
        "substring",
        "substring_index",
        "success",
        "sum",
        "super",
        "symmetric",
        "sync_snapshot",
        "sync",
        "_sync",
        "_sync2",
        "_sync_partitions",
        "_sync_snapshot",
        "synchronize",
        "sysid",
        "system",
        "table",
        "table_checksum",
        "tables",
        "tablespace",
        "tags",
        "tan",
        "target_size",
        "task",
        "temp",
        "template",
        "temporary",
        "temptable",
        "_term_bump",
        "terminate",
        "terminated",
        "test",
        "text",
        "then",
        "time",
        "timediff",
        "time_bucket",
        "time_format",
        "timeout",
        "timestamp",
        "timestampadd",
        "timestampdiff",
        "timezone",
        "time_to_sec",
        "tinyblob",
        "tinyint",
        "tinytext",
        "to",
        "to_base64",
        "to_char",
        "to_date",
        "to_days",
        "to_json",
        "to_number",
        "to_seconds",
        "to_timestamp",
        "tracelogs",
        "traditional",
        "trailing",
        "transform",
        "transaction",
        "_transactions_experimental",
        "treat",
        "trigger",
        "triggers",
        "trim",
        "true",
        "trunc",
        "truncate",
        "trusted",
        "two_phase",
        "_twopcid",
        "type",
        "types",
        "ucase",
        "unbounded",
        "uncommitted",
        "undefined",
        "undo",
        "unencrypted",
        "unenforced",
        "unhex",
        "unhold",
        "unicode",
        "union",
        "unique",
        "_unittest",
        "unix_timestamp",
        "unknown",
        "unlisten",
        "_unload",
        "unlock",
        "unlogged",
        "unpivot",
        "unsigned",
        "until",
        "update",
        "upgrade",
        "upper",
        "usage",
        "use",
        "user",
        "users",
        "using",
        "utc_date",
        "utc_time",
        "utc_timestamp",
        "_utf8",
        "vacuum",
        "valid",
        "validate",
        "validator",
        "value",
        "values",
        "varbinary",
        "varchar",
        "varcharacter",
        "variables",
        "variadic",
        "variance",
        "var_pop",
        "var_samp",
        "varying",
        "vector_sub",
        "verbose",
        "version",
        "view",
        "void",
        "volatile",
        "voting",
        "wait",
        "_wake",
        "warnings",
        "week",
        "weekday",
        "weekofyear",
        "when",
        "where",
        "while",
        "whitespace",
        "window",
        "with",
        "without",
        "within",
        "_wm_heartbeat",
        "work",
        "workload",
        "wrapper",
        "write",
        "xact_id",
        "xor",
        "year",
        "year_month",
        "yes",
        "zerofill",
        "zone",
    }

    def jsonextractscalar_sql(self, expression: exp.JSONExtractScalar) -> str:
        json_type = expression.args.get("json_type")
        func_name = "JSON_EXTRACT_JSON" if json_type is None else f"JSON_EXTRACT_{json_type}"
        return json_extract_segments(func_name)(self, expression)

    def jsonbextractscalar_sql(self, expression: exp.JSONBExtractScalar) -> str:
        json_type = expression.args.get("json_type")
        func_name = "BSON_EXTRACT_BSON" if json_type is None else f"BSON_EXTRACT_{json_type}"
        return json_extract_segments(func_name)(self, expression)

    def jsonextractarray_sql(self, expression: exp.JSONExtractArray) -> str:
        self.unsupported("Arrays are not supported in SingleStore")
        return self.function_fallback_sql(expression)

    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:
        if expression.args.get("on_condition"):
            self.unsupported("JSON_VALUE does not support on_condition")
        res: exp.Expr = exp.JSONExtractScalar(
            this=expression.this,
            expression=expression.args.get("path"),
            json_type="STRING",
        )

        returning = expression.args.get("returning")
        if returning is not None:
            res = exp.Cast(this=res, to=returning)

        return self.sql(res)

    def all_sql(self, expression: exp.All) -> str:
        self.unsupported("ALL subquery predicate is not supported in SingleStore")
        return super().all_sql(expression)

    def jsonarraycontains_sql(self, expression: exp.JSONArrayContains) -> str:
        json_type = expression.text("json_type").upper()

        if json_type:
            return self.func(
                f"JSON_ARRAY_CONTAINS_{json_type}", expression.expression, expression.this
            )

        return self.func(
            "JSON_ARRAY_CONTAINS_JSON",
            expression.expression,
            self.func("TO_JSON", expression.this),
        )

    def datatype_sql(self, expression: exp.DataType) -> str:
        for arg_name in ("kind", "values"):
            if expression.args.get(arg_name):
                self.unsupported(f"DATATYPE does not support {arg_name}")
        if expression.args.get("nested") and not expression.is_type(exp.DType.STRUCT):
            self.unsupported(
                f"Argument 'nested' is not supported for representation of '{expression.this.value}' in SingleStore"
            )

        if expression.is_type(exp.DType.VARBINARY) and not expression.expressions:
            # `VARBINARY` must always have a size - if it doesn't, we always generate `BLOB`
            return "BLOB"
        if expression.is_type(
            exp.DType.DECIMAL32,
            exp.DType.DECIMAL64,
            exp.DType.DECIMAL128,
            exp.DType.DECIMAL256,
        ):
            scale = self.expressions(expression, flat=True)

            if expression.is_type(exp.DType.DECIMAL32):
                precision = "9"
            elif expression.is_type(exp.DType.DECIMAL64):
                precision = "18"
            elif expression.is_type(exp.DType.DECIMAL128):
                precision = "38"
            else:
                # 65 is a maximum precision supported in SingleStore
                precision = "65"
            if scale is not None:
                return f"DECIMAL({precision}, {scale[0]})"
            else:
                return f"DECIMAL({precision})"
        if expression.is_type(exp.DType.VECTOR):
            expressions = expression.expressions
            if len(expressions) == 2:
                type_name = self.sql(expressions[0])
                if type_name in self.dialect.INVERSE_VECTOR_TYPE_ALIASES:
                    type_name = self.dialect.INVERSE_VECTOR_TYPE_ALIASES[type_name]

                return f"VECTOR({self.sql(expressions[1])}, {type_name})"

        return super().datatype_sql(expression)

    def collate_sql(self, expression: exp.Collate) -> str:
        # SingleStore does not support setting a collation for column in the SELECT query,
        # so we cast column to a LONGTEXT type with specific collation
        return self.binary(expression, ":> LONGTEXT COLLATE")

    def currentdate_sql(self, expression: exp.CurrentDate) -> str:
        timezone = expression.this
        if timezone:
            if isinstance(timezone, exp.Literal) and timezone.name.lower() == "utc":
                return self.func("UTC_DATE")
            self.unsupported("CurrentDate with timezone is not supported in SingleStore")

        return self.func("CURRENT_DATE")

    def currenttime_sql(self, expression: exp.CurrentTime) -> str:
        arg = expression.this
        if arg:
            if isinstance(arg, exp.Literal) and arg.name.lower() == "utc":
                return self.func("UTC_TIME")
            if isinstance(arg, exp.Literal) and arg.is_number:
                return self.func("CURRENT_TIME", arg)
            self.unsupported("CurrentTime with timezone is not supported in SingleStore")

        return self.func("CURRENT_TIME")

    def currenttimestamp_sql(self, expression: exp.CurrentTimestamp) -> str:
        arg = expression.this
        if arg:
            if isinstance(arg, exp.Literal) and arg.name.lower() == "utc":
                return self.func("UTC_TIMESTAMP")
            if isinstance(arg, exp.Literal) and arg.is_number:
                return self.func("CURRENT_TIMESTAMP", arg)
            self.unsupported("CurrentTimestamp with timezone is not supported in SingleStore")

        return self.func("CURRENT_TIMESTAMP")

    def standardhash_sql(self, expression: exp.StandardHash) -> str:
        hash_function = expression.expression
        if hash_function is None:
            return self.func("SHA", expression.this)
        if isinstance(hash_function, exp.Literal):
            if hash_function.name.lower() == "sha":
                return self.func("SHA", expression.this)
            if hash_function.name.lower() == "md5":
                return self.func("MD5", expression.this)

            self.unsupported(f"{hash_function.this} hash method is not supported in SingleStore")
            return self.func("SHA", expression.this)

        self.unsupported("STANDARD_HASH function is not supported in SingleStore")
        return self.func("SHA", expression.this)

    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:
        for arg_name in ("is_database", "exists", "cluster", "identity", "option", "partition"):
            if expression.args.get(arg_name):
                self.unsupported(f"TRUNCATE TABLE does not support {arg_name}")
        statements = []
        for table in expression.expressions:
            statements.append(f"TRUNCATE {self.sql(table)}")

        return "; ".join(statements)

    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:
        if expression.args.get("exists"):
            self.unsupported("RENAME COLUMN does not support exists")
        old_column = self.sql(expression, "this")
        new_column = self.sql(expression, "to")
        return f"CHANGE {old_column} {new_column}"

    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:
        for arg_name in ("drop", "comment", "allow_null", "visible", "using"):
            if expression.args.get(arg_name):
                self.unsupported(f"ALTER COLUMN does not support {arg_name}")
        alter = super().altercolumn_sql(expression)

        collate = self.sql(expression, "collate")
        collate = f" COLLATE {collate}" if collate else ""
        return f"{alter}{collate}"

    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:
        this = self.sql(expression, "this")
        not_null = " NOT NULL" if expression.args.get("not_null") else ""
        type = self.sql(expression, "data_type") or "AUTO"
        return f"AS {this} PERSISTED {type}{not_null}"
