/**
 * Creates a [WhereClause](https://developers.arcgis.com/javascript/latest/references/core/core/sql/WhereClause/) expression that adheres to standardized SQL expressions.
 * SQL expression is a combination of one or more values, operators and SQL functions that
 * results in to a value.
 *
 * @since 4.14
 * @see [Standardized SQL functions in ArcGIS Online](https://doc.arcgis.com/en/arcgis-online/reference/sql-agol.htm)
 */
import type WhereClause from "./sql/WhereClause.js";
import type FieldsIndex from "../layers/support/FieldsIndex.js";
import type { FieldType } from "../layers/support/types.js";
import type { FieldType as FieldTypeJSON } from "../portal/jsonTypes.js";
import type { TimeZone } from "../time/types.js";

/**
 * Parses the given where clause string and returns an instance of [WhereClause](https://developers.arcgis.com/javascript/latest/references/core/core/sql/WhereClause/) when resolved.
 * The `WhereClause` object can be used repeatedly against different features, to test if they individually meet the condition.
 * The parser does not parse a whole SQL statement (ie. `SELECT X… WHERE…`). It only parses the expression after the
 * `where` token.
 *
 * @param clause - The SQL where clause expression.
 * @param fieldsIndex - The [fields index](https://developers.arcgis.com/javascript/latest/references/core/layers/FeatureLayer/#fieldsIndex)
 * of the layer. The fields index is used to match the fields found in the where clause against the service, to fix casing for example.
 * @returns Parses a string where clause and returns a promise that resolves to
 * an object with the [WhereClause](https://developers.arcgis.com/javascript/latest/references/core/core/sql/WhereClause/) specification.
 * @example
 * sql.parseWhereClause("POPULATION > 100000", layer.fieldsIndex)
 * .then(function(clause){
 *   let testResult = clause.testFeature(
 *     new Graphic({
 *       attributes: {
 *         POPULATION: 300000
 *       }
 *     })
 *   );
 *   console.log(testResult); // prints true
 * });
 * @example
 * sql.parseWhereClause(
 *   "START_TIME BETWEEN TIMESTAMP '2023-01-01 00:00:00' AND TIMESTAMP '2023-12-31 23:59:59'",
 *   layer.fieldsIndex
 * )
 * .then(function(clause){
 *   const utcDate = Date.UTC(2023, 0, 1, 0, 0, 0);
 *   let testResult = clause.testFeature(
 *     new Graphic({
 *       attributes: {
 *         START_TIME: utcDate
 *       }
 *     })
 *   );
 *   console.log(testResult); // true
 * });
 */
export function parseWhereClause(clause: string, fieldsIndex: FieldsIndex | null | undefined): Promise<WhereClause>;

/**
 * Builds an SQL IN clause for the given field and list of values. Returns null if no values are provided.
 *
 * @param fieldName - The name of the field on the table.
 * @param values - The value of the field.
 */
export function sqlIn(fieldName: string, values: (string | number)[]): string | null | undefined;

/** Accepted field type input for SQL literal conversion functions. */
export type AnyFieldType = { type: FieldType | FieldTypeJSON; } | FieldType | FieldTypeJSON;

/**
 * Returns a standardized SQL boolean literal from a boolean value. Possible values are: `TRUE`, `FALSE`, and `NULL`.
 *
 * @param value - The boolean value to convert to a standardized SQL literal.
 * @returns The resulting standardized SQL boolean literal.
 * @since 5.0
 */
export function sqlBooleanLiteral(value: boolean | null | undefined): string;

/**
 * Returns a standardized SQL numeric literal from a value.
 *
 * @param value - The value to convert to a standardized SQL numeric literal.
 * @param valueType - The field type used to interpret the provided value.
 * @returns The resulting standardized SQL numeric literal. If the input is `null`, `undefined`, or cannot be converted to a finite number
 * (for example, if it results in NaN, Infinity, or -Infinity), the function returns `NULL`.
 * @example
 * sqlNumericLiteral("123"); // returns "123"
 * sqlNumericLiteral(true);  // returns "1"
 * sqlNumericLiteral(null);  // returns "NULL"
 * @since 5.0
 */
