import { linkifyHtml } from './linkify'

/**
 * Rich rendering for C_ContactActivity notes (lead / partner timelines).
 *
 * Activity text stays PLAIN TEXT in iDempiere (Comments, 2000 chars) so mails,
 * search and the mobile app keep readable text. Only the timeline renders it:
 *
 *   Label: value            → <strong>Label:</strong> value   (line-initial, or after " · ")
 *   Label (…): https://…    → the label rendered as the link (raw URL hidden)
 *   http(s)://… / www.…     → <a target="_blank">            (via linkifyHtml)
 *   name@host.tld           → <a href="mailto:…">
 *   Something_2026.pdf      → <code class="act-file">        (file names)
 *   **bold** / _italic_     → <strong> / <em>
 *   <b> <i> <u> <a> <br> …  → kept when the note was written as simple HTML
 *   blank line              → paragraph gap
 *
 * Safety: plain text goes escape → linkify → markers (never inside <a>). Notes
 * that already contain HTML are reduced to a small whitelist of tags/attributes
 * (no scripts, no event handlers, http(s)/mailto hrefs only), then linkified in
 * their text nodes. The result is safe for `v-html`.
 */

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

// A label is a short phrase before the first ':' of a line (or after " · "),
// with balanced parentheses — so "Online-Bestätigungs-Link (gültig bis …):" is a
// label but "… e-mailed to x@y.de (CC:" is not (unbalanced "(" before the colon).
const LABEL_MAX = 90
const isLabel = (s: string): boolean => {
  const t = s.trim()
  if (!t || t.length > LABEL_MAX) return false
  if (/https?:\/\/|www\./i.test(t)) return false
  let depth = 0
  for (const ch of t) { if (ch === '(') depth++; else if (ch === ')') depth--; if (depth < 0) return false }
  if (depth !== 0) return false
  return /^[\p{L}\p{N}][\p{L}\p{N} .,'’\-/&()]*$/u.test(t)
}

const EMAIL_RE = /\b[\w.+-]+@[\w-]+(?:\.[\w-]+)+\b/g
const FILE_RE = /\b[\w][\w.\-()]*\.(?:pdf|docx?|xlsx?|csv|png|jpe?g|zip)\b/gi
const BOLD_RE = /\*\*(\S(?:[^*\n]*?\S)?)\*\*/g
const ITALIC_RE = /(^|[\s(>])_(\S(?:[^_\n]*?\S)?)_(?=$|[\s).,!?:;<])/g

// Marks applied to an already-escaped text part (never inside <a>…</a>).
const decoratePlainPart = (html: string): string => html
  .replace(EMAIL_RE, (m) => `<a href="mailto:${m}">${m}</a>`)
  .replace(FILE_RE, (m) => `<code class="act-file">${m}</code>`)
  .replace(BOLD_RE, '<strong>$1</strong>')
  .replace(ITALIC_RE, '$1<em>$2</em>')

const inline = (segment: string): string => linkifyHtml(segment)
  .split(/(<a [^>]*>.*?<\/a>)/)
  .map((part, i) => (i % 2 === 1 ? part : decoratePlainPart(part)))
  .join('')

const formatLine = (line: string): string => {
  if (!line.trim()) return ''
  // Split on " · " separators so mid-line labels (· E-Mail: …) get bolded too.
  const chunks = line.split(/( · )/)
  return chunks.map((chunk, i) => {
    if (i % 2 === 1) return '<span class="act-sep">·</span>'
    const colon = chunk.indexOf(':')
    if (colon > 0) {
      const label = chunk.slice(0, colon)
      if (isLabel(label)) {
        const rest = chunk.slice(colon + 1)
        // "Label (…): https://…" → the label itself becomes the link (the raw
        // token URL is noise for the reader; the href keeps it).
        const only = rest.trim()
        if (/^(?:https?:\/\/|www\.)\S+$/i.test(only)) {
          const href = /^www\./i.test(only) ? `https://${only}` : only
          return `<a class="act-label-link" href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer"><i class="ti ti-external-link"></i> ${escapeHtml(label.trim())}</a>`
        }
        return `<strong class="act-label">${escapeHtml(label.trim())}:</strong>${inline(rest)}`
      }
    }
    return inline(chunk)
  }).join('')
}

const formatPlain = (text: string): string => {
  const lines = text.replace(/\r\n?/g, '\n').split('\n')
  const out: string[] = []
  let gap = false
  for (const raw of lines) {
    if (!raw.trim()) { gap = true; continue }
    if (gap && out.length) out.push('<div class="act-gap"></div>')
    gap = false
    out.push(`<div class="act-line">${formatLine(raw)}</div>`)
  }
  return out.join('')
}

// ---- simple-HTML notes ------------------------------------------------------
const ALLOWED_TAGS = new Set(['b', 'strong', 'i', 'em', 'u', 's', 'br', 'p', 'div', 'ul', 'ol', 'li', 'a', 'code', 'span', 'h1', 'h2', 'h3', 'h4'])
const looksLikeHtml = (text: string): boolean => /<\/?(?:b|strong|i|em|u|s|br|p|div|ul|ol|li|a|code|span|h[1-4])\b[^>]*>/i.test(text)

const sanitizeHtml = (html: string): string => {
  if (typeof document === 'undefined') return escapeHtml(html)
  const tpl = document.createElement('template')
  tpl.innerHTML = html
  const walk = (node: Node): string => {
    if (node.nodeType === Node.TEXT_NODE) return inline(node.textContent || '')
    if (node.nodeType !== Node.ELEMENT_NODE) return ''
    const el = node as Element
    const tag = el.tagName.toLowerCase()
    const inner = Array.from(el.childNodes).map(walk).join('')
    if (!ALLOWED_TAGS.has(tag)) return inner
    if (tag === 'br') return '<br>'
    if (tag === 'a') {
      const href = String(el.getAttribute('href') || '').trim()
      if (!/^(https?:\/\/|mailto:)/i.test(href)) return inner
      return `<a href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer">${inner}</a>`
    }
    return `<${tag}>${inner}</${tag}>`
  }
  return Array.from(tpl.content.childNodes).map(walk).join('')
}

export const formatActivityHtml = (input: any): string => {
  const text = input == null ? '' : String(input)
  if (!text.trim()) return ''
  return looksLikeHtml(text) ? sanitizeHtml(text) : formatPlain(text)
}

export default formatActivityHtml
