import { randomUUID } from "node:crypto";
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { InboxEmail, InboxProposal, InboxProposalAction, InboxDiscrepancy, InboxSettings } from "../data/entities.js";
import { matchContacts } from "../lib/contactMatcher.js";
import { buildExtractionSystemPrompt, buildExtractionUserPrompt } from "../lib/extractionPrompt.js";
import { REQUIRED_FEATURES_MAP } from "../lib/constants.js";
import { fetchCatalogProductsForExtraction } from "../lib/catalogLookup.js";
import { enrichOrderPayload } from "../lib/payloadEnrichment.js";
import { validatePrices } from "../lib/priceValidator.js";
import { extractParticipantsFromThread } from "../lib/emailParser.js";
import { runExtractionWithConfiguredProvider } from "../lib/llmProvider.js";
import { safeParsePayloadJson } from "../lib/validation.js";
import { htmlToPlainText } from "../lib/htmlToPlainText.js";
import { runWithCacheTenant } from "@open-mercato/cache";
import { emitInboxOpsEvent } from "../events.js";
import { createMessageRecordForEmail } from "../lib/messagesIntegration.js";
import { resolveCache, invalidateCountsCache } from "../lib/cache.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("inbox_ops").child({ component: "extraction-worker" });
const SYSTEM_USER_ID = "00000000-0000-0000-0000-000000000000";
const metadata = {
  event: "inbox_ops.email.received",
  persistent: true,
  id: "inbox_ops:extraction-worker"
};
function tryResolve(ctx, name) {
  try {
    return ctx.resolve(name);
  } catch {
    logger.debug("Optional dependency not available", { name });
    return void 0;
  }
}
function resolveEntityClasses(ctx) {
  return {
    customerEntity: tryResolve(ctx, "CustomerEntity"),
    catalogProduct: tryResolve(ctx, "CatalogProduct"),
    catalogProductPrice: tryResolve(ctx, "CatalogProductPrice"),
    salesOrder: tryResolve(ctx, "SalesOrder"),
    salesChannel: tryResolve(ctx, "SalesChannel"),
    customerAddress: tryResolve(ctx, "CustomerAddress")
  };
}
function createDiscrepancy(em, proposalId, allActions, input, scope) {
  return em.create(InboxDiscrepancy, {
    proposalId,
    actionId: input.actionIndex !== void 0 && allActions[input.actionIndex] ? allActions[input.actionIndex].id : null,
    type: input.type,
    severity: input.severity,
    description: input.description,
    expectedValue: input.expectedValue || null,
    foundValue: input.foundValue || null,
    resolved: false,
    organizationId: scope.organizationId,
    tenantId: scope.tenantId
  });
}
async function handle(payload, ctx) {
  const em = ctx.resolve("em").fork();
  const entityClasses = resolveEntityClasses(ctx);
  const claimed = await em.nativeUpdate(
    InboxEmail,
    { id: payload.emailId, status: "received" },
    { status: "processing" }
  );
  if (claimed === 0) return;
  const email = await findOneWithDecryption(
    em,
    InboxEmail,
    { id: payload.emailId },
    void 0,
    { tenantId: payload.tenantId, organizationId: payload.organizationId }
  );
  if (!email) {
    logger.error("Email not found", { emailId: payload.emailId });
    return;
  }
  try {
    const scope = {
      tenantId: email.tenantId,
      organizationId: email.organizationId
    };
    const settings = await findOneWithDecryption(em, InboxSettings, { organizationId: scope.organizationId, tenantId: scope.tenantId, deletedAt: null }, void 0, scope);
    const workingLanguage = settings?.workingLanguage || "en";
    const fullText = buildFullTextForExtraction(email);
    if (!fullText.trim()) {
      email.status = "failed";
      email.processingError = "No text content found in email";
      await em.flush();
      return;
    }
    const threadParticipants = extractParticipantsFromThread(email);
    const contactMatches = await matchContacts(
      em,
      threadParticipants,
      scope,
      entityClasses.customerEntity ? { customerEntityClass: entityClasses.customerEntity } : void 0
    );
    const catalogProducts = await fetchCatalogProductsForExtraction(
      em,
      scope,
      entityClasses.catalogProduct && entityClasses.catalogProductPrice ? { catalogProductClass: entityClasses.catalogProduct, catalogProductPriceClass: entityClasses.catalogProductPrice } : void 0
    );
    const maxTextSize = parseInt(process.env.INBOX_OPS_MAX_TEXT_SIZE || "204800", 10);
    const truncatedText = fullText.slice(0, maxTextSize);
    const systemPrompt = await buildExtractionSystemPrompt(contactMatches, catalogProducts, void 0, workingLanguage);
    const userPrompt = buildExtractionUserPrompt(truncatedText);
    let extractionResult;
    let tokensUsed = 0;
    let modelUsed = "";
    try {
      const timeoutMsRaw = Number.parseInt(process.env.INBOX_OPS_LLM_TIMEOUT_MS || "90000", 10);
      const timeoutMs = Number.isFinite(timeoutMsRaw) && timeoutMsRaw > 0 ? timeoutMsRaw : 9e4;
      const extraction = await runExtractionWithConfiguredProvider({
        systemPrompt,
        userPrompt,
        modelOverride: process.env.INBOX_OPS_LLM_MODEL,
        timeoutMs
      });
      extractionResult = extraction.object;
      tokensUsed = extraction.totalTokens;
      modelUsed = extraction.modelWithProvider;
    } catch (llmError) {
      email.status = "failed";
      email.processingError = `LLM extraction failed: ${llmError instanceof Error ? llmError.message : String(llmError)}`;
      await em.flush();
      try {
        await emitInboxOpsEvent("inbox_ops.email.failed", {
          emailId: email.id,
          tenantId: email.tenantId,
          organizationId: email.organizationId,
          error: email.processingError
        });
      } catch (eventError) {
        logger.error("Failed to emit email.failed event", { err: eventError });
      }
      return;
    }
    const confidenceThresholdRaw = Number.parseFloat(process.env.INBOX_OPS_CONFIDENCE_THRESHOLD || "0.5");
    const confidenceThreshold = Number.isFinite(confidenceThresholdRaw) ? Math.min(Math.max(confidenceThresholdRaw, 0), 1) : 0.5;
    const requiresReview = extractionResult.confidence < confidenceThreshold;
    const orderActions = extractionResult.proposedActions.map((action, index) => ({
      ...action,
      payload: safeParsePayloadJson(action.payloadJson),
      index
    })).filter((a) => a.actionType === "create_order" || a.actionType === "create_quote");
    const catalogPricingService = tryResolve(ctx, "catalogPricingService");
    const priceDiscrepancies = await validatePrices(
      em,
      orderActions,
      scope,
      entityClasses.catalogProductPrice && catalogPricingService ? { catalogProductPriceClass: entityClasses.catalogProductPrice, catalogPricingService } : void 0
    );
    const duplicateOrderDiscrepancies = await detectDuplicateOrders(em, orderActions, scope, entityClasses.salesOrder);
    const headerEmails = new Set(contactMatches.map((m) => m.participant.email.toLowerCase()));
    const llmOnlyParticipants = extractionResult.participants.filter((p) => p.email && !headerEmails.has(p.email.toLowerCase())).map((p) => ({ name: p.name, email: p.email, role: p.role || "unknown" }));
    if (llmOnlyParticipants.length > 0) {
      const llmContactMatches = await matchContacts(
        em,
        llmOnlyParticipants,
        scope,
        entityClasses.customerEntity ? { customerEntityClass: entityClasses.customerEntity } : void 0
      );
      contactMatches.push(...llmContactMatches);
    }
    const enrichedParticipants = extractionResult.participants.map((p) => {
      const match = contactMatches.find(
        (m) => m.participant.email.toLowerCase() === p.email.toLowerCase()
      );
      return {
        ...p,
        matchedContactId: match?.match?.contactId || null,
        matchedContactType: match?.match?.contactType || null,
        matchConfidence: match?.match?.confidence
      };
    });
    const possiblyIncomplete = extractionResult.possiblyIncomplete || detectPartialForward(email);
    const enrichmentDiscrepancies = [];
    for (const [actionIndex, action] of extractionResult.proposedActions.entries()) {
      if (action.actionType === "create_order" || action.actionType === "create_quote") {
        const parsedPayload = safeParsePayloadJson(action.payloadJson);
        normalizeOrderPayloadFields(parsedPayload);
        const { payload: enriched, warnings } = await enrichOrderPayload(parsedPayload, {
          em,
          scope,
          contactMatches,
          catalogProducts,
          senderEmail: email.forwardedByAddress,
          salesChannelClass: entityClasses.salesChannel,
          customerAddressClass: entityClasses.customerAddress
        });
        action.payloadJson = JSON.stringify(enriched);
        for (const warning of warnings) {
          if (warning === "no_channel_resolved") {
            enrichmentDiscrepancies.push({
              actionIndex,
              type: "other",
              severity: "error",
              description: "inbox_ops.discrepancy.desc.no_channel"
            });
          } else if (warning === "no_currency_resolved") {
            enrichmentDiscrepancies.push({
              actionIndex,
              type: "currency_mismatch",
              severity: "warning",
              description: "inbox_ops.discrepancy.desc.no_currency"
            });
          }
        }
      }
    }
    const participantEmailMap = buildParticipantEmailMap(contactMatches, extractionResult.participants);
    enrichCreateContactEmails(extractionResult.proposedActions, participantEmailMap);
    enrichDraftReplyTargets(extractionResult.draftReplies, participantEmailMap);
    const productNotFoundDiscrepancies = [];
    const autoProductActions = [];
    const seenProductNames = /* @__PURE__ */ new Set();
    for (const [actionIndex, action] of extractionResult.proposedActions.entries()) {
      if (action.actionType !== "create_order" && action.actionType !== "create_quote") continue;
      const parsedPayload = safeParsePayloadJson(action.payloadJson);
      const lineItems = Array.isArray(parsedPayload.lineItems) ? parsedPayload.lineItems : [];
      for (const item of lineItems) {
        if (!item.productId) {
          const productName = typeof item.productName === "string" ? item.productName : typeof item.description === "string" ? item.description : "Unknown";
          productNotFoundDiscrepancies.push({
            actionIndex,
            type: "product_not_found",
            severity: "error",
            description: "inbox_ops.discrepancy.desc.product_not_matched",
            foundValue: productName
          });
          const nameKey = productName.toLowerCase().trim();
          if (nameKey && nameKey !== "unknown" && !seenProductNames.has(nameKey)) {
            seenProductNames.add(nameKey);
            const sku = typeof item.sku === "string" ? item.sku : void 0;
            const unitPrice = typeof item.unitPrice === "string" ? item.unitPrice : void 0;
            const currencyCode = typeof parsedPayload.currencyCode === "string" ? parsedPayload.currencyCode : void 0;
            autoProductActions.push({
              actionType: "create_product",
              description: "inbox_ops.action.desc.create_product",
              confidence: 0.9,
              requiredFeature: REQUIRED_FEATURES_MAP.create_product,
              payloadJson: JSON.stringify({
                title: productName,
                ...sku && { sku },
                ...unitPrice && { unitPrice },
                ...currencyCode && { currencyCode },
                kind: "product"
              })
            });
          }
        }
      }
    }
    const proposalId = randomUUID();
    const proposal = em.create(InboxProposal, {
      id: proposalId,
      inboxEmailId: email.id,
      summary: extractionResult.summary,
      category: extractionResult.category || null,
      participants: enrichedParticipants,
      confidence: String(extractionResult.confidence.toFixed(2)),
      detectedLanguage: extractionResult.detectedLanguage || email.detectedLanguage,
      status: "pending",
      possiblyIncomplete,
      llmModel: modelUsed,
      llmTokensUsed: tokensUsed,
      workingLanguage,
      organizationId: email.organizationId,
      tenantId: email.tenantId
    });
    em.persist(proposal);
    const autoContactActions = buildContactActionsForUnmatchedParticipants(
      contactMatches,
      extractionResult.proposedActions,
      email.toAddress,
      email.forwardedByAddress
    );
    const llmContactActions = buildContactActionsForUnmatchedLlmParticipants(
      enrichedParticipants,
      contactMatches,
      extractionResult.proposedActions,
      autoContactActions,
      email.toAddress
    );
    autoContactActions.push(...llmContactActions);
    const autoLinkActions = buildLinkContactActionsForMatchedParticipants(
      contactMatches,
      extractionResult.proposedActions,
      email.toAddress
    );
    const dedupedProposedActions = deduplicateCompanyActions([
      ...autoContactActions,
      ...autoLinkActions,
      ...autoProductActions,
      ...extractionResult.proposedActions
    ]);
    const combinedProposedActions = dedupedProposedActions;
    const allActions = [
      ...combinedProposedActions.map((action, index) => {
        const parsedPayload = safeParsePayloadJson(action.payloadJson);
        return em.create(InboxProposalAction, {
          id: randomUUID(),
          proposalId,
          sortOrder: index,
          actionType: action.actionType,
          description: action.description,
          payload: parsedPayload,
          status: "pending",
          confidence: String(action.confidence.toFixed(2)),
          requiredFeature: action.requiredFeature || REQUIRED_FEATURES_MAP[action.actionType] || null,
          organizationId: email.organizationId,
          tenantId: email.tenantId
        });
      }),
      ...extractionResult.draftReplies.map(
        (reply, index) => em.create(InboxProposalAction, {
          id: randomUUID(),
          proposalId,
          sortOrder: combinedProposedActions.length + index,
          actionType: "draft_reply",
          description: "inbox_ops.action.desc.draft_reply",
          payload: {
            to: reply.to,
            toName: reply.toName,
            subject: reply.subject,
            body: reply.body,
            context: reply.context,
            replyTo: email.replyTo,
            inReplyToMessageId: email.messageId,
            references: email.emailReferences
          },
          status: "pending",
          confidence: String(extractionResult.confidence.toFixed(2)),
          requiredFeature: "inbox_ops.replies.send",
          organizationId: email.organizationId,
          tenantId: email.tenantId
        })
      )
    ];
    allActions.forEach((a) => em.persist(a));
    const actionIndexOffset = autoContactActions.length + autoLinkActions.length + autoProductActions.length;
    const offsetIndex = (d) => d.actionIndex !== void 0 ? { ...d, actionIndex: d.actionIndex + actionIndexOffset } : d;
    const allDiscrepancies = [
      ...extractionResult.discrepancies.map(
        (d) => createDiscrepancy(em, proposalId, allActions, offsetIndex(d), scope)
      ),
      ...priceDiscrepancies.map(
        (d) => createDiscrepancy(em, proposalId, allActions, offsetIndex(d), scope)
      ),
      ...duplicateOrderDiscrepancies.map(
        (d) => createDiscrepancy(em, proposalId, allActions, offsetIndex(d), scope)
      ),
      ...productNotFoundDiscrepancies.map(
        (d) => createDiscrepancy(em, proposalId, allActions, offsetIndex(d), scope)
      ),
      ...enrichmentDiscrepancies.map(
        (d) => createDiscrepancy(em, proposalId, allActions, offsetIndex(d), scope)
      )
    ];
    const contactDiscrepancyEmails = /* @__PURE__ */ new Set();
    for (const match of contactMatches) {
      if (!match.match && match.participant.email) {
        const emailLower = match.participant.email.toLowerCase();
        contactDiscrepancyEmails.add(emailLower);
        allDiscrepancies.push(
          createDiscrepancy(em, proposalId, allActions, {
            type: "unknown_contact",
            severity: "warning",
            description: "inbox_ops.discrepancy.desc.no_matching_contact",
            foundValue: `${match.participant.name} (${match.participant.email})`
          }, scope)
        );
      }
    }
    for (const participant of enrichedParticipants) {
      if (participant.matchedContactId) continue;
      const emailLower = (participant.email || "").toLowerCase();
      if (!emailLower || contactDiscrepancyEmails.has(emailLower)) continue;
      contactDiscrepancyEmails.add(emailLower);
      allDiscrepancies.push(
        createDiscrepancy(em, proposalId, allActions, {
          type: "unknown_contact",
          severity: "warning",
          description: "inbox_ops.discrepancy.desc.no_matching_contact",
          foundValue: `${participant.name} (${participant.email})`
        }, scope)
      );
    }
    const matchedEmails = new Set(
      contactMatches.filter((m) => m.match?.contactId).map((m) => m.participant.email.toLowerCase())
    );
    for (const [actionIndex, action] of allActions.entries()) {
      if (action.actionType !== "draft_reply") continue;
      const payload2 = action.payload;
      const toEmail = typeof payload2?.to === "string" ? payload2.to.trim().toLowerCase() : "";
      if (toEmail && !matchedEmails.has(toEmail)) {
        allDiscrepancies.push(
          createDiscrepancy(em, proposalId, allActions, {
            actionIndex,
            type: "unknown_contact",
            severity: "error",
            description: "inbox_ops.discrepancy.desc.draft_reply_no_contact",
            foundValue: toEmail
          }, scope)
        );
      }
    }
    allDiscrepancies.forEach((d) => em.persist(d));
    email.status = requiresReview ? "needs_review" : "processed";
    email.detectedLanguage = extractionResult.detectedLanguage || email.detectedLanguage;
    await em.flush();
    try {
      const cache = resolveCache(ctx);
      await runWithCacheTenant(email.tenantId, () => invalidateCountsCache(cache, email.tenantId));
    } catch (cacheErr) {
      logger.warn("Cache invalidation failed (non-fatal)", { err: cacheErr });
    }
    try {
      await createMessageRecordForEmail(
        {
          id: email.id,
          subject: email.subject,
          cleanedText: email.cleanedText,
          rawText: email.rawText,
          forwardedByAddress: email.forwardedByAddress,
          forwardedByName: email.forwardedByName,
          status: email.status
        },
        {
          container: ctx,
          scope: {
            tenantId: email.tenantId,
            organizationId: email.organizationId,
            userId: SYSTEM_USER_ID
          }
        }
      );
    } catch (msgErr) {
      logger.error("Messages integration failed (non-fatal)", { err: msgErr });
    }
    try {
      await emitInboxOpsEvent("inbox_ops.email.processed", {
        emailId: email.id,
        tenantId: email.tenantId,
        organizationId: email.organizationId
      });
      await emitInboxOpsEvent("inbox_ops.proposal.created", {
        proposalId: proposal.id,
        emailId: email.id,
        tenantId: email.tenantId,
        organizationId: email.organizationId,
        actionCount: allActions.length,
        discrepancyCount: allDiscrepancies.length,
        confidence: proposal.confidence,
        summary: proposal.summary
      });
    } catch (eventError) {
      logger.error("Failed to emit events", { err: eventError });
    }
  } catch (err) {
    email.status = "failed";
    email.processingError = err instanceof Error ? err.message : String(err);
    await em.flush();
    try {
      await emitInboxOpsEvent("inbox_ops.email.failed", {
        emailId: email.id,
        tenantId: email.tenantId,
        organizationId: email.organizationId,
        error: email.processingError
      });
    } catch (eventError) {
      logger.error("Failed to emit email.failed event", { err: eventError });
    }
    logger.error("Extraction failed", { err });
  }
}
function normalizeOrderPayloadFields(payload) {
  const lineItems = Array.isArray(payload.lineItems) ? payload.lineItems : [];
  for (const item of lineItems) {
    if (!item.productName && typeof item.description === "string") {
      item.productName = item.description;
    }
    if (typeof item.quantity === "number") {
      item.quantity = String(item.quantity);
    }
    if (typeof item.unitPrice === "number") {
      item.unitPrice = String(item.unitPrice);
    }
  }
}
function buildContactActionsForUnmatchedParticipants(contactMatches, existingActions, inboxAddress, forwardedByAddress) {
  const alreadyProposed = new Set(
    existingActions.filter((a) => a.actionType === "create_contact").map((a) => {
      const p = safeParsePayloadJson(a.payloadJson);
      return typeof p.email === "string" ? p.email.toLowerCase() : "";
    }).filter(Boolean)
  );
  const inboxLower = (inboxAddress || "").toLowerCase();
  const forwardedByLower = (forwardedByAddress || "").toLowerCase();
  const systemPatterns = ["noreply", "no-reply", "donotreply", "mailer-daemon", "postmaster"];
  return contactMatches.filter((m) => {
    if (m.match?.contactId) return false;
    const emailLower = m.participant.email.toLowerCase();
    if (!emailLower || !emailLower.includes("@")) return false;
    if (alreadyProposed.has(emailLower)) return false;
    if (emailLower === inboxLower) return false;
    if (forwardedByLower && emailLower === forwardedByLower) return false;
    return !systemPatterns.some((p) => emailLower.includes(p));
  }).map((m) => ({
    actionType: "create_contact",
    description: "inbox_ops.action.desc.create_contact",
    confidence: 0.9,
    requiredFeature: REQUIRED_FEATURES_MAP.create_contact,
    payloadJson: JSON.stringify({
      type: "person",
      name: m.participant.name,
      email: m.participant.email,
      source: "inbox_ops"
    })
  }));
}
function buildLinkContactActionsForMatchedParticipants(contactMatches, existingActions, inboxAddress) {
  const alreadyProposed = new Set(
    existingActions.filter((a) => a.actionType === "link_contact").map((a) => {
      const p = safeParsePayloadJson(a.payloadJson);
      const email = typeof p.emailAddress === "string" ? p.emailAddress : typeof p.email === "string" ? p.email : "";
      return email.toLowerCase();
    }).filter(Boolean)
  );
  const inboxLower = (inboxAddress || "").toLowerCase();
  const systemPatterns = ["noreply", "no-reply", "donotreply", "mailer-daemon", "postmaster"];
  return contactMatches.filter((m) => {
    if (!m.match?.contactId) return false;
    const emailLower = m.participant.email.toLowerCase();
    if (alreadyProposed.has(emailLower)) return false;
    if (emailLower === inboxLower) return false;
    return !systemPatterns.some((p) => emailLower.includes(p));
  }).map((m) => ({
    actionType: "link_contact",
    description: "inbox_ops.action.desc.link_contact",
    confidence: 0.95,
    requiredFeature: REQUIRED_FEATURES_MAP.link_contact,
    payloadJson: JSON.stringify({
      emailAddress: m.participant.email,
      contactId: m.match.contactId,
      contactType: m.match.contactType || "person",
      contactName: m.participant.name
    })
  }));
}
function buildContactActionsForUnmatchedLlmParticipants(enrichedParticipants, contactMatches, existingActions, alreadyAutoCreated, inboxAddress) {
  const headerEmails = new Set(
    contactMatches.map((m) => m.participant.email.toLowerCase())
  );
  const alreadyProposed = /* @__PURE__ */ new Set([
    ...existingActions.filter((a) => a.actionType === "create_contact").map((a) => {
      const p = safeParsePayloadJson(a.payloadJson);
      return typeof p.email === "string" ? p.email.toLowerCase() : "";
    }).filter(Boolean),
    ...alreadyAutoCreated.map((a) => {
      const p = safeParsePayloadJson(a.payloadJson);
      return typeof p.email === "string" ? p.email.toLowerCase() : "";
    }).filter(Boolean)
  ]);
  const inboxLower = (inboxAddress || "").toLowerCase();
  const systemPatterns = ["noreply", "no-reply", "donotreply", "mailer-daemon", "postmaster"];
  return enrichedParticipants.filter((p) => {
    if (p.matchedContactId) return false;
    const emailLower = (p.email || "").toLowerCase();
    if (!emailLower) return false;
    if (headerEmails.has(emailLower)) return false;
    if (alreadyProposed.has(emailLower)) return false;
    if (emailLower === inboxLower) return false;
    return !systemPatterns.some((pat) => emailLower.includes(pat));
  }).map((p) => ({
    actionType: "create_contact",
    description: "inbox_ops.action.desc.create_contact",
    confidence: 0.85,
    requiredFeature: REQUIRED_FEATURES_MAP.create_contact,
    payloadJson: JSON.stringify({
      type: "person",
      name: p.name,
      email: p.email,
      source: "inbox_ops"
    })
  }));
}
async function detectDuplicateOrders(em, orderActions, scope, salesOrderClass) {
  if (!salesOrderClass) return [];
  const discrepancies = [];
  for (const action of orderActions) {
    if (action.actionType !== "create_order") continue;
    const customerReference = typeof action.payload.customerReference === "string" ? action.payload.customerReference.trim() : null;
    if (!customerReference) continue;
    try {
      const existing = await findOneWithDecryption(
        em,
        salesOrderClass,
        {
          customerReference,
          tenantId: scope.tenantId,
          organizationId: scope.organizationId,
          deletedAt: null
        },
        void 0,
        scope
      );
      if (existing) {
        discrepancies.push({
          type: "duplicate_order",
          severity: "error",
          description: "inbox_ops.discrepancy.desc.duplicate_order_reference",
          expectedValue: existing.orderNumber || existing.id,
          foundValue: customerReference,
          actionIndex: action.index
        });
      }
    } catch {
    }
  }
  return discrepancies;
}
function detectPartialForward(email) {
  const subject = email.subject || "";
  const hasReOrFw = /^(RE|FW|Fwd):/i.test(subject);
  const messageCount = email.threadMessages?.length || 0;
  return hasReOrFw && messageCount < 2;
}
function buildParticipantEmailMap(contactMatches, llmParticipants) {
  const nameToEmail = /* @__PURE__ */ new Map();
  for (const m of contactMatches) {
    if (m.participant.name && m.participant.email) {
      nameToEmail.set(m.participant.name.trim().toLowerCase(), m.participant.email.trim().toLowerCase());
    }
  }
  for (const p of llmParticipants) {
    if (p.name && p.email) {
      const key = p.name.trim().toLowerCase();
      if (!nameToEmail.has(key)) {
        nameToEmail.set(key, p.email.trim().toLowerCase());
      }
    }
  }
  return nameToEmail;
}
function enrichCreateContactEmails(actions, participantEmailMap) {
  for (const action of actions) {
    if (action.actionType !== "create_contact") continue;
    const payload = safeParsePayloadJson(action.payloadJson);
    if (payload.email) continue;
    const name = typeof payload.name === "string" ? payload.name.trim() : "";
    if (!name) continue;
    const email = participantEmailMap.get(name.toLowerCase()) ?? findPartialNameMatch(name, participantEmailMap);
    if (email) {
      payload.email = email;
      action.payloadJson = JSON.stringify(payload);
    }
  }
}
function enrichDraftReplyTargets(draftReplies, participantEmailMap) {
  const knownEmails = new Set(participantEmailMap.values());
  for (const reply of draftReplies) {
    const toEmail = reply.to.trim().toLowerCase();
    if (knownEmails.has(toEmail)) continue;
    const toName = (reply.toName || "").trim();
    if (!toName) continue;
    const correctedEmail = participantEmailMap.get(toName.toLowerCase()) ?? findPartialNameMatch(toName, participantEmailMap);
    if (correctedEmail) {
      reply.to = correctedEmail;
    }
  }
}
function buildFullTextForExtraction(email) {
  let text = email.rawText || "";
  if (!text && email.rawHtml) {
    text = htmlToPlainText(email.rawHtml);
  }
  return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\t/g, " ").replace(/ {2,}/g, " ").replace(/\n{3,}/g, "\n\n").trim();
}
function deduplicateCompanyActions(actions) {
  const personCompanyNames = /* @__PURE__ */ new Set();
  for (const action of actions) {
    if (action.actionType !== "create_contact") continue;
    const payload = safeParsePayloadJson(action.payloadJson);
    if (payload.type === "person" && typeof payload.companyName === "string" && payload.companyName.trim()) {
      personCompanyNames.add(payload.companyName.trim().toLowerCase());
    }
  }
  if (personCompanyNames.size === 0) return actions;
  return actions.filter((action) => {
    if (action.actionType !== "create_contact") return true;
    const payload = safeParsePayloadJson(action.payloadJson);
    if (payload.type !== "company") return true;
    const companyName = typeof payload.name === "string" ? payload.name.trim().toLowerCase() : "";
    return !companyName || !personCompanyNames.has(companyName);
  });
}
function findPartialNameMatch(name, map) {
  const lower = name.toLowerCase();
  const parts = lower.split(/\s*[\/,]\s*/).map((p) => p.trim()).filter(Boolean);
  for (const part of parts) {
    const match = map.get(part);
    if (match) return match;
  }
  for (const [mapName, mapEmail] of map) {
    if (lower.includes(mapName) || mapName.includes(lower)) {
      return mapEmail;
    }
    for (const part of parts) {
      if (part.includes(mapName) || mapName.includes(part)) {
        return mapEmail;
      }
    }
  }
  return void 0;
}
export {
  handle as default,
  metadata
};
//# sourceMappingURL=extractionWorker.js.map
