import { isRaw, JsonProperty, Platform, raw, Utils } from '@mikro-orm/core';
import { SqlEntityRepository } from './SqlEntityRepository.js';
import { SqlSchemaGenerator } from './schema/SqlSchemaGenerator.js';
import { NativeQueryBuilder } from './query/NativeQueryBuilder.js';
/** Base class for SQL database platforms, providing SQL generation and quoting utilities. */
export class AbstractSqlPlatform extends Platform {
  static #JSON_PROPERTY_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
  schemaHelper;
  usesPivotTable() {
    return true;
  }
  indexForeignKeys() {
    return true;
  }
  getRepositoryClass() {
    return SqlEntityRepository;
  }
  getSchemaHelper() {
    return this.schemaHelper;
  }
  /** @inheritDoc */
  lookupExtensions(orm) {
    SqlSchemaGenerator.register(orm);
  }
  /* v8 ignore next: kept for type inference only */
  getSchemaGenerator(driver, em) {
    return new SqlSchemaGenerator(em ?? driver);
  }
  /** @internal */
  /* v8 ignore next */
  createNativeQueryBuilder() {
    return new NativeQueryBuilder(this);
  }
  getBeginTransactionSQL(options) {
    if (options?.isolationLevel) {
      return [`set transaction isolation level ${options.isolationLevel}`, 'begin'];
    }
    return ['begin'];
  }
  getCommitTransactionSQL() {
    return 'commit';
  }
  getRollbackTransactionSQL() {
    return 'rollback';
  }
  getSavepointSQL(savepointName) {
    return `savepoint ${this.quoteIdentifier(savepointName)}`;
  }
  getRollbackToSavepointSQL(savepointName) {
    return `rollback to savepoint ${this.quoteIdentifier(savepointName)}`;
  }
  getReleaseSavepointSQL(savepointName) {
    return `release savepoint ${this.quoteIdentifier(savepointName)}`;
  }
  quoteValue(value) {
    if (isRaw(value)) {
      return this.formatQuery(value.sql, value.params);
    }
    if (Utils.isPlainObject(value) || value?.[JsonProperty]) {
      return this.escape(JSON.stringify(value));
    }
    return this.escape(value);
  }
  getSearchJsonPropertySQL(path, type, aliased) {
    return this.getSearchJsonPropertyKey(path.split('->'), type, aliased);
  }
  getSearchJsonPropertyKey(path, type, aliased, value) {
    const [a, ...b] = path;
    if (aliased) {
      return raw(
        alias => `json_extract(${this.quoteIdentifier(`${alias}.${a}`)}, '$.${b.map(this.quoteJsonKey).join('.')}')`,
      );
    }
    return raw(`json_extract(${this.quoteIdentifier(a)}, '$.${b.map(this.quoteJsonKey).join('.')}')`);
  }
  /**
   * Quotes a key for use inside a JSON path expression (e.g. `$.key`).
   * Simple alphanumeric keys are left unquoted; others are wrapped in double quotes.
   * @internal
   */
  quoteJsonKey(key) {
    return /^[a-z]\w*$/i.exec(key) ? key : `"${key}"`;
  }
  getJsonIndexDefinition(index) {
    return index.columnNames.map(column => {
      if (!column.includes('.')) {
        return column;
      }
      const [root, ...path] = column.split('.');
      return `(json_extract(${root}, '$.${path.join('.')}'))`;
    });
  }
  supportsUnionWhere() {
    return true;
  }
  supportsSchemas() {
    return false;
  }
  /** @inheritDoc */
  generateCustomOrder(escapedColumn, values) {
    let ret = '(case ';
    values.forEach((v, i) => {
      ret += `when ${escapedColumn} = ${this.quoteValue(v)} then ${i} `;
    });
    return ret + 'else null end)';
  }
  /**
   * @internal
   */
  getOrderByExpression(column, direction, collation) {
    if (collation) {
      return [`${column} collate ${this.quoteCollation(collation)} ${direction.toLowerCase()}`];
    }
    return [`${column} ${direction.toLowerCase()}`];
  }
  /**
   * Quotes a collation name for use in COLLATE clauses.
   * @internal
   */
  quoteCollation(collation) {
    this.validateCollationName(collation);
    return this.quoteIdentifier(collation);
  }
  /** @internal */
  validateCollationName(collation) {
    if (!/^[\w]+$/.test(collation)) {
      throw new Error(`Invalid collation name: '${collation}'. Collation names must contain only word characters.`);
    }
  }
  /** @internal */
  validateJsonPropertyName(name) {
    if (!AbstractSqlPlatform.#JSON_PROPERTY_NAME_RE.test(name)) {
      throw new Error(
        `Invalid JSON property name: '${name}'. JSON property names must contain only alphanumeric characters and underscores.`,
      );
    }
  }
  /**
   * Returns FROM clause for JSON array iteration.
   * @internal
   */
  getJsonArrayFromSQL(column, alias, _properties) {
    return `json_each(${column}) as ${this.quoteIdentifier(alias)}`;
  }
  /**
   * Returns SQL expression to access an element's property within a JSON array iteration.
   * @internal
   */
  getJsonArrayElementPropertySQL(alias, property, _type) {
    return `${this.quoteIdentifier(alias)}.${this.quoteIdentifier(property)}`;
  }
  /**
   * Wraps JSON array FROM clause and WHERE condition into a full EXISTS condition.
   * MySQL overrides this because `json_table` doesn't support correlated subqueries.
   * @internal
   */
  getJsonArrayExistsSQL(from, where) {
    return `exists (select 1 from ${from} where ${where})`;
  }
  /**
   * Maps a runtime type name (e.g. 'string', 'number') to a driver-specific bind type constant.
   * Used by NativeQueryBuilder for output bindings.
   * @internal
   */
  mapToBindType(type) {
    return type;
  }
}
