import { useEffect, useMemo, useState, type ReactNode } from "react";
import { Archive, Link2, LockKeyhole, Paperclip, X } from "lucide-react";
import { i18nMessage, usePlatformLanguage } from "../i18n/LanguageContext";
import SegmentedControl from "./SegmentedControl";

export type MessageDisplayField = {
  label: string;
  value?: ReactNode | null;
};

export type MessageDisplayAttachment = {
  id?: string | null;
  filename?: string | null;
  contentType?: string | null;
  sizeBytes?: number | null;
  detail?: string | null;
  linkedToCampaign?: boolean | null;
  archiveGroup?: string | null;
  archiveLabel?: string | null;
  protected?: boolean | null;
  protectionNote?: string | null;
};

type MessageDisplayBodyMode = "text" | "html";

const MESSAGE_PREVIEW_CONTENT_SECURITY_POLICY = [
  "default-src 'none'",
  "base-uri 'none'",
  "connect-src 'none'",
  "font-src 'none'",
  "form-action 'none'",
  "frame-src 'none'",
  "img-src data: cid:",
  "manifest-src 'none'",
  "media-src 'none'",
  "object-src 'none'",
  "prefetch-src 'none'",
  "script-src 'none'",
  "style-src 'unsafe-inline'",
  "worker-src 'none'"
].join("; ");

const URL_ATTRIBUTE_PATTERN = /(\s)(src|href|xlink:href|poster|background|action|formaction|cite|longdesc|ping|data)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/gi;
const SOURCE_SET_ATTRIBUTE_PATTERN = /\s(?:srcset|imagesrcset)\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+)/gi;
const EVENT_HANDLER_ATTRIBUTE_PATTERN = /\son[a-z][\w:-]*\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+)/gi;
const CSS_IMPORT_PATTERN = /@import\s+(?:url\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)|"[^"]*"|'[^']*')[^;]*;?/gi;
const CSS_URL_PATTERN = /url\(\s*(?:"([^"]*)"|'([^']*)'|([^)]*))\s*\)/gi;
const UNSAFE_DOCUMENT_ELEMENT_PATTERN = /<\s*(?:base|link|meta)\b[^>]*>/gi;
const SCRIPT_BLOCK_PATTERN = /<\s*script\b[^>]*>[\s\S]*?<\s*\/\s*script\s*>/gi;
const SCRIPT_TAG_PATTERN = /<\s*\/?\s*script\b[^>]*>/gi;

type MessageDisplayPanelProps = {
  title?: string | null;
  fields?: MessageDisplayField[];
  bodyText?: string | null;
  bodyHtml?: string | null;
  bodyPreview?: string | null;
  preferredBodyMode?: MessageDisplayBodyMode;
  deriveTextFromHtml?: boolean;
  headers?: Record<string, string>;
  attachments?: MessageDisplayAttachment[];
  emptyText?: string;
};

