import crypto from "node:crypto";
import { Tenant } from "@open-mercato/core/modules/directory/data/entities";
import { getModules } from "@open-mercato/shared/lib/i18n/server";
import { getEntityIds } from "@open-mercato/shared/lib/encryption/entityIds";
import { ensureCustomFieldDefinitions } from "./field-definitions.js";
import { upsertCustomEntity } from "./register.js";
import { invalidateDefinitionsCache } from "../api/definitions.cache.js";
const FIELD_DETAIL_KEYS = [
  "label",
  "description",
  "options",
  "optionsUrl",
  "defaultValue",
  "required",
  "multi",
  "filterable",
  "formEditable",
  "listVisible",
  "indexed",
  "editor",
  "input",
  "relatedEntityId",
  "validation",
  "maxAttachmentSizeMb",
  "acceptExtensions"
];
function normalizeValue(value) {
  if (Array.isArray(value)) return value.map((item) => normalizeValue(item));
  if (value && typeof value === "object") {
    return Object.keys(value).sort((a, b) => a.localeCompare(b)).reduce((acc, key) => {
      acc[key] = normalizeValue(value[key]);
      return acc;
    }, {});
  }
  return value;
}
function computeChecksum(payload) {
  return crypto.createHash("md5").update(JSON.stringify(normalizeValue(payload))).digest("hex");
}
function systemEntityIds() {
  const ids = /* @__PURE__ */ new Set();
  const GeneratedEntities = getEntityIds();
  for (const moduleEntities of Object.values(GeneratedEntities)) {
    for (const id of Object.values(moduleEntities)) {
      ids.add(id);
    }
  }
  return ids;
}
function buildAggregatedConfigs() {
  const map = /* @__PURE__ */ new Map();
  const modules = getModules();
  for (const mod of modules) {
    const moduleId = mod.id;
    const entitySpecs = mod.customEntities ?? [];
    for (const spec of entitySpecs) {
      const existing = map.get(spec.id) ?? { entityId: spec.id, moduleIds: /* @__PURE__ */ new Set(), spec: void 0, fieldSets: [] };
      existing.moduleIds.add(moduleId);
      if (!existing.spec) existing.spec = spec;
      map.set(spec.id, existing);
    }
    const fieldSets = mod.customFieldSets ?? [];
    for (const set of fieldSets) {
      const existing = map.get(set.entity) ?? { entityId: set.entity, moduleIds: /* @__PURE__ */ new Set(), spec: void 0, fieldSets: [] };
      existing.moduleIds.add(moduleId);
      existing.fieldSets.push(set);
      map.set(set.entity, existing);
    }
  }
  return Array.from(map.values());
}
function resolveFields(fieldSets) {
  const byKey = /* @__PURE__ */ new Map();
  for (const set of fieldSets) {
    for (const field of set.fields ?? []) {
      byKey.set(field.key, { ...field });
    }
  }
  return Array.from(byKey.values()).sort((a, b) => a.key.localeCompare(b.key));
}
function normalizeField(field) {
  const payload = {
    key: field.key,
    kind: field.kind
  };
  for (const key of FIELD_DETAIL_KEYS) {
    const value = field[key];
    if (value !== void 0) payload[key] = value;
  }
  if (field.id) payload.id = field.id;
  return payload;
}
function buildChecksumPayload(params) {
  const { entityId, scope, spec, global, fields } = params;
  return {
    entityId,
    scope,
    global,
    label: spec?.label ?? null,
    description: spec?.description ?? null,
    labelField: spec?.labelField ?? null,
    defaultEditor: spec?.defaultEditor ?? null,
    showInSidebar: spec?.showInSidebar ?? false,
    fields: fields.map((f) => normalizeField(f))
  };
}
async function installCustomEntitiesFromModules(em, cache, options = {}) {
  const requestedEntityIds = options.entityIds ? new Set(options.entityIds.filter((id) => typeof id === "string" && id.length > 0)) : null;
  const aggregated = buildAggregatedConfigs().filter((entry) => {
    if (!requestedEntityIds) return true;
    return requestedEntityIds.has(entry.entityId);
  });
  const systemIds = systemEntityIds();
  const includeGlobal = options.includeGlobal !== false;
  const dryRun = options.dryRun === true;
  const createOnly = options.createOnly === true;
  const force = options.force === true;
  const logger = options.logger;
  let tenantIds;
  if (options.tenantIds !== void 0) {
    tenantIds = Array.from(new Set(options.tenantIds.filter((id) => typeof id === "string" && id.length > 0)));
  }
  const ensureTenantIds = async () => {
    if (tenantIds !== void 0) return tenantIds;
    const rows = await em.find(Tenant, { deletedAt: null }, { fields: ["id"] });
    tenantIds = rows.map((row) => row.id);
    return tenantIds ?? [];
  };
  let processed = 0;
  let synchronized = 0;
  let skipped = 0;
  let fieldChanges = 0;
  for (const entry of aggregated) {
    const { entityId } = entry;
    const spec = entry.spec;
    const fields = resolveFields(entry.fieldSets);
    const isSystem = systemIds.has(entityId);
    const registerEntity = !isSystem && !!spec;
    const isGlobal = spec?.global === true;
    const scopes = [];
    if (isGlobal) {
      if (includeGlobal) scopes.push({ tenantId: null });
    } else {
      const ids = await ensureTenantIds();
      if (!ids.length) {
        skipped++;
        continue;
      }
      for (const tenantId of ids) scopes.push({ tenantId });
    }
    if (!scopes.length && fields.length === 0 && !registerEntity) {
      skipped++;
      continue;
    }
    for (const scope of scopes) {
      processed++;
      const scopeKey = scope.tenantId ? `tenant:${scope.tenantId}` : "global";
      const checksumPayload = buildChecksumPayload({
        entityId,
        scope,
        spec,
        global: isGlobal,
        fields
      });
      const checksum = computeChecksum(checksumPayload);
      const cacheKey = `custom-entities:v1:${scopeKey}:${entityId}`;
      if (!dryRun && !force && cache) {
        try {
          const cached = await cache.get(cacheKey);
          if (typeof cached === "string" && cached === checksum) {
            skipped++;
            continue;
          }
        } catch {
        }
      }
      let entityResult = "unchanged";
      if (registerEntity) {
        entityResult = await upsertCustomEntity(em, entityId, {
          label: spec?.label ?? entityId,
          description: spec?.description ?? null,
          organizationId: null,
          tenantId: scope.tenantId,
          showInSidebar: spec?.showInSidebar ?? false,
          labelField: spec?.labelField ?? null,
          defaultEditor: spec?.defaultEditor ?? null,
          isActive: true,
          dryRun,
          createOnly
        });
      }
      let fieldResult = { created: 0, updated: 0, unchanged: 0 };
      if (fields.length) {
        fieldResult = await ensureCustomFieldDefinitions(
          em,
          [{ entity: entityId, fields }],
          { organizationId: null, tenantId: scope.tenantId, dryRun, createOnly }
        );
      }
      const changed = entityResult !== "unchanged" || fieldResult.created > 0 || fieldResult.updated > 0;
      const shouldInvalidateDefinitions = !dryRun && (changed || force);
      if (shouldInvalidateDefinitions) {
        await invalidateDefinitionsCache(cache ?? void 0, {
          tenantId: scope.tenantId,
          organizationId: null,
          entityIds: [entityId]
        });
      }
      if (changed) {
        synchronized++;
        fieldChanges += fieldResult.created + fieldResult.updated;
        if (!dryRun && cache) {
          try {
            await cache.set(cacheKey, checksum, { tags: [`custom-entity:${entityId}`, `custom-entity-scope:${scopeKey}`] });
          } catch {
          }
        }
        if (logger) {
          const parts = [];
          if (entityResult !== "unchanged") parts.push(`entity ${entityResult}`);
          if (fieldResult.created || fieldResult.updated) {
            parts.push(`fields +${fieldResult.created} / ~${fieldResult.updated}`);
          }
          logger(`Synced ${entityId} for ${scopeKey}${parts.length ? ` (${parts.join(", ")})` : ""}`);
        }
      } else {
        skipped++;
        if (!dryRun && cache) {
          try {
            await cache.set(cacheKey, checksum, { tags: [`custom-entity:${entityId}`, `custom-entity-scope:${scopeKey}`] });
          } catch {
          }
        }
      }
    }
  }
  return { processed, synchronized, skipped, fieldChanges };
}
function listCustomEntityIds() {
  return buildAggregatedConfigs().map((entry) => entry.entityId);
}
function getAggregatedCustomEntityConfigs() {
  return buildAggregatedConfigs();
}
export {
  getAggregatedCustomEntityConfigs,
  installCustomEntitiesFromModules,
  listCustomEntityIds
};
//# sourceMappingURL=install-from-ce.js.map
