import { useMemo, useRef, useState } from "react";
import type { CSSProperties } from "react";
import {
  AlignCenter,
  AlignLeft,
  AlignRight,
  BarChart3,
  Bold,
  Download,
  Eraser,
  FileText,
  FileUp,
  FunctionSquare,
  Italic,
  Link,
  LineChart,
  PaintBucket,
  PieChart,
  Plus,
  Save,
  Sigma,
  Strikethrough,
  Trash2,
  Type,
  Underline,
  WrapText
} from "lucide-react";
import type { CellFormat, SheetChart, SheetChartType, Workbook } from "../../types";
import { cellId, columnLabelsForCount, evaluateCell, formatCellValueForDisplay, mergedCellInfo, summarizeColumn, usedSheetBounds } from "../../utils/spreadsheet";
import { downloadBlobFile, downloadTextFile } from "../../utils/download";
import { chartDataPoints, exportXlsxBlob, importXlsxFile } from "../../io/excellOffice";
import { replaceFileExtension } from "../../io/fileHelpers";
import { isDesktopOfficeBridgeAvailable, openDesktopOfficeFile, saveDesktopOfficeFile } from "../../io/desktopOfficeFiles";
import { exportPdfOrPrint } from "../../io/pdfExport";
import { useQOfficeCommand } from "../../hooks/useQOfficeCommand";

interface QExcellProps {
  workbook: Workbook;
  onChange: (workbook: Workbook) => void;
}

const extraEditableRows = 1;
const extraEditableColumns = 1;
const emptyHyperlinks: Record<string, string> = {};
const emptyCellFormats: Record<string, CellFormat> = {};
const emptyCharts: SheetChart[] = [];

const fillSwatches = [
  { color: "", label: "Без заливки" },
  { color: "FFF2CC", label: "Светло-желтая заливка" },
  { color: "D9EAD3", label: "Светло-зеленая заливка" },
  { color: "DDEBF7", label: "Светло-синяя заливка" },
  { color: "FCE4D6", label: "Светло-коралловая заливка" },
  { color: "EADCF8", label: "Светло-фиолетовая заливка" }
];

const textColorSwatches = [
  { color: "", label: "Авто цвет текста" },
  { color: "202124", label: "Черный текст" },
  { color: "0F5FAE", label: "Синий текст" },
  { color: "0F766E", label: "Зеленый текст" },
  { color: "C2410C", label: "Красный текст" },
  { color: "7C3AED", label: "Фиолетовый текст" }
];

const numberFormatOptions = [
  { value: "", label: "Обычный" },
  { value: "0.00", label: "Число" },
  { value: "# ##0.00 ₽", label: "Деньги" },
  { value: "0%", label: "Процент" },
  { value: "dd.mm.yyyy", label: "Дата" },
  { value: "dd.mm.yyyy hh:mm", label: "Дата и время" }
];

const chartTypeOptions: Array<{ value: SheetChartType; label: string }> = [
  { value: "bar", label: "Столбчатая" },
  { value: "line", label: "Линейная" },
  { value: "pie", label: "Круговая" }
];

