import { FormatOptionsWithLanguage } from 'sql-formatter';

declare function normalized(value: any): string;

interface FormatOptions extends FormatOptionsWithLanguage {
}

interface TableOptions {
    name: string;
    alias?: string;
}
interface ColumnOptions {
    name: string;
    alias?: string;
}
interface JoinTableOptions extends TableOptions {
    type: 'LEFT' | 'RIGHT' | 'INNER';
    condition?: string;
    select?: boolean | string | Record<string, string> | (string | Record<string, string>)[];
}

declare class QueryBuilder {
    private _queryType;
    private _insertColumns;
    private _values;
    private _updatedColumns;
    private _fields;
    private _table;
    private _where;
    private _groupBy;
    private _having;
    private _order;
    private _offset;
    private _limit;
    private _joins;
    private _rawFront;
    private _rawEnd;
    private _query;
    constructor(table?: string, alias?: string);
    insertInto(table: string, ...columns: string[]): this;
    columns(...columns: string[]): this;
    values(...values: any[][]): this;
    update(table: string, data?: Record<string, any>): this;
    set(column: string, value: any): this;
    set(data: Record<string, any>): this;
    delete(table: string): this;
    from(table: string, alias?: string): this;
    select(selection: string | Record<string, string> | (string | Record<string, string>)[], fromTable?: string): this;
    addSelect: (selection: string | Record<string, string> | (string | Record<string, string>)[], fromTable?: string) => this;
    private agg;
    count(column?: string, alias?: string): this;
    countDistinct(column: string, alias?: string): this;
    sum(column: string, alias?: string): this;
    avg(column: string, alias?: string): this;
    min(column: string, alias?: string): this;
    max(column: string, alias?: string): this;
    where(...conditions: string[]): this;
    andWhere: (...conditions: string[]) => this;
    groupBy(...columns: string[]): this;
    having(...conditions: string[]): this;
    andHaving: (...conditions: string[]) => this;
    orderBy(order: string | string[] | Record<string, 'ASC' | 'DESC'>): this;
    offset(n: number): this;
    limit(n: number): this;
    join(joinTable: JoinTableOptions): this;
    innerJoin(joinTable: Omit<JoinTableOptions, 'type'>): this;
    leftJoin(joinTable: Omit<JoinTableOptions, 'type'>): this;
    rightJoin(joinTable: Omit<JoinTableOptions, 'type'>): this;
    subQuery(): QueryBuilder;
    subQuery(cb: (qb: QueryBuilder) => QueryBuilder): string;
    rawFront(rawSql: string): this;
    rawEnd(rawSql: string): this;
    private handleWhereConditions;
    build(): this;
    format(formatOptions?: FormatOptions): this | never;
    getSql(): string;
    clear(): this;
}

declare enum JoinType {
    LEFT = "LEFT",
    RIGHT = "RIGHT",
    INNER = "INNER"
}
declare enum ORDER {
    DESC = "DESC",
    ASC = "ASC"
}

declare function $contain(column: string, sub: string): string;
declare function $concat(...strings: string[]): string;
declare function createQueryBuilder(table?: string, alias?: string): QueryBuilder;

type ComparisonOperator = '=' | '<>' | '!=' | '>' | '>=' | '<' | '<=';
declare function $AND(...conditions: string[]): string;
declare function $OR(...conditions: string[]): string;
declare function $NOT(expr: string): string;
declare function $IN(elem: string, list: string | string[] | QueryBuilder | ((qb: QueryBuilder) => QueryBuilder)): string;
declare function $BETWEEN(value: string, l: string | number, r: string | number): string;
declare function $ALL(value: string, operator: ComparisonOperator, subQuery: string | QueryBuilder | ((qb: QueryBuilder) => QueryBuilder)): string;
declare function $ANY(value: string, operator: ComparisonOperator, subQuery: string | QueryBuilder | ((qb: QueryBuilder) => QueryBuilder)): string;
declare function $isNull(value: string): string;
declare function $notNull(value: string): string;

type ConstructorFunction = new (...args: any[]) => any;
type CascadeType = 'RESTRICT' | 'CASCADE' | 'SET NULL' | 'DEFAULT' | 'NO ACTION';

interface TableMetadata {
    name: string;
    indexes: IndexMetadata[];
    primaryKey: string[];
    foreignKeys: ForeignKeyMetadata[];
    columns: ColumnMetadata[];
}
/**
 * Define Index options
 */
interface IndexMetadata {
    /**
     * Define the index's name
     */
    name: string;
    /**
     * Define indexable columns
     */
    columns: string[];
    /**
     * @optional Define index uniqueness
     * @default false
     */
    unique?: boolean;
}
/**
 * Define foreign key option
 */
interface ForeignKeyMetadata {
    /**
     * Define column name of the foreign key
     *
     * @required if \@ForeignKey decorator is used on a class.
     * @optional if \@ForeignKey is used on a property.
     * @default propertyKey
     */
    column?: string;
    /**
     * Define which table & column this foreign key references
     *
     * @example
     * reference: 'user(id)'
     */
    reference: string;
    /**
     * @optional Define delete cascade option
     */
    onDelete?: CascadeType;
    /**
     * @optional Define update cascade option
     */
    onUpdate?: CascadeType;
}
/**
 * Define column options
 */
