import "server-only";

import sanitizeHtml from "sanitize-html";

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

const spacingPattern =
  /^-?\d+(?:\.\d+)?(?:px|em|rem|pt|%|ex)?(?:\s+-?\d+(?:\.\d+)?(?:px|em|rem|pt|%|ex)?){0,3}$/i;
const sizePattern = /^(auto|\d+(?:\.\d+)?(?:px|em|rem|pt|%|vw|vh))$/i;
const sizeWithImportantPattern =
  /^(auto|\d+(?:\.\d+)?(?:px|em|rem|pt|%|vw|vh))(?:\s*!important)?$/i;
const colorWithImportantPattern =
  /^(#[0-9a-f]{3,8}|rgb(a)?\([^)]*\)|[a-z]+)(?:\s*!important)?$/i;
const spacingWithImportantPattern =
  /^-?\d+(?:\.\d+)?(?:px|em|rem|pt|%|ex)?(?:\s+-?\d+(?:\.\d+)?(?:px|em|rem|pt|%|ex)?){0,3}(?:\s*!important)?$/i;
const borderWithImportantPattern =
  /^(?:0|none|\d+(?:\.\d+)?px\s+(?:solid|dashed|dotted)\s+(?:#[0-9a-f]{3,8}|rgb(a)?\([^)]*\)|[a-z]+))(?:\s*!important)?$/i;
const borderRadiusPattern =
  /^\d+(?:\.\d+)?(?:px|em|rem|pt|%)?(?:\s+\d+(?:\.\d+)?(?:px|em|rem|pt|%)?){0,3}(?:\s*!important)?$/i;
const zeroOrSizedPattern =
  /^(?:0|auto|\d+(?:\.\d+)?(?:px|em|rem|pt|%|vw|vh))(?:\s*!important)?$/i;

const MAIL_HTML_SANITIZE_OPTIONS: sanitizeHtml.IOptions = {
  allowedAttributes: {
    "*": ["class", "dir", "style"],
    a: ["href", "name", "target", "rel"],
    img: ["src", "alt", "title", "width", "height", "border"],
    table: ["align", "border", "cellpadding", "cellspacing", "width"],
    td: ["align", "colspan", "rowspan", "valign", "width", "height"],
    th: ["align", "colspan", "rowspan", "valign", "width", "height"],
    tr: ["align", "valign", "width", "height"],
  },
  allowedClasses: {
    "*": ["gmail_attr", "gmail_quote", "gmail_quote_container"],
  },
  allowedSchemes: ["http", "https", "mailto"],
  allowedSchemesByTag: {
    img: ["cid", "data", "http", "https"],
  },
  allowedStyles: {
    "*": {
      background: [colorWithImportantPattern],
      "background-color": [colorWithImportantPattern],
      border: [borderWithImportantPattern],
      "border-bottom": [borderWithImportantPattern],
      "border-left": [borderWithImportantPattern],
      "border-right": [borderWithImportantPattern],
      "border-top": [borderWithImportantPattern],
      "border-collapse": [/^(collapse|separate)(?:\s*!important)?$/i],
      "border-radius": [borderRadiusPattern],
      "border-spacing": [spacingWithImportantPattern],
      color: [colorWithImportantPattern],
      display: [/^(none|block|inline|inline-block|flex|grid|table|table-row|table-cell)(?:\s*!important)?$/i],
      float: [/^(left|right|none)(?:\s*!important)?$/i],
      "font-family": [/^[a-z0-9\s"',-]+(?:\s*!important)?$/i],
      "font-size": [/^(?:0|\d+(?:\.\d+)?(?:px|em|rem|pt|%))(?:\s*!important)?$/i],
      "font-style": [/^(normal|italic|oblique)(?:\s*!important)?$/i],
      "font-weight": [/^(normal|bold|[1-9]00)(?:\s*!important)?$/i],
      height: [sizeWithImportantPattern, sizePattern],
      "line-height": [/^(normal|\d+(?:\.\d+)?(?:px|em|rem|pt|%)?)(?:\s*!important)?$/i],
      margin: [spacingWithImportantPattern, spacingPattern],
      "margin-bottom": [spacingWithImportantPattern, spacingPattern],
      "margin-left": [spacingWithImportantPattern, spacingPattern],
      "margin-right": [spacingWithImportantPattern, spacingPattern],
      "margin-top": [spacingWithImportantPattern, spacingPattern],
      "max-height": [zeroOrSizedPattern, sizePattern],
      "max-width": [zeroOrSizedPattern, sizePattern],
      "min-height": [zeroOrSizedPattern, sizePattern],
      "min-width": [zeroOrSizedPattern, sizePattern],
      "mso-hide": [/^(all|none)(?:\s*!important)?$/i],
      opacity: [/^(0|0?\.\d+|1)(?:\s*!important)?$/i],
      outline: [/^(?:none|0)(?:\s*!important)?$/i, borderWithImportantPattern],
      overflow: [/^(visible|hidden|scroll|auto)(?:\s*!important)?$/i],
      padding: [spacingWithImportantPattern, spacingPattern],
      "padding-bottom": [spacingWithImportantPattern, spacingPattern],
      "padding-left": [spacingWithImportantPattern, spacingPattern],
      "padding-right": [spacingWithImportantPattern, spacingPattern],
      "padding-top": [spacingWithImportantPattern, spacingPattern],
      "text-align": [/^(left|right|center|justify)(?:\s*!important)?$/i],
      "text-decoration": [/^[a-z\s-]+$/i],
      "vertical-align": [/^(top|middle|bottom|baseline)(?:\s*!important)?$/i],
      visibility: [/^(visible|hidden|collapse)(?:\s*!important)?$/i],
      "white-space": [/^(normal|nowrap|pre|pre-line|pre-wrap)$/i],
      width: [sizeWithImportantPattern, sizePattern],
    },
  },
  allowedTags: [
    ...sanitizeHtml.defaults.allowedTags,
    "blockquote",
    "br",
    "div",
    "h1",
    "h2",
    "h3",
    "h4",
    "h5",
    "h6",
    "hr",
    "img",
    "span",
    "table",
    "tbody",
    "td",
    "tfoot",
    "th",
    "thead",
    "tr",
  ],
  disallowedTagsMode: "discard",
  transformTags: {
    a: (_tagName, attribs) => ({
      attribs: {
        ...attribs,
        rel: "noopener noreferrer nofollow",
        target: "_blank",
      },
      tagName: "a",
    }),
  },
};

export function normalizeStoredMailboxHtml(value: string | null | undefined) {
  if (typeof value !== "string") {
    return null;
  }

  const normalized = value.trim();
  return normalized ? normalized : null;
}

export function plainTextToHtml(value: string) {
  const normalized = value.replace(/\r/g, "").trim();

  if (!normalized) {
    return "";
  }

  return normalized
    .split(/\n{2,}/)
    .map((block) => {
      const html = block
        .split("\n")
        .map((line) => {
          const escaped = escapeHtml(line).replace(/ {2}/g, " &nbsp;");
          return escaped || "<br>";
        })
        .join("<br>");

      return `<p>${html}</p>`;
    })
    .join("");
}

export function sanitizeMailboxHtml(
  value: string | null | undefined,
  fallbackText?: string | null,
) {
  const normalized = normalizeStoredMailboxHtml(value);

  if (!normalized) {
    return fallbackText?.trim() ? plainTextToHtml(fallbackText) : null;
  }

  const sanitized = sanitizeHtml(normalized, MAIL_HTML_SANITIZE_OPTIONS).trim();

  if (sanitized) {
    return sanitized;
  }

  return fallbackText?.trim() ? plainTextToHtml(fallbackText) : null;
}
