import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
import { useState } from "react";
import { afterEach, describe, expect, it } from "vitest";
import { vi } from "vitest";
import type { WordDocument } from "../../types";
import { QWord, qwordCurrentPageFromViewport } from "./QWord";

afterEach(() => {
  cleanup();
});

describe("qwordCurrentPageFromViewport", () => {
  it("вычисляет текущую страницу по положению листа в области просмотра", () => {
    expect(qwordCurrentPageFromViewport({ stageTop: 34, paperTop: 227, pageHeight: 1123, pageCount: 3, zoom: 1 })).toBe(1);
    expect(qwordCurrentPageFromViewport({ stageTop: 34, paperTop: -1090, pageHeight: 1123, pageCount: 3, zoom: 1 })).toBe(2);
    expect(qwordCurrentPageFromViewport({ stageTop: 34, paperTop: -6000, pageHeight: 1123, pageCount: 3, zoom: 1 })).toBe(3);
  });

  it("учитывает масштаб при расчете текущей страницы", () => {
    expect(qwordCurrentPageFromViewport({ stageTop: 34, paperTop: -1200, pageHeight: 1123, pageCount: 3, zoom: 1 })).toBe(2);
    expect(qwordCurrentPageFromViewport({ stageTop: 34, paperTop: -1200, pageHeight: 1123, pageCount: 3, zoom: 1.2 })).toBe(1);
  });
});

