// mjml@5 is async (breaking change from v4). @types/mjml ships v4 types — we type the result locally.
import mjmlPkg from 'mjml';
import { convert as htmlToText } from 'html-to-text';
import * as cheerio from 'cheerio';

const mjml2html = mjmlPkg as unknown as (
  input: string,
  options?: Record<string, unknown>,
) => Promise<{ html: string; errors: Array<{ line: number; message: string; tagName?: string }> }>;

export interface MjmlRenderResult {
  html: string;
  text: string;
  errors: Array<{ line: number; message: string; tagName?: string }>;
  bytes: number;
}

export interface MjmlRenderOptions {
  trackingPixelUrl?: string;
  unsubscribeUrl?: string;
  rewriteLinksWithMessageId?: string;
  baseUrl?: string;
}

export async function renderMjml(mjmlSource: string, opts: MjmlRenderOptions = {}): Promise<MjmlRenderResult> {
  const result = await mjml2html(mjmlSource, {
    keepComments: false,
    minify: false,
    validationLevel: 'soft',
  });

  let html = result.html;

  if (opts.rewriteLinksWithMessageId || opts.trackingPixelUrl) {
    html = postProcessHtml(html, opts);
  }

  const text = htmlToText(html, {
    wordwrap: 78,
    selectors: [
      { selector: 'img', format: 'skip' },
      { selector: 'a', options: { hideLinkHrefIfSameAsText: true } },
      { selector: 'h1', options: { uppercase: false } },
      { selector: 'h2', options: { uppercase: false } },
    ],
  });

  return {
    html,
    text,
    errors: result.errors.map((e) => ({
      line: e.line,
      message: e.message,
      tagName: e.tagName,
    })),
    bytes: Buffer.byteLength(html, 'utf8'),
  };
}

function postProcessHtml(html: string, opts: MjmlRenderOptions): string {
  const $ = cheerio.load(html, { xml: false }, false);

  if (opts.rewriteLinksWithMessageId && opts.baseUrl) {
    const mid = encodeURIComponent(opts.rewriteLinksWithMessageId);
    $('a[href]').each((_, el) => {
      const href = $(el).attr('href');
      if (!href) return;
      if (href.startsWith('mailto:') || href.startsWith('tel:') || href.startsWith('#')) return;
      if (href.includes('/u/') || href.includes('/t/')) return;
      const linkHash = simpleHash(href);
      const wrapped = `${opts.baseUrl}/c/${mid}/${linkHash}?u=${encodeURIComponent(href)}`;
      $(el).attr('href', wrapped);
    });
  }

  if (opts.trackingPixelUrl) {
    const pixel = `<img src="${escapeAttr(opts.trackingPixelUrl)}" width="1" height="1" alt="" style="display:block;border:0;width:1px;height:1px;" />`;
    if ($('body').length) {
      $('body').append(pixel);
    } else {
      return html.replace('</body>', `${pixel}</body>`);
    }
  }

  return $.html();
}

function simpleHash(s: string): string {
  let h = 0;
  for (let i = 0; i < s.length; i++) {
    h = ((h << 5) - h + s.charCodeAt(i)) | 0;
  }
  return Math.abs(h).toString(36).padStart(6, '0').slice(0, 8);
}

function escapeAttr(s: string): string {
  return s.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
}
