import { parseBooleanFromUnknown } from "@open-mercato/shared/lib/boolean";
import {
  CustomerInteraction,
  CustomerTodoLink
} from "../data/entities.js";
import {
  CUSTOMER_INTERACTION_TASK_SOURCE,
  EXAMPLE_TODO_SOURCE,
  resolveExampleIntegrationHref
} from "./interactionCompatibility.js";
import { hydrateCanonicalInteractions, loadCustomerSummaries } from "./interactionReadModel.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("customers");
function resolveLegacyTodoSource(source) {
  return typeof source === "string" && source.trim().length > 0 ? source : EXAMPLE_TODO_SOURCE;
}
function extractTodoTitle(record) {
  const candidates = ["title", "subject", "name", "summary", "text", "description"];
  for (const key of candidates) {
    const value = record[key];
    if (typeof value === "string" && value.trim().length > 0) {
      return value.trim();
    }
  }
  return null;
}
function parseNumber(value) {
  if (typeof value === "number" && Number.isFinite(value)) return value;
  if (typeof value === "string") {
    const trimmed = value.trim();
    if (!trimmed.length) return null;
    const parsed = Number(trimmed);
    if (!Number.isNaN(parsed)) return parsed;
  }
  return null;
}
function parseDateValue(value) {
  if (value instanceof Date) {
    return Number.isNaN(value.getTime()) ? null : value.toISOString();
  }
  if (typeof value === "string") {
    const trimmed = value.trim();
    if (!trimmed.length) return null;
    const parsed = new Date(trimmed);
    return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
  }
  return null;
}
function readCustomField(record, key) {
  const custom = record.custom ?? record.customFields ?? record.cf;
  if (custom && typeof custom === "object") {
    const bucket = custom;
    if (key in bucket) return bucket[key];
  }
  return void 0;
}
function normalizeTodoSearch(value) {
  if (typeof value !== "string") return null;
  const trimmed = value.trim().toLowerCase();
  return trimmed.length > 0 ? trimmed : null;
}
function sortTodoRows(rows) {
  return [...rows].sort((left, right) => {
    const leftTime = new Date(left.createdAt).getTime();
    const rightTime = new Date(right.createdAt).getTime();
    if (leftTime === rightTime) {
      return right.id.localeCompare(left.id);
    }
    return rightTime - leftTime;
  });
}
function filterTodoRows(rows, search) {
  if (!search) return rows;
  return rows.filter((row) => {
    const haystack = [
      row.customer.displayName,
      row.todoTitle,
      row.todoDescription
    ].filter((value) => typeof value === "string" && value.trim().length > 0).join(" ").toLowerCase();
    return haystack.includes(search);
  });
}
function paginateTodoRows(rows, page, pageSize, exportAll) {
  const total = rows.length;
  if (exportAll) {
    return {
      items: rows,
      total,
      page: 1,
      pageSize: total,
      totalPages: 1
    };
  }
  const start = (page - 1) * pageSize;
  return {
    items: rows.slice(start, start + pageSize),
    total,
    page,
    pageSize,
    totalPages: Math.max(1, Math.ceil(total / pageSize))
  };
}
async function resolveLegacyTodoDetails(queryEngine, links, tenantId, organizationIds) {
  const details = /* @__PURE__ */ new Map();
  if (!links.length || !tenantId) return details;
  const scopedOrganizationIds = organizationIds.filter(
    (value) => typeof value === "string" && value.trim().length > 0
  );
  const idsBySource = /* @__PURE__ */ new Map();
  for (const link of links) {
    const source = resolveLegacyTodoSource(link.todoSource);
    const id = typeof link.todoId === "string" && link.todoId.trim().length > 0 ? link.todoId : String(link.todoId ?? "");
    if (!id) continue;
    if (!idsBySource.has(source)) idsBySource.set(source, /* @__PURE__ */ new Set());
    idsBySource.get(source).add(id);
  }
  for (const [source, idSet] of idsBySource.entries()) {
    const ids = Array.from(idSet);
    if (!ids.length) continue;
    try {
      const result = await queryEngine.query(source, {
        tenantId,
        organizationIds: scopedOrganizationIds.length > 0 ? scopedOrganizationIds : void 0,
        filters: { id: { $in: ids } },
        includeCustomFields: ["priority", "due_at", "severity", "description"],
        page: { page: 1, pageSize: Math.max(ids.length, 1) }
      });
      for (const item of result.items ?? []) {
        if (!item || typeof item !== "object") continue;
        const record = item;
        const rawId = typeof record.id === "string" && record.id.trim().length > 0 ? record.id : String(record.id ?? "");
        if (!rawId) continue;
        const isDone = (() => {
          const direct = parseBooleanFromUnknown(record.is_done);
          if (direct !== null) return direct;
          const custom = parseBooleanFromUnknown(readCustomField(record, "is_done"));
          if (custom !== null) return custom;
          const generic = parseBooleanFromUnknown(record.isDone);
          if (generic !== null) return generic;
          return parseBooleanFromUnknown(readCustomField(record, "isDone"));
        })();
        const priority = (() => {
          const candidates = [
            record["cf:priority"],
            record["cf_priority"],
            record.priority,
            readCustomField(record, "priority")
          ];
          for (const candidate of candidates) {
            const parsed = parseNumber(candidate);
            if (parsed !== null) return parsed;
          }
          return null;
        })();
        const severity = (() => {
          const candidates = [
            record["cf:severity"],
            record["cf_severity"],
            record.severity,
            readCustomField(record, "severity")
          ];
          for (const candidate of candidates) {
            if (typeof candidate === "string" && candidate.trim().length > 0) {
              return candidate.trim();
            }
          }
          return null;
        })();
        const description = (() => {
          const candidates = [
            record.description,
            record["cf:description"],
            record["cf_description"],
            readCustomField(record, "description")
          ];
          for (const candidate of candidates) {
            if (typeof candidate === "string" && candidate.trim().length > 0) {
              return candidate.trim();
            }
          }
          return null;
        })();
        const dueAt = (() => {
          const candidates = [
            record.due_at,
            record.dueAt,
            record["cf:due_at"],
            record["cf_due_at"],
            readCustomField(record, "due_at"),
            readCustomField(record, "dueAt")
          ];
          for (const candidate of candidates) {
            const parsed = parseDateValue(candidate);
            if (parsed) return parsed;
          }
          return null;
        })();
        const organizationId = (() => {
          const candidates = [
            record.organization_id,
            record.organizationId,
            record["cf:organization_id"],
            record["cf_organization_id"],
            readCustomField(record, "organization_id"),
            readCustomField(record, "organizationId")
          ];
          for (const candidate of candidates) {
            if (typeof candidate === "string" && candidate.trim().length > 0) {
              return candidate.trim();
            }
          }
          return null;
        })();
        const customValues = {};
        const assignCustomValue = (key, value) => {
          const trimmedKey = key.trim();
          if (!trimmedKey.length) return;
          customValues[trimmedKey] = value === void 0 ? null : value;
        };
        for (const [rawKey, rawValue] of Object.entries(record)) {
          if (rawKey.startsWith("cf:")) {
            assignCustomValue(rawKey.slice(3), rawValue);
          } else if (rawKey.startsWith("cf_")) {
            assignCustomValue(rawKey.slice(3), rawValue);
          }
        }
        const nestedCustom = record.custom ?? record.customFields ?? record.cf;
        if (nestedCustom && typeof nestedCustom === "object") {
          for (const [key, value] of Object.entries(nestedCustom)) {
            assignCustomValue(key, value);
          }
        }
        const updatedAt = (() => {
          const candidates = [record.updated_at, record.updatedAt];
          for (const candidate of candidates) {
            const parsed = parseDateValue(candidate);
            if (parsed) return parsed;
          }
          return null;
        })();
        details.set(`${source}:${rawId}`, {
          title: extractTodoTitle(record),
          isDone,
          priority,
          severity,
          description,
          dueAt,
          organizationId,
          updatedAt,
          customValues: Object.keys(customValues).length > 0 ? customValues : null
        });
      }
    } catch (err) {
      logger.warn("Failed to resolve todo details", { component: "todoCompatibility", source, err });
      continue;
    }
  }
  return details;
}
async function listLegacyTodoRows(em, queryEngine, tenantId, organizationIds, entityId, options) {
  const where = { tenantId };
  if (organizationIds && organizationIds.length > 0) {
    where.organizationId = { $in: organizationIds };
  }
  if (entityId) {
    where.entity = entityId;
  }
  const findOptions = {
    populate: ["entity"],
    orderBy: { createdAt: "desc" }
  };
  if (typeof options?.limit === "number" && Number.isFinite(options.limit) && options.limit > 0) {
    findOptions.limit = options.limit;
  }
  const links = await em.find(CustomerTodoLink, where, findOptions);
  const details = await resolveLegacyTodoDetails(
    queryEngine,
    links,
    tenantId,
    organizationIds ?? []
  );
  return links.map((link) => {
    const source = resolveLegacyTodoSource(link.todoSource);
    return mapLegacyTodoLinkToRow(
      link,
      details.get(`${source}:${link.todoId}`) ?? null
    );
  });
}
async function listCanonicalTodoRows(em, container, auth, selectedOrganizationId, organizationIds, options) {
  const where = {
    tenantId: auth.tenantId,
    interactionType: "task"
  };
  if (!options?.includeDeleted) {
    where.deletedAt = null;
  }
  if (organizationIds && organizationIds.length > 0) {
    where.organizationId = { $in: organizationIds };
  }
  if (options?.entityId) {
    where.entity = options.entityId;
  }
  if (options?.source) {
    where.source = Array.isArray(options.source) ? { $in: options.source } : options.source;
  }
  const trimmedSearch = typeof options?.searchText === "string" ? options.searchText.trim() : "";
  if (trimmedSearch.length > 0) {
    const pattern = `%${trimmedSearch}%`;
    where.$or = [
      { title: { $ilike: pattern } },
      { body: { $ilike: pattern } }
    ];
  }
  const findOptions = {
    orderBy: { createdAt: "desc" }
  };
  const pagination = options?.pagination ?? null;
  if (pagination) {
    findOptions.offset = Math.max(0, (pagination.page - 1) * pagination.pageSize);
    findOptions.limit = pagination.pageSize;
  } else if (typeof options?.limit === "number" && Number.isFinite(options.limit) && options.limit > 0) {
    findOptions.limit = options.limit;
  }
  let interactions;
  let total;
  if (pagination) {
    const [rows, count] = await em.findAndCount(CustomerInteraction, where, findOptions);
    interactions = rows;
    total = count;
  } else {
    interactions = await em.find(CustomerInteraction, where, findOptions);
    total = interactions.filter((interaction) => !interaction.deletedAt).length;
  }
  const activeInteractions = interactions.filter((interaction) => !interaction.deletedAt);
  const groups = /* @__PURE__ */ new Map();
  for (const interaction of activeInteractions) {
    const organizationId = typeof interaction.organizationId === "string" && interaction.organizationId.trim().length > 0 ? interaction.organizationId : selectedOrganizationId ?? "";
    const bucket = groups.get(organizationId);
    if (bucket) {
      bucket.push(interaction);
    } else {
      groups.set(organizationId, [interaction]);
    }
  }
  const rowByInteractionId = /* @__PURE__ */ new Map();
  for (const [groupOrganizationId, groupedInteractions] of groups.entries()) {
    const scopedOrganizationId = groupOrganizationId.length > 0 ? groupOrganizationId : null;
    const hydrated = await hydrateCanonicalInteractions({
      em,
      container,
      auth: {
        ...auth,
        orgId: auth.orgId ?? null
      },
      selectedOrganizationId: scopedOrganizationId,
      interactions: groupedInteractions
    });
    const customerIds = Array.from(
      new Set(
        hydrated.map((interaction) => interaction.entityId ?? null).filter((value) => !!value)
      )
    );
    const customerSummaries = await loadCustomerSummaries(
      em,
      customerIds,
      auth.tenantId,
      scopedOrganizationId
    );
    for (const interaction of hydrated) {
      rowByInteractionId.set(
        interaction.id,
        mapInteractionRecordToTodoRow(
          interaction,
          interaction.entityId ? customerSummaries.get(interaction.entityId) ?? null : null
        )
      );
    }
  }
  return {
    items: activeInteractions.map((interaction) => rowByInteractionId.get(interaction.id) ?? null).filter((row) => !!row),
    bridgeIds: new Set(interactions.map((interaction) => interaction.id)),
    total
  };
}
function mapLegacyTodoLinkToRow(link, detail, customerOverride) {
  const entity = customerOverride ?? {
    id: typeof link.entity === "string" ? null : link.entity.id,
    displayName: typeof link.entity === "string" ? null : link.entity.displayName ?? null,
    kind: typeof link.entity === "string" ? null : link.entity.kind ?? null
  };
  return {
    id: link.id,
    todoId: link.todoId,
    todoSource: resolveLegacyTodoSource(link.todoSource),
    todoTitle: detail?.title ?? null,
    todoIsDone: detail?.isDone ?? null,
    todoPriority: detail?.priority ?? null,
    todoSeverity: detail?.severity ?? null,
    todoDescription: detail?.description ?? null,
    todoDueAt: detail?.dueAt ?? null,
    todoCustomValues: detail?.customValues ?? null,
    todoOrganizationId: detail?.organizationId ?? link.organizationId ?? null,
    todoUpdatedAt: detail?.updatedAt ?? null,
    organizationId: link.organizationId,
    tenantId: link.tenantId,
    createdAt: link.createdAt.toISOString(),
    _integrations: void 0,
    customer: entity
  };
}
function mapInteractionRecordToTodoRow(interaction, customer, options) {
  const customValues = { ...interaction.customValues ?? {} };
  if (interaction.priority !== void 0 && customValues.priority === void 0) {
    customValues.priority = interaction.priority ?? null;
  }
  if (interaction.body !== void 0 && customValues.description === void 0) {
    customValues.description = interaction.body ?? null;
  }
  if (interaction.scheduledAt !== void 0 && customValues.due_at === void 0) {
    customValues.due_at = interaction.scheduledAt ?? null;
  }
  return {
    id: typeof options?.rowId === "string" && options.rowId.trim().length > 0 ? options.rowId : interaction.id,
    todoId: interaction.id,
    todoSource: typeof options?.todoSource === "string" && options.todoSource.trim().length > 0 ? options.todoSource : CUSTOMER_INTERACTION_TASK_SOURCE,
    todoTitle: interaction.title ?? null,
    todoIsDone: interaction.status === "done",
    todoPriority: interaction.priority ?? null,
    todoSeverity: typeof customValues.severity === "string" && customValues.severity.trim().length > 0 ? customValues.severity.trim() : null,
    todoDescription: interaction.body ?? null,
    todoDueAt: interaction.scheduledAt ?? null,
    todoCustomValues: Object.keys(customValues).length > 0 ? customValues : null,
    todoOrganizationId: interaction.organizationId ?? null,
    todoUpdatedAt: interaction.updatedAt ?? null,
    organizationId: interaction.organizationId ?? "",
    tenantId: interaction.tenantId ?? "",
    createdAt: interaction.createdAt,
    externalHref: resolveExampleIntegrationHref(interaction),
    _integrations: interaction._integrations ?? void 0,
    customer: customer ?? {
      id: interaction.entityId ?? null,
      displayName: null,
      kind: null
    }
  };
}
export {
  filterTodoRows,
  listCanonicalTodoRows,
  listLegacyTodoRows,
  mapInteractionRecordToTodoRow,
  mapLegacyTodoLinkToRow,
  normalizeTodoSearch,
  paginateTodoRows,
  resolveLegacyTodoDetails,
  sortTodoRows
};
//# sourceMappingURL=todoCompatibility.js.map
