import { getPool } from "../../db/pg-pool.js";
import { resolveDomain, } from "./domain.js";
// ─── WhereBuilder ─────────────────────────────────────────────────────────────
class WhereBuilder {
    conditions = [];
    params = [];
    add(condition, ...values) {
        // Replace positional placeholders $? with the next $N values
        let idx = this.params.length;
        const rewritten = condition.replace(/\?\?/g, () => {
            idx++;
            return `$${idx}`;
        });
        this.conditions.push(rewritten);
        this.params.push(...values);
        return this;
    }
    /** Append a raw condition with explicit $N references already set relative to current param count */
    addRaw(condition, ...values) {
        this.conditions.push(condition);
        this.params.push(...values);
        return this;
    }
    build() {
        const clause = this.conditions.length > 0
            ? "WHERE " + this.conditions.join(" AND ")
            : "";
        return { clause, params: [...this.params] };
    }
    clone() {
        const cloned = new WhereBuilder();
        cloned.conditions = [...this.conditions];
        cloned.params = [...this.params];
        return cloned;
    }
}
// ─── Row mapping ──────────────────────────────────────────────────────────────
function toRecord(row, cfg) {
    const rec = {
        id: row["id"],
        name: row["name"],
        code: row["code"] ?? null,
        description: row["description"] ?? null,
        isActive: cfg.noIsActive ? true : row["is_active"],
        sortOrder: row["sort_order"] ?? 0,
        metadata: row["metadata"] ?? {},
        effectiveFrom: row["effective_from"].toISOString(),
        effectiveTo: row["effective_to"] != null ? row["effective_to"].toISOString() : null,
        createdAt: row["created_at"].toISOString(),
        createdBy: row["created_by"] ?? null,
        updatedAt: cfg.noUpdatedAt ? null : (row["updated_at"] != null ? row["updated_at"].toISOString() : null),
        updatedBy: row["updated_by"] ?? null,
    };
    if (cfg.hierarchical || cfg.extraCols?.includes("parent_id")) {
        rec.parentId = row["parent_id"] ?? null;
    }
    if (cfg.extraCols?.includes("path")) {
        rec.path = row["path"] ?? null;
    }
    if (cfg.extraCols?.includes("depth")) {
        rec.depth = row["depth"] ?? null;
    }
    if (cfg.extraCols?.includes("area_type")) {
        rec.areaType = row["area_type"] ?? null;
    }
    if (cfg.extraCols?.includes("owner_id")) {
        rec.ownerId = row["owner_id"] ?? null;
    }
    if (cfg.extraCols?.includes("group_id")) {
        rec.groupId = row["group_id"] ?? null;
    }
    return rec;
}
function buildSelectCols(cfg) {
    const nameExpr = cfg.nameCol && cfg.nameCol !== "name"
        ? `${cfg.nameCol} AS name`
        : "name";
    const isActiveCol = cfg.noIsActive ? [] : ["is_active"];
    const auditCols = cfg.noAuditCols ? [] : ["created_by", "updated_by"];
    const updatedAtCol = cfg.noUpdatedAt ? [] : ["updated_at"];
    const base = [
        "id", nameExpr, "code", "description", ...isActiveCol, "sort_order",
        "metadata", "effective_from", "effective_to",
        "created_at", ...auditCols, ...updatedAtCol,
    ];
    const extra = cfg.extraCols ? [...cfg.extraCols] : [];
    if (cfg.hierarchical && !extra.includes("parent_id")) {
        extra.push("parent_id");
    }
    return [...base, ...extra].join(", ");
}
// ─── Repository implementation ────────────────────────────────────────────────
export class PgMasterRepository {
    pool;
    constructor() {
        this.pool = getPool();
    }
    async list(domain, filter) {
        const cfg = resolveDomain(domain);
        const cols = buildSelectCols(cfg);
        const nameCol = cfg.nameCol ?? "name";
        const wb = new WhereBuilder();
        wb.addRaw("deleted_at IS NULL");
        if (!cfg.noIsActive) {
            if (filter.isActive === undefined || filter.isActive === true) {
                wb.addRaw("is_active = TRUE");
            }
            else if (filter.isActive === false) {
                wb.addRaw("is_active = FALSE");
            }
        }
        if (filter.q) {
            const likeVal = `%${filter.q.toLowerCase()}%`;
            const nextIdx = wb["params"].length + 1;
            wb.addRaw(`(LOWER(${nameCol}) LIKE $${nextIdx} OR LOWER(COALESCE(code, '')) LIKE $${nextIdx})`, likeVal);
        }
        if (filter.parentId && cfg.hierarchical) {
            const nextIdx = wb["params"].length + 1;
            wb.addRaw(`parent_id = $${nextIdx}`, filter.parentId);
        }
        const { clause, params } = wb.build();
        // COUNT
        const countResult = await this.pool.query(`SELECT COUNT(*) AS count FROM ${cfg.table} ${clause}`, params);
        const total = parseInt(countResult.rows[0]?.count ?? "0", 10);
        // Pagination params
        const page = filter.page;
        const limit = filter.limit;
        const offset = (page - 1) * limit;
        const dataResult = await this.pool.query(`SELECT ${cols} FROM ${cfg.table} ${clause}
       ORDER BY sort_order ASC, ${nameCol} ASC
       LIMIT $${params.length + 1} OFFSET $${params.length + 2}`, [...params, limit, offset]);
        return {
            items: dataResult.rows.map((row) => toRecord(row, cfg)),
            total,
            page,
            limit,
        };
    }
    async getById(domain, id) {
        const cfg = resolveDomain(domain);
        const cols = buildSelectCols(cfg);
        const result = await this.pool.query(`SELECT ${cols} FROM ${cfg.table} WHERE id = $1 AND deleted_at IS NULL`, [id]);
        if (result.rowCount === 0)
            return null;
        return toRecord(result.rows[0], cfg);
    }
    async create(domain, data) {
        const cfg = resolveDomain(domain);
        const colNames = ["name"];
        const colValues = [data.name];
        function addCol(col, val) {
            colNames.push(col);
            colValues.push(val);
        }
        if (data.code !== undefined)
            addCol("code", data.code);
        if (data.description !== undefined)
            addCol("description", data.description);
        if (data.sortOrder !== undefined)
            addCol("sort_order", data.sortOrder);
        if (data.isActive !== undefined)
            addCol("is_active", data.isActive);
        if (data.metadata !== undefined)
            addCol("metadata", JSON.stringify(data.metadata));
        if (data.effectiveFrom !== undefined)
            addCol("effective_from", data.effectiveFrom);
        if (data.effectiveTo !== undefined)
            addCol("effective_to", data.effectiveTo);
        // Hierarchical / domain-specific
        if (cfg.hierarchical && data.parentId !== undefined)
            addCol("parent_id", data.parentId);
        if (cfg.extraCols?.includes("owner_id") && data.ownerId !== undefined)
            addCol("owner_id", data.ownerId);
        if (cfg.extraCols?.includes("group_id") && data.groupId !== undefined)
            addCol("group_id", data.groupId);
        if (cfg.extraCols?.includes("path") && data.path !== undefined)
            addCol("path", data.path);
        if (cfg.extraCols?.includes("depth") && data.depth !== undefined)
            addCol("depth", data.depth);
        if (cfg.extraCols?.includes("area_type") && data.areaType !== undefined)
            addCol("area_type", data.areaType);
        const placeholders = colNames.map((_, i) => `$${i + 1}`).join(", ");
        const cols = buildSelectCols(cfg);
        const result = await this.pool.query(`INSERT INTO ${cfg.table} (${colNames.join(", ")})
       VALUES (${placeholders})
       RETURNING ${cols}`, colValues);
        return toRecord(result.rows[0], cfg);
    }
    async update(domain, id, data) {
        const cfg = resolveDomain(domain);
        const setClauses = [];
        const setValues = [];
        function addSet(col, val) {
            setValues.push(val);
            setClauses.push(`${col} = $${setValues.length}`);
        }
        if (data.name !== undefined)
            addSet("name", data.name);
        if (data.code !== undefined)
            addSet("code", data.code);
        if (data.description !== undefined)
            addSet("description", data.description);
        if (data.sortOrder !== undefined)
            addSet("sort_order", data.sortOrder);
        if (data.isActive !== undefined)
            addSet("is_active", data.isActive);
        if (data.metadata !== undefined)
            addSet("metadata", JSON.stringify(data.metadata));
        if (data.effectiveFrom !== undefined)
            addSet("effective_from", data.effectiveFrom);
        if (data.effectiveTo !== undefined)
            addSet("effective_to", data.effectiveTo);
        if (cfg.hierarchical && data.parentId !== undefined)
            addSet("parent_id", data.parentId);
        if (cfg.extraCols?.includes("owner_id") && data.ownerId !== undefined)
            addSet("owner_id", data.ownerId);
        if (cfg.extraCols?.includes("group_id") && data.groupId !== undefined)
            addSet("group_id", data.groupId);
        if (cfg.extraCols?.includes("path") && data.path !== undefined)
            addSet("path", data.path);
        if (cfg.extraCols?.includes("depth") && data.depth !== undefined)
            addSet("depth", data.depth);
        if (cfg.extraCols?.includes("area_type") && data.areaType !== undefined)
            addSet("area_type", data.areaType);
        if (setClauses.length === 0) {
            // Nothing to update — just return current record
            return this.getById(domain, id);
        }
        // Always bump updated_at
        setClauses.push("updated_at = NOW()");
        const idIdx = setValues.length + 1;
        const cols = buildSelectCols(cfg);
        const result = await this.pool.query(`UPDATE ${cfg.table}
       SET ${setClauses.join(", ")}
       WHERE id = $${idIdx} AND deleted_at IS NULL
       RETURNING ${cols}`, [...setValues, id]);
        if ((result.rowCount ?? 0) === 0)
            return null;
        return toRecord(result.rows[0], cfg);
    }
    async softDelete(domain, id) {
        const cfg = resolveDomain(domain);
        const result = await this.pool.query(`UPDATE ${cfg.table}
       SET deleted_at = NOW()
       WHERE id = $1 AND deleted_at IS NULL`, [id]);
        return (result.rowCount ?? 0) > 0;
    }
}