export default function MessageDisplayPanel({
  title,
  fields = [],
  bodyText,
  bodyHtml,
  bodyPreview,
  preferredBodyMode,
  deriveTextFromHtml = true,
  headers = {},
  attachments = [],
  emptyText = "i18n:govoplan-core.select_an_item_to_inspect_its_content.1f67f131"
}: MessageDisplayPanelProps) {
  const { translateText } = usePlatformLanguage();
  const visibleFields = fields.filter((field) => hasRenderableValue(field.value));
  const headerEntries = Object.entries(headers);
  const hasHtml = Boolean(bodyHtml?.trim());
  const explicitTextBody = bodyText?.trim() || bodyPreview?.trim() || "";
  const textBody = explicitTextBody || (deriveTextFromHtml && hasHtml ? stripHtml(bodyHtml || "") : "");
  const hasText = Boolean(textBody.trim());
  const defaultBodyMode = preferredBodyMode === "html" && hasHtml ? "html" : preferredBodyMode === "text" && hasText ? "text" : hasHtml ? "html" : "text";
  const [bodyMode, setBodyMode] = useState<MessageDisplayBodyMode>(defaultBodyMode);
  const groupedAttachments = useMemo(() => groupAttachments(attachments), [attachments]);

  useEffect(() => {
    setBodyMode(defaultBodyMode);
  }, [defaultBodyMode, title, bodyText, bodyHtml, bodyPreview]);

  if (!title && visibleFields.length === 0 && !hasText && !hasHtml && attachments.length === 0 && headerEntries.length === 0) {
    return <p className="muted">{translateText(emptyText)}</p>;
  }

  const activeBodyMode = bodyMode === "html" && hasHtml ? "html" : "text";
  const showBodySwitch = hasHtml && hasText;
  const htmlBodyFallback = translateText("i18n:govoplan-core.p_no_html_body_content_p.c305bfc6");
  const textBodyFallback = translateText("i18n:govoplan-core.no_readable_body_content.37643a01");
  const safeHtmlDocument = buildSafeMessageHtmlDocument(bodyHtml || htmlBodyFallback);

  return (
    <div className="message-display-panel">
      <div className="message-display-header">
        <h3>{title || "i18n:govoplan-core.no_subject.49b20da0"}</h3>
        {visibleFields.length > 0 &&
        <dl className="message-display-fields">
            {visibleFields.map((field) =>
          <div key={field.label}>
                <dt>{field.label}</dt>
                <dd>{field.value}</dd>
              </div>
          )}
          </dl>
        }
      </div>

      <section className="message-display-body-section" aria-label="i18n:govoplan-core.message_body.daee2627">
        <div className="message-display-section-heading">
          <h4>i18n:govoplan-core.message.68f4145f</h4>
          {showBodySwitch &&
          <SegmentedControl
            ariaLabel="i18n:govoplan-core.message_body_format.5fec42d2"
            value={activeBodyMode}
            onChange={setBodyMode}
            options={[
              { id: "html", label: "i18n:govoplan-core.html.9f738ce8" },
              { id: "text", label: "i18n:govoplan-core.text.c3328c39" }
            ]}
          />
          }
        </div>
        {activeBodyMode === "html" ?
        <iframe
          className="message-display-html-frame"
          title="i18n:govoplan-core.rendered_html_message_body.8638b634"
          sandbox=""
          referrerPolicy="no-referrer"
          srcDoc={safeHtmlDocument} /> :

        <pre className="message-display-body">{textBody || textBodyFallback}</pre>
        }
      </section>

      {attachments.length ?
      <div className="message-display-attachments">
          <h4>i18n:govoplan-core.attachments.6771ade6</h4>
          <div className="message-display-attachments-scroll">
            {groupedAttachments.direct.length > 0 &&
          <div className="message-display-attachment-list">
                {groupedAttachments.direct.map((attachment, index) => <AttachmentRow key={attachmentKey(attachment, index)} attachment={attachment} index={index} />)}
              </div>
          }
            {groupedAttachments.archives.map((archive) =>
          <section className="message-display-attachment-archive" key={archive.key}>
                <header>
                  <div>
                    <strong><Archive size={15} aria-hidden="true" /> {archive.label}</strong>
                    <span>{archive.items.length} file{archive.items.length === 1 ? "" : "s"} i18n:govoplan-core.inside_zip.68c3fd85</span>
                  </div>
                  {archive.protected &&
              <div className="message-display-attachment-protection">
                      <small><LockKeyhole size={13} aria-hidden="true" /> i18n:govoplan-core.password_protected.09d9e174</small>
                      {archive.protectionNote && <small className="message-display-attachment-protection-note">{formatProtectionNote(archive.protectionNote)}</small>}
                    </div>
              }
                </header>
                <div className="message-display-attachment-list">
                  {archive.items.map((attachment, index) => <AttachmentRow key={attachmentKey(attachment, index)} attachment={attachment} index={index} />)}
                </div>
              </section>
          )}
          </div>
        </div> :
      null}

      {headerEntries.length > 0 &&
      <details className="message-display-headers">
          <summary>i18n:govoplan-core.headers.520de744</summary>
          <dl>
            {headerEntries.map(([key, value]) =>
          <div key={key}><dt>{key}</dt><dd>{value}</dd></div>
          )}
          </dl>
        </details>
      }
    </div>);

}

