import React, { useRef, useEffect, useState, useCallback } from 'react'
import ExerciseInlineFileMediaModal from './ExerciseInlineFileMediaModal'
import ExerciseInlineEmbedModal from './ExerciseInlineEmbedModal'
import { sanitizeInlineMediaCaption } from '../utils/inlineMediaCaption'
import {
  SHINKAN_EXERCISE_MEDIA_DRAG_MIME,
  parseExerciseMediaDragPayload,
} from '../utils/exerciseInlineMediaRefs'
import { autoScrollForDragNearEdges } from '../utils/dragAutoScroll'

function exec(cmd, value = null) {
  try {
    return document.execCommand(cmd, false, value)
  } catch (_) {
    return false
  }
}

/** Selection speichern, bevor Toolbar-Klicks sie zerstört (mousedown + preventDefault allein reicht nicht überall). */
function saveSelectionInside(editorEl) {
  const sel = window.getSelection()
  if (!sel || sel.rangeCount === 0 || !editorEl) return null
  try {
    const range = sel.getRangeAt(0)
    if (!editorEl.contains(range.commonAncestorContainer)) return null
    return range.cloneRange()
  } catch {
    return null
  }
}

function restoreSelection(range) {
  if (!range) return
  try {
    const sel = window.getSelection()
    sel.removeAllRanges()
    sel.addRange(range)
  } catch {
    /* noop */
  }
}

/** Browser: formatBlock erwartet oft Tag in Großschreibung. */
function formatBlock(tag) {
  const t = String(tag).toUpperCase()
  if (!exec('formatBlock', t)) {
    exec('formatBlock', tag.toLowerCase())
  }
}

function normalText() {
  exec('removeFormat')
  formatBlock('p')
}

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

function buildInlineExerciseMediaHtml(mediaId, displaySize = 'medium', caption = '') {
  const sid = parseInt(String(mediaId), 10)
  if (!Number.isFinite(sid) || sid < 1) return null
  const sz = ['small', 'medium', 'full'].includes(displaySize) ? displaySize : 'medium'
  const cap = sanitizeInlineMediaCaption(caption)
  let attrs = `data-shinkan-exercise-media="${sid}" data-shinkan-exercise-media-size="${sz}" class="shinkan-inline-media"`
  if (cap) attrs += ` data-shinkan-exercise-media-caption="${escapeHtmlAttr(cap)}"`
  return `<span ${attrs}>\u2060</span>`
}

function patchExitGlyphAfterPlaceholder(editorEl, mediaId, sel) {
  const sid = String(parseInt(String(mediaId), 10))
  const hits = editorEl.querySelectorAll(`span.shinkan-inline-media[data-shinkan-exercise-media="${sid}"]`)
  const span = hits[hits.length - 1]
  if (!span?.parentNode || !sel) return
  let n = span.nextSibling
  if (!n || n.nodeType !== Node.TEXT_NODE || !n.textContent.includes('\u200B')) {
    const zn = document.createTextNode('\u200B')
    span.parentNode.insertBefore(zn, span.nextSibling)
    n = zn
  }
  try {
    const r = document.createRange()
    r.setStart(n, n.textContent.length)
    r.collapse(true)
    sel.removeAllRanges()
    sel.addRange(r)
  } catch {
    /* noop */
  }
}

function insertExerciseMediaPlaceholder(editorEl, mediaId, displaySize = 'medium', caption = '') {
  if (!editorEl || mediaId == null) return false
  const html = buildInlineExerciseMediaHtml(mediaId, displaySize, caption)
  if (!html) return false

  editorEl.focus()
  const sel = window.getSelection()
  if (!sel) return false

  let caretInside = false
  if (sel.rangeCount > 0) {
    try {
      const r0 = sel.getRangeAt(0)
      caretInside = editorEl.contains(r0.commonAncestorContainer)
    } catch {
      caretInside = false
    }
  }
  if (!caretInside) {
    const anchor = document.createRange()
    try {
      anchor.selectNodeContents(editorEl)
      anchor.collapse(false)
    } catch {
      return false
    }
    sel.removeAllRanges()
    sel.addRange(anchor)
  }

  let inserted = false
  try {
    inserted = document.execCommand('insertHTML', false, html)
  } catch {
    inserted = false
  }
  if (inserted) {
    patchExitGlyphAfterPlaceholder(editorEl, mediaId, sel)
    return true
  }

  let range = null
  try {
    range = sel.rangeCount ? sel.getRangeAt(0).cloneRange() : null
  } catch {
    range = null
  }
  if (!range || !editorEl.contains(range.commonAncestorContainer)) {
    range = document.createRange()
    range.selectNodeContents(editorEl)
    range.collapse(false)
    sel.removeAllRanges()
    sel.addRange(range)
  }
  try {
    const tpl = document.createElement('template')
    tpl.innerHTML = html
    const span = tpl.content.firstChild
    if (!span) return false
    range.deleteContents()
    range.insertNode(span)
    const zn = document.createTextNode('\u200B')
    span.parentNode.insertBefore(zn, span.nextSibling)
    range.setStart(zn, zn.textContent.length)
    range.collapse(true)
    sel.removeAllRanges()
    sel.addRange(range)
    return true
  } catch {
    return false
  }
}

