import { useRef, useState } from "react";
import type { CSSProperties } from "react";
import { Bold, Copy, Download, EyeOff, FileText, FileUp, Image as ImageIcon, Italic, Link, List, ListOrdered, ListX, Palette, Plus, Save, Trash2, Underline } from "lucide-react";
import type { Slide, SlideDeck, SlideImage, SlideListStyle, SlideTransition } from "../../types";
import { downloadBlobFile, downloadTextFile } from "../../utils/download";
import { exportPptxBlob, importPptxFile } from "../../io/powerPointOffice";
import { replaceFileExtension } from "../../io/fileHelpers";
import { isDesktopOfficeBridgeAvailable, openDesktopOfficeFile, saveDesktopOfficeFile } from "../../io/desktopOfficeFiles";
import { exportPdfOrPrint } from "../../io/pdfExport";
import { useQOfficeCommand } from "../../hooks/useQOfficeCommand";

interface QPowerPointProps {
  deck: SlideDeck;
  onChange: (deck: SlideDeck) => void;
}

type SlideTextFormatField = "titleBold" | "titleItalics" | "titleUnderline" | "subtitleBold" | "subtitleItalics" | "subtitleUnderline";

const themeLabels: Record<Slide["theme"], string> = {
  classic: "Классическая",
  ocean: "Океан",
  graphite: "Графит"
};

const backgroundColorSwatches = [
  { color: "", label: "Фон темы" },
  { color: "FFFFFF", label: "Белый фон" },
  { color: "EAF6FF", label: "Светло-синий фон" },
  { color: "D9EAD3", label: "Светло-зеленый фон" },
  { color: "FFF2CC", label: "Светло-желтый фон" },
  { color: "FCE4D6", label: "Коралловый фон" },
  { color: "1F2937", label: "Темный фон" }
];
const textColorSwatches = [
  { color: "", label: "Цвет темы" },
  { color: "202124", label: "Черный текст" },
  { color: "FFFFFF", label: "Белый текст" },
  { color: "0F5FAE", label: "Синий текст" },
  { color: "0F766E", label: "Зеленый текст" },
  { color: "C2410C", label: "Красный текст" }
];
const slideFontFamilyOptions = ["Aptos", "Calibri", "Courier New", "Times New Roman"];
const titleFontSizeOptions = [24, 28, 30, 34, 40, 44, 48];
const subtitleFontSizeOptions = [14, 16, 18, 20, 22, 24, 28];
const slideTransitionOptions: Array<{ value: SlideTransition | ""; label: string }> = [
  { value: "", label: "Без перехода" },
  { value: "fade", label: "Растворение" },
  { value: "push", label: "Сдвиг" },
  { value: "wipe", label: "Шторка" }
];

const slideWidthInches = 13.333;
const slideHeightInches = 7.5;

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 || 1280, height: image.naturalHeight || 720 }), { once: true });
    image.addEventListener("error", () => reject(new Error("Не удалось загрузить изображение.")), { once: true });
    image.src = src;
  });
}

function scaledSlideImageSize(size: { width: number; height: number }) {
  const aspectRatio = size.width > 0 && size.height > 0 ? size.width / size.height : 16 / 9;
  let width = 4.1;
  let height = width / aspectRatio;

  if (height > 2.6) {
    height = 2.6;
    width = height * aspectRatio;
  }

  return {
    width: roundSlideNumber(width),
    height: roundSlideNumber(height)
  };
}

function roundSlideNumber(value: number) {
  return Math.round(value * 10) / 10;
}

function slideImageStyle(image: SlideImage) {
  return {
    left: `${(image.x / slideWidthInches) * 100}%`,
    top: `${(image.y / slideHeightInches) * 100}%`,
    width: `${(image.width / slideWidthInches) * 100}%`,
    height: `${(image.height / slideHeightInches) * 100}%`
  };
}

function slideCanvasStyle(slide: Slide) {
  const backgroundColor = normalizedHexColor(slide.backgroundColor);
  return backgroundColor ? { background: `#${backgroundColor}` } : undefined;
}

