import { useCallback, useLayoutEffect, useMemo, useRef, useState } from "react";
import type { CSSProperties, MouseEvent } from "react";
import {
  AlignCenter,
  AlignJustify,
  AlignLeft,
  AlignRight,
  Bold,
  ClipboardPaste,
  Columns2,
  Copy,
  Download,
  FileText,
  FileUp,
  Image as ImageIcon,
  Italic,
  Link,
  List,
  ListOrdered,
  PaintBucket,
  RemoveFormatting,
  Replace,
  Save,
  Scissors,
  Search,
  SeparatorHorizontal,
  Strikethrough,
  Subscript,
  Superscript,
  Table2,
  Type,
  Rows3,
  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;
  zoom?: number;
  viewMode?: WordViewMode;
  onChange: (document: WordDocument) => void;
  onStatusChange?: (status: QWordStatus) => void;
  onViewModeChange?: (viewMode: WordViewMode) => void;
}

interface TableCellLocation {
  tableIndex: number;
  rowIndex: number;
  cellIndex: number;
}

export interface QWordStatus {
  currentPage: number;
  pageCount: number;
  wordCount: number;
}

type PageSearchWindow = Window &
  typeof globalThis & {
    find?: (query: string) => boolean;
  };

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 fontFamilyOptions = ["Calibri", "Aptos", "Arial", "Times New Roman", "Courier New"];
const fontSizeOptions = [9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 36];
const fontSizeCommandMap: Record<number, string> = {
  9: "1",
  10: "2",
  11: "2",
  12: "3",
  14: "4",
  16: "4",
  18: "5",
  20: "5",
  24: "6",
  28: "6",
  36: "7"
};
const styleGalleryOptions = [
  { value: "p", label: "Обычный" },
  { value: "h1", label: "Заголовок 1" },
  { value: "h2", label: "Заголовок 2" },
  { value: "h3", label: "Заголовок 3" },
  { value: "h4", label: "Заголовок 4" },
  { value: "h5", label: "Заголовок 5" },
  { value: "h6", label: "Заголовок 6" },
  { value: "blockquote", label: "Цитата" }
];
type WordRibbonTabId = "file" | "home" | "insert" | "design" | "layout" | "references" | "mailings" | "review" | "view" | "help";
export type WordViewMode = "print" | "read" | "web";

const wordRibbonTabs: Array<{ id: WordRibbonTabId; label: string }> = [
  { id: "file", label: "Файл" },
  { id: "home", label: "Главная" },
  { id: "insert", label: "Вставка" },
  { id: "design", label: "Конструктор" },
  { id: "layout", label: "Макет" },
  { id: "references", label: "Ссылки" },
  { id: "mailings", label: "Рассылки" },
  { id: "review", label: "Рецензирование" },
  { id: "view", label: "Вид" },
  { id: "help", label: "Справка" }
];

const wordViewModes: Array<{ id: WordViewMode; label: string }> = [
  { id: "print", label: "Разметка" },
  { id: "read", label: "Чтение" },
  { id: "web", label: "Веб-документ" }
];
const tableSizeOptions = [1, 2, 3, 4, 5, 6, 7, 8];

function findInPage(query: string) {
  return (window as PageSearchWindow).find?.(query) ?? false;
}

const defaultPageViewHeight = 1180;
const defaultPageGap = 28;

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

function clampedTableSize(value: number) {
  return Number.isFinite(value) ? Math.min(8, Math.max(1, Math.trunc(value))) : 3;
}

function qwordTableHtml(rows: number, columns: number) {
  const rowCount = clampedTableSize(rows);
  const columnCount = clampedTableSize(columns);
  const cells = Array.from({ length: columnCount }, () => "<td><br></td>").join("");
  const tableRows = Array.from({ length: rowCount }, () => `<tr>${cells}</tr>`).join("");
  return `<table><tbody>${tableRows}</tbody></table>`;
}

function createBlankTableCell(tagName: "td" | "th" = "td") {
  const cell = globalThis.document.createElement(tagName);
  cell.append(globalThis.document.createElement("br"));
  return cell;
}

function tableCellFromSelection(editor: HTMLElement): HTMLTableCellElement | undefined {
  const node = window.getSelection()?.anchorNode;
  const element = node instanceof Element ? node : node?.parentElement;
  const cell = element?.closest("td,th");
  return cell instanceof HTMLTableCellElement && editor.contains(cell) ? cell : undefined;
}