function AttachmentRow({ attachment, index }: {attachment: MessageDisplayAttachment;index: number;}) {
  const contentType = formatContentType(attachment.contentType);
  const size = formatBytes(attachment.sizeBytes);
  const hasMeta = Boolean(contentType || size);
  const LinkIcon = attachment.linkedToCampaign === true ? Link2 : attachment.linkedToCampaign === false ? X : Paperclip;
  const linkStateClass = attachment.linkedToCampaign === true ?
  " is-linked" :
  attachment.linkedToCampaign === false ?
  " is-unlinked" :
  "";
  return (
    <div className={`message-display-attachment-row${linkStateClass}`}>
      <LinkIcon size={14} aria-hidden="true" />
      <span>
        <strong>{attachment.filename || i18nMessage("i18n:govoplan-core.attachment_value.01801a54", { value0: index + 1 })}</strong>
        {attachment.detail && <small className="message-display-attachment-detail">{attachment.detail}</small>}
        {hasMeta &&
        <small className="message-display-attachment-meta">
            {contentType && <span title={contentType.full}>{contentType.label}</span>}
            {size && <span>{size}</span>}
          </small>
        }
      </span>
    </div>);

}

function groupAttachments(attachments: MessageDisplayAttachment[]) {
  const direct = attachments.filter((attachment) => !attachment.archiveGroup);
  const archiveMap = new Map<string, MessageDisplayAttachment[]>();
  for (const attachment of attachments) {
    if (!attachment.archiveGroup) continue;
    const current = archiveMap.get(attachment.archiveGroup) ?? [];
    current.push(attachment);
    archiveMap.set(attachment.archiveGroup, current);
  }
  return {
    direct,
    archives: [...archiveMap.entries()].map(([key, items]) => ({
      key,
      label: items[0]?.archiveLabel || key,
      protected: items.some((item) => item.protected),
      protectionNote: items.find((item) => item.protectionNote)?.protectionNote ?? null,
      items
    }))
  };
}

function attachmentKey(attachment: MessageDisplayAttachment, index: number): string {
  return String(attachment.id || `${attachment.archiveGroup || "direct"}:${attachment.filename || "attachment"}:${index}`);
}

function hasRenderableValue(value: ReactNode | null | undefined): boolean {
  if (value === null || value === undefined || value === false) return false;
  if (typeof value === "string") return value.trim() !== "";
  return true;
}

function formatProtectionNote(value: string): string {
  const normalized = value.
  replace(/^Password-protected ZIP(?: using)?\s*/i, "").
  replace(/^Password protected(?: using)?\s*/i, "").
  replace(/^using\s+/i, "").
  trim();
  const encryptionMatch = normalized.match(/^(.*?)\.\s*Encryption:\s*(.*)$/i);
  if (!encryptionMatch) return normalized;
  const source = encryptionMatch[1].trim();
  const method = encryptionMatch[2].trim();
  return source ?
  i18nMessage("i18n:govoplan-core.value_encryption_value", { value0: source, value1: method }) :
  i18nMessage("i18n:govoplan-core.encryption_value", { value0: method });
}

function formatContentType(value?: string | null): {label: string;full: string;} | null {
  const full = value?.trim();
  if (!full) return null;
  const normalized = full.toLowerCase();
  const known: Record<string, string> = {
    "application/pdf": "i18n:govoplan-core.pdf.d613d88c",
    "application/zip": "i18n:govoplan-core.zip_archive.5a2430dd",
    "application/x-zip-compressed": "i18n:govoplan-core.zip_archive.5a2430dd",
    "application/octet-stream": "i18n:govoplan-core.binary_file.a4de1904",
    "text/plain": "i18n:govoplan-core.plain_text.9580fcbc",
    "text/html": "i18n:govoplan-core.html.9f738ce8",
    "text/csv": "i18n:govoplan-core.csv.32811883",
    "application/json": "i18n:govoplan-core.json.031a4e76",
    "image/jpeg": "i18n:govoplan-core.jpeg_image.6876b7ff",
    "image/png": "i18n:govoplan-core.png_image.6715d4b8",
    "image/gif": "i18n:govoplan-core.gif_image.d092a779",
    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "i18n:govoplan-core.xlsx_spreadsheet.db8c2fc9",
    "application/vnd.ms-excel": "i18n:govoplan-core.excel_spreadsheet.7107fea2",
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "i18n:govoplan-core.docx_document.f0b09822",
    "application/msword": "i18n:govoplan-core.word_document.f593629d",
    "application/vnd.openxmlformats-officedocument.presentationml.presentation": "i18n:govoplan-core.pptx_presentation.855d193b",
    "application/vnd.ms-powerpoint": "i18n:govoplan-core.powerpoint_presentation.b32b7115"
  };
  if (known[normalized]) return { label: known[normalized], full };
  const subtype = normalized.split("/")[1]?.split(";")[0] || normalized;
  const cleaned = subtype.
  replace(/^vnd\./, "").
  replace(/^x-/, "").
  replace(/openxmlformats-officedocument\./g, "").
  replace(/[.+_-]+/g, " ").
  trim();
  const label = titleCase(cleaned || normalized);
  return { label: label.length > 34 ? `${label.slice(0, 31)}...` : label, full };
}

