import DOMPurify from 'dompurify';
import { useEffect, useRef } from 'react';

import type { BookDoc } from '@/libs/document';
import type { ReviewRow } from '@/services/reviewEditorService';
import { useReaderStore } from '@/store/readerStore';
import { useReviewModeStore } from '@/store/reviewModeStore';

const STYLE_ID = 'readest-inline-review-style';
const ROW_ATTR = 'data-readest-review-row-id';
const ROLE_ATTR = 'data-readest-review-role';
const ORIGINAL_HTML_ATTR = 'data-readest-review-original-html';

const reviewHtmlPurifyOptions = {
  ALLOWED_TAGS: [
    'ruby',
    'rb',
    'rt',
    'rp',
    'span',
    'mark',
    'br',
    'em',
    'strong',
    'b',
    'i',
    'u',
    's',
    'sub',
    'sup',
  ],
  ALLOWED_ATTR: ['class', 'title'],
  ALLOW_DATA_ATTR: false,
};

const sanitizeReviewHtml = (html: string) =>
  DOMPurify.sanitize(html || '', reviewHtmlPurifyOptions);

const emptyReviewRows: ReviewRow[] = [];

const normalizeFile = (value?: string) =>
  (() => {
    const raw = (value || '').split('#')[0] || '';
    try {
      return decodeURIComponent(raw);
    } catch {
      return raw;
    }
  })()
    .replaceAll('\\', '/')
    .replace(/^\/+/, '')
    .toLowerCase();

const sameFile = (rowFile: string, sectionHref?: string) => {
  const row = normalizeFile(rowFile);
  const href = normalizeFile(sectionHref);
  if (!row || !href) return false;
  return row === href || row.endsWith(`/${href}`) || href.endsWith(`/${row}`);
};

const sectionHrefAt = (bookDoc: BookDoc, index: number | undefined) => {
  const section = bookDoc.sections[index ?? -1];
  return section?.href || section?.id;
};

const clearReviewMarks = (doc: Document) => {
  doc.getElementById(STYLE_ID)?.remove();
  doc.querySelectorAll(`[${ROW_ATTR}]`).forEach((element) => {
    const originalHtml = element.getAttribute(ORIGINAL_HTML_ATTR);
    if (originalHtml !== null) {
      element.innerHTML = originalHtml;
    }
    element.removeAttribute(ROW_ATTR);
    element.removeAttribute(ROLE_ATTR);
    element.removeAttribute(ORIGINAL_HTML_ATTR);
    element.classList.remove(
      'readest-review-source',
      'readest-review-target',
      'readest-review-active',
    );
  });
};

const ensureReviewStyle = (doc: Document) => {
  if (doc.getElementById(STYLE_ID)) return;
  const style = doc.createElement('style');
  style.id = STYLE_ID;
  style.textContent = `
    [${ROW_ATTR}] {
      cursor: pointer;
      user-select: text;
      outline-offset: 0.18em;
      transition: background-color 120ms ease, outline-color 120ms ease;
    }
    .readest-review-source {
      background: rgba(127, 127, 127, 0.12);
      outline: 1px dashed rgba(127, 127, 127, 0.64);
    }
    .readest-review-target {
      background: rgba(22, 163, 74, 0.14);
      outline: 1px solid rgba(22, 163, 74, 0.72);
    }
    .readest-review-active {
      background: rgba(37, 99, 235, 0.18) !important;
      outline: 2px solid currentColor !important;
    }
  `;
  doc.head.appendChild(style);
};

const closestReviewTarget = (target: EventTarget | null): HTMLElement | null => {
  const element = target as Element | null;
  if (!element || typeof element.closest !== 'function') return null;
  if (element.closest('a, button, input, textarea, select, video, audio')) return null;
  return element.closest(`[${ROW_ATTR}]`) as HTMLElement | null;
};

const closestReviewTargetFromNode = (node: Node | null): HTMLElement | null => {
  const element =
    node?.nodeType === Node.ELEMENT_NODE
      ? (node as Element)
      : (node?.parentElement as Element | null);
  if (!element || typeof element.closest !== 'function') return null;
  return element.closest(`[${ROW_ATTR}]`) as HTMLElement | null;
};

const selectedReviewTarget = (doc: Document): HTMLElement | null => {
  const selection = doc.getSelection();
  if (!selection || selection.isCollapsed || selection.rangeCount === 0) return null;
  const range = selection.getRangeAt(0);
  return (
    closestReviewTargetFromNode(range.commonAncestorContainer) ||
    closestReviewTargetFromNode(range.startContainer) ||
    closestReviewTargetFromNode(range.endContainer)
  );
};

const markParagraph = (
  element: Element | undefined,
  row: ReviewRow,
  role: 'source' | 'target',
  active: boolean,
) => {
  const htmlElement = element as HTMLElement | undefined;
  if (!htmlElement || typeof htmlElement.setAttribute !== 'function') return;
  htmlElement.setAttribute(ROW_ATTR, row.id);
  htmlElement.setAttribute(ROLE_ATTR, role);
  htmlElement.classList.add(role === 'source' ? 'readest-review-source' : 'readest-review-target');
  htmlElement.classList.toggle('readest-review-active', active);
  if (role === 'target' && row.current_html) {
    if (!htmlElement.hasAttribute(ORIGINAL_HTML_ATTR)) {
      htmlElement.setAttribute(ORIGINAL_HTML_ATTR, htmlElement.innerHTML);
    }
    htmlElement.innerHTML = sanitizeReviewHtml(row.current_html);
  }
};