export function sqlNumericLiteral(value: unknown, valueType?: AnyFieldType | null | undefined): string;

/**
 * Returns a standardized SQL string literal from a value.
 *
 * @param value - The value to convert to a standardized SQL string literal.
 * @param valueType - The field type used to interpret the provided value.
 * @param timeZone - The time zone to use for formatting when the value is a date. Defaults to UTC.
 * @returns The resulting standardized SQL string literal. Returns `NULL` if the value is `null` or `undefined`.
 * @example
 * sqlStringLiteral(new Date("2008-01-20T20:00:00-05:00")); // returns "'2008-01-21 01:00:00.000'"
 * sqlStringLiteral(true);  // returns "'true'"
 * sqlStringLiteral("quote'd"); // returns "'quote''d'"
 * sqlStringLiteral(1200877200000, establishedDateField); // returns "'2008-01-21 01:00:00.000'"
 * sqlStringLiteral(1200877200000, establishedDateField, "America/Toronto"); // returns "'2008-01-20 20:00:00.000'"
 * @since 5.0
 */
export function sqlStringLiteral(value: unknown, valueType?: AnyFieldType | null | undefined, timeZone?: TimeZone | null | undefined): string;

/**
 * Returns a standardized SQL date literal from a value.
 *
 * @param value - The value to convert to a standardized SQL date literal.
 * @param valueType - The field type used to interpret the provided value.
 * @param timeZone - The time zone to use for formatting when the value is a date. Defaults to UTC.
 * @returns The resulting standardized SQL string literal. Returns `NULL` if the value is `null` or `undefined`.
 * @example
 * sqlDateLiteral(new Date("2008-01-20T20:00:00-05:00")); // returns "DATE'2008-01-21'"
 * sqlDateLiteral(1200877200000, dateField); // returns "DATE'2008-01-21'"
 * sqlDateLiteral(1200877200000, dateField, "America/Toronto"); // returns "DATE'2008-01-20'"
 * @since 5.0
 */
export function sqlDateLiteral(value: unknown, valueType?: AnyFieldType | null | undefined, timeZone?: TimeZone | null | undefined): string;

/**
 * Returns a standardized SQL time literal from a value.
 *
 * @param value - The value to convert to a standardized SQL time literal.
 * @param valueType - The field type used to interpret the provided value.
 * @param timeZone - The time zone to use for formatting when the value is a date. Defaults to UTC.
 * @returns The resulting standardized SQL time literal. Returns `NULL` if the value is `null` or `undefined`.
 * @example
 * sqlTimeLiteral(1200877200000); // returns "TIME'01:00:00.000'"
 * sqlTimeLiteral(new Date("2008-01-20T20:00:00-05:00"), dateField); // returns "TIME'01:00:00.000'"
 * sqlTimeLiteral(1200877200000, dateField, "America/Toronto"); // returns "TIME'20:00:00.000'"
 * sqlTimeLiteral("2008-01-20T20:00:00.000-05:00", timestampOffsetField); // returns "TIME'20:00:00.000'",
 * @since 5.0
 */
export function sqlTimeLiteral(value: unknown, valueType?: AnyFieldType | null | undefined, timeZone?: TimeZone | null | undefined): string;

/**
 * Returns a standardized SQL timestamp literal from a value.
 *
 * @param value - The value to convert to a standardized SQL timestamp literal.
 * @param valueType - The field type used to interpret the provided value.
 * @param timeZone - The time zone to use for formatting when the value is a date. Defaults to UTC.
 * @returns The resulting standardized SQL timestamp literal. Returns `NULL` if the value is `null` or `undefined`.
 * @example
 * sqlTimestampLiteral("2008-01-20 20:00:00 -05:00"); // returns "TIMESTAMP'2008-01-20 20:00:00 -05:00'"
 * sqlTimestampLiteral("2008-01-20T20:00:00-05:00", timestampOffsetField); // returns "TIMESTAMP'2008-01-20 20:00:00 -05:00'"
 * sqlTimestampLiteral(1200877200000); // returns "TIMESTAMP'2008-01-21 01:00:00.000'"
 * sqlTimestampLiteral(1200877200000, "date", "America/Toronto"); // returns "TIMESTAMP'2008-01-20 20:00:00.000'"
 * @since 5.0
 */