function tableCellLocation(editor: HTMLElement, cell: HTMLTableCellElement): TableCellLocation | undefined {
  const table = cell.closest("table");
  const row = cell.parentElement;
  if (!(table instanceof HTMLTableElement) || !(row instanceof HTMLTableRowElement) || !editor.contains(table)) {
    return undefined;
  }

  const tableIndex = Array.from(editor.querySelectorAll("table")).indexOf(table);
  const rowIndex = Array.from(table.rows).indexOf(row);
  const cellIndex = cell.cellIndex;
  return tableIndex >= 0 && rowIndex >= 0 && cellIndex >= 0 ? { cellIndex, rowIndex, tableIndex } : undefined;
}

function tableCellFromLocation(editor: HTMLElement, location: TableCellLocation): HTMLTableCellElement | undefined {
  const table = Array.from(editor.querySelectorAll("table"))[location.tableIndex];
  const cell = table?.rows[location.rowIndex]?.cells[location.cellIndex];
  return cell instanceof HTMLTableCellElement ? cell : undefined;
}

function placeCaretInTableCell(cell: HTMLTableCellElement) {
  if (cell.childNodes.length === 0) {
    cell.append(globalThis.document.createElement("br"));
  }

  const range = globalThis.document.createRange();
  range.selectNodeContents(cell);
  range.collapse(true);
  const selection = window.getSelection();
  selection?.removeAllRanges();
  selection?.addRange(range);
}

function keepEditorSelection(event: MouseEvent<HTMLButtonElement>) {
  event.preventDefault();
}

export function qwordCurrentPageFromViewport(input: { stageTop: number; paperTop: number; pageHeight: number; pageCount: number; zoom: number }) {
  const pageCount = Math.max(1, input.pageCount);
  const zoom = Number.isFinite(input.zoom) && input.zoom > 0 ? input.zoom : 1;
  const visualPageHeight = Math.max(1, input.pageHeight * zoom);
  const visiblePaperOffset = Math.max(0, input.stageTop - input.paperTop);
  return Math.min(pageCount, Math.max(1, Math.floor(visiblePaperOffset / visualPageHeight) + 1));
}