const applyActiveReviewMark = (doc: Document, selectedRowId: string) => {
  doc.querySelectorAll(`[${ROW_ATTR}]`).forEach((element) => {
    element.classList.toggle(
      'readest-review-active',
      element.getAttribute(ROW_ATTR) === selectedRowId,
    );
  });
};

const applyReviewMarks = (
  doc: Document,
  sectionHref: string | undefined,
  rows: ReviewRow[],
  selectedRowId: string,
) => {
  clearReviewMarks(doc);
  const sectionRows = rows.filter((row) => sameFile(row.file, sectionHref));
  if (!sectionRows.length) return;

  ensureReviewStyle(doc);
  const paragraphs = Array.from(doc.querySelectorAll('p'));
  for (const row of sectionRows) {
    const active = row.id === selectedRowId;
    markParagraph(paragraphs[Number(row.ja_p_index)], row, 'source', active);
    markParagraph(paragraphs[Number(row.cn_p_index)], row, 'target', active);
  }
};

export const __testing = {
  applyReviewMarks,
  applyActiveReviewMark,
  clearReviewMarks,
  sectionHrefAt,
};

const ReviewModeController: React.FC<{ bookKey: string; bookDoc: BookDoc }> = ({
  bookKey,
  bookDoc,
}) => {
  const view = useReaderStore((state) => state.viewStates[bookKey]?.view);
  const enabled = useReviewModeStore((state) => !!state.books[bookKey]?.enabled);
  const rows = useReviewModeStore((state) => state.books[bookKey]?.rows ?? emptyReviewRows);
  const selectedRowId = useReviewModeStore((state) => state.books[bookKey]?.selectedRowId || '');
  const selectRow = useReviewModeStore((state) => state.selectRow);
  const selectedRowIdRef = useRef(selectedRowId);

  useEffect(() => {
    selectedRowIdRef.current = selectedRowId;
  }, [selectedRowId]);

  useEffect(() => {
    if (!view) return;

    const cleanupLoadedDocs = () => {
      for (const { doc } of view.renderer.getContents()) {
        clearReviewMarks(doc);
      }
    };

    if (!enabled || !rows.length) {
      cleanupLoadedDocs();
      return;
    }

    const applyToLoadedDocs = () => {
      for (const { doc, index } of view.renderer.getContents()) {
        applyReviewMarks(doc, sectionHrefAt(bookDoc, index), rows, selectedRowIdRef.current);
      }
    };

    const handleClick = (event: Event) => {
      const target = closestReviewTarget(event.target);
      if (!target) return;
      const rowId = target.getAttribute(ROW_ATTR);
      if (!rowId) return;
      event.preventDefault();
      event.stopPropagation();
      if ('stopImmediatePropagation' in event) {
        event.stopImmediatePropagation();
      }
      selectRow(bookKey, rowId);
    };

    const handleSelectionEnd = (event: Event) => {
      const doc = event.currentTarget as Document | null;
      if (!doc?.getSelection) return;
      window.setTimeout(() => {
        const target = selectedReviewTarget(doc);
        const rowId = target?.getAttribute(ROW_ATTR);
        if (rowId) selectRow(bookKey, rowId);
      }, 0);
    };

    const listenedDocs = new Set<Document>();

    const addDocListeners = (doc: Document) => {
      if (listenedDocs.has(doc)) return;
      listenedDocs.add(doc);
      doc.addEventListener('click', handleClick, true);
      doc.addEventListener('mouseup', handleSelectionEnd, true);
      doc.addEventListener('touchend', handleSelectionEnd, true);
      doc.addEventListener('keyup', handleSelectionEnd, true);
    };

    const removeDocListeners = (doc: Document) => {
      listenedDocs.delete(doc);
      doc.removeEventListener('click', handleClick, true);
      doc.removeEventListener('mouseup', handleSelectionEnd, true);
      doc.removeEventListener('touchend', handleSelectionEnd, true);
      doc.removeEventListener('keyup', handleSelectionEnd, true);
    };

    const handleLoad = (event: Event) => {
      const detail = (event as CustomEvent<{ doc?: Document; index?: number }>).detail;
      if (!detail?.doc) return;
      applyReviewMarks(
        detail.doc,
        sectionHrefAt(bookDoc, detail.index),
        rows,
        selectedRowIdRef.current,
      );
      addDocListeners(detail.doc);
    };

    for (const { doc } of view.renderer.getContents()) {
      addDocListeners(doc);
    }
    applyToLoadedDocs();
    view.addEventListener('load', handleLoad);

    return () => {
      view.removeEventListener('load', handleLoad);
      for (const doc of listenedDocs) {
        removeDocListeners(doc);
        clearReviewMarks(doc);
      }
    };
  }, [bookDoc, bookKey, enabled, rows, selectRow, view]);

  useEffect(() => {
    if (!view || !enabled || !rows.length) return;
    for (const { doc } of view.renderer.getContents()) {
      applyActiveReviewMark(doc, selectedRowId);
    }
  }, [bookKey, enabled, rows.length, selectedRowId, view]);

  return null;
};

export default ReviewModeController;
