import dataclasses
import zlib
from typing import Self

from folkugat_web.model import temes
from folkugat_web.utils import batched


def get_hash(*v: bytes) -> bytes:
    return zlib.adler32(b"".join(v)).to_bytes(4)


@dataclasses.dataclass
class LyricsParagraph:
    lines: list[str]

    def hash(self) -> bytes:
        return get_hash(*(get_hash(line.encode()) for line in self.lines))


@dataclasses.dataclass
class LyricsLine:
    paragraphs: list[LyricsParagraph]

    def hash(self) -> bytes:
        return get_hash(*(paragraph.hash() for paragraph in self.paragraphs))


@dataclasses.dataclass
class LyricsText:
    lines: list[LyricsLine]

    @classmethod
    def from_lyrics(cls, lyrics: temes.Lyrics, max_cols: int = 3) -> Self:
        # Use stored max_columns if available, otherwise use the provided max_cols
        effective_max_cols = lyrics.max_columns if lyrics.max_columns is not None else max_cols
        # Remove ~ characters before processing
        paragraphs = [
            LyricsParagraph(lines=[line.replace("~", "") for line in par_str.splitlines()])
            for par_str in lyrics.content.split("\n\n") if par_str
        ]
        return cls(lines=[
            LyricsLine(paragraphs=list(line_pars))
            for line_pars in batched(paragraphs, effective_max_cols)
        ])

    def hash(self) -> bytes:
        return get_hash(*(line.hash() for line in self.lines))


@dataclasses.dataclass
class HeaderData:
    title: str
    composer: str | None

    @classmethod
    def from_tema(cls, tema: temes.Tema) -> Self:
        return cls(
            title=tema.title,
            composer=tema.composer() or tema.origin(),
        )

    def hash(self) -> bytes:
        return get_hash(
            self.title.encode(),
            (self.composer or "").encode(),
        )


@dataclasses.dataclass
class LilypondTune:
    header: HeaderData
    score_source: str | None
    lyrics: LyricsText | None
    is_unknown: bool = False

    def hash(self) -> bytes:
        return get_hash(
            self.header.hash(),
            (self.score_source or "").encode(),
            self.lyrics.hash() if self.lyrics else b"",
        )


@dataclasses.dataclass
class LilypondSet:
    title: str
    tunes: list[LilypondTune]

    def hash(self) -> bytes:
        return get_hash(
            self.title.encode(),
            *(tune.hash() for tune in self.tunes),
        )


@dataclasses.dataclass
class LilypondPlaylist:
    title: str
    sets: list[LilypondSet]

    def hash(self) -> bytes:
        return get_hash(
            self.title.encode(),
            *(lilypond_set.hash() for lilypond_set in self.sets),
        )