export function sqlTimestampLiteral(value: unknown, valueType?: AnyFieldType | null | undefined, timeZone?: TimeZone | null | undefined): string;

/**
 * Returns a standardized SQL timestamp literal with offset  from a value.
 *
 * @param value - The value to convert to a standardized SQL timestamp literal.
 * @param valueType - The field type used to interpret the provided value.
 * @param timeZone - The time zone to use for formatting when the value is a date. Defaults to UTC.
 * @returns The resulting standardized SQL timestamp literal. Returns `NULL` if the value is `null` or `undefined`.
 * @example
 * sqlTimestampOffsetLiteral("2008-01-20 20:00:00 -05:00"); // returns "TIMESTAMP'2008-01-20 20:00:00 -05:00'"
 * sqlTimestampOffsetLiteral("2008-01-20T20:00:00-05:00", timestampOffsetField); // returns "TIMESTAMP'2008-01-20 20:00:00 -05:00'"
 * sqlTimestampOffsetLiteral(1200877200000); // returns "TIMESTAMP'2008-01-21 01:00:00.000 +00:00'"
 * sqlTimestampOffsetLiteral(1200877200000, "date", "America/Toronto"); // returns "TIMESTAMP'2008-01-20 20:00:00.000 -05:00'"
 * @since 5.0
 */
export function sqlTimestampOffsetLiteral(value: unknown, valueType?: AnyFieldType | null | undefined, timeZone?: TimeZone | null | undefined): string;

/** @since 5.0 */
export interface SqlTypeInfo {
  /** @since 5.0 */
  type?: AnyFieldType | null;
  /** @since 5.0 */
  timeZone?: TimeZone | null;
}

/**
 * Returns a standardized SQL literal for the given value. If `type` and/or `as` are provided, they control how the value is interpreted and which literal format is produced.
 *
 * @param value - The value to convert to a standardized SQL literal.
 * @param type - The field type info used to interpret the provided value.
 * @param as - The target field type info for the resulting SQL literal.
 * @returns The resulting standardized SQL literal. Returns `NULL` if the value is `null` or `undefined`.
 * @example
 * sqlLiteral(123); // returns "123"
 * sqlLiteral(true);  // returns "TRUE"
 * sqlLiteral("quote'd"); // returns "'quote''d'"
 * sqlLiteral(new Date("2008-01-20T20:00:00-05:00")); // returns "TIMESTAMP'2008-01-21 01:00:00.000'"
 * sqlLiteral(1200877200000, { type: dateField }); // returns "TIMESTAMP'2008-01-21 01:00:00.000'"
 * sqlLiteral(
 *   1200877200000,
 *   { type: fieldsIndex.get("date_pst"), timeZone: fieldsIndex.getTimeZone("date_pst") },
 * ); // returns "TIMESTAMP'2008-01-20 17:00:00.000'"
 * sqlLiteral(
 *   1200877200000,
 *   { type: fieldsIndex.get("date_pst"), timeZone: fieldsIndex.getTimeZone("date_pst") },
 *   { type: fieldsIndex.get("date_est"), timeZone: fieldsIndex.getTimeZone("date_est") },
 * ); // returns "TIMESTAMP'2008-01-20 20:00:00.000'"
 * sql.sqlLiteral(
 *   1200877200000,
 *   { type: fieldsIndex.get("date_pst"), timeZone: fieldsIndex.getTimeZone("date_pst") },
 *   { type: "date-only" },
 * ); // returns "DATE'2008-01-20'"
 * @since 5.0
 */
export function sqlLiteral(value: unknown, type?: SqlTypeInfo | null | undefined, as?: SqlTypeInfo | null | undefined): string;

/**
 * Returns a standardized SQL identifier literal for the given name, quoting and escaping it when needed.
 *
 * @param name - The identifier name (for example, a field or table name) to format for use in an SQL expression.
 * @returns The resulting standardized SQL identifier literal
 * @example sqlName(field.name); // returns field name quoted if necessary
 * @since 5.0
 */
export function sqlName(name: string): string;