type Raw = {
  [Symbol.toStringTag]: 'raw'
}

// Todo:
// how to declare table is an function and has some properties?
type Table = {
  [Symbol.toStringTag]: 'raw#table'
}

export function raw(sql: string | Raw): Raw
export function isRaw(sql: any): boolean

export function table(tablename: string, as?: string): Table
export function isTable(value: any): boolean

export function escape(value: string | Raw): Raw
export function escapeId(column: string | Raw): Raw

type Column = string | Raw

type Operator = '=' | '!=' | '>' | '>=' | '<' | '<=' | '+=' | '-=' | '/=' | '*=' | 'like' | 'startsWith' | 'endsWith' | 'between'

type Value = string | number | boolean | Raw | null

declare class Builder {
  toSQL(isCountSql?: boolean): string

  get sql(): string
}

declare class SelectBuilder extends Builder {
  select(...columns: Column[]): this

  from(table: string, as?: string): this
  from(table: Table): this

  join(table: string, as?: string): this
  join(table: Table): this

  leftJoin(table: string, as?: string): this
  leftJoin(table: Table): this

  rightJoin(table: string, as?: string): this
  rightJoin(table: Table): this

  innerJoin(table: string, as?: string): this
  innerJoin(table: Table): this

  on(column: Column, operator: Operator, value: Value): this
  on(column: Column, value: Value): this
  on(column: object): this

  where(column: Column, operator: Operator, value: Value): this
  where(column: Column, value: Value): this
  where(column: object): this

  and(column: Column, operator: Operator, value: Value): this
  and(column: Column, value: Value): this
  and(column: object): this

  or(column: Column, operator: Operator, value: Value): this
  or(column: Column, value: Value): this
  or(column: object): this

  groupBy(column: Column): this
  groupBy(columns: Column[]): this

  orderBy(column: Column): this
  orderBy(...columns: Column[]): this

  limit(rows: number): this
  limit(offset: number, rows: number): this
}

declare class InsertBuilder extends Builder {
  into(table: string, nickname: string): this
  into(table: Table): this

  ignoreInto(table: string, nickname: string): this
  ignoreInto(table: Table): this

  set(column: string, value: any): this
  set(column: object): this
}

declare class UpdateBuilder extends Builder {
  into(table: string, nickname: string): this
  into(table: Table): this

  ignoreInto(table: string, nickname: string): this
  ignoreInto(table: Table): this

  set(column: string, value: any): this
  set(column: object): this

  where(): this
  and(): this
  or(): this
}

export class SQLBuilder {
  static select(...columns: Column[]): SelectBuilder

  static insert(): InsertBuilder
  static insert(column: string, value: any): InsertBuilder
  static insert(column: object): InsertBuilder
}

export function select(...columns: Column[]): SelectBuilder
export function insert(): InsertBuilder
export function update(): UpdateBuilder
