// The CRM's InboxAdapter — the SDK <Inbox> rendered over the be-crm data door
// (crm.inbox.* + crm.mail.*). Folds mail threads into the unified inbox exactly as the old
// inbox-api did; the CRM keeps auth/tenancy server-side.

import type {
  InboxAdapter,
  InboxItem,
  InboxState,
  MailAttachment,
  MailMessage,
  MailPerson,
} from "@insignia/iios-messaging-ui";
import type { DataDoor } from "./crm-messaging-adapter";

const MAX_ATTACHMENT_BYTES = 26 * 1024 * 1024; // matches IIOS's cap

// Some types (notably .md) have no OS-registered MIME, so the browser reports an empty file.type.
const EXT_MIME: Record<string, string> = {
  md: "text/markdown", markdown: "text/markdown", html: "text/html", htm: "text/html", txt: "text/plain", csv: "text/csv",
};
function mimeForFile(file: File): string {
  if (file.type) return file.type;
  const ext = file.name.toLowerCase().split(".").pop() ?? "";
  return EXT_MIME[ext] ?? "application/octet-stream";
}

interface InboxItemDTO {
  id: string; kind: string; state: InboxState; title: string; summary?: string; priority: string; threadId?: string; createdAt: string;
}
interface MailThreadDTO { threadId: string; subject: string | null; participants: string[]; unread: number; lastMessage?: string; lastAt?: string }
interface MailMessageDTO {
  interactionId: string; actorId: string | null; kind: string; occurredAt: string;
  html: string | null; text: string | null;
  attachment: { contentRef: string; mimeType: string; sizeBytes: number; filename: string | null } | null;
}
interface DirectoryDTO { id: string; displayName: string; kind: "staff" | "customer" }

const escapeHtml = (s: string): string => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");

export class CrmInboxAdapter implements InboxAdapter {
  constructor(private readonly sdk: DataDoor) {}

  async listInbox(state?: InboxState): Promise<InboxItem[]> {
    const showMail = !state || state === "OPEN";
    const [items, mail] = await Promise.all([
      this.sdk.query<InboxItemDTO[]>("crm.inbox.list", state ? { state } : {}),
      showMail ? this.sdk.query<MailThreadDTO[]>("crm.mail.list", {}) : Promise.resolve([] as MailThreadDTO[]),
    ]);
    const mailItems: InboxItem[] = mail.map((t) => ({
      id: `mail:${t.threadId}`,
      kind: "MAIL",
      state: "OPEN",
      title: t.subject || "(no subject)",
      ...(t.lastMessage ? { summary: t.lastMessage } : {}),
      priority: t.unread > 0 ? "HIGH" : "LOW",
      threadId: t.threadId,
      createdAt: t.lastAt ?? "",
    }));
    return [...mailItems, ...items].sort((a, b) => (b.createdAt ?? "").localeCompare(a.createdAt ?? ""));
  }

  async transition(id: string, state: InboxState): Promise<void> {
    await this.sdk.command("crm.inbox.transition", { id, state });
  }

  async mailHistory(threadId: string): Promise<MailMessage[]> {
    const rows = await this.sdk.query<MailMessageDTO[]>("crm.mail.history", { threadId });
    return rows.map((m) => ({
      id: m.interactionId,
      actorId: m.actorId,
      kind: m.kind,
      at: m.occurredAt,
      html: m.html,
      text: m.text,
      attachment: m.attachment,
    }));
  }

  async mailReply(threadId: string, content: string, attachment?: MailAttachment): Promise<void> {
    await this.sdk.command("crm.mail.reply", {
      threadId,
      content,
      ...(attachment ? { attachment: { filename: attachment.filename ?? "attachment", contentRef: attachment.contentRef, mimeType: attachment.mimeType, sizeBytes: attachment.sizeBytes } } : {}),
    });
  }

  async uploadAttachment(file: File): Promise<MailAttachment> {
    if (file.size > MAX_ATTACHMENT_BYTES) throw new Error("File is too large (max 25 MB).");
    const mime = mimeForFile(file);
    const { objectKey, uploadUrl } = await this.sdk.command<{ objectKey: string; uploadUrl: string }>("crm.media.presignUpload", { mime, sizeBytes: file.size });
    const res = await fetch(uploadUrl, { method: "PUT", body: file });
    if (!res.ok) throw new Error(`Upload failed (${res.status}).`);
    return { contentRef: objectKey, mimeType: mime, sizeBytes: file.size, filename: file.name };
  }

  async downloadAttachment(attachment: MailAttachment): Promise<string> {
    const { url } = await this.sdk.command<{ url: string }>("crm.media.presignDownload", {
      contentRef: attachment.contentRef,
      ...(attachment.mimeType ? { mime: attachment.mimeType } : {}),
    });
    return url;
  }

  async directory(): Promise<MailPerson[]> {
    const rows = await this.sdk.query<DirectoryDTO[]>("crm.messenger.directory", { kind: "all", limit: 100 });
    return rows.map((d) => ({ id: d.id, name: d.displayName, kind: d.kind }));
  }

  async composeInternal(recipientUserId: string, subject: string, text: string, attachments?: MailAttachment[]): Promise<void> {
    await this.sdk.command("crm.mail.internal", { recipientUserId, subject, text, html: `<p>${escapeHtml(text)}</p>`, ...attachmentsVar(attachments) });
  }

  async composeExternal(target: string, subject: string, text: string, attachments?: MailAttachment[]): Promise<void> {
    await this.sdk.command("crm.mail.send", { target, subject, text, html: `<p>${escapeHtml(text)}</p>`, ...attachmentsVar(attachments) });
  }
}

function attachmentsVar(attachments?: MailAttachment[]): { attachments?: Array<{ filename: string; contentRef: string; mimeType: string; sizeBytes: number }> } {
  if (!attachments || attachments.length === 0) return {};
  return { attachments: attachments.map((a) => ({ filename: a.filename ?? "attachment", contentRef: a.contentRef, mimeType: a.mimeType, sizeBytes: a.sizeBytes })) };
}
