const PREVIEW_STYLE = `
:root { color-scheme: light dark; }
html, body { min-block-size: 100%; }
html { overflow: auto; }
body {
  box-sizing: border-box;
  margin: 0;
  padding: 1rem;
  overflow-wrap: anywhere;
  font: 1rem/1.6 sans-serif;
}
body > :first-child { margin-block-start: 0; }
body > :last-child { margin-block-end: 0; }
h2 { margin-block: 1.5rem 0.75rem; font-size: 1.5rem; line-height: 1.25; }
p, ul { margin-block: 0 1rem; }
ul { padding-inline-start: 1.5rem; list-style: disc; }
li + li { margin-block-start: 0.5rem; }
img { max-inline-size: 100%; block-size: auto; }
`

const DROP_WITH_CONTENT = new Set([
  'SCRIPT',
  'STYLE',
  'IFRAME',
  'OBJECT',
  'EMBED',
  'TEMPLATE',
  'NOSCRIPT',
  'SVG',
  'MATH',
])

const DROP_ELEMENT = new Set(['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA', 'OPTION'])
const SAFE_CONTAINER = new Set(['P', 'H1', 'H2', 'STRONG', 'EM', 'UL', 'LI', 'BR'])

function safeImageSource(value: string): boolean {
  try {
    const url = new URL(value)
    return url.protocol === 'https:' && url.username === '' && url.password === ''
  } catch {
    return false
  }
}

function positiveFiniteAttribute(value: string | null): string | null {
  if (value === null || value.trim() === '') return null
  const parsed = Number(value)
  return Number.isFinite(parsed) && parsed > 0 ? value : null
}

function copyDirection(source: Element, target: Element): void {
  const dir = source.getAttribute('dir')
  if (dir === 'ltr' || dir === 'rtl') target.setAttribute('dir', dir)
}

function safeNode(source: Node, targetDocument: Document): Node {
  if (source.nodeType === Node.TEXT_NODE) {
    return targetDocument.createTextNode(source.textContent ?? '')
  }

  if (source.nodeType !== Node.ELEMENT_NODE) {
    return targetDocument.createDocumentFragment()
  }

  const element = source as Element
  const tagName = element.tagName.toUpperCase()

  if (DROP_WITH_CONTENT.has(tagName) || DROP_ELEMENT.has(tagName)) {
    return targetDocument.createDocumentFragment()
  }

  if (tagName === 'IMG') {
    const src = element.getAttribute('src') ?? ''
    if (!safeImageSource(src)) return targetDocument.createDocumentFragment()

    const image = targetDocument.createElement('img')
    image.setAttribute('src', src)
    image.setAttribute('alt', element.getAttribute('alt') ?? '')
    const width = positiveFiniteAttribute(element.getAttribute('width'))
    const height = positiveFiniteAttribute(element.getAttribute('height'))
    if (width !== null) image.setAttribute('width', width)
    if (height !== null) image.setAttribute('height', height)
    copyDirection(element, image)
    return image
  }

  const normalizedTag = tagName === 'B' ? 'STRONG' : tagName === 'I' ? 'EM' : tagName
  const target = SAFE_CONTAINER.has(normalizedTag)
    ? targetDocument.createElement(normalizedTag.toLowerCase())
    : targetDocument.createDocumentFragment()

  if (target.nodeType === Node.ELEMENT_NODE) copyDirection(element, target as Element)

  for (const child of Array.from(element.childNodes)) {
    target.appendChild(safeNode(child, targetDocument))
  }

  return target
}

export function constrainPreviewHtml(sourceHtml: string): string {
  if (typeof DOMParser === 'undefined') {
    return sourceHtml
      .replaceAll('&', '&amp;')
      .replaceAll('<', '&lt;')
      .replaceAll('>', '&gt;')
      .replaceAll('"', '&quot;')
      .replaceAll("'", '&#39;')
  }

  const sourceDocument = new DOMParser().parseFromString(sourceHtml, 'text/html')
  const targetDocument = document.implementation.createHTMLDocument('')
  for (const node of Array.from(sourceDocument.body.childNodes)) {
    targetDocument.body.appendChild(safeNode(node, targetDocument))
  }
  return targetDocument.body.innerHTML
}

export function buildPreviewDocument(sourceHtml: string, dir: 'ltr' | 'rtl'): string {
  const html = constrainPreviewHtml(sourceHtml)
  return `<!doctype html>
<html dir="${dir}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="referrer" content="no-referrer">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src https:; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; object-src 'none'">
<style>${PREVIEW_STYLE}</style>
</head>
<body>${html}</body>
</html>`
}
