import { useLayoutEffect, useMemo, useRef, useState } from "react";
import {
  AlignCenter,
  AlignLeft,
  AlignRight,
  Bold,
  Download,
  FileText,
  FileUp,
  Image as ImageIcon,
  Italic,
  Link,
  List,
  PaintBucket,
  Save,
  SeparatorHorizontal,
  Strikethrough,
  Type,
  Underline
} from "lucide-react";
import type { WordDocument } from "../../types";
import { downloadBlobFile, downloadTextFile } from "../../utils/download";
import { exportDocxBlob, importDocxFile } from "../../io/wordOffice";
import { replaceFileExtension } from "../../io/fileHelpers";
import { isDesktopOfficeBridgeAvailable, openDesktopOfficeFile, saveDesktopOfficeFile } from "../../io/desktopOfficeFiles";
import { exportPdfOrPrint } from "../../io/pdfExport";
import { useQOfficeCommand } from "../../hooks/useQOfficeCommand";

interface QWordProps {
  document: WordDocument;
  onChange: (document: WordDocument) => void;
}

function countWords(html: string) {
  const text = html.replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").trim();
  return text ? text.split(/\s+/).length : 0;
}

function normalizedLinkInput(value: string | null) {
  const raw = value?.trim();
  if (!raw) {
    return "";
  }

  const candidate = /^[a-z][a-z0-9+.-]*:/i.test(raw) ? raw : `https://${raw}`;
  try {
    const url = new URL(candidate);
    return ["http:", "https:", "mailto:", "tel:", "ftp:"].includes(url.protocol.toLowerCase()) ? url.href : "";
  } catch {
    window.alert("Введите корректный адрес ссылки.");
    return "";
  }
}

function readFileAsDataUrl(file: File) {
  return new Promise<string>((resolve, reject) => {
    const reader = new FileReader();
    reader.addEventListener("load", () => resolve(String(reader.result ?? "")));
    reader.addEventListener("error", () => reject(new Error("Не удалось прочитать изображение.")));
    reader.readAsDataURL(file);
  });
}

function readImageSize(src: string) {
  return new Promise<{ width: number; height: number }>((resolve, reject) => {
    const image = new window.Image();
    image.addEventListener("load", () => resolve({ width: image.naturalWidth || 520, height: image.naturalHeight || 260 }), { once: true });
    image.addEventListener("error", () => reject(new Error("Не удалось загрузить изображение.")), { once: true });
    image.src = src;
  });
}

function scaledImageSize(size: { width: number; height: number }, maxWidth = 680) {
  const scale = size.width > maxWidth ? maxWidth / size.width : 1;
  return {
    width: Math.max(1, Math.round(size.width * scale)),
    height: Math.max(1, Math.round(size.height * scale))
  };
}

const textColorSwatches = [
  { color: "#202124", label: "Черный текст" },
  { color: "#0f5fae", label: "Синий текст" },
  { color: "#0f766e", label: "Зеленый текст" },
  { color: "#c2410c", label: "Красный текст" },
  { color: "#7c3aed", label: "Фиолетовый текст" }
];

const highlightSwatches = [
  { color: "#fff2cc", label: "Желтая подсветка" },
  { color: "#d9ead3", label: "Зеленая подсветка" },
  { color: "#ddebf7", label: "Синяя подсветка" },
  { color: "#fce4d6", label: "Коралловая подсветка" },
  { color: "#ffffff", label: "Без подсветки" }
];

const defaultPageViewHeight = 1180;

function pageHeightFromStyles(element: HTMLElement) {
  const pageHeight = Number.parseFloat(window.getComputedStyle(element).getPropertyValue("--qword-page-height"));
  return Number.isFinite(pageHeight) && pageHeight > 0 ? pageHeight : defaultPageViewHeight;
}