interface ColumnMetadata {
    /**
     * Define column name.
     * @default propertyKey
     */
    name?: string;
    /**
     * Define column type in raw sql (depending on the used database)
     * @example 'INTEGER' 'SERIAL' 'VARCHAR(50)'
     */
    type: string;
    /**
     * Define nullability
     * @default true
     */
    nullable?: boolean;
    /**
     * Define uniqueness
     * @default false
     */
    unique?: boolean;
    /**
     * @optional Define default value for column.
     *
     * If passed as a function () => string (raw sql), it will take the returned value.
     * otherwise it will be normalized.
     *
     * @example
     * default: 'default_value' | sql: DEFAULT 'default_value' (normalized with quotes)
     *
     * default: 1 | sql: DEFAULT 1 (normalized)
     *
     * default: () => 'default_value' | sql: DEFAULT default_value (without quotes)
     */
    default?: any;
    /**
     * @optional Define check constraint.
     */
    check?: string;
    /**
     * Define if this column is (or part of) the primary key.
     * Same can be achieved when \@PrimaryKey decorator is used.
     * @default false
     */
    primary?: boolean;
    /**
     * Define foreign key constraint.
     * Same can be achieved when \@ForeignKey decorator is used.
     */
    foreignKey?: Omit<ForeignKeyMetadata, 'column'>;
}

/**
 * Decorator to specify a class as a database table.
 * Database schema will be generated for all classes decorated with it.
 *
 * @param {string} [name] The name of the table. If not provided, the lowercase class name is used.
 * @returns {ClassDecorator} The class decorator function.
 */
declare function Table(name?: string): ClassDecorator;

/**
 * Decorator to specify a class property as a table column.
 *
 * @param {ColumnMetadata} columnOptions Define column options
 * @returns {PropertyDecorator} The property decorator function
 */
declare function Column(columnOptions: ColumnMetadata): PropertyDecorator;
declare function normalizeColumnOptions(columnOptions: ColumnMetadata): ColumnMetadata;

/**
 * Decorator for indexing tables.
 *
 * @param {IndexMetadata} indexOptions Index options.
 * @returns {ClassDecorator} The class decorator.
 */
declare function Index(indexOptions: IndexMetadata): ClassDecorator;

/**
 * Class & Property decorator to specify a column as a foreign key inside a table.
 *
 * @param {ForeignKeyMetadata} fkOptions
 * Define foreign key options
 * (make sure to define 'column' property if used as class decorator, otherwise it's optional)
 *
 * @returns {ClassDecorator & PropertyDecorator}
 * The class/property decorator
 */
declare function ForeignKey(fkOptions: ForeignKeyMetadata): ClassDecorator & PropertyDecorator;

/**
 * Define if this column is (or part of) the primary key.
 *
 * @param {string} [column] Column name.
 * If not specified propertyKey is taken (same as \@Column decorator)
 * @returns {PropertyDecorator} The property decorator
 */
declare function PrimaryKey(column?: string): PropertyDecorator;
/**
 * Define primary key for a table.
 * @param {string[]} columns Define which column/columns are the primary key for the table.
 * @returns {ClassDecorator} The class decorator
 */
declare function PrimaryKey(columns: string[]): ClassDecorator;

interface SingleFileSchema {
    path: string;
}
interface MultipleFilesSchema {
    dirname: string;
}
type SchemaBuilderOptions = SingleFileSchema | MultipleFilesSchema;
/**
 * Generates database schema.
 *
 * @param {SingleFileSchema | MultipleFilesSchema} options
 * Define schema generation options.
 *
 * If passed as `MultipleFilesSchema`.
 * Each table (classes decorated with \@Table) will have its own schema written in separate file (table_name.schema.sql).
 *
 * Otherwise, a single file will be created, containing the whole database schema.
 *
 * @param {FormatOptions} [formatOptions] Define formatting options.
 */
declare function buildSchema(options: SchemaBuilderOptions, formatOptions?: FormatOptions & {
    comments?: boolean;
}): void;

declare function tableSchema(table: Function, formatOptions?: FormatOptions): string;
declare function getTableMetadata(table: Function): TableMetadata;

declare const TABLE_METADATA_KEY = "schema_builder:table_metadata";
declare const COLUMNS_METADATA_KEY = "schema_builder:table_columns";
declare const PK_METADATA_KEY = "schema_builder:table_primary_key";
declare const FKS_METADATA_KEY = "schema_builder:table_foreign_keys";
declare const INDEXES_METADATA_KEY = "schema_builder:table_indexes";

export { $ALL, $AND, $ANY, $BETWEEN, $IN, $NOT, $OR, $concat, $contain, $isNull, $notNull, COLUMNS_METADATA_KEY, type CascadeType, Column, type ColumnMetadata, type ColumnOptions, type ComparisonOperator, type ConstructorFunction, FKS_METADATA_KEY, ForeignKey, type ForeignKeyMetadata, type FormatOptions, INDEXES_METADATA_KEY, Index, type IndexMetadata, type JoinTableOptions, JoinType, ORDER, PK_METADATA_KEY, PrimaryKey, QueryBuilder, TABLE_METADATA_KEY, Table, type TableMetadata, type TableOptions, buildSchema, createQueryBuilder, getTableMetadata, normalizeColumnOptions, normalized, tableSchema };
