/*-
 * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 *  https://oss.oracle.com/licenses/upl/
 */

import type { ExtractByType, Expand } from "./type_utils";

/**
 * For Javascript users:
 * <p>
 * This is a marker type for records such as table rows or query results.
 * <p>
 * The driver uses plain JavaScript objects to represent table rows for
 * put operations and as results of get operations and query results.  A row
 * object consists of properties each designating a table field (on input) or
 * field generated by a query (on output).  The property keys must
 * match their corresponding field names and property values are used as
 * corresponding field values.  The match between the keys and field names
 * is case-insensitive.  For put operations such as {@link NoSQLClient#put},
 * the property values must be of a type that maps to the corresponding data
 * type of the table field as described in {@link FieldValue}.  When a row
 * object is received on output, such as from {@link NoSQLClient#get}, its
 * value will always conform to the schema of the table from which the it was
 * received or the implied schema of a query projection.
 * <p>
 * Note the following:
 * <ul>
 * <li>To represent rows as objects, you may only use plain JavaScript
 * objects, that is object literals.  The driver will reject any class
 * instances</li>
 * <li>You may also specify a valid JSON string as a row value, in which case
 * it will be parsed via <em>JSON.parse</em> and the result subjected to the
 * same validation as above</li>
 * </ul>
 * <p>
 * Typescript-specific:
 * <p>
 * This type is used as a default for <em>TRow</em> type parameter for APIs
 * such as {@link NoSQLClient#get}, {@link NoSQLClient#put} and others as well
 * as interfaces representing the results of these operations. It represents
 * object of any type.
 * <p>
 * The users are encouraged to define interface or type that describes the
 * shape of their table rows and use it as <em>TRow</em> type parameter for
 * these APIs.
 */
export type AnyRow = {
    [name: string]: any;
};

/**
 * Typescript-specific.
 * <p>
 * Interface that may be used to add custom type definitions to the set of
 * values allowed by {@link FieldValue} type.  For example, when using 3rd
 * party number library (see {@link DBNumberConfig}) you can use this
 * interface to include the 3rd party number type as an allowed
 * {@link FieldValue} in order to avoid compilation errors.  For this, define
 * <em>dbNumber</em> property as shown in the example below.
 * <p>
 * Use module agumentation to add custom type definitions via this interface
 * as shown in the example. Note that it should be sufficient to augment
 * <em>oracle-nosql</em> module only once for your application project.
 * 
 * @example
 * Using CustomFieldTypes interface for 3rd party number support.
 * ```ts
 * import type { NoSQLClient } from "oracle-nosqldb";
 * import type { Decimal } from "decimal.js";
 * 
 * declare module "oracle-nosqldb" {
 *     interface CustomFieldTypes {
 *         dbNumber: Decimal;
 *     }
 * }
 * 
 * async function test(client: NoSQLClient): Promise<void> {
 *     .....
 *     // Ok to use Decimal instance as field value
 *     const res = await client.put("my_table", {
 *         partNo: 1000,
 *         price: new Decimal(123.45)
 *     });
 *     .....
 * }
 * ```
 * @see {@link KeyField}
 * @see {@link FieldValue}
 */
export interface CustomFieldTypes {}

declare type CustomNumberType =
    CustomFieldTypes extends { dbNumber: infer T } ? T : never;

/**
 * Represents field types that can be used as a part of primary keys or
 * secondary index keys in a table.  These include numeric types, including
 * custom number type if provided (see {@link CustomFieldTypes}) as well as
 * <em>String</em>, <em>Boolean</em> and <em>Timestamp</em>.
 */
export type KeyField = string | number | bigint | boolean | Date |
    CustomNumberType;

/**
 * Represents atomic field types.  These include {@link KeyField} types as
 * well as <em>Binary</em>, <em>Null</em> and <em>JSON Null</em>.
 * @see {@link FieldValue}
 */
export type AtomicField = KeyField | Buffer | null | undefined;

/**
 * Represents field types that can be used for Identity or UUID fields.
 */
export type IdentityField =  string | number | bigint | CustomNumberType;

/**
 * For Javascript users:
 * <p> 
 * This is a marker type for primary keys, index keys, full or partial. The
 * key is represented in the same way as {@link AnyRow}, as plain JavaScript
 * object, but only key fields are included.
 * <p>
 * Typescript-specific:
 * <p>
 * This type is used as a default for <em>TKey</em> type parameter for APIs
 * such as {@link NoSQLClient#get}, {@link NoSQLClient#delete} and others when
 * using default value for <em>TRow</em> type parameter (see {@link AnyRow}).
 * It represents object of any type.
 * <p>
 * The users are encouraged to define interface or type that describes the
 * shape of their table rows and use it as <em>TRow</em> type parameter for
 * these APIs.
 */
export type AnyKey = {
    [name: string]: any;
}

