import { queryWithRLS, queryOneWithRLS, mutateWithRLS } from "../db.js";
const SYSTEM_FIELDS = ["id", "userId", "teamId", "createdAt", "updatedAt"];
class BaseEntityService {
  config;
  constructor(config) {
    this.config = {
      searchableFields: [],
      defaultOrderBy: "createdAt",
      defaultOrderDir: "desc",
      defaultLimit: 20,
      ...config
    };
  }
  // ============================================
  // PROTECTED HELPERS
  // ============================================
  /**
   * Get all selectable fields (entity fields + system fields)
   */
  get selectFields() {
    return [...SYSTEM_FIELDS, ...this.config.fields];
  }
  /**
   * Build SELECT clause with proper quoting
   */
  buildSelectClause() {
    return this.selectFields.map((f) => f.includes("_") || /[A-Z]/.test(f) ? `"${f}"` : f).join(", ");
  }
  /**
   * Quote a field name if needed
   */
  quoteField(field) {
    return field.includes("_") || /[A-Z]/.test(field) ? `"${field}"` : field;
  }
  /**
   * Build WHERE clause from conditions
   */
  buildWhereClause(where, startIndex = 1) {
    const conditions = [];
    const params = [];
    let paramIndex = startIndex;
    for (const [key, value] of Object.entries(where)) {
      if (value === void 0) continue;
      const quotedKey = this.quoteField(key);
      if (value === null) {
        conditions.push(`${quotedKey} IS NULL`);
      } else if (Array.isArray(value)) {
        conditions.push(`${quotedKey} = ANY($${paramIndex})`);
        params.push(value);
        paramIndex++;
      } else {
        conditions.push(`${quotedKey} = $${paramIndex}`);
        params.push(value);
        paramIndex++;
      }
    }
    return {
      clause: conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "",
      params,
      nextIndex: paramIndex
    };
  }
  /**
   * Build search condition for text search
   */
  buildSearchCondition(search, paramIndex) {
    if (!search || this.config.searchableFields.length === 0) {
      return null;
    }
    const searchConditions = this.config.searchableFields.map((f) => `${this.quoteField(f)} ILIKE $${paramIndex}`).join(" OR ");
    return {
      condition: `(${searchConditions})`,
      param: `%${search}%`
    };
  }
  /**
   * Map database row to entity (override for custom mapping)
   */
  mapRowToEntity(row) {
    const entity = {};
    for (const [key, value] of Object.entries(row)) {
      entity[key] = value === null ? void 0 : value;
    }
    return entity;
  }
  // ============================================
  // PUBLIC CRUD METHODS
  // ============================================
  /**
   * Get entity by ID
   *
   * @param id - Entity ID
   * @param userId - Current user ID for RLS
   * @returns Entity or null if not found
   */
  async getById(id, userId) {
    if (!(id == null ? void 0 : id.trim())) {
      throw new Error("Entity ID is required");
    }
    if (!(userId == null ? void 0 : userId.trim())) {
      throw new Error("User ID is required for authentication");
    }
    const row = await queryOneWithRLS(
      `SELECT ${this.buildSelectClause()}
       FROM ${this.config.tableName}
       WHERE id = $1`,
      [id],
      userId
    );
    return row ? this.mapRowToEntity(row) : null;
  }
  /**
   * List entities with pagination, filtering, and search
   *
   * @param userId - Current user ID for RLS
   * @param options - List options (limit, offset, where, search, orderBy)
   * @returns Paginated result with data and total count
   */
  async list(userId, options = {}) {
    var _a;
    if (!(userId == null ? void 0 : userId.trim())) {
      throw new Error("User ID is required for authentication");
    }
    const {
      limit = this.config.defaultLimit,
      offset = 0,
      orderBy = this.config.defaultOrderBy,
      orderDir = this.config.defaultOrderDir,
      where = {},
      search
    } = options;
    const { clause: whereClause, params, nextIndex } = this.buildWhereClause(where);
    let fullWhereClause = whereClause;
    const allParams = [...params];
    let paramIdx = nextIndex;
    if (search) {
      const searchResult = this.buildSearchCondition(search, paramIdx);
      if (searchResult) {
        const connector = whereClause ? " AND " : "WHERE ";
        fullWhereClause = `${whereClause}${connector}${searchResult.condition}`;
        allParams.push(searchResult.param);
        paramIdx++;
      }
    }
    const validOrderBy = this.selectFields.includes(orderBy) ? orderBy : this.config.defaultOrderBy;
    const validOrderDir = orderDir === "asc" ? "ASC" : "DESC";
    const orderColumn = this.quoteField(validOrderBy);
    const countResult = await queryWithRLS(
      `SELECT COUNT(*)::text as count FROM ${this.config.tableName} ${fullWhereClause}`,
      allParams,
      userId
    );
    const total = parseInt(((_a = countResult[0]) == null ? void 0 : _a.count) || "0", 10);
    const limitIdx = paramIdx;
    const offsetIdx = paramIdx + 1;
    const rows = await queryWithRLS(
      `SELECT ${this.buildSelectClause()}
       FROM ${this.config.tableName}
       ${fullWhereClause}
       ORDER BY ${orderColumn} ${validOrderDir}
       LIMIT $${limitIdx} OFFSET $${offsetIdx}`,
      [...allParams, limit, offset],
      userId
    );
    return {
      data: rows.map((row) => this.mapRowToEntity(row)),
      total,
      limit,
      offset
    };
  }
  /**
   * Query entities with custom conditions (simpler than list)
   *
   * @param userId - Current user ID for RLS
   * @param options - Query options
   * @returns Array of entities
   */
  async query(userId, options = {}) {
    const result = await this.list(userId, { ...options, offset: 0 });
    return result.data;
  }
  /**
   * Create a new entity
   *
   * @param userId - Current user ID for RLS and ownership
   * @param teamId - Team ID for team isolation
   * @param data - Entity data
   * @returns Created entity
   */
  async create(userId, teamId, data) {
    if (!(userId == null ? void 0 : userId.trim())) {
      throw new Error("User ID is required");
    }
    if (!(teamId == null ? void 0 : teamId.trim())) {
      throw new Error("Team ID is required");
    }
    const insertData = data;
    const fields = ["userId", "teamId"];
    const values = [userId, teamId];
    const placeholders = ["$1", "$2"];
    let paramIndex = 3;
    for (const field of this.config.fields) {
      if (field in insertData && insertData[field] !== void 0) {
        fields.push(field);
        values.push(insertData[field]);
        placeholders.push(`$${paramIndex}`);
        paramIndex++;
      }
    }
    const quotedFields = fields.map((f) => this.quoteField(f)).join(", ");
    const result = await mutateWithRLS(
      `INSERT INTO ${this.config.tableName} (${quotedFields})
       VALUES (${placeholders.join(", ")})
       RETURNING ${this.buildSelectClause()}`,
      values,
      userId
    );
    if (!result.rows[0]) {
      throw new Error("Failed to create entity");
    }
    return this.mapRowToEntity(result.rows[0]);
  }
  /**
   * Update an existing entity
   *
   * @param id - Entity ID
   * @param userId - Current user ID for RLS
   * @param data - Fields to update
   * @returns Updated entity
   */
  async update(id, userId, data) {
    if (!(id == null ? void 0 : id.trim())) {
      throw new Error("Entity ID is required");
    }
    if (!(userId == null ? void 0 : userId.trim())) {
      throw new Error("User ID is required");
    }
    const updateData = data;
    const setClauses = [];
    const values = [];
    let paramIndex = 1;
    for (const field of this.config.fields) {
      if (field in updateData) {
        setClauses.push(`${this.quoteField(field)} = $${paramIndex}`);
        values.push(updateData[field]);
        paramIndex++;
      }
    }
    if (setClauses.length === 0) {
      throw new Error("No fields to update");
    }
    setClauses.push(`"updatedAt" = now()`);
    values.push(id);
    const result = await mutateWithRLS(
      `UPDATE ${this.config.tableName}
       SET ${setClauses.join(", ")}
       WHERE id = $${paramIndex}
       RETURNING ${this.buildSelectClause()}`,
      values,
      userId
    );
    if (!result.rows[0]) {
      throw new Error("Entity not found or not authorized");
    }
    return this.mapRowToEntity(result.rows[0]);
  }
  /**
   * Delete an entity
   *
   * @param id - Entity ID
   * @param userId - Current user ID for RLS
   * @returns true if deleted, false if not found
   */
  async delete(id, userId) {
    if (!(id == null ? void 0 : id.trim())) {
      throw new Error("Entity ID is required");
    }
    if (!(userId == null ? void 0 : userId.trim())) {
      throw new Error("User ID is required");
    }
    const result = await mutateWithRLS(
      `DELETE FROM ${this.config.tableName} WHERE id = $1`,
      [id],
      userId
    );
    return result.rowCount > 0;
  }
  /**
   * Check if an entity exists
   *
   * @param id - Entity ID
   * @param userId - Current user ID for RLS
   * @returns true if exists and accessible
   */
  async exists(id, userId) {
    if (!(id == null ? void 0 : id.trim()) || !(userId == null ? void 0 : userId.trim())) {
      return false;
    }
    const result = await queryOneWithRLS(
      `SELECT EXISTS(SELECT 1 FROM ${this.config.tableName} WHERE id = $1) as exists`,
      [id],
      userId
    );
    return (result == null ? void 0 : result.exists) ?? false;
  }
  /**
   * Count entities with optional filtering
   *
   * @param userId - Current user ID for RLS
   * @param where - Filter conditions
   * @returns Count of matching entities
   */
  async count(userId, where = {}) {
    var _a;
    if (!(userId == null ? void 0 : userId.trim())) {
      throw new Error("User ID is required");
    }
    const { clause, params } = this.buildWhereClause(where);
    const result = await queryWithRLS(
      `SELECT COUNT(*)::text as count FROM ${this.config.tableName} ${clause}`,
      params,
      userId
    );
    return parseInt(((_a = result[0]) == null ? void 0 : _a.count) || "0", 10);
  }
}
export {
  BaseEntityService
};