export function QWord({ document, zoom = 100, viewMode, onChange, onStatusChange, onViewModeChange }: QWordProps) {
  const stageRef = useRef<HTMLElement>(null);
  const editorRef = useRef<HTMLDivElement>(null);
  const activeTableCellRef = useRef<HTMLTableCellElement | null>(null);
  const activeTableCellLocationRef = useRef<TableCellLocation | null>(null);
  const restoreActiveTableCellAfterRenderRef = useRef(false);
  const fileInputRef = useRef<HTMLInputElement>(null);
  const imageInputRef = useRef<HTMLInputElement>(null);
  const [activeRibbonTab, setActiveRibbonTab] = useState<WordRibbonTabId>("home");
  const [internalViewMode, setInternalViewMode] = useState<WordViewMode>("print");
  const [tableRowCount, setTableRowCount] = useState(3);
  const [tableColumnCount, setTableColumnCount] = useState(3);
  const [pageView, setPageView] = useState({ count: 1, current: 1, height: defaultPageViewHeight });
  const wordCount = useMemo(() => countWords(document.html), [document.html]);
  const stageStyle = { "--qword-zoom": String(zoom / 100) } as CSSProperties;
  const pageFrameStyle = {
    "--qword-page-count": String(pageView.count),
    "--qword-page-gap": `${defaultPageGap}px`,
    minHeight: `${pageView.count * pageView.height + Math.max(0, pageView.count - 1) * defaultPageGap}px`,
    paddingBottom: pageView.count > 1 ? `${(pageView.count - 1) * defaultPageGap}px` : undefined
  } as CSSProperties;
  const activeRibbonTabLabel = wordRibbonTabs.find((tab) => tab.id === activeRibbonTab)?.label ?? "Главная";
  const activeViewMode = viewMode ?? internalViewMode;
  const stageClassName = ["document-stage", `qword-view-${activeViewMode}`].join(" ");

  const changeViewMode = useCallback(
    (nextViewMode: WordViewMode) => {
      setInternalViewMode(nextViewMode);
      onViewModeChange?.(nextViewMode);
    },
    [onViewModeChange]
  );

  useLayoutEffect(() => {
    const editor = editorRef.current;
    if (!editor || editor.innerHTML === document.html) {
      return;
    }

    editor.innerHTML = document.html;
  }, [document.html]);

  function currentPageFromStage(pageCount: number, pageHeight: number) {
    const stage = stageRef.current;
    const editor = editorRef.current;
    if (!stage || !editor) {
      return 1;
    }

    const stageRect = stage.getBoundingClientRect();
    const paperRect = editor.getBoundingClientRect();
    const currentZoom = Number.parseFloat(window.getComputedStyle(stage).getPropertyValue("--qword-zoom"));
    return qwordCurrentPageFromViewport({
      stageTop: stageRect.top,
      paperTop: paperRect.top,
      pageHeight,
      pageCount,
      zoom: Number.isFinite(currentZoom) ? currentZoom : 1
    });
  }

  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));
      const currentPage = currentPageFromStage(count, height);
      setPageView((current) =>
        current.count === count && current.height === height && current.current === currentPage ? current : { count, current: currentPage, height }
      );
    });
  }

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

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

  useLayoutEffect(() => {
    if (!restoreActiveTableCellAfterRenderRef.current) {
      return;
    }

    restoreActiveTableCellAfterRenderRef.current = false;
    const editor = editorRef.current;
    const location = activeTableCellLocationRef.current;
    if (!editor || !location) {
      return;
    }

    const restoredCell = tableCellFromLocation(editor, location);
    if (restoredCell) {
      activeTableCellRef.current = restoredCell;
      placeCaretInTableCell(restoredCell);
    }
  }, [document.html]);

  useLayoutEffect(() => {
    onStatusChange?.({ currentPage: pageView.current, pageCount: pageView.count, wordCount });
  }, [onStatusChange, pageView.count, pageView.current, wordCount]);

  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 applyFontFamily(fontFamily: string) {
    if (fontFamily) {
      runCommand("fontName", fontFamily);
    }
  }

  function applyFontSize(fontSize: string) {
    const pointSize = Number(fontSize);
    if (Number.isFinite(pointSize)) {
      runCommand("fontSize", fontSizeCommandMap[pointSize] ?? "3");
    }
  }

  function findText() {
    const query = window.prompt("Найти текст");
    if (!query) {
      return;
    }

    editorRef.current?.focus();
    findInPage(query);
  }

  function replaceText() {
    const query = window.prompt("Найти текст для замены");
    if (!query) {
      return;
    }

    editorRef.current?.focus();
    if (!findInPage(query)) {
      return;
    }

    const replacement = window.prompt("Заменить на", "") ?? "";
    globalThis.document.execCommand("insertText", false, replacement);
    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>');
  }

  function setActiveTableCell(cell: HTMLTableCellElement) {
    const editor = editorRef.current;
    activeTableCellRef.current = cell;
    if (editor) {
      activeTableCellLocationRef.current = tableCellLocation(editor, cell) ?? activeTableCellLocationRef.current;
    }
  }

  function queueActiveTableCellRestore(cell: HTMLTableCellElement) {
    setActiveTableCell(cell);
    restoreActiveTableCellAfterRenderRef.current = true;
  }

  function rememberActiveTableCell(): HTMLTableCellElement | undefined {
    const editor = editorRef.current;
    if (!editor) {
      return undefined;
    }
    const cell = tableCellFromSelection(editor);
    if (cell) {
      setActiveTableCell(cell);
    }
    return cell;
  }

  function rememberActiveTableCellFromEvent(event: MouseEvent<HTMLElement>) {
    const editor = editorRef.current;
    const target = event.target;
    const cell = target instanceof Element ? target.closest("td,th") : undefined;
    if (editor && cell instanceof HTMLTableCellElement && editor.contains(cell)) {
      setActiveTableCell(cell);
      return;
    }

    rememberActiveTableCell();
  }

  function activeTableCell(): HTMLTableCellElement | undefined {
    const editor = editorRef.current;
    if (!editor) {
      return undefined;
    }

    const selectedCell = rememberActiveTableCell();
    if (selectedCell) {
      return selectedCell;
    }

    const rememberedCell = activeTableCellRef.current;
    if (rememberedCell && editor.contains(rememberedCell)) {
      return rememberedCell;
    }

    const rememberedLocation = activeTableCellLocationRef.current;
    if (!rememberedLocation) {
      return undefined;
    }

    const restoredCell = tableCellFromLocation(editor, rememberedLocation);
    if (restoredCell) {
      activeTableCellRef.current = restoredCell;
    }
    return restoredCell;
  }

  function insertTable() {
    const editor = editorRef.current;
    if (!editor) {
      return;
    }

    editor.focus();
    const html = `${qwordTableHtml(tableRowCount, tableColumnCount)}<p><br></p>`;
    const selection = window.getSelection();
    const range = selection?.rangeCount ? selection.getRangeAt(0) : undefined;
    if (range && (range.commonAncestorContainer === editor || editor.contains(range.commonAncestorContainer))) {
      range.deleteContents();
      const fragment = globalThis.document.createRange().createContextualFragment(html);
      const firstCell = fragment.querySelector("td,th") as HTMLTableCellElement | null;
      const lastNode = fragment.lastChild;
      range.insertNode(fragment);
      if (firstCell) {
        queueActiveTableCellRestore(firstCell);
        placeCaretInTableCell(firstCell);
      } else if (lastNode) {
        range.setStartAfter(lastNode);
        range.collapse(true);
        selection?.removeAllRanges();
        selection?.addRange(range);
      }
    } else {
      editor.insertAdjacentHTML("beforeend", html);
      const tables = editor.querySelectorAll("table");
      const firstCell = tables[tables.length - 1]?.querySelector("td,th") as HTMLTableCellElement | null;
      if (firstCell) {
        queueActiveTableCellRestore(firstCell);
        placeCaretInTableCell(firstCell);
      }
    }

    updateHtml();
  }

  function insertTableRowBelow() {
    const editor = editorRef.current;
    if (!editor) {
      return;
    }

    const cell = activeTableCell();
    const row = cell?.parentElement;
    if (!cell || !(row instanceof HTMLTableRowElement)) {
      window.alert("Поместите курсор в ячейку таблицы.");
      return;
    }

    const newRow = globalThis.document.createElement("tr");
    Array.from({ length: Math.max(1, row.cells.length) }, () => createBlankTableCell()).forEach((newCell) => newRow.append(newCell));
    row.after(newRow);
    const nextCell = newRow.cells[Math.min(cell.cellIndex, newRow.cells.length - 1)] ?? newRow.cells[0];
    queueActiveTableCellRestore(nextCell);
    placeCaretInTableCell(nextCell);
    updateHtml();
  }

  function insertTableColumnRight() {
    const editor = editorRef.current;
    if (!editor) {
      return;
    }

    const cell = activeTableCell();
    const row = cell?.parentElement;
    const table = cell?.closest("table");
    if (!cell || !(row instanceof HTMLTableRowElement) || !(table instanceof HTMLTableElement)) {
      window.alert("Поместите курсор в ячейку таблицы.");
      return;
    }

    const columnIndex = cell.cellIndex;
    let selectedNewCell: HTMLTableCellElement | undefined;
    Array.from(table.rows).forEach((tableRow) => {
      const referenceCell = tableRow.cells[Math.min(columnIndex, Math.max(0, tableRow.cells.length - 1))];
      const tagName = referenceCell?.tagName.toLowerCase() === "th" ? "th" : "td";
      const newCell = createBlankTableCell(tagName);
      if (referenceCell?.nextSibling) {
        tableRow.insertBefore(newCell, referenceCell.nextSibling);
      } else {
        tableRow.append(newCell);
      }

      if (tableRow === row) {
        selectedNewCell = newCell;
      }
    });

    if (selectedNewCell) {
      queueActiveTableCellRestore(selectedNewCell);
      placeCaretInTableCell(selectedNewCell);
    }
    updateHtml();
  }

  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() {
    rememberActiveTableCell();
    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">
      <div className="module-toolbar word-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="Вставить изображение"
          />
          <div className="word-ribbon-tabs" role="tablist" aria-label="Вкладки QWord">
            {wordRibbonTabs.map((tab) => (
              <button
                key={tab.id}
                id={`qword-ribbon-tab-${tab.id}`}
                className={activeRibbonTab === tab.id ? "is-active" : ""}
                type="button"
                role="tab"
                aria-controls="qword-ribbon-panel"
                aria-selected={activeRibbonTab === tab.id}
                onClick={() => setActiveRibbonTab(tab.id)}
              >
                {tab.label}
              </button>
            ))}
          </div>
          <div
            id="qword-ribbon-panel"
            className={`word-ribbon word-ribbon-${activeRibbonTab}`}
            role="tabpanel"
            aria-label={`Лента команд QWord: ${activeRibbonTabLabel}`}
            aria-labelledby={`qword-ribbon-tab-${activeRibbonTab}`}
          >
            {activeRibbonTab === "file" ? (
            <section className="word-ribbon-group word-file-group" aria-label="Файл">
              <div className="word-file-actions">
                <button className="word-icon-command" type="button" onClick={() => void chooseDocx()} title="Открыть" aria-label="Открыть">
                  <FileUp size={16} />
                </button>
                <button className="word-icon-command" type="button" onClick={() => void saveDocx()} title="Сохранить" aria-label="Сохранить">
                  <Save size={16} />
                </button>
                <button
                  className="word-icon-command"
                  type="button"
                  onClick={() => downloadTextFile(document.title.replace(/\.qdocx$/i, ".html"), document.html, "text/html")}
                  title="Сохранить как HTML"
                  aria-label="Сохранить как HTML"
                >
                  <Download size={16} />
                </button>
                <button className="word-icon-command" type="button" onClick={() => void exportPdf()} title="Экспорт PDF" aria-label="Экспорт PDF">
                  <FileText size={16} />
                </button>
              </div>
              <span className="word-ribbon-title">Файл</span>
            </section>
            ) : null}

            {activeRibbonTab === "home" ? (
            <section className="word-ribbon-group word-clipboard-group" aria-label="Буфер обмена">
              <button className="word-large-command" type="button" onClick={() => runCommand("paste")} title="Вставить">
                <ClipboardPaste size={22} />
                <span>Вставить</span>
              </button>
              <div className="word-command-stack">
                <button type="button" onClick={() => runCommand("cut")} title="Вырезать">
                  <Scissors size={15} />
                  Вырезать
                </button>
                <button type="button" onClick={() => runCommand("copy")} title="Копировать">
                  <Copy size={15} />
                  Копировать
                </button>
                <button type="button" onClick={() => runCommand("removeFormat")} title="Очистить формат">
                  <RemoveFormatting size={15} />
                  Очистить
                </button>
              </div>
              <span className="word-ribbon-title">Буфер обмена</span>
            </section>
            ) : null}

            {activeRibbonTab === "home" || activeRibbonTab === "design" ? (
            <section className="word-ribbon-group word-font-group" aria-label="Шрифт">
              <div className="word-select-row">
                <select className="word-font-select" aria-label="Шрифт" defaultValue="Calibri" onChange={(event) => applyFontFamily(event.target.value)}>
                  {fontFamilyOptions.map((fontFamily) => (
                    <option key={fontFamily} value={fontFamily}>
                      {fontFamily}
                    </option>
                  ))}
                </select>
                <select className="word-font-size-select" aria-label="Размер шрифта" defaultValue="11" onChange={(event) => applyFontSize(event.target.value)}>
                  {fontSizeOptions.map((fontSize) => (
                    <option key={fontSize} value={fontSize}>
                      {fontSize}
                    </option>
                  ))}
                </select>
              </div>
              <div className="segmented-tools word-format-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("subscript")} title="Нижний индекс">
                  <Subscript size={16} />
                </button>
                <button type="button" onClick={() => runCommand("superscript")} title="Верхний индекс">
                  <Superscript size={16} />
                </button>
              </div>
              <div className="word-color-row">
                <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>
              </div>
              <span className="word-ribbon-title">Шрифт</span>
            </section>
            ) : null}

            {activeRibbonTab === "home" || activeRibbonTab === "layout" ? (
            <section className="word-ribbon-group word-paragraph-group" aria-label="Абзац">
              <div className="segmented-tools" aria-label="Списки">
                <button type="button" onClick={() => runCommand("insertUnorderedList")} title="Маркированный список">
                  <List size={16} />
                </button>
                <button type="button" onClick={() => runCommand("insertOrderedList")} title="Нумерованный список">
                  <ListOrdered size={16} />
                </button>
              </div>
              <div className="segmented-tools" aria-label="Выравнивание">
                <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>
                <button type="button" onClick={() => runCommand("justifyFull")} title="По ширине">
                  <AlignJustify size={16} />
                </button>
              </div>
              <button className="compact-command" type="button" onClick={insertPageBreak} title="Разрыв страницы" aria-label="Разрыв страницы">
                <SeparatorHorizontal size={16} />
                Разрыв
              </button>
              <span className="word-ribbon-title">Абзац</span>
            </section>
            ) : null}

            {activeRibbonTab === "home" || activeRibbonTab === "design" ? (
            <section className="word-ribbon-group word-styles-group" aria-label="Стили">
              <div className="word-style-gallery">
                {styleGalleryOptions.map((style) => (
                  <button key={style.value} type="button" onClick={() => runCommand("formatBlock", style.value)}>
                    <span>AaБбВв</span>
                    {style.label}
                  </button>
                ))}
              </div>
              <span className="word-ribbon-title">Стили</span>
            </section>
            ) : null}

            {activeRibbonTab === "insert" || activeRibbonTab === "references" ? (
            <section className="word-ribbon-group word-insert-group" aria-label="Вставка">
              <button className="compact-command" type="button" onClick={insertLink}>
                <Link size={16} />
                Ссылка
              </button>
              <button className="compact-command" type="button" onClick={() => imageInputRef.current?.click()}>
                <ImageIcon size={16} />
                Рисунок
              </button>
              <span className="word-ribbon-title">Вставка</span>
            </section>
            ) : null}

            {activeRibbonTab === "insert" ? (
            <section className="word-ribbon-group word-table-group" aria-label="Таблица">
              <div className="word-table-controls">
                <label className="word-mini-field">
                  <span>Строки</span>
                  <select aria-label="Строки таблицы" value={tableRowCount} onChange={(event) => setTableRowCount(clampedTableSize(Number(event.target.value)))}>
                    {tableSizeOptions.map((size) => (
                      <option key={size} value={size}>
                        {size}
                      </option>
                    ))}
                  </select>
                </label>
                <label className="word-mini-field">
                  <span>Столбцы</span>
                  <select aria-label="Столбцы таблицы" value={tableColumnCount} onChange={(event) => setTableColumnCount(clampedTableSize(Number(event.target.value)))}>
                    {tableSizeOptions.map((size) => (
                      <option key={size} value={size}>
                        {size}
                      </option>
                    ))}
                  </select>
                </label>
                <button className="compact-command" type="button" onMouseDown={keepEditorSelection} onClick={insertTable}>
                  <Table2 size={16} />
                  Таблица
                </button>
                <button className="compact-command word-table-structure-command" type="button" onMouseDown={keepEditorSelection} onClick={insertTableRowBelow}>
                  <Rows3 size={16} />
                  Строка ниже
                </button>
                <button className="compact-command word-table-structure-command" type="button" onMouseDown={keepEditorSelection} onClick={insertTableColumnRight}>
                  <Columns2 size={16} />
                  Столбец справа
                </button>
              </div>
              <span className="word-ribbon-title">Таблица</span>
            </section>
            ) : null}

            {activeRibbonTab === "view" ? (
            <section className="word-ribbon-group word-view-group" aria-label="Режимы просмотра">
              <div className="word-view-mode-row">
                {wordViewModes.map((mode) => (
                  <button
                    key={mode.id}
                    className={`compact-command${activeViewMode === mode.id ? " is-active" : ""}`}
                    type="button"
                    aria-label={mode.label}
                    aria-pressed={activeViewMode === mode.id}
                    onClick={() => changeViewMode(mode.id)}
                  >
                    <FileText size={16} />
                    {mode.label}
                  </button>
                ))}
              </div>
              <span className="word-ribbon-title">Вид</span>
            </section>
            ) : null}

            {activeRibbonTab === "home" || activeRibbonTab === "review" ? (
            <section className="word-ribbon-group word-edit-group" aria-label="Редактирование">
              <button className="compact-command" type="button" onClick={findText}>
                <Search size={16} />
                Найти
              </button>
              <button className="compact-command" type="button" onClick={replaceText}>
                <Replace size={16} />
                Заменить
              </button>
              <span className="word-ribbon-title">Редактирование</span>
            </section>
            ) : null}
          </div>
      </div>

      <section ref={stageRef} className={stageClassName} style={stageStyle} onScroll={refreshPageView} aria-label="Редактор QWord">
        <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" style={pageFrameStyle}>
          <div className="page-sheets" aria-hidden="true">
            {Array.from({ length: pageView.count }, (_, index) => (
              <span key={index} className="page-sheet" />
            ))}
          </div>
          <article
            ref={editorRef}
            className="paper document-content"
            contentEditable
            suppressContentEditableWarning
            onInput={updateHtml}
            onFocus={rememberActiveTableCell}
            onKeyUp={rememberActiveTableCell}
            onMouseDown={rememberActiveTableCellFromEvent}
            onMouseUp={rememberActiveTableCellFromEvent}
            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 + index * defaultPageGap}px` }} />
              ))}
            </div>
          ) : null}
        </div>
      </section>
    </div>
  );
}