/**
 * Typescript-specific.
 * <p>
 * This type is used to infer the primary key type from the row type as
 * indicated by the <em>TRow</em> type parameter. As such, it represents all
 * possible subsets of properties of <em>TRow</em> that include only
 * properties of types that can be used as primary or secondary index keys
 * (@see {@link KeyField}).
 * <p>
 * This type is used as a default value of <em>TKey</em> type parameter for
 * APIs that take primary key, such as {@link NoSQLClient#get},
 * {@link NoSQLClient#delete} and others.  Note that this is only a
 * best-effort basis to determine the type of the primary key since the driver
 * is not aware of what fields of the table constitute the primary key.  You
 * can specify the exact shape of the primary key by explicitly providing the
 * value of <em>TKey</em> type parameter to these APIs.
 * <p>
 * If using untyped versions of the above APIs (see {@link AnyRow}), the key
 * will be of type {@link AnyKey}.
 * @see {@link AnyKey}
 * @see {@link AnyRow}
 */
export type RowKey<TRow> = Expand<Partial<ExtractByType<TRow, KeyField>>>;

/**
 * For Javascript users:
 * <p>
 * Marker type that represents types of field values in a table, which are as
 * used property values for rows and primary keys.
 * <p>
 * FieldValue represents a data item in Oracle NoSQL Database.
 * FieldValue objects can be one of several JavaScript and Node.js types,
 * the type being dependent on how this value maps onto a given database
 * type.  For put and other operations where field values are used as input
 * their type must be one of the allowable types that maps onto given
 * database type (usually being the type of a table field).
 * <p>
 * FieldValue objects used for put operations are not validated against the
 * target table schema in the driver. Validation happens where the table
 * schema is available. If an instance does not match the target table an
 * error results.
 * <p>
 * You may specify field value as a (synchronous) function.  In this case the
 * function will be called with no arguments and its return value will be used
 * as the field value.  This could be useful if this function is returned from
 * a closure context or bound to a class that automatically generates field
 * values in some manner.
 * <p>
 * FieldValue objects returned by the driver (such as from get or query
 * operations) always conform to a table schema, or to the shape implied
 * by a query projection.
 * <p>
 * Here we will discuss how JavaScript types map onto the database types and
 * vice versa.  When field values are used as an input (such as by
 * {@link NoSQLClient#put}), some conversions are allowed so there could be
 * more than one JavaScript type used for given database type.  In this
 * instance, we will list preferred JavaScript type first followed by others
 * if any.  For output field values returned by the driver (such as by
 * {@link NoSQLClient#get} and {@link NoSQLClient#query})
 * there is a definite JavaScript type used for a given database type.  For
 * completeness, we will list mappings on input and on output separately.
 * Note that for composite database types such as Array, Map, Record and
 * JSON, their constituent elements also follow these mappings.
 * <p>
 * For datatype <em>Number</em>, the driver supports integration with 3rd
 * party number libraries such as decimal.js, bignumber.js and others.  See
 * {@link DBNumberConfig} for details.  If this feature is enabled, the
 * field value will be an object representing number in the 3rd party
 * library, indicated in the table below as "3rd party number".  Thus 3rd
 * party number will be the output type and preferred input type.  If this
 * feature is not enabled, the output type and preferred input type will be
 * Javascript number.  In both cases, you may also use string (representing
 * number) as an input type.
 * <p>
 * Oracle NoSQL Database has a special type JSON NULL, which represents a
 * {@link https://www.json.org | JSON} type NULL.  JSON NULL may occur as a
 * value of a field of Oracle NoSQL Database type JSON or one of its
 * subfields that has a value 'null'.  JSON NULL value is different and
 * separate from SQL NULL.  For example, if table <em>MyTable</em> has a field
 * <em>info</em> of type JSON, a query such as
 * <em>SELECT info.name from MyTable</em> will yield different results for
 * records where the value of <em>info.name</em> is null (e.g. if the value of
 * <em>info</em> is \{ "name": null \}) with result being JSON NULL and for
 * records where the value of the <em>info</em> field itself is NULL
 * (SQL NULL), with result being a SQL NULL.  In addition, the <em>info</em>
 * field itself may take values of SQL NULL or JSON NULL which are distinct
 * values, the latter being a value of a JSON type NULL (i.e. the value of
 * <em>info</em> is JSON value <em>null</em>).  For more details, please see
 * {@link https://docs.oracle.com/en/database/other-databases/nosql-database/23.1/sqlreferencefornosql/sql-reference-guide.pdf | SQL Reference Guide}
 * and SQL for Oracle NoSQL Database Specification.
 * <p>
 * The driver represents JSON NULL as JavaScript type <b>null</b> and SQL NULL
 * as JavaScript type <b>undefined</b>.  When such distinction is not
 * important, you may use non-strict comparison (e.g. <em>value == null</em>)
 * to determine if the field value is NULL (either SQL or JSON).  To
 * distinquish between JSON and SQL NULL, use strict comparison (e.g.
 * <em>value === undefined</em>).  Note that for non-JSON fields, on input you
 * may pass either undefined or null, or omit a field alltogether for
 * {@link NoSQLClient#put} operations, all of the above being interpreted as
 * SQL NULL. For JSON fields, on input you may use either null or undefined
 * as a value of the subfield (e.g. <em>info.name</em>) which will be
 * interpreted as JSON NULL, but for the field itself (e.g. <em>info</em>)
 * null and undefined will be treated as distinct values (JSON NULL and SQL
 * NULL correspondingly) as mentioned above.
 *
 * <table>
 * <tr><th>Database Type</th><th>JavaScript Input Type(s)</th><th>JavaScript
 * Output Type</th></tr>
 * <tr>
 * <td>Array</td><td>Array</td>
 * <td>Array</td>
 * </tr>
 * <tr>
 * <td>Binary</td><td>Buffer</td>
 * <td>Buffer</td>
 * </tr>
 * <tr>
 * <td>Boolean</td><td>boolean</td><td>boolean</td>
 * </tr>
 * <tr>
 * <td>Double</td><td>number</td><td>number</td>
 * </tr>
 * <tr>
 * <td>Enum</td><td>string</td><td>string</td>
 * </tr>
 * <tr>
 * <td>Fixed Binary</td><td>Buffer</td>
 * <td>Buffer</td>
 * </tr>
 * <tr>
 * <td>Float</td><td>number</td><td>number</td>
 * </tr>
 * <tr>
 * <td>Integer</td><td>number</td><td>number</td>
 * </tr>
 * <tr>
 * <td>Json</td><td>object, string, Map</td>
 * <td>object</td>
 * </tr>
 * <tr>
 * <td>Json Null</td><td>null, undefined (only as sub-field of JSON field)</td>
 * <td>null</td>
 * </tr>
 * <tr>
 * <td>Long</td><td>number, bigint</td>
 * <td>number, bigint</td>
 * </tr>
 * <tr>
 * <td>Map</td><td>object, Map</td><td>object</td>
 * </tr>
 * <tr>
 * <td>SQL Null</td><td>undefined (or omit field), null (only for non-JSON fields)</td>
 * <td>undefined</td>
 * </tr>
 * <tr>
 * <td>Number</td><td>3rd party number, Javascript number (with limitation), string</td><td>3rd party number or Javascript number (with limitation)</td>
 * </tr>
 * <tr>
 * <td>Record</td><td>object, Map</td><td>object</td>
 * </tr>
 * <tr>
 * <td>String</td><td>string</td><td>string</td>
 * </tr>
 * <tr>
 * <td>Timestamp</td><td>Date, string</td>
 * <td>Date</td>
 * </tr>
 * </table>
 * <p>
 * Note the following:
 * <ul>
 * <li>For database types Binary and FixedBinary the driver uses Node.js
 * <em>Buffer</em> class</li>
 * <li> For database type <em>Long</em>, you have an option to use either
 * JavaScript type <em>number</em> or <em>bigint</em>.  By default,
 * <em>number</em> is used.  Note that JavaScript type <em>number</em> cannot
 * have precise representation of integers outside of safe integer range,
 * that is outside of range from
 * {@link !Number.MIN_SAFE_INTEGER | Number.MIN_SAFE_INTEGER} to
 * {@link !Number.MAX_SAFE_INTEGER | Number.MAX_SAFE_INTEGER}. For values
 * outside that range (that is when their magnitude exceeds 53 bits in size),
 * loss of precision may occur.  Alternatively, you can use JavaScript type
 * {@link !BigInt | BigInt} by setting option {@link Config#longAsBigInt}.
 * In this case, values of datatype <em>Long</em> returned by get and query
 * operations will be represented as <em>bigint</em>, which avoids the loss of
 * precision for the entire range of datatype <em>Long</em>.  For values
 * passed to operations such as put and delete, you may use either
 * <em>bigint</em> or <em>number</em>.  Note that in either case <em>Long</em>
 * values passed to the driver may not be outside the range of datatype
 * <em>Long</em> (from -2^63 to 2^63-1)</li>
 * <li>For datatype <em>Number</em>, unless you are using 3rd party number
 * library feature (see {@link DBNumberConfig}), the driver will return
 * Javascript number values in the results, which means loss of precision and
 * rounding errors may occur in some cases.  On input (for put operations),
 * you may pass NoSQL Number values either as 3rd party numbers (if enabled),
 * Javascript numbers or strings</li>
 * <li>To represent database type Timestamp, you may use either
 * JavaScript <em>Date</em> class or a string.  The string
 * should represent a date in valid ISO 8601 format.  Timestamps are always
 * stored and managed in UTC</li>
 * </ul>
 * <p>
 * Typescript-specific:
 * <p>
 * Use the above description as a guide to what types are allowed for use as
 * field values.  The users are encouraged to create interfaces or types that
 * represent the shape of their table rows based on the types described above
 * and use them as <em>TRow</em> type parameters for APIs such as
 * {@link NoSQLClient#get}, {@link NoSQLClient#put},
 * {@link NoSQLClient#delete}, {@link NoSQLClient#writeMany},
 * {@link NoSQLClient#query}, etc.
 */
export type FieldValue = AtomicField | FieldValue[] |
    { [name: string]: FieldValue } | Map<string, FieldValue> |
    CustomFieldTypes[keyof CustomFieldTypes];
