import { ALIAS_REPLACEMENT, ARRAY_OPERATORS, raw, RawQueryFragment, Type, Utils } from '@mikro-orm/core';
import { AbstractSqlPlatform } from '../../AbstractSqlPlatform.js';
import { PostgreSqlNativeQueryBuilder } from './PostgreSqlNativeQueryBuilder.js';
import { PostgreSqlSchemaHelper } from './PostgreSqlSchemaHelper.js';
import { PostgreSqlExceptionConverter } from './PostgreSqlExceptionConverter.js';
import { FullTextType } from './FullTextType.js';
export class BasePostgreSqlPlatform extends AbstractSqlPlatform {
  schemaHelper = new PostgreSqlSchemaHelper(this);
  exceptionConverter = new PostgreSqlExceptionConverter();
  /** Maps JS runtime type names to PostgreSQL cast types for JSON property access. @internal */
  #jsonTypeCasts = { number: 'float8', bigint: 'int8', boolean: 'bool' };
  createNativeQueryBuilder() {
    return new PostgreSqlNativeQueryBuilder(this);
  }
  usesReturningStatement() {
    return true;
  }
  usesCascadeStatement() {
    return true;
  }
  supportsNativeEnums() {
    return true;
  }
  usesEnumCheckConstraints() {
    return true;
  }
  supportsMaterializedViews() {
    return true;
  }
  supportsCustomPrimaryKeyNames() {
    return true;
  }
  getCurrentTimestampSQL(length) {
    return `current_timestamp(${length})`;
  }
  getDateTimeTypeDeclarationSQL(column) {
    /* v8 ignore next */
    return 'timestamptz' + (column.length != null ? `(${column.length})` : '');
  }
  getDefaultDateTimeLength() {
    return 6;
  }
  getTimeTypeDeclarationSQL() {
    return 'time(0)';
  }
  getIntegerTypeDeclarationSQL(column) {
    if (column.autoincrement && !column.generated) {
      return 'serial';
    }
    return 'int';
  }
  getBigIntTypeDeclarationSQL(column) {
    /* v8 ignore next */
    if (column.autoincrement) {
      return `bigserial`;
    }
    return 'bigint';
  }
  getTinyIntTypeDeclarationSQL(column) {
    return 'smallint';
  }
  getUuidTypeDeclarationSQL(column) {
    return `uuid`;
  }
  getFullTextWhereClause(prop) {
    if (prop.customType instanceof FullTextType) {
      return `:column: @@ plainto_tsquery('${prop.customType.regconfig}', :query)`;
    }
    /* v8 ignore next */
    if (prop.columnTypes[0] === 'tsvector') {
      return `:column: @@ plainto_tsquery('simple', :query)`;
    }
    return `to_tsvector('simple', :column:) @@ plainto_tsquery('simple', :query)`;
  }
  supportsCreatingFullTextIndex() {
    return true;
  }
  getFullTextIndexExpression(indexName, schemaName, tableName, columns) {
    /* v8 ignore next */
    const quotedTableName = this.quoteIdentifier(schemaName ? `${schemaName}.${tableName}` : tableName);
    const quotedColumnNames = columns.map(c => this.quoteIdentifier(c.name));
    const quotedIndexName = this.quoteIdentifier(indexName);
    if (columns.length === 1 && columns[0].type === 'tsvector') {
      return `create index ${quotedIndexName} on ${quotedTableName} using gin(${quotedColumnNames[0]})`;
    }
    return `create index ${quotedIndexName} on ${quotedTableName} using gin(to_tsvector('simple', ${quotedColumnNames.join(` || ' ' || `)}))`;
  }
  normalizeColumnType(type, options) {
    const simpleType = this.extractSimpleType(type);
    if (['int', 'int4', 'integer'].includes(simpleType)) {
      return this.getIntegerTypeDeclarationSQL({});
    }
    if (['bigint', 'int8'].includes(simpleType)) {
      return this.getBigIntTypeDeclarationSQL({});
    }
    if (['smallint', 'int2'].includes(simpleType)) {
      return this.getSmallIntTypeDeclarationSQL({});
    }
    if (['boolean', 'bool'].includes(simpleType)) {
      return this.getBooleanTypeDeclarationSQL();
    }
    if (['varchar', 'character varying'].includes(simpleType)) {
      return this.getVarcharTypeDeclarationSQL(options);
    }
    if (['char', 'bpchar'].includes(simpleType)) {
      return this.getCharTypeDeclarationSQL(options);
    }
    if (['decimal', 'numeric'].includes(simpleType)) {
      return this.getDecimalTypeDeclarationSQL(options);
    }
    if (['interval'].includes(simpleType)) {
      return this.getIntervalTypeDeclarationSQL(options);
    }
    return super.normalizeColumnType(type, options);
  }
  getMappedType(type) {
    switch (this.extractSimpleType(type)) {
      case 'tsvector':
        return Type.getType(FullTextType);
      default:
        return super.getMappedType(type);
    }
  }
  getRegExpOperator(val, flags) {
    /* v8 ignore next */
    if ((val instanceof RegExp && val.flags.includes('i')) || flags?.includes('i')) {
      return '~*';
    }
    return '~';
  }
  /* v8 ignore next */
  getRegExpValue(val) {
    if (val.flags.includes('i')) {
      return { $re: val.source, $flags: val.flags };
    }
    return { $re: val.source };
  }
  isBigIntProperty(prop) {
    return super.isBigIntProperty(prop) || ['bigserial', 'int8'].includes(prop.columnTypes?.[0]);
  }
  getArrayDeclarationSQL() {
    return 'text[]';
  }
  getFloatDeclarationSQL() {
    return 'real';
  }
  getDoubleDeclarationSQL() {
    return 'double precision';
  }
  getEnumTypeDeclarationSQL(column) {
    /* v8 ignore next */
    if (column.nativeEnumName) {
      return column.nativeEnumName;
    }
    if (column.items?.every(item => typeof item === 'string')) {
      return 'text';
    }
    return `smallint`;
  }
  supportsMultipleStatements() {
    return true;
  }
  getBeginTransactionSQL(options) {
    if (options?.isolationLevel || options?.readOnly) {
      let sql = 'start transaction';
      sql += options.isolationLevel ? ` isolation level ${options.isolationLevel}` : '';
      sql += options.readOnly ? ` read only` : '';
      return [sql];
    }
    return ['begin'];
  }
  marshallArray(values) {
    const quote = v => (v === '' || /["{},\\]/.exec(v) ? JSON.stringify(v) : v);
    return `{${values.map(v => quote('' + v)).join(',')}}`;
  }
  /* v8 ignore next */
  unmarshallArray(value) {
    if (value === '{}') {
      return [];
    }
    return value
      .substring(1, value.length - 1)
      .split(',')
      .map(v => {
        if (v === `""`) {
          return '';
        }
        if (/"(.*)"/.exec(v)) {
          return v.substring(1, v.length - 1).replaceAll('\\"', '"');
        }
        return v;
      });
  }
  getVarcharTypeDeclarationSQL(column) {
    if (column.length === -1) {
      return 'varchar';
    }
    return super.getVarcharTypeDeclarationSQL(column);
  }
  getCharTypeDeclarationSQL(column) {
    if (column.length === -1) {
      return 'char';
    }
    return super.getCharTypeDeclarationSQL(column);
  }
  getIntervalTypeDeclarationSQL(column) {
    return 'interval' + (column.length != null ? `(${column.length})` : '');
  }
  getBlobDeclarationSQL() {
    return 'bytea';
  }
  getJsonDeclarationSQL() {
    return 'jsonb';
  }
  getSearchJsonPropertyKey(path, type, aliased, value) {
    const first = path.shift();
    const last = path.pop();
    const root = this.quoteIdentifier(aliased ? `${ALIAS_REPLACEMENT}.${first}` : first);
    type = typeof type === 'string' ? this.getMappedType(type).runtimeType : String(type);
    const cast = key => raw(type in this.#jsonTypeCasts ? `(${key})::${this.#jsonTypeCasts[type]}` : key);
    let lastOperator = '->>';
    // force `->` for operator payloads with array values
    if (
      Utils.isPlainObject(value) &&
      Object.keys(value).every(key => ARRAY_OPERATORS.includes(key) && Array.isArray(value[key]))
    ) {
      lastOperator = '->';
    }
    if (path.length === 0) {
      return cast(`${root}${lastOperator}'${last}'`);
    }
    return cast(`${root}->${path.map(a => this.quoteValue(a)).join('->')}${lastOperator}'${last}'`);
  }
  getJsonIndexDefinition(index) {
    return index.columnNames.map(column => {
      if (!column.includes('.')) {
        return column;
      }
      const path = column.split('.');
      const first = path.shift();
      const last = path.pop();
      if (path.length === 0) {
        return `(${this.quoteIdentifier(first)}->>${this.quoteValue(last)})`;
      }
      return `(${this.quoteIdentifier(first)}->${path.map(c => this.quoteValue(c)).join('->')}->>${this.quoteValue(last)})`;
    });
  }
  quoteIdentifier(id, quote = '"') {
    if (RawQueryFragment.isKnownFragment(id)) {
      return super.quoteIdentifier(id);
    }
    return `${quote}${id.toString().replace('.', `${quote}.${quote}`)}${quote}`;
  }
  pad(number, digits) {
    return String(number).padStart(digits, '0');
  }
  /** @internal */
  formatDate(date) {
    if (this.timezone === 'Z') {
      return date.toISOString();
    }
    let offset = -date.getTimezoneOffset();
    let year = date.getFullYear();
    const isBCYear = year < 1;
    /* v8 ignore next */
    if (isBCYear) {
      year = Math.abs(year) + 1;
    }
    const datePart = `${this.pad(year, 4)}-${this.pad(date.getMonth() + 1, 2)}-${this.pad(date.getDate(), 2)}`;
    const timePart = `${this.pad(date.getHours(), 2)}:${this.pad(date.getMinutes(), 2)}:${this.pad(date.getSeconds(), 2)}.${this.pad(date.getMilliseconds(), 3)}`;
    let ret = `${datePart}T${timePart}`;
    /* v8 ignore next */
    if (offset < 0) {
      ret += '-';
      offset *= -1;
    } else {
      ret += '+';
    }
    ret += this.pad(Math.floor(offset / 60), 2) + ':' + this.pad(offset % 60, 2);
    /* v8 ignore next */
    if (isBCYear) {
      ret += ' BC';
    }
    return ret;
  }
  indexForeignKeys() {
    return false;
  }
  getDefaultMappedType(type) {
    const normalizedType = this.extractSimpleType(type);
    const map = {
      int2: 'smallint',
      smallserial: 'smallint',
      int: 'integer',
      int4: 'integer',
      serial: 'integer',
      serial4: 'integer',
      int8: 'bigint',
      bigserial: 'bigint',
      serial8: 'bigint',
      numeric: 'decimal',
      bool: 'boolean',
      real: 'float',
      float4: 'float',
      float8: 'double',
      timestamp: 'datetime',
      timestamptz: 'datetime',
      bytea: 'blob',
      jsonb: 'json',
      'character varying': 'varchar',
      bpchar: 'character',
    };
    return super.getDefaultMappedType(map[normalizedType] ?? type);
  }
  supportsSchemas() {
    return true;
  }
  getDefaultSchemaName() {
    return 'public';
  }
  /**
   * Returns the default name of index for the given columns
   * cannot go past 63 character length for identifiers in MySQL
   */
  getIndexName(tableName, columns, type) {
    const indexName = super.getIndexName(tableName, columns, type);
    if (indexName.length > 63) {
      const suffix = type === 'primary' ? 'pkey' : type;
      return `${indexName.substring(0, 55 - type.length)}_${Utils.hash(indexName, 5)}_${suffix}`;
    }
    return indexName;
  }
  getDefaultPrimaryName(tableName, columns) {
    const indexName = `${tableName}_pkey`;
    if (indexName.length > 63) {
      return `${indexName.substring(0, 55 - 'pkey'.length)}_${Utils.hash(indexName, 5)}_pkey`;
    }
    return indexName;
  }
  /**
   * @inheritDoc
   */
  castColumn(prop) {
    switch (prop?.columnTypes?.[0]) {
      case this.getUuidTypeDeclarationSQL({}):
        return '::text';
      case this.getBooleanTypeDeclarationSQL():
        return '::int';
      default:
        return '';
    }
  }
  getJsonArrayFromSQL(column, alias, _properties) {
    return `jsonb_array_elements(${column}) as ${this.quoteIdentifier(alias)}`;
  }
  getJsonArrayElementPropertySQL(alias, property, type) {
    const expr = `${this.quoteIdentifier(alias)}->>${this.quoteValue(property)}`;
    return type in this.#jsonTypeCasts ? `(${expr})::${this.#jsonTypeCasts[type]}` : expr;
  }
  getDefaultClientUrl() {
    return 'postgresql://postgres@127.0.0.1:5432';
  }
}