function slideTextStyle(
  color?: string,
  fontSize?: number,
  fontFamily?: string,
  textFormat: { bold?: boolean; italics?: boolean; underline?: boolean; defaultBold?: boolean } = {}
): CSSProperties | undefined {
  const textColor = normalizedHexColor(color);
  const normalizedFontSize = normalizeFontSize(fontSize);
  const normalizedFamily = normalizedFontFamily(fontFamily);
  const effectiveBold = textFormat.bold ?? textFormat.defaultBold;
  const style: CSSProperties = {
    ...(textColor ? { color: `#${textColor}` } : {}),
    ...(normalizedFontSize ? { fontSize: `${normalizedFontSize}pt` } : {}),
    ...(normalizedFamily ? { fontFamily: normalizedFamily } : {}),
    ...(effectiveBold !== undefined ? { fontWeight: effectiveBold ? 780 : 400 } : {}),
    ...(textFormat.italics ? { fontStyle: "italic" } : {}),
    ...(textFormat.underline ? { textDecoration: "underline" } : {})
  };

  return Object.keys(style).length > 0 ? style : undefined;
}

function normalizedHexColor(value?: string) {
  const raw = value?.trim().replace(/^#/, "").toUpperCase() ?? "";
  return /^[0-9A-F]{6}$/.test(raw) ? raw : "";
}

function normalizeFontSize(value?: number) {
  if (!Number.isFinite(value) || !value || value <= 0) {
    return undefined;
  }

  return Math.round(value * 2) / 2;
}

function normalizedFontFamily(value?: string) {
  const normalized = value?.trim().replace(/["<>]/g, "").slice(0, 80) ?? "";
  return normalized && normalized.toLowerCase() !== "arial" ? normalized : "";
}

function clonedSlideImages(images?: SlideImage[]) {
  const suffix = Date.now();
  return images?.map((image, index) => ({ ...image, id: `${image.id}-copy-${suffix}-${index}` }));
}

export function QPowerPoint({ deck, onChange }: QPowerPointProps) {
  const fileInputRef = useRef<HTMLInputElement>(null);
  const imageInputRef = useRef<HTMLInputElement>(null);
  const [selectedImageId, setSelectedImageId] = useState("");
  const activeSlide = deck.slides.find((slide) => slide.id === deck.activeSlideId) ?? deck.slides[0];
  const activeImages = activeSlide.images ?? [];
  const selectedImage = activeImages.find((image) => image.id === selectedImageId);

  function updateSlide(nextSlide: Slide) {
    onChange({
      ...deck,
      slides: deck.slides.map((slide) => (slide.id === nextSlide.id ? nextSlide : slide))
    });
  }

  function toggleSlideTextFormat(field: SlideTextFormatField, defaultValue = false) {
    const enabled = Boolean(activeSlide[field] ?? defaultValue);
    updateSlide({ ...activeSlide, [field]: !enabled });
  }

  function updateImages(images: SlideImage[]) {
    updateSlide({ ...activeSlide, images });
  }

  function updateSelectedImage(patch: Partial<SlideImage>) {
    if (!selectedImage) {
      return;
    }

    updateImages(activeImages.map((image) => (image.id === selectedImage.id ? { ...image, ...patch } : image)));
  }

  function updateSelectedImageNumber(key: "x" | "y" | "width" | "height", value: string, max: number) {
    const number = Number.parseFloat(value);
    if (!Number.isFinite(number)) {
      return;
    }

    updateSelectedImage({ [key]: roundSlideNumber(Math.min(Math.max(number, 0), max)) });
  }

  function deleteSelectedImage() {
    if (!selectedImage) {
      return;
    }

    updateImages(activeImages.filter((image) => image.id !== selectedImage.id));
    setSelectedImageId("");
  }

  function updateSubtitleListStyle(listStyle: SlideListStyle | "") {
    updateSlide({ ...activeSlide, subtitleListStyle: listStyle || undefined });
  }

  function addSlide() {
    const id = `slide-${Date.now()}`;
    onChange({
      ...deck,
      activeSlideId: id,
      slides: [
        ...deck.slides,
        {
          id,
          title: "Новый слайд",
          subtitle: "Добавьте основной тезис презентации.",
          notes: "",
          theme: "classic",
          images: []
        }
      ]
    });
  }

  function duplicateSlide() {
    const id = `slide-${Date.now()}`;
    onChange({
      ...deck,
      activeSlideId: id,
      slides: [...deck.slides, { ...activeSlide, id, title: `${activeSlide.title} — копия`, images: clonedSlideImages(activeSlide.images) }]
    });
  }

  function deleteSlide() {
    if (deck.slides.length === 1) {
      return;
    }

    const nextSlides = deck.slides.filter((slide) => slide.id !== activeSlide.id);
    onChange({ ...deck, slides: nextSlides, activeSlideId: nextSlides[0].id });
  }

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

    try {
      const imported = await importPptxFile(file);
      onChange({
        ...deck,
        title: imported.title,
        activeSlideId: imported.slides[0]?.id ?? "slide-1",
        slides: imported.slides.length > 0 ? imported.slides : deck.slides
      });
    } catch (error) {
      window.alert(error instanceof Error ? error.message : "Не удалось открыть PPTX.");
    } finally {
      if (fileInputRef.current) {
        fileInputRef.current.value = "";
      }
    }
  }

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

    try {
      const src = await readFileAsDataUrl(file);
      const size = scaledSlideImageSize(await readImageSize(src));
      const image: SlideImage = {
        id: `slide-image-${Date.now()}`,
        src,
        alt: file.name,
        x: roundSlideNumber(Math.max(0.4, slideWidthInches - size.width - 0.9)),
        y: roundSlideNumber(Math.max(1.4, slideHeightInches - size.height - 0.8)),
        ...size
      };

      updateImages([...activeImages, image]);
      setSelectedImageId(image.id);
    } catch (error) {
      window.alert(error instanceof Error ? error.message : "Не удалось вставить изображение.");
    } finally {
      if (imageInputRef.current) {
        imageInputRef.current.value = "";
      }
    }
  }

  async function savePptx() {
    try {
      const fileName = replaceFileExtension(deck.title, ".pptx");
      const blob = await exportPptxBlob(deck);
      if (isDesktopOfficeBridgeAvailable()) {
        await saveDesktopOfficeFile("pptx", fileName, blob);
        return;
      }

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

  async function choosePptx() {
    if (isDesktopOfficeBridgeAvailable()) {
      const file = await openDesktopOfficeFile("pptx");
      await openPptx(file ?? undefined);
      return;
    }

    fileInputRef.current?.click();
  }

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

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

  return (
    <div className="editor-layout slides-layout">
      <aside className="slide-strip" aria-label="Слайды">
        <button className="wide-command" type="button" onClick={addSlide}>
          <Plus size={16} />
          Слайд
        </button>
        {deck.slides.map((slide, index) => (
          <button
            type="button"
            key={slide.id}
            className={`slide-thumb theme-${slide.theme} ${slide.id === activeSlide.id ? "is-active" : ""} ${slide.hidden ? "is-hidden" : ""}`}
            onClick={() => onChange({ ...deck, activeSlideId: slide.id })}
          >
            <span>{index + 1}</span>
            <strong>{slide.title}</strong>
            {slide.hidden ? (
              <span className="slide-hidden-badge">
                <EyeOff size={14} />
                Скрыт
              </span>
            ) : null}
          </button>
        ))}
      </aside>

      <section className="slide-stage" aria-label="Редактор QPowerPoint">
        <div className="module-toolbar">
          <input
            ref={fileInputRef}
            className="hidden-file-input"
            type="file"
            accept=".pptx,application/vnd.openxmlformats-officedocument.presentationml.presentation"
            onChange={(event) => void openPptx(event.target.files?.[0])}
            aria-label="Открыть PPTX"
          />
          <input
            ref={imageInputRef}
            className="hidden-file-input"
            type="file"
            accept="image/png,image/jpeg,image/gif,image/bmp,image/svg+xml"
            onChange={(event) => void insertImage(event.target.files?.[0])}
            aria-label="Вставить изображение"
          />
          <input
            className="file-title-input"
            value={deck.title}
            aria-label="Название презентации"
            onChange={(event) => onChange({ ...deck, title: event.target.value })}
          />
          <button className="compact-command" type="button" onClick={duplicateSlide}>
            <Copy size={16} />
            Дублировать
          </button>
          <button className="compact-command" type="button" onClick={deleteSlide} disabled={deck.slides.length === 1}>
            <Trash2 size={16} />
            Удалить
          </button>
          <button className="compact-command" type="button" onClick={() => imageInputRef.current?.click()}>
            <ImageIcon size={16} />
            Изображение
          </button>
          <button className="compact-command" type="button" onClick={() => void choosePptx()}>
            <FileUp size={16} />
            Открыть PPTX
          </button>
          <button className="compact-command" type="button" onClick={() => void savePptx()}>
            <Save size={16} />
            Сохранить PPTX
          </button>
          <button
            className="compact-command"
            type="button"
            onClick={() => downloadTextFile(deck.title.replace(/\.qpptx$/i, ".json"), JSON.stringify(deck, null, 2), "application/json")}
          >
            <Download size={16} />
            Экспорт JSON
          </button>
          <button className="compact-command" type="button" onClick={() => void exportPdf()}>
            <FileText size={16} />
            Экспорт PDF
          </button>
        </div>

        <div className="office-workbar slide-workbar" aria-label="Параметры QPowerPoint">
          <div className="office-tool-section">
            <span className="office-tool-title">Оформление</span>
            <label className="office-check">
              <input
                type="checkbox"
                checked={Boolean(activeSlide.hidden)}
                onChange={(event) => updateSlide({ ...activeSlide, hidden: event.target.checked || undefined })}
              />
              Скрыть слайд
            </label>
            <label className="office-field">
              <span>Тема</span>
              <select value={activeSlide.theme} onChange={(event) => updateSlide({ ...activeSlide, theme: event.target.value as Slide["theme"] })}>
                {Object.entries(themeLabels).map(([theme, label]) => (
                  <option key={theme} value={theme}>
                    {label}
                  </option>
                ))}
              </select>
            </label>
            <label className="office-field">
              <span>Переход</span>
              <select
                value={activeSlide.transition ?? ""}
                onChange={(event) => {
                  const transition = event.target.value as SlideTransition | "";
                  updateSlide({ ...activeSlide, transition: transition || undefined });
                }}
              >
                {slideTransitionOptions.map((option) => (
                  <option key={option.value || "none"} value={option.value}>
                    {option.label}
                  </option>
                ))}
              </select>
            </label>
            <div className="slide-background-swatches" aria-label="Фон слайда">
              <Palette size={16} aria-hidden="true" />
              {backgroundColorSwatches.map((swatch) => (
                <button
                  key={swatch.label}
                  className={`fill-swatch ${swatch.color ? "" : "is-empty"} ${normalizedHexColor(activeSlide.backgroundColor) === swatch.color || (!activeSlide.backgroundColor && !swatch.color) ? "is-active" : ""}`.trim()}
                  type="button"
                  onClick={() => updateSlide({ ...activeSlide, backgroundColor: swatch.color || undefined })}
                  title={swatch.label}
                  aria-label={swatch.label}
                  style={swatch.color ? { backgroundColor: `#${swatch.color}` } : undefined}
                />
              ))}
            </div>
          </div>

          <div className="office-tool-section">
            <span className="office-tool-title">Текст</span>
            <label className="office-field">
              <span>Заголовок</span>
              <select
                value={activeSlide.titleFontSize ?? ""}
                onChange={(event) => updateSlide({ ...activeSlide, titleFontSize: event.target.value ? Number(event.target.value) : undefined })}
                aria-label="Размер заголовка"
              >
                <option value="">Размер темы</option>
                {titleFontSizeOptions.map((fontSize) => (
                  <option key={fontSize} value={fontSize}>
                    {fontSize}
                  </option>
                ))}
              </select>
            </label>
            <label className="office-field">
              <span>Шрифт</span>
              <select
                value={activeSlide.titleFontFamily ?? ""}
                onChange={(event) => updateSlide({ ...activeSlide, titleFontFamily: event.target.value || undefined })}
                aria-label="Семейство шрифта заголовка"
                title="Семейство шрифта заголовка"
              >
                <option value="">Arial</option>
                {slideFontFamilyOptions.map((fontFamily) => (
                  <option key={fontFamily} value={fontFamily}>
                    {fontFamily}
                  </option>
                ))}
              </select>
            </label>
            <div className="slide-background-swatches" aria-label="Цвет заголовка">
              {textColorSwatches.map((swatch) => (
                <button
                  key={`title-${swatch.label}`}
                  className={`fill-swatch ${swatch.color ? "" : "is-empty"} ${normalizedHexColor(activeSlide.titleColor) === swatch.color || (!activeSlide.titleColor && !swatch.color) ? "is-active" : ""}`.trim()}
                  type="button"
                  onClick={() => updateSlide({ ...activeSlide, titleColor: swatch.color || undefined })}
                  title={swatch.label}
                  aria-label={swatch.label}
                  style={swatch.color ? { backgroundColor: `#${swatch.color}` } : undefined}
                />
              ))}
            </div>
            <div className="cell-format-tools slide-format-tools" aria-label="Начертание заголовка">
              <button
                className={`icon-button format-toggle ${(activeSlide.titleBold ?? true) ? "is-active" : ""}`.trim()}
                type="button"
                onClick={() => toggleSlideTextFormat("titleBold", true)}
                title="Полужирный заголовок"
                aria-label="Полужирный заголовок"
              >
                <Bold size={16} />
              </button>
              <button
                className={`icon-button format-toggle ${activeSlide.titleItalics ? "is-active" : ""}`.trim()}
                type="button"
                onClick={() => toggleSlideTextFormat("titleItalics")}
                title="Курсив заголовка"
                aria-label="Курсив заголовка"
              >
                <Italic size={16} />
              </button>
              <button
                className={`icon-button format-toggle ${activeSlide.titleUnderline ? "is-active" : ""}`.trim()}
                type="button"
                onClick={() => toggleSlideTextFormat("titleUnderline")}
                title="Подчеркнутый заголовок"
                aria-label="Подчеркнутый заголовок"
              >
                <Underline size={16} />
              </button>
            </div>
            <label className="office-field office-field-wide">
              <span>
                <Link size={14} />
                Ссылка заголовка
              </span>
              <input
                value={activeSlide.titleHyperlink ?? ""}
                aria-label="Ссылка заголовка слайда"
                placeholder="https://example.com"
                onChange={(event) => updateSlide({ ...activeSlide, titleHyperlink: event.target.value || undefined })}
              />
            </label>
            <label className="office-field">
              <span>Подзаголовок</span>
              <select
                value={activeSlide.subtitleFontSize ?? ""}
                onChange={(event) => updateSlide({ ...activeSlide, subtitleFontSize: event.target.value ? Number(event.target.value) : undefined })}
                aria-label="Размер подзаголовка"
              >
                <option value="">Размер темы</option>
                {subtitleFontSizeOptions.map((fontSize) => (
                  <option key={fontSize} value={fontSize}>
                    {fontSize}
                  </option>
                ))}
              </select>
            </label>
            <label className="office-field">
              <span>Шрифт</span>
              <select
                value={activeSlide.subtitleFontFamily ?? ""}
                onChange={(event) => updateSlide({ ...activeSlide, subtitleFontFamily: event.target.value || undefined })}
                aria-label="Семейство шрифта подзаголовка"
                title="Семейство шрифта подзаголовка"
              >
                <option value="">Arial</option>
                {slideFontFamilyOptions.map((fontFamily) => (
                  <option key={fontFamily} value={fontFamily}>
                    {fontFamily}
                  </option>
                ))}
              </select>
            </label>
            <div className="slide-background-swatches" aria-label="Цвет подзаголовка">
              {textColorSwatches.map((swatch) => (
                <button
                  key={`subtitle-${swatch.label}`}
                  className={`fill-swatch ${swatch.color ? "" : "is-empty"} ${normalizedHexColor(activeSlide.subtitleColor) === swatch.color || (!activeSlide.subtitleColor && !swatch.color) ? "is-active" : ""}`.trim()}
                  type="button"
                  onClick={() => updateSlide({ ...activeSlide, subtitleColor: swatch.color || undefined })}
                  title={swatch.label}
                  aria-label={swatch.label}
                  style={swatch.color ? { backgroundColor: `#${swatch.color}` } : undefined}
                />
              ))}
            </div>
            <div className="cell-format-tools slide-format-tools" aria-label="Начертание подзаголовка">
              <button
                className={`icon-button format-toggle ${activeSlide.subtitleBold ? "is-active" : ""}`.trim()}
                type="button"
                onClick={() => toggleSlideTextFormat("subtitleBold")}
                title="Полужирный подзаголовок"
                aria-label="Полужирный подзаголовок"
              >
                <Bold size={16} />
              </button>
              <button
                className={`icon-button format-toggle ${activeSlide.subtitleItalics ? "is-active" : ""}`.trim()}
                type="button"
                onClick={() => toggleSlideTextFormat("subtitleItalics")}
                title="Курсив подзаголовка"
                aria-label="Курсив подзаголовка"
              >
                <Italic size={16} />
              </button>
              <button
                className={`icon-button format-toggle ${activeSlide.subtitleUnderline ? "is-active" : ""}`.trim()}
                type="button"
                onClick={() => toggleSlideTextFormat("subtitleUnderline")}
                title="Подчеркнутый подзаголовок"
                aria-label="Подчеркнутый подзаголовок"
              >
                <Underline size={16} />
              </button>
            </div>
            <label className="office-field office-field-wide">
              <span>
                <Link size={14} />
                Ссылка подзаголовка
              </span>
              <input
                value={activeSlide.subtitleHyperlink ?? ""}
                aria-label="Ссылка подзаголовка слайда"
                placeholder="https://example.com"
                onChange={(event) => updateSlide({ ...activeSlide, subtitleHyperlink: event.target.value || undefined })}
              />
            </label>
            <div className="segmented-tools slide-list-tools" aria-label="Список подзаголовка">
              <button
                type="button"
                className={!activeSlide.subtitleListStyle ? "is-active" : ""}
                onClick={() => updateSubtitleListStyle("")}
                title="Без списка"
                aria-label="Без списка"
              >
                <ListX size={16} />
              </button>
              <button
                type="button"
                className={activeSlide.subtitleListStyle === "bullet" ? "is-active" : ""}
                onClick={() => updateSubtitleListStyle("bullet")}
                title="Маркированный список"
                aria-label="Маркированный список"
              >
                <List size={16} />
              </button>
              <button
                type="button"
                className={activeSlide.subtitleListStyle === "number" ? "is-active" : ""}
                onClick={() => updateSubtitleListStyle("number")}
                title="Нумерованный список"
                aria-label="Нумерованный список"
              >
                <ListOrdered size={16} />
              </button>
            </div>
          </div>

          <div className="office-tool-section">
            <span className="office-tool-title">Объекты</span>
            <div className="slide-image-list">
              {activeImages.map((image, index) => (
                <button
                  key={image.id}
                  type="button"
                  className={image.id === selectedImageId ? "is-active" : ""}
                  onClick={() => setSelectedImageId(image.id)}
                >
                  {image.alt || `Изображение ${index + 1}`}
                </button>
              ))}
            </div>
            {selectedImage ? (
              <>
                <div className="image-geometry-grid">
                  <label>
                    X
                    <input
                      type="number"
                      min="0"
                      max={slideWidthInches}
                      step="0.1"
                      value={selectedImage.x}
                      onChange={(event) => updateSelectedImageNumber("x", event.target.value, slideWidthInches)}
                    />
                  </label>
                  <label>
                    Y
                    <input
                      type="number"
                      min="0"
                      max={slideHeightInches}
                      step="0.1"
                      value={selectedImage.y}
                      onChange={(event) => updateSelectedImageNumber("y", event.target.value, slideHeightInches)}
                    />
                  </label>
                  <label>
                    Ширина
                    <input
                      type="number"
                      min="0.1"
                      max={slideWidthInches}
                      step="0.1"
                      value={selectedImage.width}
                      onChange={(event) => updateSelectedImageNumber("width", event.target.value, slideWidthInches)}
                    />
                  </label>
                  <label>
                    Высота
                    <input
                      type="number"
                      min="0.1"
                      max={slideHeightInches}
                      step="0.1"
                      value={selectedImage.height}
                      onChange={(event) => updateSelectedImageNumber("height", event.target.value, slideHeightInches)}
                    />
                  </label>
                </div>
                <label className="office-field office-field-wide">
                  <span>
                    <Link size={14} />
                    Ссылка изображения
                  </span>
                  <input
                    value={selectedImage.hyperlink ?? ""}
                    aria-label="Ссылка изображения слайда"
                    placeholder="https://example.com"
                    onChange={(event) => updateSelectedImage({ hyperlink: event.target.value || undefined })}
                  />
                </label>
                <button className="compact-command danger-command" type="button" onClick={deleteSelectedImage}>
                  <Trash2 size={16} />
                  Удалить изображение
                </button>
              </>
            ) : null}
          </div>

          <label className="office-tool-section notes-inline">
            <span className="office-tool-title">Заметки</span>
            <textarea
              className="notes-field"
              value={activeSlide.notes}
              aria-label="Заметки докладчика"
              onChange={(event) => updateSlide({ ...activeSlide, notes: event.target.value })}
            />
          </label>
        </div>

        <div className={`slide-canvas theme-${activeSlide.theme}`} style={slideCanvasStyle(activeSlide)}>
          {activeImages.map((image) => (
            <button
              key={image.id}
              type="button"
              className={`slide-image-object ${image.id === selectedImageId ? "is-selected" : ""}`}
              style={slideImageStyle(image)}
              onClick={() => setSelectedImageId(image.id)}
              title={image.alt || "Изображение"}
              aria-label={image.alt || "Изображение слайда"}
            >
              <img src={image.src} alt={image.alt || ""} />
            </button>
          ))}
          <input
            className="slide-title-input"
            value={activeSlide.title}
            aria-label="Заголовок слайда"
            style={slideTextStyle(activeSlide.titleColor, activeSlide.titleFontSize, activeSlide.titleFontFamily, {
              bold: activeSlide.titleBold,
              italics: activeSlide.titleItalics,
              underline: activeSlide.titleUnderline,
              defaultBold: true
            })}
            onChange={(event) => updateSlide({ ...activeSlide, title: event.target.value })}
          />
          <textarea
            className={`slide-subtitle-input ${activeSlide.subtitleListStyle ? `is-${activeSlide.subtitleListStyle}-list` : ""}`.trim()}
            value={activeSlide.subtitle}
            aria-label="Подзаголовок слайда"
            style={slideTextStyle(activeSlide.subtitleColor, activeSlide.subtitleFontSize, activeSlide.subtitleFontFamily, {
              bold: activeSlide.subtitleBold,
              italics: activeSlide.subtitleItalics,
              underline: activeSlide.subtitleUnderline
            })}
            onChange={(event) => updateSlide({ ...activeSlide, subtitle: event.target.value })}
          />
          <div className="slide-accent-line" />
        </div>
      </section>

    </div>
  );
}
