from dataclasses import dataclass
import sympy
from sympy.abc import x


def display_integral_root(
    root: int, multiplicity: int, pad_to: int | None = None
) -> str:
    return (
        "("
        # denote sign
        + ("\\pm" if root != 0 else "")
        + str(root)
        + ")^{"
        + str(multiplicity)
        # pad multiplicity
        + (
            ("\\phantom{" + ("0" * (pad_to - len(str(multiplicity))) + "}"))
            if pad_to is not None and len(str(multiplicity)) < pad_to
            else ""
        )
        + "}"
    )


@dataclass
class SymmetricSpectrum:
    """
    Dataclass for symmetric spectra -- those for which, if a spectrum contains a root,
    it also contains its negative.

    If a root polynomial is not symmetric, then its symmetric counterpart will *not* be specified.
    """

    # Either the integral root or a polynomial with the root, and the multiplicity
    roots: list[tuple[sympy.Expr | int, int]]
    not_shown_count: int = 0

    def to_markdown(self) -> str:
        """
        Display a spectrum as a markdown string
        """
        integer_roots = [
            (root, multiplicity)
            for root, multiplicity in self.roots
            if isinstance(root, int)
        ]
        # symmetrify polynomials
        polynomial_roots = [
            (root, multiplicity)
            for base_root, multiplicity in self.roots
            if isinstance(base_root, sympy.Expr)
            for root in set(
                [base_root, (-1) ** (sympy.degree(base_root)) * base_root.subs(x, -x)]
            )
        ]

        shown_roots = len(polynomial_roots) > 0 or len(integer_roots) > 0
        not_shown_string = (
            (
                f"*Not shown: {self.not_shown_count} other roots*"
                if shown_roots
                else f"*Not shown: all {self.not_shown_count} roots*"
            )
            if self.not_shown_count > 0
            else ""
        )

        # strictly integral spectra
        if len(polynomial_roots) == 0 and len(integer_roots) != 0:
            longest_integer = max(
                len(str(multiplicity)) for _, multiplicity in integer_roots
            )
            return (
                (
                    "$$\\begin{gather*}"
                    + " \\\\ ".join(
                        display_integral_root(*rm, pad_to=longest_integer)
                        for rm in integer_roots
                    )
                    + "\\end{gather*}$$"
                )
                if shown_roots
                else ""
            ) + not_shown_string

        # conditionally split integral roots onto multiple lines
        integral_roots_lines = (
            " ".join(display_integral_root(*rm) for rm in integer_roots)
            if len(integer_roots) < 5
            else (
                " ".join(
                    display_integral_root(*rm)
                    for rm in integer_roots[: len(integer_roots) // 2]
                )
                + "\\\\"
                + " ".join(
                    display_integral_root(*rm)
                    for rm in integer_roots[len(integer_roots) // 2 :]
                )
            )
        )

        return (
            (
                "$$\\begin{gather*}"
                + integral_roots_lines
                + " \\\\ "
                + " \\\\ ".join(
                    sympy.latex(root**multiplicity)
                    for root, multiplicity in polynomial_roots
                )
                + "\\end{gather*}$$"
            )
            if shown_roots
            else ""
        ) + not_shown_string


def count_roots_spectrum(spectrum: SymmetricSpectrum) -> int:
    return (
        sum(
            [
                (
                    (multiplicity * 2 if root != 0 else multiplicity)
                    if isinstance(root, int)
                    else (int(sympy.degree(root, x)) * multiplicity)
                )
                for root, multiplicity in spectrum.roots
            ]
        )
        + spectrum.not_shown_count
    )


@dataclass
class GraphData:
    spectrum: SymmetricSpectrum
    vertex_count: int | None = None
    edge_count: int | None = None
    distance_classes: list[int] | None = None