describe("QWord", () => {
  it("переключает вкладки ленты и показывает команды выбранной вкладки", () => {
    const originalResizeObserver = globalThis.ResizeObserver;
    const originalRequestAnimationFrame = window.requestAnimationFrame;

    globalThis.ResizeObserver = class {
      observe() {}
      disconnect() {}
    } as unknown as typeof ResizeObserver;
    window.requestAnimationFrame = ((callback: FrameRequestCallback) => {
      callback(0);
      return 1;
    }) as typeof window.requestAnimationFrame;

    const document: WordDocument = {
      id: "word-ribbon-tabs-test",
      title: "Лента.docx",
      updatedAt: "2026-06-02T00:00:00.000Z",
      html: "<p>Текст</p>"
    };

    try {
      render(<QWord document={document} onChange={vi.fn()} />);

      expect(screen.getByRole("tab", { name: "Главная" })).toHaveAttribute("aria-selected", "true");
      expect(screen.getByRole("region", { name: "Шрифт" })).toBeInTheDocument();

      fireEvent.click(screen.getByRole("tab", { name: "Вставка" }));

      expect(screen.getByRole("tab", { name: "Вставка" })).toHaveAttribute("aria-selected", "true");
      expect(screen.getByRole("region", { name: "Вставка" })).toBeInTheDocument();
      expect(screen.queryByRole("region", { name: "Шрифт" })).not.toBeInTheDocument();

      fireEvent.click(screen.getByRole("tab", { name: "Файл" }));

      expect(screen.getByRole("tab", { name: "Файл" })).toHaveAttribute("aria-selected", "true");
      expect(screen.getByRole("region", { name: "Файл" })).toBeInTheDocument();
      expect(screen.queryByRole("region", { name: "Вставка" })).not.toBeInTheDocument();
    } finally {
      globalThis.ResizeObserver = originalResizeObserver;
      window.requestAnimationFrame = originalRequestAnimationFrame;
    }
  });

  it("переключает режим просмотра документа на вкладке Вид", () => {
    const originalResizeObserver = globalThis.ResizeObserver;
    const originalRequestAnimationFrame = window.requestAnimationFrame;

    globalThis.ResizeObserver = class {
      observe() {}
      disconnect() {}
    } as unknown as typeof ResizeObserver;
    window.requestAnimationFrame = ((callback: FrameRequestCallback) => {
      callback(0);
      return 1;
    }) as typeof window.requestAnimationFrame;

    const document: WordDocument = {
      id: "word-view-mode-test",
      title: "Вид.docx",
      updatedAt: "2026-06-02T00:00:00.000Z",
      html: "<p>Текст</p>"
    };

    try {
      render(<QWord document={document} onChange={vi.fn()} />);
      const stage = screen.getByLabelText("Редактор QWord");

      expect(stage).toHaveClass("qword-view-print");

      fireEvent.click(screen.getByRole("tab", { name: "Вид" }));
      fireEvent.click(screen.getByRole("button", { name: "Веб-документ" }));

      expect(stage).toHaveClass("qword-view-web");
      expect(screen.getByRole("button", { name: "Веб-документ" })).toHaveAttribute("aria-pressed", "true");
    } finally {
      globalThis.ResizeObserver = originalResizeObserver;
      window.requestAnimationFrame = originalRequestAnimationFrame;
    }
  });

  it("вставляет таблицу выбранного размера через вкладку Вставка", () => {
    const originalResizeObserver = globalThis.ResizeObserver;
    const originalRequestAnimationFrame = window.requestAnimationFrame;
    const onChange = vi.fn();

    globalThis.ResizeObserver = class {
      observe() {}
      disconnect() {}
    } as unknown as typeof ResizeObserver;
    window.requestAnimationFrame = ((callback: FrameRequestCallback) => {
      callback(0);
      return 1;
    }) as typeof window.requestAnimationFrame;

    const document: WordDocument = {
      id: "word-insert-table-test",
      title: "Таблица.docx",
      updatedAt: "2026-06-02T00:00:00.000Z",
      html: "<p>Перед таблицей</p>"
    };

    try {
      render(<QWord document={document} onChange={onChange} />);

      fireEvent.click(screen.getByRole("tab", { name: "Вставка" }));
      fireEvent.change(screen.getByLabelText("Строки таблицы"), { target: { value: "2" } });
      fireEvent.change(screen.getByLabelText("Столбцы таблицы"), { target: { value: "3" } });
      fireEvent.click(screen.getByRole("button", { name: "Таблица" }));

      const updatedDocument = onChange.mock.calls[onChange.mock.calls.length - 1]?.[0] as WordDocument | undefined;
      expect(updatedDocument?.html).toContain("<table>");
      expect(updatedDocument?.html.match(/<tr/g)).toHaveLength(2);
      expect(updatedDocument?.html.match(/<td/g)).toHaveLength(6);
    } finally {
      globalThis.ResizeObserver = originalResizeObserver;
      window.requestAnimationFrame = originalRequestAnimationFrame;
    }
  });

  it("сохраняет активную ячейку после вставки таблицы и перерендера", () => {
    const originalResizeObserver = globalThis.ResizeObserver;
    const originalRequestAnimationFrame = window.requestAnimationFrame;
    const onChange = vi.fn();

    globalThis.ResizeObserver = class {
      observe() {}
      disconnect() {}
    } as unknown as typeof ResizeObserver;
    window.requestAnimationFrame = ((callback: FrameRequestCallback) => {
      callback(0);
      return 1;
    }) as typeof window.requestAnimationFrame;

    const document: WordDocument = {
      id: "word-insert-table-restore-test",
      title: "Активная таблица.docx",
      updatedAt: "2026-06-02T00:00:00.000Z",
      html: "<p>Перед таблицей</p>"
    };

    function StatefulQWord() {
      const [currentDocument, setCurrentDocument] = useState(document);
      return (
        <QWord
          document={currentDocument}
          onChange={(nextDocument) => {
            onChange(nextDocument);
            setCurrentDocument(nextDocument);
          }}
        />
      );
    }

    try {
      render(<StatefulQWord />);

      fireEvent.click(screen.getByRole("tab", { name: "Вставка" }));
      const tableButton = screen.getByRole("button", { name: "Таблица" });
      expect(fireEvent.mouseDown(tableButton)).toBe(false);
      fireEvent.click(tableButton);

      const rowButton = screen.getByRole("button", { name: "Строка ниже" });
      expect(fireEvent.mouseDown(rowButton)).toBe(false);
      fireEvent.click(rowButton);

      const updatedDocument = onChange.mock.calls[onChange.mock.calls.length - 1]?.[0] as WordDocument | undefined;
      expect(updatedDocument?.html.match(/<tr/g)).toHaveLength(4);
      expect(updatedDocument?.html.match(/<td/g)).toHaveLength(12);
    } finally {
      globalThis.ResizeObserver = originalResizeObserver;
      window.requestAnimationFrame = originalRequestAnimationFrame;
    }
  });

  it("добавляет строку и столбец к выбранной таблице", () => {
    const originalResizeObserver = globalThis.ResizeObserver;
    const originalRequestAnimationFrame = window.requestAnimationFrame;
    const onChange = vi.fn();

    globalThis.ResizeObserver = class {
      observe() {}
      disconnect() {}
    } as unknown as typeof ResizeObserver;
    window.requestAnimationFrame = ((callback: FrameRequestCallback) => {
      callback(0);
      return 1;
    }) as typeof window.requestAnimationFrame;

    const document: WordDocument = {
      id: "word-table-structure-test",
      title: "Структура таблицы.docx",
      updatedAt: "2026-06-02T00:00:00.000Z",
      html: "<table><tbody><tr><td>A</td><td>B</td></tr></tbody></table>"
    };

    function selectCell(text: string) {
      const cell = screen.getByText(text).closest("td");
      expect(cell).toBeInTheDocument();
      const range = window.document.createRange();
      range.selectNodeContents(cell as HTMLTableCellElement);
      window.getSelection()?.removeAllRanges();
      window.getSelection()?.addRange(range);
    }

    function StatefulQWord() {
      const [currentDocument, setCurrentDocument] = useState(document);
      return (
        <QWord
          document={currentDocument}
          onChange={(nextDocument) => {
            onChange(nextDocument);
            setCurrentDocument(nextDocument);
          }}
        />
      );
    }

    try {
      render(<StatefulQWord />);

      fireEvent.click(screen.getByRole("tab", { name: "Вставка" }));
      selectCell("A");
      const rowButton = screen.getByRole("button", { name: "Строка ниже" });
      expect(fireEvent.mouseDown(rowButton)).toBe(false);
      fireEvent.click(rowButton);

      let updatedDocument = onChange.mock.calls[onChange.mock.calls.length - 1]?.[0] as WordDocument | undefined;
      expect(updatedDocument?.html.match(/<tr/g)).toHaveLength(2);
      expect(updatedDocument?.html.match(/<td/g)).toHaveLength(4);

      selectCell("A");
      const columnButton = screen.getByRole("button", { name: "Столбец справа" });
      expect(fireEvent.mouseDown(columnButton)).toBe(false);
      fireEvent.click(columnButton);

      updatedDocument = onChange.mock.calls[onChange.mock.calls.length - 1]?.[0] as WordDocument | undefined;
      expect(updatedDocument?.html.match(/<tr/g)).toHaveLength(2);
      expect(updatedDocument?.html.match(/<td/g)).toHaveLength(6);
    } finally {
      globalThis.ResizeObserver = originalResizeObserver;
      window.requestAnimationFrame = originalRequestAnimationFrame;
    }
  });

  it("обновляет текущую страницу в статусе при прокрутке документа", async () => {
    const originalResizeObserver = globalThis.ResizeObserver;
    const originalRequestAnimationFrame = window.requestAnimationFrame;
    const onStatusChange = vi.fn();

    globalThis.ResizeObserver = class {
      observe() {}
      disconnect() {}
    } as unknown as typeof ResizeObserver;
    window.requestAnimationFrame = ((callback: FrameRequestCallback) => {
      callback(0);
      return 1;
    }) as typeof window.requestAnimationFrame;

    const document: WordDocument = {
      id: "word-scroll-test",
      title: "Прокрутка.docx",
      updatedAt: "2026-06-02T00:00:00.000Z",
      html: "<p>Первая страница</p>"
    };

    try {
      const view = render(<QWord document={document} onChange={vi.fn()} onStatusChange={onStatusChange} />);
      const stage = view.getByLabelText("Редактор QWord");
      const paper = view.getByLabelText("Содержимое документа");

      Object.defineProperty(paper, "scrollHeight", { configurable: true, value: 2400 });
      stage.getBoundingClientRect = () => ({ bottom: 884, height: 850, left: 0, right: 1008, top: 34, width: 1008, x: 0, y: 34, toJSON: () => ({}) });
      paper.getBoundingClientRect = () => ({ bottom: -77, height: 1123, left: 107, right: 901, top: -1200, width: 794, x: 107, y: -1200, toJSON: () => ({}) });

      fireEvent.scroll(stage);

      await waitFor(() => expect(onStatusChange).toHaveBeenLastCalledWith({ currentPage: 2, pageCount: 3, wordCount: 2 }));
    } finally {
      globalThis.ResizeObserver = originalResizeObserver;
      window.requestAnimationFrame = originalRequestAnimationFrame;
    }
  });
});