const horizontalAlignOptions = [
  { value: "left", label: "По левому краю", icon: AlignLeft },
  { value: "center", label: "По центру", icon: AlignCenter },
  { value: "right", label: "По правому краю", icon: AlignRight }
] as const;
const fontFamilyOptions = ["Aptos", "Arial", "Courier New", "Times New Roman"];
const fontSizeOptions = [10, 11, 12, 14, 16, 18, 20, 24];

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 normalizedFillColor(value?: string) {
  const raw = value?.trim().replace(/^#/, "").toUpperCase() ?? "";
  return /^[0-9A-F]{6}$/.test(raw) ? raw : "";
}

function normalizedCellFormat(format: CellFormat): CellFormat | undefined {
  const textColor = normalizedFillColor(format.textColor);
  const fillColor = normalizedFillColor(format.fillColor);
  const horizontalAlign = normalizedHorizontalAlign(format.horizontalAlign);
  const numberFormat = format.numberFormat?.trim();
  const fontSize = normalizedFontSize(format.fontSize);
  const fontFamily = normalizedFontFamily(format.fontFamily);
  const normalized: CellFormat = {
    ...(format.bold ? { bold: true } : {}),
    ...(format.italics ? { italics: true } : {}),
    ...(format.underline ? { underline: true } : {}),
    ...(format.strike ? { strike: true } : {}),
    ...(fontSize ? { fontSize } : {}),
    ...(fontFamily ? { fontFamily } : {}),
    ...(textColor ? { textColor } : {}),
    ...(fillColor ? { fillColor } : {}),
    ...(horizontalAlign ? { horizontalAlign } : {}),
    ...(format.wrapText ? { wrapText: true } : {}),
    ...(numberFormat ? { numberFormat } : {})
  };

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

function cellInputStyle(format?: CellFormat): CSSProperties {
  const textDecoration = [format?.underline ? "underline" : "", format?.strike ? "line-through" : ""].filter(Boolean).join(" ");
  return {
    ...(format?.bold ? { fontWeight: 700 } : {}),
    ...(format?.italics ? { fontStyle: "italic" } : {}),
    ...(textDecoration ? { textDecoration } : {}),
    ...(format?.fontSize ? { fontSize: `${format.fontSize}pt` } : {}),
    ...(format?.fontFamily ? { fontFamily: format.fontFamily } : {}),
    ...(format?.textColor ? { color: `#${format.textColor}` } : {}),
    ...(format?.fillColor ? { backgroundColor: `#${format.fillColor}` } : {}),
    ...(format?.horizontalAlign ? { textAlign: format.horizontalAlign } : {}),
    ...(format?.wrapText ? { whiteSpace: "pre-wrap", overflowWrap: "anywhere" } : {})
  };
}

function normalizedHorizontalAlign(value?: string) {
  return value === "left" || value === "center" || value === "right" ? value : "";
}

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

  const normalized = Math.round(value * 2) / 2;
  return normalized === 11 ? undefined : normalized;
}

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

function normalizedChartRange(value: string) {
  const raw = value.trim().replace(/\$/g, "").toUpperCase();
  const [start, end = start] = raw.split(":");
  if (!/^[A-Z]+[1-9][0-9]*$/.test(start) || !/^[A-Z]+[1-9][0-9]*$/.test(end)) {
    return "";
  }

  return `${start}:${end}`;
}

function chartTypeIcon(type: SheetChartType) {
  if (type === "line") {
    return LineChart;
  }
  if (type === "pie") {
    return PieChart;
  }
  return BarChart3;
}

function chartTypeLabel(type: SheetChartType) {
  return chartTypeOptions.find((option) => option.value === type)?.label ?? "Столбчатая";
}

export function QExcell({ workbook, onChange }: QExcellProps) {
  const fileInputRef = useRef<HTMLInputElement>(null);
  const [selectedCell, setSelectedCell] = useState("A1");
  const [selectedChartId, setSelectedChartId] = useState("");
  const [chartTitle, setChartTitle] = useState("Итого по продуктам");
  const [chartType, setChartType] = useState<SheetChartType>("bar");
  const [chartLabelRange, setChartLabelRange] = useState("A2:A4");
  const [chartValueRange, setChartValueRange] = useState("E2:E4");
  const activeSheet = workbook.sheets.find((sheet) => sheet.id === workbook.activeSheet) ?? workbook.sheets[0] ?? { id: "sheet-1", name: "Лист 1", cells: {}, mergedCells: [] };
  const activeMergedCells = activeSheet.mergedCells ?? [];
  const activeHyperlinks = activeSheet.hyperlinks ?? emptyHyperlinks;
  const activeCellFormats = activeSheet.cellFormats ?? emptyCellFormats;
  const activeCharts = activeSheet.charts ?? emptyCharts;
  const selectedRawValue = activeSheet.cells[selectedCell] ?? "";
  const selectedHyperlink = activeHyperlinks[selectedCell] ?? "";
  const selectedCellFormat = activeCellFormats[selectedCell] ?? {};
  const selectedFormattedValue = formatCellValueForDisplay(evaluateCell(selectedCell, activeSheet.cells), selectedCellFormat);
  const boundsCells = useMemo(
    () =>
      [...Object.keys(activeHyperlinks), ...Object.keys(activeCellFormats)].reduce<Record<string, string>>(
        (cells, address) => ({ ...cells, [address]: cells[address] || activeHyperlinks[address] || "format" }),
        { ...activeSheet.cells }
      ),
    [activeCellFormats, activeHyperlinks, activeSheet.cells]
  );
  const sheetBounds = useMemo(() => usedSheetBounds(boundsCells, activeMergedCells), [activeMergedCells, boundsCells]);
  const rows = useMemo(() => Array.from({ length: sheetBounds.rowCount + extraEditableRows }, (_, index) => index), [sheetBounds.rowCount]);
  const visibleColumnLabels = useMemo(
    () => columnLabelsForCount(sheetBounds.columnCount + extraEditableColumns),
    [sheetBounds.columnCount]
  );

  const totals = useMemo(() => {
    return summarizeColumn(activeSheet.cells, 4);
  }, [activeSheet.cells]);

  function updateCell(id: string, value: string) {
    onChange({
      ...workbook,
      sheets: workbook.sheets.map((sheet) =>
        sheet.id === activeSheet.id ? { ...sheet, cells: { ...sheet.cells, [id]: value } } : sheet
      )
    });
  }

  function updateHyperlink(id: string, href: string) {
    const normalizedId = id.toUpperCase();
    const nextHyperlinks = { ...activeHyperlinks };
    if (href) {
      nextHyperlinks[normalizedId] = href;
    } else {
      delete nextHyperlinks[normalizedId];
    }

    onChange({
      ...workbook,
      sheets: workbook.sheets.map((sheet) => (sheet.id === activeSheet.id ? { ...sheet, hyperlinks: nextHyperlinks } : sheet))
    });
  }

  function updateCellFormat(id: string, formatPatch: CellFormat) {
    const normalizedId = id.toUpperCase();
    const nextFormats = { ...activeCellFormats };
    const nextFormat = normalizedCellFormat({ ...(nextFormats[normalizedId] ?? {}), ...formatPatch });
    if (nextFormat) {
      nextFormats[normalizedId] = nextFormat;
    } else {
      delete nextFormats[normalizedId];
    }

    onChange({
      ...workbook,
      sheets: workbook.sheets.map((sheet) => (sheet.id === activeSheet.id ? { ...sheet, cellFormats: nextFormats } : sheet))
    });
  }

  function toggleSelectedFormat(key: "bold" | "italics" | "underline" | "strike" | "wrapText") {
    updateCellFormat(selectedCell, { [key]: !selectedCellFormat[key] });
  }

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

    const nextCells = selectedRawValue ? activeSheet.cells : { ...activeSheet.cells, [selectedCell]: href };
    onChange({
      ...workbook,
      sheets: workbook.sheets.map((sheet) =>
        sheet.id === activeSheet.id
          ? {
              ...sheet,
              cells: nextCells,
              hyperlinks: { ...activeHyperlinks, [selectedCell]: href }
            }
          : sheet
      )
    });
  }

  function clearSelectedCell() {
    const nextCells = { ...activeSheet.cells };
    const nextHyperlinks = { ...activeHyperlinks };
    const nextFormats = { ...activeCellFormats };
    delete nextCells[selectedCell];
    delete nextHyperlinks[selectedCell];
    delete nextFormats[selectedCell];
    onChange({
      ...workbook,
      sheets: workbook.sheets.map((sheet) =>
        sheet.id === activeSheet.id ? { ...sheet, cells: nextCells, hyperlinks: nextHyperlinks, cellFormats: nextFormats } : sheet
      )
    });
  }

  function updateActiveSheetName(name: string) {
    onChange({
      ...workbook,
      sheets: workbook.sheets.map((sheet) => (sheet.id === activeSheet.id ? { ...sheet, name } : sheet))
    });
  }

  function selectChart(chart: SheetChart) {
    setSelectedChartId(chart.id);
    setChartTitle(chart.title);
    setChartType(chart.type);
    setChartLabelRange(chart.labelRange);
    setChartValueRange(chart.valueRange);
  }

  function resetChartForm() {
    setSelectedChartId("");
    setChartTitle("Итого по продуктам");
    setChartType("bar");
    setChartLabelRange("A2:A4");
    setChartValueRange("E2:E4");
  }

  function upsertChart() {
    const labelRange = normalizedChartRange(chartLabelRange);
    const valueRange = normalizedChartRange(chartValueRange);
    if (!labelRange || !valueRange) {
      window.alert("Введите диапазоны диаграммы в формате A2:A4.");
      return;
    }

    const nextChart: SheetChart = {
      id: selectedChartId || `chart-${Date.now()}`,
      type: chartType,
      title: chartTitle.trim() || chartTypeLabel(chartType),
      labelRange,
      valueRange
    };
    const nextCharts = selectedChartId
      ? activeCharts.map((chart) => (chart.id === selectedChartId ? nextChart : chart))
      : [...activeCharts, nextChart];

    onChange({
      ...workbook,
      sheets: workbook.sheets.map((sheet) => (sheet.id === activeSheet.id ? { ...sheet, charts: nextCharts } : sheet))
    });
    setSelectedChartId(nextChart.id);
  }

  function deleteChart(id: string) {
    const nextCharts = activeCharts.filter((chart) => chart.id !== id);
    onChange({
      ...workbook,
      sheets: workbook.sheets.map((sheet) => (sheet.id === activeSheet.id ? { ...sheet, charts: nextCharts } : sheet))
    });
    if (selectedChartId === id) {
      resetChartForm();
    }
  }

  function exportCsv() {
    const exportBounds = usedSheetBounds(activeSheet.cells, activeMergedCells);
    const exportRows = Array.from({ length: exportBounds.rowCount }, (_, index) => index);
    const exportColumnLabels = columnLabelsForCount(exportBounds.columnCount);
    const csv = exportRows
      .map((row) =>
        exportColumnLabels
          .map((_, column) => {
            const value = activeSheet.cells[cellId(column, row)] ?? "";
            return `"${value.replace(/"/g, '""')}"`;
          })
          .join(",")
      )
      .join("\n");
    downloadTextFile(workbook.title.replace(/\.qxlsx$/i, ".csv"), csv, "text/csv");
  }

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

    try {
      const imported = await importXlsxFile(file);
      onChange({
        ...workbook,
        title: imported.title,
        activeSheet: imported.sheets[0]?.id ?? "sheet-1",
        sheets: imported.sheets.length > 0 ? imported.sheets : [{ id: "sheet-1", name: "Лист 1", cells: {}, mergedCells: [] }]
      });
    } catch (error) {
      window.alert(error instanceof Error ? error.message : "Не удалось открыть XLSX.");
    } finally {
      if (fileInputRef.current) {
        fileInputRef.current.value = "";
      }
    }
  }

  async function saveXlsx() {
    try {
      const fileName = replaceFileExtension(workbook.title, ".xlsx");
      const blob = await exportXlsxBlob(workbook);
      if (isDesktopOfficeBridgeAvailable()) {
        await saveDesktopOfficeFile("xlsx", fileName, blob);
        return;
      }

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

  async function chooseXlsx() {
    if (isDesktopOfficeBridgeAvailable()) {
      const file = await openDesktopOfficeFile("xlsx");
      await openXlsx(file ?? undefined);
      return;
    }

    fileInputRef.current?.click();
  }

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

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

  return (
    <div className="editor-layout sheet-layout">
      <section className="spreadsheet-stage" aria-label="Редактор QExcell">
        <div className="module-toolbar">
          <input
            ref={fileInputRef}
            className="hidden-file-input"
            type="file"
            accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
            onChange={(event) => void openXlsx(event.target.files?.[0])}
            aria-label="Открыть XLSX"
          />
          <input
            className="file-title-input"
            value={workbook.title}
            aria-label="Название книги"
            onChange={(event) => onChange({ ...workbook, title: event.target.value })}
          />
          <button className="compact-command" type="button" onClick={() => updateCell(selectedCell, "=SUM(B2:D2)")}>
            <Sigma size={16} />
            Сумма
          </button>
          <button className="compact-command" type="button" onClick={() => updateCell(selectedCell, "=AVG(B2:D2)")}>
            <FunctionSquare size={16} />
            Среднее
          </button>
          <button className="compact-command" type="button" onClick={clearSelectedCell}>
            <Eraser size={16} />
            Очистить
          </button>
          <button className="compact-command" type="button" onClick={insertLink}>
            <Link size={16} />
            Ссылка
          </button>
          <div className="cell-format-tools" aria-label="Формат ячейки">
            <button
              className={`icon-button format-toggle ${selectedCellFormat.bold ? "is-active" : ""}`.trim()}
              type="button"
              onClick={() => toggleSelectedFormat("bold")}
              title="Полужирный"
              aria-label="Полужирный"
            >
              <Bold size={16} />
            </button>
            <button
              className={`icon-button format-toggle ${selectedCellFormat.italics ? "is-active" : ""}`.trim()}
              type="button"
              onClick={() => toggleSelectedFormat("italics")}
              title="Курсив"
              aria-label="Курсив"
            >
              <Italic size={16} />
            </button>
            <button
              className={`icon-button format-toggle ${selectedCellFormat.underline ? "is-active" : ""}`.trim()}
              type="button"
              onClick={() => toggleSelectedFormat("underline")}
              title="Подчеркнуть"
              aria-label="Подчеркнуть"
            >
              <Underline size={16} />
            </button>
            <button
              className={`icon-button format-toggle ${selectedCellFormat.strike ? "is-active" : ""}`.trim()}
              type="button"
              onClick={() => toggleSelectedFormat("strike")}
              title="Зачеркнуть"
              aria-label="Зачеркнуть"
            >
              <Strikethrough size={16} />
            </button>
            {horizontalAlignOptions.map((option) => {
              const Icon = option.icon;
              const isActive = selectedCellFormat.horizontalAlign === option.value;
              return (
                <button
                  key={option.value}
                  className={`icon-button format-toggle ${isActive ? "is-active" : ""}`.trim()}
                  type="button"
                  onClick={() => updateCellFormat(selectedCell, { horizontalAlign: isActive ? undefined : option.value })}
                  title={option.label}
                  aria-label={option.label}
                >
                  <Icon size={16} />
                </button>
              );
            })}
            <button
              className={`icon-button format-toggle ${selectedCellFormat.wrapText ? "is-active" : ""}`.trim()}
              type="button"
              onClick={() => toggleSelectedFormat("wrapText")}
              title="Перенос текста"
              aria-label="Перенос текста"
            >
              <WrapText size={16} />
            </button>
            <select
              value={selectedCellFormat.fontFamily ?? ""}
              onChange={(event) => updateCellFormat(selectedCell, { fontFamily: event.target.value || undefined })}
              aria-label="Семейство шрифта"
              title="Семейство шрифта"
            >
              <option value="">Calibri</option>
              {fontFamilyOptions.map((fontFamily) => (
                <option key={fontFamily} value={fontFamily}>
                  {fontFamily}
                </option>
              ))}
            </select>
            <select
              value={selectedCellFormat.fontSize ?? ""}
              onChange={(event) =>
                updateCellFormat(selectedCell, { fontSize: event.target.value ? Number(event.target.value) : undefined })
              }
              aria-label="Размер шрифта"
              title="Размер шрифта"
            >
              <option value="">Размер</option>
              {fontSizeOptions.map((fontSize) => (
                <option key={fontSize} value={fontSize}>
                  {fontSize}
                </option>
              ))}
            </select>
            <div className="fill-swatches" aria-label="Заливка">
              <PaintBucket size={16} aria-hidden="true" />
              {fillSwatches.map((swatch) => (
                <button
                  key={swatch.label}
                  className={`fill-swatch ${swatch.color ? "" : "is-empty"} ${selectedCellFormat.fillColor === swatch.color || (!selectedCellFormat.fillColor && !swatch.color) ? "is-active" : ""}`.trim()}
                  type="button"
                  onClick={() => updateCellFormat(selectedCell, { fillColor: swatch.color })}
                  title={swatch.label}
                  aria-label={swatch.label}
                  style={swatch.color ? { backgroundColor: `#${swatch.color}` } : undefined}
                />
              ))}
            </div>
            <div className="fill-swatches" aria-label="Цвет текста">
              <Type size={16} aria-hidden="true" />
              {textColorSwatches.map((swatch) => (
                <button
                  key={swatch.label}
                  className={`fill-swatch ${swatch.color ? "" : "is-empty"} ${selectedCellFormat.textColor === swatch.color || (!selectedCellFormat.textColor && !swatch.color) ? "is-active" : ""}`.trim()}
                  type="button"
                  onClick={() => updateCellFormat(selectedCell, { textColor: swatch.color })}
                  title={swatch.label}
                  aria-label={swatch.label}
                  style={swatch.color ? { backgroundColor: `#${swatch.color}` } : undefined}
                />
              ))}
            </div>
          </div>
          <button className="compact-command" type="button" onClick={() => void chooseXlsx()}>
            <FileUp size={16} />
            Открыть XLSX
          </button>
          <button className="compact-command" type="button" onClick={() => void saveXlsx()}>
            <Save size={16} />
            Сохранить XLSX
          </button>
          <button className="compact-command" type="button" onClick={exportCsv}>
            <Download size={16} />
            Экспорт CSV
          </button>
          <button className="compact-command" type="button" onClick={() => void exportPdf()}>
            <FileText size={16} />
            Экспорт PDF
          </button>
        </div>

        <div className="formula-bar">
          <span>{selectedCell}</span>
          <input
            value={selectedRawValue}
            aria-label="Строка формул"
            onChange={(event) => updateCell(selectedCell, event.target.value)}
            placeholder="Введите значение или формулу, например =SUM(B2:D2)"
          />
        </div>

        <div className="grid-wrap">
          <table className="sheet-grid">
            <thead>
              <tr>
                <th aria-label="Номера строк" />
                {visibleColumnLabels.map((column) => (
                  <th key={column}>{column}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {rows.map((row) => (
                <tr key={row}>
                  <th>{row + 1}</th>
                  {visibleColumnLabels.map((_, column) => {
                    const id = cellId(column, row);
                    const merge = mergedCellInfo(id, activeMergedCells);
                    if (merge && !merge.isOrigin) {
                      return null;
                    }

                    const raw = activeSheet.cells[id] ?? "";
                    const evaluatedDisplay = raw.startsWith("=") ? evaluateCell(id, activeSheet.cells) : raw;
                    const hyperlink = activeHyperlinks[id] ?? "";
                    const cellFormat = activeCellFormats[id];
                    const display = formatCellValueForDisplay(evaluatedDisplay, cellFormat);
                    return (
                      <td
                        key={id}
                        className={`${selectedCell === id ? "is-selected" : ""} ${merge ? "is-merged" : ""} ${hyperlink ? "is-linked" : ""} ${cellFormat ? "is-formatted" : ""} ${cellFormat?.wrapText ? "is-wrapped" : ""}`.trim()}
                        colSpan={merge?.columnSpan}
                        rowSpan={merge?.rowSpan}
                      >
                        {cellFormat?.wrapText ? (
                          <textarea
                            value={selectedCell === id ? raw : display}
                            aria-label={`Ячейка ${id}`}
                            title={hyperlink || undefined}
                            style={cellInputStyle(cellFormat)}
                            onFocus={() => setSelectedCell(id)}
                            onChange={(event) => updateCell(id, event.target.value)}
                          />
                        ) : (
                          <input
                            value={selectedCell === id ? raw : display}
                            aria-label={`Ячейка ${id}`}
                            title={hyperlink || undefined}
                            style={cellInputStyle(cellFormat)}
                            onFocus={() => setSelectedCell(id)}
                            onChange={(event) => updateCell(id, event.target.value)}
                          />
                        )}
                      </td>
                    );
                  })}
                </tr>
              ))}
            </tbody>
          </table>
        </div>

        {activeCharts.length > 0 ? (
          <div className="chart-board" aria-label="Диаграммы листа">
            {activeCharts.map((chart) => (
              <SheetChartPreview
                key={chart.id}
                chart={chart}
                cells={activeSheet.cells}
                selected={chart.id === selectedChartId}
                onSelect={() => selectChart(chart)}
                onDelete={() => deleteChart(chart.id)}
              />
            ))}
          </div>
        ) : null}
      </section>

      <aside className="inspector" aria-label="Сводка книги">
        <section>
          <h3>Книга</h3>
          <label>
            Лист
            <select value={workbook.activeSheet} onChange={(event) => onChange({ ...workbook, activeSheet: event.target.value })}>
              {workbook.sheets.map((sheet) => (
                <option key={sheet.id} value={sheet.id}>
                  {sheet.name}
                </option>
              ))}
            </select>
          </label>
          <button
            className="wide-command"
            type="button"
            onClick={() => {
              const id = `sheet-${workbook.sheets.length + 1}`;
              onChange({
                ...workbook,
                activeSheet: id,
                sheets: [...workbook.sheets, { id, name: `Лист ${workbook.sheets.length + 1}`, cells: {}, mergedCells: [] }]
              });
            }}
          >
            <Plus size={16} />
            Новый лист
          </button>
          <label>
            Имя листа
            <input value={activeSheet.name} onChange={(event) => updateActiveSheetName(event.target.value)} />
          </label>
        </section>
        <section>
          <h3>Выделение</h3>
          <p className="stat-line">{selectedCell}</p>
          <p className="muted">Значение: {selectedRawValue || "пусто"}</p>
          <p className="muted">Расчет: {selectedFormattedValue || "пусто"}</p>
          <label>
            Формат числа
            <select value={selectedCellFormat.numberFormat ?? ""} onChange={(event) => updateCellFormat(selectedCell, { numberFormat: event.target.value })}>
              {numberFormatOptions.map((option) => (
                <option key={option.label} value={option.value}>
                  {option.label}
                </option>
              ))}
            </select>
          </label>
          <label>
            Ссылка
            <input
              value={selectedHyperlink}
              placeholder="https://example.com"
              onChange={(event) => updateHyperlink(selectedCell, event.target.value)}
              onBlur={(event) => updateHyperlink(selectedCell, normalizedLinkInput(event.target.value))}
            />
          </label>
          <button className="wide-command" type="button" onClick={insertLink}>
            <Link size={16} />
            Вставить ссылку
          </button>
          <p className="muted">Объединённых диапазонов: {activeMergedCells.length}</p>
        </section>
        <section>
          <h3>Диаграммы</h3>
          <label>
            Заголовок
            <input value={chartTitle} onChange={(event) => setChartTitle(event.target.value)} />
          </label>
          <label>
            Тип
            <select value={chartType} onChange={(event) => setChartType(event.target.value as SheetChartType)}>
              {chartTypeOptions.map((option) => (
                <option key={option.value} value={option.value}>
                  {option.label}
                </option>
              ))}
            </select>
          </label>
          <label>
            Подписи
            <input value={chartLabelRange} onChange={(event) => setChartLabelRange(event.target.value)} placeholder="A2:A4" />
          </label>
          <label>
            Значения
            <input value={chartValueRange} onChange={(event) => setChartValueRange(event.target.value)} placeholder="E2:E4" />
          </label>
          <button className="wide-command" type="button" onClick={upsertChart}>
            <BarChart3 size={16} />
            {selectedChartId ? "Сохранить диаграмму" : "Добавить диаграмму"}
          </button>
          {selectedChartId ? (
            <button className="wide-command" type="button" onClick={resetChartForm}>
              <Plus size={16} />
              Новая диаграмма
            </button>
          ) : null}
          <p className="muted">Диаграмм на листе: {activeCharts.length}</p>
        </section>
        <section>
          <h3>Сводка</h3>
          <p className="big-number">{totals.toLocaleString("ru-RU")}</p>
          <p className="muted">Сумма числовых значений в столбце E.</p>
        </section>
      </aside>
    </div>
  );
}

function SheetChartPreview({
  chart,
  cells,
  selected,
  onSelect,
  onDelete
}: {
  chart: SheetChart;
  cells: Record<string, string>;
  selected: boolean;
  onSelect: () => void;
  onDelete: () => void;
}) {
  const Icon = chartTypeIcon(chart.type);
  const points = chartDataPoints(chart, cells);

  return (
    <article className={`sheet-chart ${selected ? "is-selected" : ""}`.trim()} onClick={onSelect}>
      <header>
        <div>
          <Icon size={18} />
          <strong>{chart.title}</strong>
        </div>
        <button
          className="icon-button"
          type="button"
          title="Удалить диаграмму"
          aria-label={`Удалить диаграмму ${chart.title}`}
          onClick={(event) => {
            event.stopPropagation();
            onDelete();
          }}
        >
          <Trash2 size={16} />
        </button>
      </header>
      <ChartSvg type={chart.type} points={points} />
      <p className="muted">
        {chartTypeLabel(chart.type)} · {chart.labelRange} / {chart.valueRange}
      </p>
    </article>
  );
}

function ChartSvg({ type, points }: { type: SheetChartType; points: Array<{ label: string; value: number }> }) {
  if (points.length === 0) {
    return (
      <svg className="chart-svg" viewBox="0 0 360 180" role="img" aria-label="Пустая диаграмма">
        <text x="180" y="95" textAnchor="middle">
          Нет данных
        </text>
      </svg>
    );
  }

  if (type === "pie") {
    return <PieChartSvg points={points} />;
  }

  if (type === "line") {
    return <LineChartSvg points={points} />;
  }

  return <BarChartSvg points={points} />;
}

function BarChartSvg({ points }: { points: Array<{ label: string; value: number }> }) {
  const max = Math.max(1, ...points.map((point) => Math.abs(point.value)));
  const width = 320 / points.length;

  return (
    <svg className="chart-svg" viewBox="0 0 360 180" role="img" aria-label="Столбчатая диаграмма">
      <line x1="28" y1="142" x2="346" y2="142" />
      {points.map((point, index) => {
        const barHeight = Math.round((Math.abs(point.value) / max) * 112);
        const x = 32 + index * width;
        return (
          <g key={`${point.label}-${index}`}>
            <rect x={x + 8} y={142 - barHeight} width={Math.max(16, width - 18)} height={barHeight} rx="4" />
            <text x={x + width / 2} y="162" textAnchor="middle">
              {point.label.slice(0, 10)}
            </text>
          </g>
        );
      })}
    </svg>
  );
}

function LineChartSvg({ points }: { points: Array<{ label: string; value: number }> }) {
  const max = Math.max(1, ...points.map((point) => Math.abs(point.value)));
  const step = points.length > 1 ? 300 / (points.length - 1) : 0;
  const coordinates = points.map((point, index) => {
    const x = points.length > 1 ? 30 + index * step : 180;
    const y = 142 - (Math.abs(point.value) / max) * 112;
    return { x, y, label: point.label };
  });
  const path = coordinates.map((point) => `${point.x},${point.y}`).join(" ");

  return (
    <svg className="chart-svg" viewBox="0 0 360 180" role="img" aria-label="Линейная диаграмма">
      <line x1="28" y1="142" x2="346" y2="142" />
      <polyline points={path} />
      {coordinates.map((point, index) => (
        <g key={`${point.label}-${index}`}>
          <circle cx={point.x} cy={point.y} r="4" />
          <text x={point.x} y="162" textAnchor="middle">
            {point.label.slice(0, 10)}
          </text>
        </g>
      ))}
    </svg>
  );
}

function PieChartSvg({ points }: { points: Array<{ label: string; value: number }> }) {
  const total = points.reduce((sum, point) => sum + Math.max(0, point.value), 0) || 1;
  let startAngle = -90;

  return (
    <svg className="chart-svg" viewBox="0 0 360 180" role="img" aria-label="Круговая диаграмма">
      {points.map((point, index) => {
        const value = Math.max(0, point.value);
        const angle = (value / total) * 360;
        const path = pieSlicePath(95, 90, 58, startAngle, startAngle + angle);
        startAngle += angle;
        return <path key={`${point.label}-${index}`} d={path} className={`pie-slice slice-${index % 6}`} />;
      })}
      {points.slice(0, 5).map((point, index) => (
        <g key={`${point.label}-${index}`} className="pie-legend">
          <rect x="190" y={42 + index * 20} width="10" height="10" className={`pie-slice slice-${index % 6}`} />
          <text x="208" y={52 + index * 20}>
            {point.label.slice(0, 18)}
          </text>
        </g>
      ))}
    </svg>
  );
}

function pieSlicePath(cx: number, cy: number, radius: number, startAngle: number, endAngle: number) {
  const start = polarToCartesian(cx, cy, radius, endAngle);
  const end = polarToCartesian(cx, cy, radius, startAngle);
  const largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
  return `M ${cx} ${cy} L ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArcFlag} 0 ${end.x} ${end.y} Z`;
}

function polarToCartesian(cx: number, cy: number, radius: number, angle: number) {
  const radians = (angle * Math.PI) / 180;
  return {
    x: cx + radius * Math.cos(radians),
    y: cy + radius * Math.sin(radians)
  };
}
