import { resolveEntityTableName } from "@open-mercato/shared/lib/query/engine";
import { resolveTenantEncryptionService } from "@open-mercato/shared/lib/encryption/customFieldValues";
import { decryptIndexDocForSearch, encryptIndexDocForStorage } from "@open-mercato/shared/lib/encryption/indexDoc";
import { sql } from "kysely";
import { replaceSearchTokensForRecord, deleteSearchTokensForRecord } from "./search-tokens.js";
import { attachAggregateSearchField } from "./document.js";
async function buildIndexDoc(em, params) {
  const db = em.getKysely();
  const baseTable = resolveEntityTableName(em, params.entityType);
  const baseRow = await db.selectFrom(baseTable).selectAll().where("id", "=", params.recordId).executeTakeFirst();
  if (!baseRow) return null;
  const docSources = [];
  let parentEntityRow = null;
  if (params.entityType === "customers:customer_person_profile" || params.entityType === "customers:customer_company_profile") {
    const entityId = baseRow.entity_id ?? baseRow.entityId;
    if (entityId) {
      const entityRow = await db.selectFrom("customer_entities").selectAll().where("id", "=", entityId).executeTakeFirst();
      if (entityRow) {
        docSources.push(entityRow);
        parentEntityRow = entityRow;
      }
    }
  }
  void parentEntityRow;
  let doc = {};
  docSources.push(baseRow);
  for (const source of docSources) {
    for (const [k, v] of Object.entries(source)) doc[k] = v;
  }
  let cfQuery = db.selectFrom("custom_field_values").select([
    "field_key",
    "value_text",
    "value_multiline",
    "value_int",
    "value_float",
    "value_bool"
  ]).where("entity_id", "=", params.entityType).where("record_id", "=", String(params.recordId));
  if (params.organizationId != null) {
    cfQuery = cfQuery.where((eb) => eb.or([
      eb("organization_id", "=", params.organizationId),
      eb("organization_id", "is", null)
    ]));
  } else {
    cfQuery = cfQuery.where("organization_id", "is", null);
  }
  if (params.tenantId != null) {
    cfQuery = cfQuery.where((eb) => eb.or([
      eb("tenant_id", "=", params.tenantId),
      eb("tenant_id", "is", null)
    ]));
  } else {
    cfQuery = cfQuery.where("tenant_id", "is", null);
  }
  const cfRows = await cfQuery.execute();
  const cfMap = {};
  for (const r of cfRows) {
    const key = String(r.field_key);
    const cfKey = `cf:${key}`;
    const val = r.value_bool ?? r.value_int ?? r.value_float ?? r.value_text ?? r.value_multiline ?? null;
    if (!cfMap[cfKey]) cfMap[cfKey] = [];
    cfMap[cfKey].push(val);
  }
  for (const [key, arr] of Object.entries(cfMap)) {
    doc[key] = arr.length <= 1 ? arr[0] : arr;
  }
  try {
    const translationRow = await db.selectFrom("entity_translations").select(["translations"]).where("entity_type", "=", params.entityType).where("entity_id", "=", String(params.recordId)).where(sql`tenant_id is not distinct from ${params.tenantId ?? null}`).where(sql`organization_id is not distinct from ${params.organizationId ?? null}`).executeTakeFirst();
    if (translationRow?.translations && typeof translationRow.translations === "object") {
      for (const [locale, fields] of Object.entries(translationRow.translations)) {
        if (!fields || typeof fields !== "object") continue;
        for (const [field, value] of Object.entries(fields)) {
          if (typeof value === "string" && value.length > 0) {
            doc[`l10n:${locale}:${field}`] = value;
          }
        }
      }
    }
  } catch {
  }
  try {
    doc = attachAggregateSearchField(doc);
    const encryption = resolveTenantEncryptionService(em);
    doc = await encryptIndexDocForStorage(
      params.entityType,
      doc,
      { tenantId: params.tenantId ?? null, organizationId: params.organizationId ?? null },
      encryption
    );
  } catch {
  }
  return doc;
}
function scopeEntityIndexes(q, args) {
  let chain = q.where("entity_type", "=", args.entityType);
  chain = chain.where("entity_id", "=", String(args.recordId));
  chain = args.organizationId == null ? chain.where("organization_id", "is", null) : chain.where("organization_id", "=", args.organizationId);
  chain = chain.where(sql`tenant_id is not distinct from ${args.tenantId ?? null}`);
  return chain;
}
async function upsertIndexRow(em, args) {
  const db = em.getKysely();
  const existing = await scopeEntityIndexes(
    db.selectFrom("entity_indexes").select(["id", "deleted_at"]),
    args
  ).executeTakeFirst();
  const existed = !!existing;
  const wasDeleted = !!existing && existing.deleted_at != null;
  const doc = await buildIndexDoc(em, args);
  if (!doc) {
    if (!args.deferSearchTokens) {
      try {
        await reindexSearchTokensForRecord(em, {
          entityType: args.entityType,
          recordId: args.recordId,
          organizationId: args.organizationId ?? null,
          tenantId: args.tenantId ?? null,
          doc: null
        });
      } catch {
      }
    }
    if (existed) {
      await scopeEntityIndexes(
        db.deleteFrom("entity_indexes"),
        args
      ).execute();
    }
    return { doc: null, existed, wasDeleted, created: false, revived: false };
  }
  const payload = {
    entity_type: args.entityType,
    entity_id: String(args.recordId),
    organization_id: args.organizationId ?? null,
    tenant_id: args.tenantId ?? null,
    doc: sql`${JSON.stringify(doc)}::jsonb`,
    index_version: 1,
    updated_at: sql`now()`,
    deleted_at: null
  };
  try {
    await db.insertInto("entity_indexes").values({ ...payload, created_at: sql`now()` }).onConflict((oc) => oc.columns(["entity_type", "entity_id", "organization_id_coalesced"]).doUpdateSet({
      tenant_id: args.tenantId ?? null,
      doc: sql`${JSON.stringify(doc)}::jsonb`,
      index_version: 1,
      updated_at: sql`now()`,
      deleted_at: null
    })).execute();
  } catch {
    const updated = await scopeEntityIndexes(
      db.updateTable("entity_indexes").set(payload),
      args
    ).executeTakeFirst();
    if (!updated || Number(updated.numUpdatedRows ?? 0) === 0) {
      try {
        await db.insertInto("entity_indexes").values({ ...payload, created_at: sql`now()` }).execute();
      } catch {
      }
    }
  }
  const created = !existed;
  const revived = existed && wasDeleted;
  if (!args.deferSearchTokens) {
    try {
      await reindexSearchTokensForRecord(em, {
        entityType: args.entityType,
        recordId: args.recordId,
        organizationId: args.organizationId ?? null,
        tenantId: args.tenantId ?? null,
        doc,
        searchTokenDoc: args.searchTokenDoc ?? null
      });
    } catch {
    }
  }
  return { doc, existed, wasDeleted, created, revived };
}
async function reindexSearchTokensForRecord(em, args) {
  const db = em.getKysely();
  if (!args.doc) {
    await deleteSearchTokensForRecord(db, {
      entityType: args.entityType,
      recordId: args.recordId,
      organizationId: args.organizationId ?? null,
      tenantId: args.tenantId ?? null
    });
    return;
  }
  const tokenDoc = args.searchTokenDoc ?? (() => {
    const encryption = resolveTenantEncryptionService(em);
    const dekKeyCache = /* @__PURE__ */ new Map();
    return decryptIndexDocForSearch(
      args.entityType,
      args.doc,
      { tenantId: args.tenantId ?? null, organizationId: args.organizationId ?? null },
      encryption,
      dekKeyCache
    );
  })();
  await replaceSearchTokensForRecord(db, {
    entityType: args.entityType,
    recordId: args.recordId,
    organizationId: args.organizationId ?? null,
    tenantId: args.tenantId ?? null,
    doc: await tokenDoc
  });
}
async function markDeleted(em, args) {
  const db = em.getKysely();
  const existing = await scopeEntityIndexes(
    db.selectFrom("entity_indexes").select(["deleted_at"]),
    args
  ).executeTakeFirst();
  const wasActive = !!existing && existing.deleted_at == null;
  if (existing) {
    try {
      await deleteSearchTokensForRecord(db, {
        entityType: args.entityType,
        recordId: args.recordId,
        organizationId: args.organizationId ?? null,
        tenantId: args.tenantId ?? null
      });
    } catch {
    }
    await scopeEntityIndexes(
      db.deleteFrom("entity_indexes"),
      args
    ).execute();
  }
  return { wasActive };
}
export {
  buildIndexDoc,
  markDeleted,
  reindexSearchTokensForRecord,
  upsertIndexRow
};
//# sourceMappingURL=indexer.js.map
