'use strict';

/**
 * sdf-reader — Sumber metadata tabel untuk `data pull` / `data push`.
 *
 * Seluruh metadata (daftar kolom, tipe canonical, nullable, primary key composite)
 * dibaca murni dari SDF IR (Schema Definition File), TANPA introspeksi DB. Modul ini
 * mereuse loader existing `dbschema-kit/loader` dan tidak membuka koneksi database.
 *
 * Keputusan arah: amandemen A2 (SDF sebagai sumber kebenaran tabel) dan A3 (deteksi
 * binary by-omission — SDF hanya bisa menyimpan 10 tipe canonical, jadi kolom binary
 * mustahil muncul). Lihat docs/plan/data-pull-push/data-pull-push-09-amendments.md.
 *
 * @module generators/lib/data/sdf-reader
 */

const { loadSchemaPath } = require('../dbschema-kit/loader');

/**
 * Muat SDF dari file atau folder menjadi Map<qualifiedName, IR>.
 *
 * Delegasi langsung ke loader existing — tidak ada introspeksi DB. `schemaPath`
 * boleh menunjuk satu file `.js` atau folder berisi banyak file SDF.
 *
 * @param {string} schemaPath - Path ke file atau folder SDF
 * @returns {Map<string, Object>} Map qualifiedName → IR model
 */
function loadSdf(schemaPath) {
  return loadSchemaPath(schemaPath);
}

/**
 * Cari satu IR dari Map hasil loadSdf berdasarkan argumen tabel.
 *
 * Urutan pencocokan:
 *   1. Match eksak via qualifiedName (key Map) → selalu satu entry, tidak ambigu.
 *   2. Match via `ir.tableName` (arg tanpa schema). Bila cocok dengan LEBIH DARI SATU
 *      entry (nama tabel sama di schema berbeda), arg dianggap ambigu → throw exitCode=2
 *      yang meminta bentuk qualified `schema.table`.
 *
 * Mereuse pola table-guard di cli/schema/apply.js:303-314. Bila tidak ditemukan,
 * throw Error dengan `err.exitCode = 2` (precondition) — penolakan terjadi SEBELUM
 * koneksi DB dibuka oleh caller.
 *
 * @param {Map<string, Object>} models - Hasil loadSdf
 * @param {string} tableArg - Nilai flag --table
 * @returns {Object} IR model yang cocok
 * @throws {Error} exitCode=2 bila tabel tidak terdaftar ATAU ambigu lintas-schema
 */
function findTableOrThrow(models, tableArg) {
  // 1. Match eksak via qualifiedName (key Map) — kunci Map unik, selalu satu entry.
  if (models.has(tableArg)) {
    return models.get(tableArg);
  }

  // 2. Match via tableName tanpa schema — bisa lebih dari satu bila nama tabel sama
  //    di schema berbeda.
  const byTableName = [];
  for (const ir of models.values()) {
    if (ir.tableName === tableArg) {
      byTableName.push(ir);
    }
  }

  if (byTableName.length === 1) {
    return byTableName[0];
  }
  if (byTableName.length > 1) {
    const err = new Error(
      `Table '${tableArg}' is ambiguous across schemas; specify 'schema.table'.`
    );
    err.exitCode = 2;
    throw err;
  }

  const err = new Error(`Table '${tableArg}' not found in SDF.`);
  err.exitCode = 2;
  throw err;
}

/**
 * Transformasi IR menjadi array kolom envelope.
 *
 * Murni in-memory, tanpa I/O. Urutan kolom mengikuti urutan deklarasi field di SDF
 * (`Object.keys(ir.fields)`).
 *
 * @param {Object} ir - IR model dari SDF
 * @returns {Array<{name: string, type: string, nullable: boolean, primary_key: boolean}>}
 */
function irToColumns(ir) {
  const primaryKey = Array.isArray(ir.primaryKey) ? ir.primaryKey : [];
  return Object.keys(ir.fields).map((name) => {
    const field = ir.fields[name];
    return {
      name,
      type: field.type,
      nullable: field.notnull !== true,
      primary_key: primaryKey.includes(name)
    };
  });
}

/**
 * Ambil namespace (schema DB) dari IR.
 *
 * @param {Object} ir - IR model dari SDF
 * @returns {{schemaName: (string|null), qualifiedName: string}}
 */
function getNamespace(ir) {
  return {
    schemaName: ir.schemaName || null,
    qualifiedName: ir.qualifiedName || ir.tableName
  };
}

/**
 * Ambil daftar primary key (mendukung composite).
 *
 * @param {Object} ir - IR model dari SDF
 * @returns {string[]} Array nama kolom primary key
 */
function getPrimaryKey(ir) {
  return ir.primaryKey;
}

module.exports = {
  loadSdf,
  findTableOrThrow,
  irToColumns,
  getNamespace,
  getPrimaryKey
};
