'use strict';

const { generateConstraintName } = require('./naming');

// Soft-delete (Fase 1) — sumber kebenaran tunggal untuk nama kolom soft-delete dan
// panjang suffix mutasi. Dipakai bersama oleh:
//   - validator SDF (validator/schema-validator.js) untuk invariant R3/R5/R7/R9,
//   - emitter DDL forward (emitters/create-table.js CHECK konsistensi R8,
//     emitters/create-index.js partial index R10/R11), dan
//   - jalur reverse Phase 03 (introspect-mapper.js deteksi R6, diff-engine no-false-drift)
//     untuk mengenali CHECK soft-delete lewat nama deterministik yang sama.
// supaya tidak ada duplikat nama kolom atau angka suffix yang harus dijaga sinkron
// di banyak tempat.
//
// Catatan scope: konstanta whitelist input SDF (`SOFT_DELETE_ALLOWED_KEYS` dkk. di
// ir-builder.js) sengaja TIDAK dipindah ke sini. Itu domain parsing input (key apa
// yang boleh ditulis di blok `softDelete`), beda concern dari nama kolom fisik DDL.

// Tiga kolom soft-delete bernama tetap (R3) dengan kontrak tipe kaku (R5).
const IS_DELETED_COLUMN = 'is_deleted';
const DELETED_AT_COLUMN = 'deleted_at';
const DELETED_BY_COLUMN = 'deleted_by';

// Urutan array adalah urutan kanonik kolom: is_deleted, deleted_at, deleted_by.
const SOFT_DELETE_COLUMNS = [IS_DELETED_COLUMN, DELETED_AT_COLUMN, DELETED_BY_COLUMN];

// Kontrak tipe logical tiga kolom soft-delete (R5). Dipakai deteksi introspect
// (reverse, R6) untuk memverifikasi tipe kolom benar sebelum mengakui sebuah tabel
// sebagai soft-delete. Validator SDF (schema-validator.js) memegang kontrak yang sama
// di sisi forward; keduanya WAJIB tetap identik (R5 fixed).
const SOFT_DELETE_COLUMN_TYPES = {
  [IS_DELETED_COLUMN]: 'boolean',
  [DELETED_AT_COLUMN]: 'timestamp',
  [DELETED_BY_COLUMN]: 'string'
};

// Suffix mutasi reuse '{code}##{uuidv7}' = '##' (2) + uuidv7 (36) = 38 char (R7).
const SOFT_DELETE_SUFFIX_LENGTH = 38;

// Suffix nama CHECK konsistensi soft-delete (R8). HARUS identik dengan emitter forward
// Phase 02 (emitters/create-table.js memakai literal yang sama) agar reverse-matching
// nama-based di Phase 03 mengenali CHECK yang sama tanpa false drift. Konsistensi
// nama-derivation antara emit (forward) dan match (reverse) adalah kunci no-false-drift.
const SOFT_DELETE_CHECK_SUFFIX = 'soft_delete_consistency';

// Dialect yang didukung soft-delete pada Fase 1. CHECK konsistensi memakai boolean
// native (`= TRUE`/`= FALSE`) dan index non-unique memakai partial index; keduanya
// adalah bentuk PostgreSQL. Adaptasi VARCHAR 'true'/'false' dan plain index untuk
// dialect lain ditunda ke fase multi-database.
const SOFT_DELETE_PHASE1_DIALECT = 'postgres';

// Predikat tunggal kehadiran soft-delete aktif pada IR. Blok `softDelete` bersifat
// optional di IR (hanya di-attach bila dideklarasikan, lihat ir-builder), jadi guard
// `softDelete && softDelete.enabled === true` dipakai konsisten di semua emitter agar
// emisi soft-delete benar-benar guarded (backward-compat byte-identik baseline).
function isSoftDeleteEnabled(ir) {
  return !!(ir && ir.softDelete && ir.softDelete.enabled === true);
}

// Guard Fase 1 PostgreSQL-only. Bila soft-delete aktif tetapi dialect bukan PostgreSQL,
// lempar ERROR jelas alih-alih meng-emit DDL salah diam-diam (bentuk VARCHAR boolean /
// plain index). Dipakai oleh emitter CHECK (create-table) dan partial index (create-index)
// supaya tiap emitter aman dipanggil mandiri (test maupun jalur generateDDL).
function assertSoftDeletePostgresOnly(ir, dialect) {
  if (!isSoftDeleteEnabled(ir)) return;
  if (dialect && dialect.name === SOFT_DELETE_PHASE1_DIALECT) return;
  const label = (ir && (ir.qualifiedName || ir.tableName)) || '<unknown>';
  const dialectName = dialect && dialect.name ? dialect.name : '<unknown>';
  throw new Error(
    `Table '${label}': soft-delete is PostgreSQL-only in Phase 1, ` +
    `but the active dialect is '${dialectName}'. ` +
    `Remove the softDelete block or generate DDL for PostgreSQL.`
  );
}

// Derivasi nama CHECK konsistensi soft-delete, identik dengan emitter forward Phase 02
// (emitters/create-table.js emitSoftDeleteCheck). `maxIdentifierLength` WAJIB sama dengan
// yang dipakai emitter (`dialect.maxIdentifierLength`, PostgreSQL = 63) agar tabel bernama
// panjang yang ter-truncate menghasilkan nama identik di kedua arah (tidak false drift).
function softDeleteCheckName(tableName, maxIdentifierLength) {
  return generateConstraintName('chk', tableName, SOFT_DELETE_CHECK_SUFFIX, maxIdentifierLength);
}

// Bangun rekomendasi SQL native PostgreSQL (R8) untuk menambahkan CHECK konsistensi
// soft-delete. Dipakai pesan ERROR introspect (R6) saat tiga kolom lengkap & bertipe
// benar tetapi CHECK hilang. Bentuk persis mengikuti plan master R8 (boolean native PG).
function buildSoftDeleteCheckSql(tableName, maxIdentifierLength) {
  const name = softDeleteCheckName(tableName, maxIdentifierLength);
  return (
    `ALTER TABLE ${tableName} ADD CONSTRAINT ${name}\n` +
    `    CHECK (\n` +
    `        (${IS_DELETED_COLUMN} = TRUE  AND ${DELETED_AT_COLUMN} IS NOT NULL AND ${DELETED_BY_COLUMN} IS NOT NULL)\n` +
    `     OR (${IS_DELETED_COLUMN} = FALSE AND ${DELETED_AT_COLUMN} IS NULL     AND ${DELETED_BY_COLUMN} IS NULL)\n` +
    `    );`
  );
}

module.exports = {
  IS_DELETED_COLUMN,
  DELETED_AT_COLUMN,
  DELETED_BY_COLUMN,
  SOFT_DELETE_COLUMNS,
  SOFT_DELETE_COLUMN_TYPES,
  SOFT_DELETE_SUFFIX_LENGTH,
  SOFT_DELETE_CHECK_SUFFIX,
  SOFT_DELETE_PHASE1_DIALECT,
  isSoftDeleteEnabled,
  assertSoftDeletePostgresOnly,
  softDeleteCheckName,
  buildSoftDeleteCheckSql
};