export function QWord({ document, onChange }: QWordProps) {
  const editorRef = useRef<HTMLDivElement>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);
  const imageInputRef = useRef<HTMLInputElement>(null);
  const [pageView, setPageView] = useState({ count: 1, height: defaultPageViewHeight });
  const wordCount = useMemo(() => countWords(document.html), [document.html]);

  function refreshPageView() {
    window.requestAnimationFrame(() => {
      const editor = editorRef.current;
      if (!editor) {
        return;
      }

      const height = pageHeightFromStyles(editor);
      const count = Math.max(1, Math.ceil(editor.scrollHeight / height));
      setPageView((current) => (current.count === count && current.height === height ? current : { count, height }));
    });
  }

  useLayoutEffect(() => {
    refreshPageView();
    const editor = editorRef.current;
    if (!editor) {
      return undefined;
    }

    const observer = new ResizeObserver(refreshPageView);
    observer.observe(editor);
    return () => observer.disconnect();
  }, [document.html]);

  function runCommand(command: string, value?: string) {
    editorRef.current?.focus();
    globalThis.document.execCommand(command, false, value);
    if (editorRef.current) {
      onChange({ ...document, html: editorRef.current.innerHTML });
    }
  }

  function applyTextColor(color: string) {
    runCommand("foreColor", color);
  }

  function applyHighlight(color: string) {
    editorRef.current?.focus();
    globalThis.document.execCommand("hiliteColor", false, color);
    globalThis.document.execCommand("backColor", false, color);
    updateHtml();
  }

  function insertLink() {
    const href = normalizedLinkInput(window.prompt("Введите адрес ссылки"));
    if (!href) {
      return;
    }

    editorRef.current?.focus();
    const selection = window.getSelection();
    if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
      const anchor = globalThis.document.createElement("a");
      anchor.href = href;
      anchor.textContent = href;
      const range = selection?.rangeCount ? selection.getRangeAt(0) : globalThis.document.createRange();
      range.insertNode(anchor);
      range.setStartAfter(anchor);
      range.collapse(true);
      selection?.removeAllRanges();
      selection?.addRange(range);
    } else {
      globalThis.document.execCommand("createLink", false, href);
    }

    updateHtml();
  }

  function insertPageBreak() {
    runCommand("insertHTML", '<span data-qoffice-page-break="true" contenteditable="false"></span><p><br></p>');
  }

  async function insertImage(file: File | undefined) {
    if (!file) {
      return;
    }

    try {
      const dataUrl = await readFileAsDataUrl(file);
      const size = scaledImageSize(await readImageSize(dataUrl));
      const paragraph = globalThis.document.createElement("p");
      const image = globalThis.document.createElement("img");
      image.src = dataUrl;
      image.alt = file.name;
      image.width = size.width;
      image.height = size.height;
      paragraph.append(image);
      editorRef.current?.append(paragraph);
      updateHtml();
    } catch (error) {
      window.alert(error instanceof Error ? error.message : "Не удалось вставить изображение.");
    } finally {
      if (imageInputRef.current) {
        imageInputRef.current.value = "";
      }
    }
  }

  function updateHtml() {
    if (editorRef.current) {
      onChange({ ...document, html: editorRef.current.innerHTML });
      refreshPageView();
    }
  }

  async function openDocx(file: File | undefined) {
    if (!file) {
      return;
    }

    try {
      const imported = await importDocxFile(file);
      onChange({ ...document, title: imported.title, html: imported.html });
    } catch (error) {
      window.alert(error instanceof Error ? error.message : "Не удалось открыть DOCX.");
    } finally {
      if (fileInputRef.current) {
        fileInputRef.current.value = "";
      }
    }
  }

  async function saveDocx() {
    try {
      const fileName = replaceFileExtension(document.title, ".docx");
      const blob = await exportDocxBlob(document.title, editorRef.current?.innerHTML ?? document.html);
      if (isDesktopOfficeBridgeAvailable()) {
        await saveDesktopOfficeFile("docx", fileName, blob);
        return;
      }

      downloadBlobFile(fileName, blob);
    } catch (error) {
      window.alert(error instanceof Error ? error.message : "Не удалось сохранить DOCX.");
    }
  }

  async function chooseDocx() {
    if (isDesktopOfficeBridgeAvailable()) {
      const file = await openDesktopOfficeFile("docx");
      await openDocx(file ?? undefined);
      return;
    }

    fileInputRef.current?.click();
  }

  async function exportPdf() {
    try {
      await exportPdfOrPrint(document.title);
    } catch (error) {
      window.alert(error instanceof Error ? error.message : "Не удалось экспортировать PDF.");
    }
  }

  useQOfficeCommand({
    open: chooseDocx,
    save: saveDocx,
    "export-pdf": exportPdf,
    print: () => window.print()
  });

  return (
    <div className="editor-layout qword-layout">
      <section className="document-stage" aria-label="Редактор QWord">
        <div className="module-toolbar">
          <input
            ref={fileInputRef}
            className="hidden-file-input"
            type="file"
            accept=".docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
            onChange={(event) => void openDocx(event.target.files?.[0])}
            aria-label="Открыть DOCX"
          />
          <input
            ref={imageInputRef}
            className="hidden-file-input"
            type="file"
            accept="image/png,image/jpeg,image/gif,image/bmp"
            onChange={(event) => void insertImage(event.target.files?.[0])}
            aria-label="Вставить изображение"
          />
          <input
            className="file-title-input"
            value={document.title}
            aria-label="Название документа"
            onChange={(event) => onChange({ ...document, title: event.target.value })}
          />
          <div className="segmented-tools" aria-label="Форматирование текста">
            <button type="button" onClick={() => runCommand("bold")} title="Полужирный">
              <Bold size={16} />
            </button>
            <button type="button" onClick={() => runCommand("italic")} title="Курсив">
              <Italic size={16} />
            </button>
            <button type="button" onClick={() => runCommand("underline")} title="Подчеркнутый">
              <Underline size={16} />
            </button>
            <button type="button" onClick={() => runCommand("strikeThrough")} title="Зачеркнутый" aria-label="Зачеркнутый">
              <Strikethrough size={16} />
            </button>
            <button type="button" onClick={() => runCommand("insertUnorderedList")} title="Маркированный список">
              <List size={16} />
            </button>
            <button type="button" onClick={insertLink} title="Вставить ссылку">
              <Link size={16} />
            </button>
            <button type="button" onClick={insertPageBreak} title="Разрыв страницы" aria-label="Разрыв страницы">
              <SeparatorHorizontal size={16} />
            </button>
            <button type="button" onClick={() => imageInputRef.current?.click()} title="Вставить изображение">
              <ImageIcon size={16} />
            </button>
            <button type="button" onClick={() => runCommand("justifyLeft")} title="По левому краю">
              <AlignLeft size={16} />
            </button>
            <button type="button" onClick={() => runCommand("justifyCenter")} title="По центру">
              <AlignCenter size={16} />
            </button>
            <button type="button" onClick={() => runCommand("justifyRight")} title="По правому краю">
              <AlignRight size={16} />
            </button>
          </div>
          <div className="text-color-tools" aria-label="Цвет текста">
            <Type size={16} aria-hidden="true" />
            {textColorSwatches.map((swatch) => (
              <button
                key={swatch.label}
                className="color-swatch"
                type="button"
                onClick={() => applyTextColor(swatch.color)}
                title={swatch.label}
                aria-label={swatch.label}
                style={{ backgroundColor: swatch.color }}
              />
            ))}
          </div>
          <div className="text-color-tools" aria-label="Подсветка текста">
            <PaintBucket size={16} aria-hidden="true" />
            {highlightSwatches.map((swatch) => (
              <button
                key={swatch.label}
                className="color-swatch"
                type="button"
                onClick={() => applyHighlight(swatch.color)}
                title={swatch.label}
                aria-label={swatch.label}
                style={{ backgroundColor: swatch.color }}
              />
            ))}
          </div>
          <select aria-label="Стиль абзаца" onChange={(event) => runCommand("formatBlock", event.target.value)}>
            <option value="p">Обычный текст</option>
            <option value="h1">Заголовок 1</option>
            <option value="h2">Заголовок 2</option>
            <option value="blockquote">Цитата</option>
          </select>
          <button className="compact-command" type="button" onClick={() => void chooseDocx()}>
            <FileUp size={16} />
            Открыть DOCX
          </button>
          <button className="compact-command" type="button" onClick={() => void saveDocx()}>
            <Save size={16} />
            Сохранить DOCX
          </button>
          <button
            className="compact-command"
            type="button"
            onClick={() => downloadTextFile(document.title.replace(/\.qdocx$/i, ".html"), document.html, "text/html")}
          >
            <Download size={16} />
            Экспорт HTML
          </button>
          <button className="compact-command" type="button" onClick={() => void exportPdf()}>
            <FileText size={16} />
            Экспорт PDF
          </button>
          <span className="toolbar-status">
            Страниц: {pageView.count} · Слов: {wordCount}
          </span>
        </div>

        <div className="page-ruler" aria-hidden="true">
          {Array.from({ length: 9 }, (_, index) => (
            <span key={index}>{index + 1}</span>
          ))}
        </div>

        <div className="vertical-page-ruler" aria-hidden="true">
          {Array.from({ length: 19 }, (_, index) => (
            <span key={index}>{index + 1}</span>
          ))}
        </div>

        <div className="paper-frame">
          <article
            ref={editorRef}
            className="paper document-content"
            contentEditable
            suppressContentEditableWarning
            onInput={updateHtml}
            dangerouslySetInnerHTML={{ __html: document.html }}
            aria-label="Содержимое документа"
          />
          {pageView.count > 1 ? (
            <div className="page-break-guides" aria-hidden="true">
              {Array.from({ length: pageView.count - 1 }, (_, index) => (
                <span key={index} style={{ top: `${(index + 1) * pageView.height}px` }}>
                  Страница {index + 2}
                </span>
              ))}
            </div>
          ) : null}
        </div>
      </section>
    </div>
  );
}
