import crypto from "node:crypto";
import { EMAIL_MAX_ATTACHMENT_BYTES } from "./email-capabilities.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("communication_channels").child({ component: "email-mime" });
const TOTAL_INBOUND_ATTACHMENTS_MAX_BYTES = EMAIL_MAX_ATTACHMENT_BYTES * 2;
function stringOrUndefined(value) {
  if (typeof value !== "string") return void 0;
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : void 0;
}
function toAddressList(value) {
  if (Array.isArray(value)) return value.map((v) => String(v).trim()).filter((v) => v.length > 0);
  if (typeof value === "string") {
    return value.split(/[,;]\s*/).map((v) => v.trim()).filter((v) => v.length > 0);
  }
  return [];
}
function referencesFromMeta(value) {
  if (!Array.isArray(value)) return void 0;
  return value.filter((entry) => typeof entry === "string");
}
function stripTagBlocks(html, tag) {
  const blockPattern = new RegExp(
    `<${tag}\\b[^>]*(?:>[\\s\\S]*?(?:<\\/${tag}[^>]*>|$)|$)`,
    "gi"
  );
  let previous;
  let current = html;
  do {
    previous = current;
    current = current.replace(blockPattern, " ");
  } while (current !== previous);
  return current;
}
function stripHtmlComments(html) {
  return html.replace(/<!--[\s\S]*?(?:-->|$)/g, " ");
}
const BASIC_HTML_ENTITIES = {
  "&nbsp;": " ",
  "&lt;": "<",
  "&gt;": ">",
  "&quot;": '"',
  "&amp;": "&"
};
function decodeBasicEntities(input) {
  return input.replace(
    /&(?:nbsp|lt|gt|quot|amp);/gi,
    (match) => BASIC_HTML_ENTITIES[match.toLowerCase()] ?? match
  );
}
function stripRemainingTags(html) {
  let previous;
  let current = html;
  do {
    previous = current;
    current = current.replace(/<[^>]*>/g, "");
  } while (current !== previous);
  return current;
}
function htmlToText(html) {
  const decoded = decodeBasicEntities(html);
  const stripped = stripTagBlocks(stripTagBlocks(stripHtmlComments(decoded), "style"), "script").replace(/<br\s*\/?>(?=\s*)/gi, "\n").replace(/<\/p\s*>/gi, "\n\n");
  return stripRemainingTags(stripped).replace(/\n{3,}/g, "\n\n").trim();
}
function escapeQuotes(value) {
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
}
function sanitizeHeaderValue(value) {
  return value.replace(/[\r\n\t]+/g, " ").trim();
}
function ensureBrackets(value) {
  const trimmed = sanitizeHeaderValue(value);
  if (trimmed.startsWith("<") && trimmed.endsWith(">")) return trimmed;
  return `<${trimmed}>`;
}
function isPureAscii(value) {
  return /^[\x00-\x7F]*$/.test(value);
}
function encodeHeaderWord(value) {
  if (isPureAscii(value)) return value;
  return `=?utf-8?B?${Buffer.from(value, "utf-8").toString("base64")}?=`;
}
function encodeAddressHeaderWord(value) {
  if (isPureAscii(value)) return value;
  const match = value.match(/^(.*?)(\s*<[^>]*>)\s*$/);
  if (match) {
    const [, displayPart, addrPart] = match;
    const displayName = displayPart.replace(/^"|"$/g, "").trim();
    if (!displayName) return value;
    return `${encodeHeaderWord(displayName)}${addrPart}`;
  }
  return encodeHeaderWord(value);
}
function generateMessageId(fromAddress, fallbackDomain = "localhost") {
  const domain = fromAddress.split("@")[1] ?? fallbackDomain;
  return `<${crypto.randomUUID()}@${domain}>`;
}
function encodeBodyPart(content) {
  if (isPureAscii(content)) return { cte: "7bit", body: content };
  const base64 = Buffer.from(content, "utf-8").toString("base64");
  const wrapped = base64.match(/.{1,76}/g)?.join("\r\n") ?? base64;
  return { cte: "base64", body: wrapped };
}
function assembleRfc2822(input) {
  const boundary = `omc_${crypto.randomUUID()}`;
  const headers = [];
  headers.push(`From: ${encodeAddressHeaderWord(sanitizeHeaderValue(input.from))}`);
  headers.push(`To: ${input.to.map((value) => encodeAddressHeaderWord(sanitizeHeaderValue(value))).join(", ")}`);
  if (input.cc.length) headers.push(`Cc: ${input.cc.map((value) => encodeAddressHeaderWord(sanitizeHeaderValue(value))).join(", ")}`);
  if (input.bcc.length) headers.push(`Bcc: ${input.bcc.map((value) => encodeAddressHeaderWord(sanitizeHeaderValue(value))).join(", ")}`);
  if (input.subject) headers.push(`Subject: ${encodeHeaderWord(sanitizeHeaderValue(input.subject))}`);
  headers.push(`Message-ID: ${ensureBrackets(input.messageId)}`);
  if (input.inReplyTo) headers.push(`In-Reply-To: ${ensureBrackets(input.inReplyTo)}`);
  if (input.references && input.references.length) {
    headers.push(`References: ${input.references.map(ensureBrackets).join(" ")}`);
  }
  headers.push("MIME-Version: 1.0");
  headers.push(`Date: ${(/* @__PURE__ */ new Date()).toUTCString()}`);
  if (input.html && input.text) {
    headers.push(`Content-Type: multipart/alternative; boundary="${boundary}"`);
    const textPart2 = encodeBodyPart(input.text);
    const htmlPart = encodeBodyPart(input.html);
    const body = [
      "",
      `--${boundary}`,
      "Content-Type: text/plain; charset=utf-8",
      `Content-Transfer-Encoding: ${textPart2.cte}`,
      "",
      textPart2.body,
      `--${boundary}`,
      "Content-Type: text/html; charset=utf-8",
      `Content-Transfer-Encoding: ${htmlPart.cte}`,
      "",
      htmlPart.body,
      `--${boundary}--`,
      ""
    ].join("\r\n");
    return Buffer.from(headers.join("\r\n") + body, "utf-8");
  }
  if (input.html) {
    const htmlPart = encodeBodyPart(input.html);
    headers.push("Content-Type: text/html; charset=utf-8");
    headers.push(`Content-Transfer-Encoding: ${htmlPart.cte}`);
    return Buffer.from(headers.join("\r\n") + "\r\n\r\n" + htmlPart.body, "utf-8");
  }
  const textPart = encodeBodyPart(input.text ?? "");
  headers.push("Content-Type: text/plain; charset=utf-8");
  headers.push(`Content-Transfer-Encoding: ${textPart.cte}`);
  return Buffer.from(headers.join("\r\n") + "\r\n\r\n" + textPart.body, "utf-8");
}
function stripBrackets(value) {
  if (!value) return void 0;
  const trimmed = value.trim();
  if (!trimmed) return void 0;
  if (trimmed.startsWith("<") && trimmed.endsWith(">")) return trimmed.slice(1, -1);
  return trimmed;
}
function parseReferences(value) {
  if (!value) return [];
  if (Array.isArray(value)) return value.map((v) => stripBrackets(v)).filter((v) => Boolean(v));
  return value.split(/\s+/).map((segment) => stripBrackets(segment)).filter((segment) => Boolean(segment));
}
function normalizeAttachments(attachments) {
  const out = [];
  let totalBytes = 0;
  for (const att of attachments) {
    if (!att.content) continue;
    const byteLength = att.content.byteLength;
    if (byteLength > EMAIL_MAX_ATTACHMENT_BYTES) {
      logger.warn("dropping oversized inbound attachment", { fileName: att.filename ?? "attachment", bytes: byteLength, maxBytes: EMAIL_MAX_ATTACHMENT_BYTES });
      continue;
    }
    if (totalBytes + byteLength > TOTAL_INBOUND_ATTACHMENTS_MAX_BYTES) {
      logger.warn("aggregate inbound attachment size exceeded; dropping remaining attachments", { maxBytes: TOTAL_INBOUND_ATTACHMENTS_MAX_BYTES });
      break;
    }
    totalBytes += byteLength;
    const base64 = Buffer.isBuffer(att.content) ? att.content.toString("base64") : Buffer.from(att.content).toString("base64");
    out.push({
      url: `data:${att.contentType ?? "application/octet-stream"};base64,${base64}`,
      mimeType: att.contentType ?? "application/octet-stream",
      fileName: att.filename ?? "attachment",
      fileSize: att.size,
      inline: Boolean(att.contentDisposition && /inline/i.test(att.contentDisposition)) || Boolean(att.cid)
    });
  }
  return out;
}
function stringifyHeaderValue(value) {
  if (typeof value === "string") return value;
  if (Array.isArray(value)) return value.map((v) => String(v)).join(", ");
  if (value instanceof Date) return value.toISOString();
  if (value && typeof value === "object") {
    try {
      return JSON.stringify(value);
    } catch {
      return void 0;
    }
  }
  if (value === void 0 || value === null) return void 0;
  return String(value);
}
function extractHeaders(headers) {
  if (!headers) return {};
  const out = {};
  if (headers instanceof Map) {
    for (const [key, value] of headers.entries()) {
      const stringified = stringifyHeaderValue(value);
      if (stringified !== void 0) out[String(key)] = stringified;
    }
    return out;
  }
  for (const [key, value] of Object.entries(headers)) {
    const stringified = stringifyHeaderValue(value);
    if (stringified !== void 0) out[key] = stringified;
  }
  return out;
}
function normalizeMimeInbound(options) {
  const { parsed } = options;
  const messageId = stripBrackets(parsed.messageId) ?? options.fallbackMessageId;
  const inReplyTo = stripBrackets(parsed.inReplyTo);
  const references = parseReferences(parsed.references);
  const conversationId = options.resolveConversationId({ messageId, references });
  const from = parsed.from?.value?.[0];
  const subject = parsed.subject?.trim() || void 0;
  const bodyHtml = parsed.html && typeof parsed.html === "string" ? parsed.html : void 0;
  const bodyText = typeof parsed.text === "string" ? parsed.text : void 0;
  const body = bodyHtml ?? bodyText ?? "";
  const bodyFormat = bodyHtml ? "html" : "text";
  const attachments = normalizeAttachments(parsed.attachments ?? []);
  const channelMetadata = {
    ...options.channelMetadata?.(parsed) ?? {},
    messageId,
    inReplyTo: inReplyTo ?? null,
    references,
    headers: extractHeaders(parsed.headers)
  };
  const channelPayload = {
    subject,
    from: from ? { address: from.address, name: from.name } : null,
    to: parsed.to?.value ?? [],
    cc: parsed.cc?.value ?? [],
    bcc: parsed.bcc?.value ?? [],
    html: bodyHtml ?? null,
    text: bodyText ?? null,
    messageId,
    ...options.channelPayload?.(parsed) ?? {}
  };
  return {
    externalMessageId: messageId,
    externalConversationId: conversationId,
    senderIdentifier: from?.address ?? options.accountIdentifier,
    senderDisplayName: from?.name?.trim() || void 0,
    subject,
    body,
    bodyFormat,
    attachments,
    timestamp: parsed.date ? new Date(parsed.date) : options.fallbackDate ?? /* @__PURE__ */ new Date(),
    replyToExternalId: inReplyTo ?? void 0,
    channelPayload,
    channelContentType: "email/mime",
    channelMetadata
  };
}
function encodeCursor(state) {
  return Buffer.from(JSON.stringify(state)).toString("base64");
}
function decodeCursor(value) {
  if (!value) return null;
  try {
    return JSON.parse(Buffer.from(value, "base64").toString("utf-8"));
  } catch {
    return null;
  }
}
export {
  assembleRfc2822,
  decodeCursor,
  encodeAddressHeaderWord,
  encodeCursor,
  encodeHeaderWord,
  ensureBrackets,
  escapeQuotes,
  extractHeaders,
  generateMessageId,
  htmlToText,
  normalizeAttachments,
  normalizeMimeInbound,
  parseReferences,
  referencesFromMeta,
  sanitizeHeaderValue,
  stringOrUndefined,
  stripBrackets,
  toAddressList
};
//# sourceMappingURL=email-mime.js.map