function titleCase(value: string): string {
  return value.replace(/\b[a-z]/g, (match) => match.toUpperCase());
}

function formatBytes(value?: number | null): string {
  if (!value) return "";
  if (value < 1024) return i18nMessage("i18n:govoplan-core.bytes_b", { value0: value });
  if (value < 1024 * 1024) return i18nMessage("i18n:govoplan-core.bytes_kb", { value0: (value / 1024).toFixed(1) });
  return i18nMessage("i18n:govoplan-core.bytes_mb", { value0: (value / 1024 / 1024).toFixed(1) });
}

function stripHtml(value: string): string {
  return value.replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
}

/**
 * Build the isolated document used for untrusted email HTML previews.
 *
 * Attribute neutralization keeps common clients from even attempting remote
 * resource resolution. The document-local CSP is the authoritative backstop
 * for malformed markup and parser edge cases: no network-capable resource is
 * permitted, while inline presentation plus embedded CID/raster data images
 * remain usable inside the already scriptless sandbox.
 */
export function buildSafeMessageHtmlDocument(value: string): string {
  const neutralized = neutralizeRemoteMessageContent(value);
  return [
    "<!doctype html>",
    "<html>",
    "<head>",
    '<meta charset="utf-8">',
    `<meta http-equiv="Content-Security-Policy" content="${MESSAGE_PREVIEW_CONTENT_SECURITY_POLICY}">`,
    '<meta name="referrer" content="no-referrer">',
    "</head>",
    `<body>${neutralized}</body>`,
    "</html>"
  ].join("");
}

function neutralizeRemoteMessageContent(value: string): string {
  return value.
  replace(SCRIPT_BLOCK_PATTERN, "").
  replace(SCRIPT_TAG_PATTERN, "").
  replace(UNSAFE_DOCUMENT_ELEMENT_PATTERN, "").
  replace(EVENT_HANDLER_ATTRIBUTE_PATTERN, "").
  replace(SOURCE_SET_ATTRIBUTE_PATTERN, "").
  replace(URL_ATTRIBUTE_PATTERN, (_match, spacing: string, attributeName: string, doubleQuoted: string | undefined, singleQuoted: string | undefined, unquoted: string | undefined) => {
    const url = doubleQuoted ?? singleQuoted ?? unquoted ?? "";
    if (!messagePreviewUrlAllowed(attributeName, url)) return "";
    return `${spacing}${attributeName.toLowerCase()}="${escapeHtmlAttribute(url.trim())}"`;
  }).
  replace(CSS_IMPORT_PATTERN, "").
  replace(CSS_URL_PATTERN, (_match, doubleQuoted: string | undefined, singleQuoted: string | undefined, unquoted: string | undefined) => {
    const url = (doubleQuoted ?? singleQuoted ?? unquoted ?? "").trim();
    return isSafeEmbeddedMessageResource(url) ? `url("${escapeCssString(url)}")` : "none";
  });
}

function messagePreviewUrlAllowed(attributeName: string, value: string): boolean {
  const attribute = attributeName.toLowerCase();
  if (["action", "formaction", "ping"].includes(attribute)) return false;
  const url = value.trim();
  if ((attribute === "href" || attribute === "xlink:href") && /^#[^\s]*$/.test(url)) return true;
  return isSafeEmbeddedMessageResource(url);
}

function isSafeEmbeddedMessageResource(value: string): boolean {
  if (/^cid:[^\s"'<>]+$/i.test(value)) return true;
  return /^data:image\/(?:png|gif|jpe?g|webp);base64,[a-z0-9+/=\s]+$/i.test(value);
}

function escapeHtmlAttribute(value: string): string {
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

function escapeCssString(value: string): string {
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/[\r\n\f]/g, "");
}
