import { queryWithRLS, mutateWithRLS } from "../db";
import {
  getEntityMetaConfig
} from "../../types/meta.types";
class MetaService {
  /**
   * Obtener configuración de entidad
   */
  static getEntityConfig(entityType) {
    const config = getEntityMetaConfig(entityType);
    if (!config) {
      throw new Error(`Entity type '${entityType}' not configured`);
    }
    return config;
  }
  /**
   * Obtener todos los meta datos de una entidad
   */
  static async getEntityMetas(entityType, entityId, userId, includePrivate = false) {
    const config = this.getEntityConfig(entityType);
    const whereClause = includePrivate ? `WHERE "${config.idColumn}" = $1` : `WHERE "${config.idColumn}" = $1 AND "isPublic" = true`;
    const metas = await queryWithRLS(
      `SELECT "metaKey", "metaValue" FROM "${config.metaTableName}" ${whereClause} ORDER BY "metaKey"`,
      [entityId],
      userId
    );
    const result = (metas || []).reduce((acc, meta) => {
      acc[meta.metaKey] = meta.metaValue;
      return acc;
    }, {});
    return result;
  }
  /**
   * Obtener meta datos para múltiples entidades en bulk (soluciona N+1 queries)
   */
  static async getBulkEntityMetas(entityType, entityIds, userId, includePrivate = false) {
    if (entityIds.length === 0) {
      return {};
    }
    const config = this.getEntityConfig(entityType);
    const idPlaceholders = entityIds.map((_, index) => `$${index + 1}`).join(",");
    const whereClause = includePrivate ? `WHERE "${config.idColumn}" IN (${idPlaceholders})` : `WHERE "${config.idColumn}" IN (${idPlaceholders}) AND "isPublic" = true`;
    const metas = await queryWithRLS(
      `SELECT "${config.idColumn}" as "entityId", "metaKey", "metaValue" 
       FROM "${config.metaTableName}" 
       ${whereClause} 
       ORDER BY "${config.idColumn}", "metaKey"`,
      entityIds,
      userId
    );
    const result = entityIds.reduce((acc, entityId) => {
      acc[entityId] = {};
      return acc;
    }, {});
    metas.forEach((meta) => {
      const entityId = meta.entityId;
      if (result[entityId]) {
        result[entityId][meta.metaKey] = meta.metaValue;
      }
    });
    return result;
  }
  /**
   * Obtener meta datos específicos de una entidad
   */
  static async getSpecificEntityMetas(entityType, entityId, metaKeys, userId) {
    const config = this.getEntityConfig(entityType);
    if (metaKeys.length === 0) {
      return {};
    }
    const placeholders = metaKeys.map((_, index) => `$${index + 2}`).join(",");
    const metas = await queryWithRLS(
      `SELECT "metaKey", "metaValue" FROM "${config.metaTableName}" 
       WHERE "${config.idColumn}" = $1 AND "metaKey" IN (${placeholders}) 
       ORDER BY "metaKey"`,
      [entityId, ...metaKeys],
      userId
    );
    const result = (metas || []).reduce((acc, meta) => {
      acc[meta.metaKey] = meta.metaValue;
      return acc;
    }, {});
    return result;
  }
  /**
   * Obtener meta datos específicos para múltiples entidades en bulk
   */
  static async getBulkSpecificEntityMetas(entityType, entityIds, metaKeys, userId) {
    if (entityIds.length === 0 || metaKeys.length === 0) {
      return {};
    }
    const config = this.getEntityConfig(entityType);
    const idPlaceholders = entityIds.map((_, index) => `$${index + 1}`).join(",");
    const keyPlaceholders = metaKeys.map((_, index) => `$${index + entityIds.length + 1}`).join(",");
    const metas = await queryWithRLS(
      `SELECT "${config.idColumn}" as "entityId", "metaKey", "metaValue" 
       FROM "${config.metaTableName}" 
       WHERE "${config.idColumn}" IN (${idPlaceholders}) 
       AND "metaKey" IN (${keyPlaceholders})
       ORDER BY "${config.idColumn}", "metaKey"`,
      [...entityIds, ...metaKeys],
      userId
    );
    const result = entityIds.reduce((acc, entityId) => {
      acc[entityId] = {};
      return acc;
    }, {});
    metas.forEach((meta) => {
      const entityId = meta.entityId;
      if (result[entityId]) {
        result[entityId][meta.metaKey] = meta.metaValue;
      }
    });
    return result;
  }
  /**
   * Obtener un meta dato específico
   */
  static async getEntityMeta(entityType, entityId, metaKey, userId) {
    var _a;
    const config = this.getEntityConfig(entityType);
    const result = await queryWithRLS(
      `SELECT "metaValue" FROM "${config.metaTableName}"
       WHERE "${config.idColumn}" = $1 AND "metaKey" = $2`,
      [entityId, metaKey],
      userId
    );
    return ((_a = result[0]) == null ? void 0 : _a.metaValue) || null;
  }
  /**
   * Establecer un meta dato
   *
   * NOTE: Metas tables do NOT have teamId column - security is inherited from parent entity via RLS.
   * This follows the CRM theme pattern where metadata access is controlled through the parent entity.
   */
  static async setEntityMeta(entityType, entityId, metaKey, metaValue, userId, options = {}) {
    const config = this.getEntityConfig(entityType);
    if (!config.metaTableName.endsWith("_metas")) {
      throw new Error(`Invalid meta table name: ${config.metaTableName}`);
    }
    if (!metaKey || metaKey.trim() === "") {
      throw new Error("Meta key cannot be empty");
    }
    if (metaKey.length > 100) {
      throw new Error("Meta key too long (max 100 characters)");
    }
    const jsonString = JSON.stringify(metaValue);
    if (new TextEncoder().encode(jsonString).length > 1048576) {
      throw new Error("Meta value too large (max 1MB)");
    }
    const query = `
      INSERT INTO "${config.metaTableName}"
        ("${config.idColumn}", "metaKey", "metaValue", "dataType", "isPublic", "isSearchable")
      VALUES ($1, $2, $3::jsonb, $4, $5, $6)
      ON CONFLICT ("${config.idColumn}", "metaKey")
      DO UPDATE SET
        "metaValue" = EXCLUDED."metaValue",
        "dataType" = EXCLUDED."dataType",
        "isPublic" = EXCLUDED."isPublic",
        "isSearchable" = EXCLUDED."isSearchable",
        "updatedAt" = CURRENT_TIMESTAMP
    `;
    const params = [
      entityId,
      metaKey,
      jsonString,
      options.dataType || "json",
      options.isPublic || false,
      options.isSearchable || false
    ];
    await mutateWithRLS(query, params, userId);
  }
  /**
   * Establecer múltiples meta datos en batch (optimizado con transacciones)
   */
  static async setBulkEntityMetas(entityType, entityId, metas, userId, options = {}) {
    const MAX_META_KEYS = 50;
    const existingCount = await this.countEntityMetas(entityType, entityId, userId);
    if (existingCount + Object.keys(metas).length > MAX_META_KEYS) {
      throw new Error(`Maximum ${MAX_META_KEYS} meta keys allowed per entity`);
    }
    const metaEntries = Object.entries(metas);
    const batchSize = 10;
    for (let i = 0; i < metaEntries.length; i += batchSize) {
      const batch = metaEntries.slice(i, i + batchSize);
      const promises = batch.map(
        ([key, value]) => this.setEntityMeta(entityType, entityId, key, value, userId, options)
      );
      await Promise.all(promises);
    }
  }
  /**
   * Eliminar un meta dato
   */
  static async deleteEntityMeta(entityType, entityId, metaKey, userId) {
    const config = this.getEntityConfig(entityType);
    await mutateWithRLS(
      `DELETE FROM "${config.metaTableName}" WHERE "${config.idColumn}" = $1 AND "metaKey" = $2`,
      [entityId, metaKey],
      userId
    );
  }
  /**
   * Eliminar todos los meta datos de una entidad
   */
  static async deleteAllEntityMetas(entityType, entityId, userId) {
    const config = this.getEntityConfig(entityType);
    await mutateWithRLS(
      `DELETE FROM "${config.metaTableName}" WHERE "${config.idColumn}" = $1`,
      [entityId],
      userId
    );
  }
  // getMetaSchemas eliminado - sistema completamente flexible sin schemas predefinidos
  /**
   * Buscar entidades por meta datos (optimizado con límites)
   */
  static async searchByMeta(entityType, metaKey, metaValue, userId, limit = 100, offset = 0) {
    var _a;
    const config = this.getEntityConfig(entityType);
    const countResult = await queryWithRLS(
      `SELECT COUNT(DISTINCT "${config.idColumn}") as count
       FROM "${config.metaTableName}" 
       WHERE "metaKey" = $1 
         AND "metaValue" @> $2
         AND "isSearchable" = true`,
      [metaKey, JSON.stringify(metaValue)],
      userId
    );
    const entities = await queryWithRLS(
      `SELECT DISTINCT "${config.idColumn}" as "entityId"
       FROM "${config.metaTableName}" 
       WHERE "metaKey" = $1 
         AND "metaValue" @> $2
         AND "isSearchable" = true
       ORDER BY "entityId"
       LIMIT $3 OFFSET $4`,
      [metaKey, JSON.stringify(metaValue), limit, offset],
      userId
    );
    return {
      entities: entities.map((e) => e.entityId),
      total: ((_a = countResult[0]) == null ? void 0 : _a.count) || 0
    };
  }
  /**
   * Contar meta datos por entidad
   */
  static async countEntityMetas(entityType, entityId, userId) {
    var _a;
    const config = this.getEntityConfig(entityType);
    const result = await queryWithRLS(
      `SELECT COUNT(*) as count FROM "${config.metaTableName}" WHERE "${config.idColumn}" = $1`,
      [entityId],
      userId
    );
    return ((_a = result[0]) == null ? void 0 : _a.count) || 0;
  }
}
export {
  MetaService
};
