import {
  DOMParser,
  XMLSerializer,
  type Document as XmlDomDocument,
  type Element as XmlElement,
} from "@xmldom/xmldom";
import { optimize } from "svgo";
import { calculateFontSizeForText } from "./calculateFontSizeForText";

export class SvgProcessor {
  private readonly doc: XmlDomDocument;
  private readonly svgElement: XmlElement;

  constructor(
    svgText: string,
    private optimizeOutput: boolean = false,
  ) {
    const parser = new DOMParser();
    this.doc = parser.parseFromString(svgText, "image/svg+xml");
    const svgElement = this.doc.getElementsByTagName("svg").item(0);
    if (!svgElement) {
      throw new Error("SVG не найден");
    }
    this.svgElement = svgElement;
  }

  addPadding(padding: number, backgroundColor = "transparent"): SvgProcessor {
    const viewBox = this.svgElement
      .getAttribute("viewBox")
      ?.split(" ")
      .map(Number);
    if (!viewBox) {
      throw new Error("viewBox не найден");
    }
    const [x, y, width, height] = viewBox;

    // Увеличиваем размеры viewBox с учётом отступов
    const newWidth = width + padding * 2;
    const newHeight = height + padding * 2;

    const newViewBox = [x, y, newWidth, newHeight].join(" ");
    this.svgElement.setAttribute("viewBox", newViewBox);

    // Добавляем прямоугольник для фона, если требуется
    if (backgroundColor !== "transparent") {
      const rect = this.doc.createElementNS(
        "http://www.w3.org/2000/svg",
        "rect",
      );
      rect.setAttribute("x", x.toString());
      rect.setAttribute("y", y.toString());
      rect.setAttribute("width", newWidth.toString());
      rect.setAttribute("height", newHeight.toString());
      rect.setAttribute("fill", backgroundColor);
      this.svgElement.insertBefore(rect, this.svgElement.firstChild);
    }

    // Сдвигаем все элементы
    const elements = Array.from(this.svgElement.childNodes).filter(
      (node) => node.nodeType === 1 && node !== this.svgElement.firstChild,
    ) as XmlElement[];

    elements.forEach((el) => {
      const isText = el.tagName === "text";

      // Для текста добавляем дополнительное смещение
      if (isText) {
        const dy = el.getAttribute("dy") || "0";
        if (!dy.includes("%")) {
          el.setAttribute("dy", (parseFloat(dy) - padding).toString());
        }
        const dx = el.getAttribute("dx") || "0";
        if (!dx.includes("%") && !el.getAttribute("x")?.includes("%")) {
          el.setAttribute("dx", padding.toString());
        }
      } else {
        const transform = el.getAttribute("transform") || "";
        el.setAttribute(
          "transform",
          `translate(${padding}, ${padding}) ${transform}`.trim(),
        );
      }
    });

    // Удаляем атрибуты width и height, чтобы размеры SVG задавались только через viewBox
    this.svgElement.removeAttribute("width");
    this.svgElement.removeAttribute("height");

    return this;
  }

  addLabel(
    label: string,
    color: string,
    marginMultiplier: number = 0.1,
  ): SvgProcessor {
    if (!label) {
      return this;
    }

    const viewBoxAttr = this.svgElement.getAttribute("viewBox");
    if (!viewBoxAttr) {
      throw new Error("viewBox не найден");
    }

    const viewBox: readonly number[] = viewBoxAttr.split(" ").map(Number);
    const [x, y, width, height] = viewBox;
    if (viewBox.length === 4) {
      // Рассчитываем размер шрифта
      const fontSize = calculateFontSizeForText(label, width);
      const textMargin = Math.min(fontSize, height * marginMultiplier);
      const extraBottomPadding = fontSize * 0.3; // 30% дополнительного пространства для висячих символов

      // Увеличиваем высоту viewBox
      const newHeight = height + textMargin + fontSize;
      this.svgElement.setAttribute(
        "viewBox",
        [x, y, width, newHeight].join(" "),
      );

      // Создаем элемент текста
      const textElement = this.doc.createElementNS(
        "http://www.w3.org/2000/svg",
        "text",
      );

      textElement.setAttribute("x", "50%");
      textElement.setAttribute("y", "100%");
      textElement.setAttribute("dy", `-${extraBottomPadding}px`);
      textElement.setAttribute("text-anchor", "middle");
      textElement.setAttribute("alignment-baseline", "baseline");
      textElement.setAttribute("dominant-baseline", "alphabetic");
      textElement.setAttribute("fill", color);
      textElement.setAttribute("font-size", fontSize.toString());
      textElement.setAttribute("font-family", "Arial, sans-serif");
      textElement.textContent = label;

      this.svgElement.appendChild(textElement);
    }

    // Удаляем атрибуты width и height, чтобы размеры SVG задавались только через viewBox
    this.svgElement.removeAttribute("width");
    this.svgElement.removeAttribute("height");

    return this;
  }

  replaceFill(fillColor: string): SvgProcessor {
    const elements = this.doc.getElementsByTagName("*");
    for (let i = 0; i < elements.length; i++) {
      const el = elements.item(i);
      if (el && el.hasAttribute("fill")) {
        el.setAttribute("fill", fillColor);
      }
    }

    return this;
  }

  /**
   * Оптимизация SVG с использованием SVGO.
   * @param svgText string изображения SVG.
   * @returns Строка оптимизированного SVG.
   */
  private optimizeSvg(svgText: string): string {
    const optimizedSvg = optimize(svgText, {
      plugins: [
        { name: "removeDoctype" },
        { name: "removeComments" },
        { name: "removeDimensions" },
        { name: "convertShapeToPath" },
        { name: "convertPathData" },
        { name: "mergePaths" },
      ],
    });

    if (!optimizedSvg.data) {
      throw new Error("Ошибка оптимизации SVG");
    }

    return optimizedSvg.data;
  }

  toString(): string {
    const serializer = new XMLSerializer();
    const result = serializer.serializeToString(this.doc);
    // console.log(result);
    if (this.optimizeOutput) {
      return this.optimizeSvg(result);
    }
    return result;
  }
}