/**
 * Leichter WYSIWYG (contenteditable).
 * @param {{
 *   linkedExerciseMedia?: object[],
 * }} [extra]
 */
export default function RichTextEditor({
  value,
  onChange,
  placeholder,
  minHeight = '140px',
  inlineExerciseId = null,
  linkedExerciseMedia = [],
  onExerciseMediaListChanged,
}) {
  const ref = useRef(null)
  const pendingRangeRef = useRef(null)
  const [focused, setFocused] = useState(false)
  const [fileModalOpen, setFileModalOpen] = useState(false)
  const [embedModalOpen, setEmbedModalOpen] = useState(false)

  const showInlineToolbar = inlineExerciseId != null && Number(inlineExerciseId) > 0

  useEffect(() => {
    const el = ref.current
    if (!el || focused) return
    const next = value ?? ''
    if (el.innerHTML !== next) {
      el.innerHTML = next
    }
  }, [value, focused])

  const sync = useCallback(() => {
    if (!ref.current) return
    onChange(ref.current.innerHTML)
  }, [onChange])

  const refreshExerciseMedia = useCallback(async () => {
    if (onExerciseMediaListChanged) {
      await onExerciseMediaListChanged()
    }
  }, [onExerciseMediaListChanged])

  const stashRangeAndOpen = useCallback((openFn) => (e) => {
    e.preventDefault()
    e.stopPropagation()
    const el = ref.current
    pendingRangeRef.current = el ? saveSelectionInside(el) : null
    openFn()
  }, [])

  const finalizeInsertFromModal = useCallback(
    (mediaId, displaySize, caption) => {
      queueMicrotask(() => {
        const shell = ref.current
        if (!shell) return
        shell.focus()
        restoreSelection(pendingRangeRef.current)
        const ok = insertExerciseMediaPlaceholder(shell, mediaId, displaySize, caption)
        if (!ok) {
          alert(
            'Einfügen ist fehlgeschlagen — bitte Cursor ins Textfeld setzen und den Schalter erneut verwenden.',
          )
          return
        }
        sync()
        shell.focus()
      })
    },
    [sync],
  )

  const onEditorKeyDown = useCallback(
    (e) => {
      const el = ref.current
      if (!el || e.key !== 'Enter') return
      const sel = window.getSelection()
      if (!sel || sel.rangeCount === 0 || !el.contains(sel.focusNode)) return
      let node = sel.focusNode
      let elNode = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement
      const host = elNode?.closest?.('.shinkan-inline-media')
      if (!host || !el.contains(host)) return
      e.preventDefault()
      patchExitGlyphAfterPlaceholder(el, host.getAttribute('data-shinkan-exercise-media'), sel)
      try {
        exec('insertParagraph')
      } catch {
        exec('insertLineBreak')
      }
      sync()
    },
    [sync],
  )

  const onEditorDragOver = useCallback(
    (e) => {
      if (!showInlineToolbar) return
      const types = e.dataTransfer?.types ? Array.from(e.dataTransfer.types) : []
      if (types.includes(SHINKAN_EXERCISE_MEDIA_DRAG_MIME)) {
        e.preventDefault()
        e.dataTransfer.dropEffect = 'copy'
        autoScrollForDragNearEdges(e)
      }
    },
    [showInlineToolbar],
  )

  const onEditorDrop = useCallback(
    (e) => {
      const el = ref.current
      if (!el || !showInlineToolbar) return
      const raw = e.dataTransfer?.getData(SHINKAN_EXERCISE_MEDIA_DRAG_MIME)
      const parsed = parseExerciseMediaDragPayload(raw)
      if (!parsed) return
      e.preventDefault()
      el.focus()
      const sel = window.getSelection()
      if (!sel) return
      let r = null
      try {
        if (document.caretRangeFromPoint) {
          r = document.caretRangeFromPoint(e.clientX, e.clientY)
        }
      } catch {
        r = null
      }
      if (!r) {
        try {
          const p = document.caretPositionFromPoint?.(e.clientX, e.clientY)
          if (p?.offsetNode) {
            const nr = document.createRange()
            nr.setStart(p.offsetNode, p.offset)
            nr.collapse(true)
            r = nr
          }
        } catch {
          r = null
        }
      }
      if (r && el.contains(r.commonAncestorContainer)) {
        sel.removeAllRanges()
        sel.addRange(r)
      } else {
        const anchor = document.createRange()
        try {
          anchor.selectNodeContents(el)
          anchor.collapse(false)
        } catch {
          return
        }
        sel.removeAllRanges()
        sel.addRange(anchor)
      }
      insertExerciseMediaPlaceholder(el, parsed.exerciseMediaId, 'medium', parsed.caption)
      sync()
      el.focus()
    },
    [sync, showInlineToolbar],
  )

  const run = (fn) => (e) => {
    e.preventDefault()
    e.stopPropagation()
    const el = ref.current
    if (!el) return
    const saved = saveSelectionInside(el)
    el.focus()
    restoreSelection(saved)
    try {
      document.execCommand('styleWithCSS', false, false)
    } catch {
      /* optional */
    }
    fn()
    sync()
  }

  const onLink = (e) => {
    e.preventDefault()
    e.stopPropagation()
    const el = ref.current
    if (!el) return
    const saved = saveSelectionInside(el)
    el.focus()
    restoreSelection(saved)
    const url = window.prompt('Link-URL (https://…)')
    if (url) {
      exec('createLink', url)
      sync()
    }
  }

  return (
    <div className="rich-text-editor-wrap">
      <div className="rich-text-toolbar" role="toolbar" aria-label="Formatierung">
        <button type="button" className="rte-btn" title="Fett" onMouseDown={run(() => exec('bold'))}>
          <strong>B</strong>
        </button>
        <button type="button" className="rte-btn" title="Kursiv" onMouseDown={run(() => exec('italic'))}>
          <em>I</em>
        </button>
        <button type="button" className="rte-btn" title="Unterstrichen" onMouseDown={run(() => exec('underline'))}>
          U
        </button>
        <span className="rte-sep" />
        <button type="button" className="rte-btn" title="Normaler Absatz" onMouseDown={run(normalText)}>
          Normal
        </button>
        <button type="button" className="rte-btn" title="Zwischenüberschrift" onMouseDown={run(() => formatBlock('h3'))}>
          Ü3
        </button>
        <span className="rte-sep" />
        <button
          type="button"
          className="rte-btn"
          title="Aufzählung (Mehrzeilen-Markierung möglich)"
          onMouseDown={run(() => exec('insertUnorderedList'))}
        >
          • Liste
        </button>
        <button type="button" className="rte-btn" title="Nummerierte Liste" onMouseDown={run(() => exec('insertOrderedList'))}>
          1. Liste
        </button>
        <span className="rte-sep" />
        <button type="button" className="rte-btn" title="Link einfügen" onMouseDown={onLink}>
          Link
        </button>
        {showInlineToolbar ? (
          <>
            <button
              type="button"
              className="rte-btn"
              title="Datei aus Mediathek oder neu hochladen, in den Text einfügen"
              onMouseDown={stashRangeAndOpen(() => setFileModalOpen(true))}
            >
              Medien im Text
            </button>
            <button
              type="button"
              className="rte-btn"
              title="Embed-URL hinzufügen und im Text einfügen"
              onMouseDown={stashRangeAndOpen(() => setEmbedModalOpen(true))}
            >
              Embed im Text
            </button>
          </>
        ) : null}
        <button
          type="button"
          className="rte-btn"
          title="Zeichenformatierung am Cursor entfernen"
          onMouseDown={run(() => {
            exec('removeFormat')
            exec('unlink')
          })}
        >
          ␡
        </button>
      </div>
      <div
        ref={ref}
        className="rich-text-editor"
        contentEditable
        suppressContentEditableWarning
        data-placeholder={placeholder || ''}
        style={{ minHeight }}
        onFocus={() => setFocused(true)}
        onBlur={() => {
          setFocused(false)
          sync()
        }}
        onInput={sync}
        onKeyDown={onEditorKeyDown}
        onDragEnter={onEditorDragOver}
        onDragOver={onEditorDragOver}
        onDrop={onEditorDrop}
      />

      {showInlineToolbar ? (
        <ExerciseInlineFileMediaModal
          open={fileModalOpen}
          onClose={() => setFileModalOpen(false)}
          exerciseId={Number(inlineExerciseId)}
          linkedExerciseMedia={linkedExerciseMedia}
          onMediaListChanged={refreshExerciseMedia}
          onInserted={(mid, sz, cap) => finalizeInsertFromModal(mid, sz, cap)}
        />
      ) : null}
      {showInlineToolbar ? (
        <ExerciseInlineEmbedModal
          open={embedModalOpen}
          onClose={() => setEmbedModalOpen(false)}
          exerciseId={Number(inlineExerciseId)}
          onMediaListChanged={refreshExerciseMedia}
          onInserted={(mid, sz, cap) => finalizeInsertFromModal(mid, sz, cap)}
        />
      ) : null}
    </div>
  )
}
