from __future__ import annotations

from collections.abc import Generator, Iterable
from contextlib import contextmanager
from functools import cache
from textwrap import wrap
from typing import TextIO


class Indent:
    def __init__(self, sheet: Sheet, indent: int = None):
        self.sheet = sheet
        if indent is not None:
            self.delta = indent
        else:
            self.delta = sheet.indent_width

        self.sheet.current_indent += self.delta

    def revert(self):
        self.sheet.current_indent -= self.delta

    def __enter__(self):
        pass

    def __exit__(self, *args):
        self.revert()


class Comment(str):
    __slot__ = ()

    def __new__(cls, input):
        return super().__new__(cls, f"/* {input} */")


class Sheet:
    def __init__(self, stream: TextIO):
        self.stream = stream
        self.current_indent = 0
        self.indent_width = 4
        self.prefix = None
        self.max_len = 80

    def __enter__(self):
        return self

    def __exit__(self, *args):
        return self.stream.__exit__(*args)

    def indent(self, width: int = None) -> Indent:
        return Indent(self, width)

    @staticmethod
    @cache
    def _indent_str(indent_char: str, width: int) -> str:
        return indent_char * width

    def indent_str(self) -> str:
        return self._indent_str(" ", self.current_indent)

    def write(self, string: str = ""):
        if self.prefix is not None:
            prefix = self.indent_str() + self.prefix
        else:
            prefix = self.indent_str()

        if string or self.prefix:
            final_string = f"{prefix}{string}".rstrip()
            final_string += "\n"
            self.stream.write(final_string)
        else:
            self.stream.write("\n")

    def writelines(self, strings: Iterable[str]):
        for string in strings:
            self.write(string)

    @contextmanager
    def block_comment(self) -> Generator[None]:
        self.write("/*")
        self.prefix = " * "

        yield

        self.prefix = None
        self.write(" */")

    def comment_paragraph(self, paragraph: str):
        current_indent = len(self.indent)
        lines = wrap(paragraph, width=(79 - (4 + current_indent)))
        self.writelines(lines)

    @contextmanager
    def rule(self, selector: str) -> Generator[None]:
        self.write(f"{selector} {{")
        with self.indent():
            yield

        self.write("}")

    @contextmanager
    def at_rule(
        self,
        rule_name: str,
        name: str | None = None,
    ) -> Generator[None]:
        self.write(f"@{rule_name}")
        if name:
            self.write(f' {name}')
        self.write(' {')
        with self.indent():
            yield
        self.write("}")

    def property(self, name: str, value: str):
        self.write(f"{name}: {value};")

    def comma_list_property(self, name: str, iter: Iterable[str | Comment]):
        values = tuple(iter)
        has_comment = any(isinstance(value, Comment) for value in values)

        if not has_comment:
            simple_joined = ", ".join(values)

            if not self.overflows(f"{name}: ", *simple_joined, ";"):
                self.property(name, simple_joined)
                return

        self.write(f"{name}: ")

        for neg_index, value in enumerate(reversed(values), start=1):
            if not isinstance(value, Comment):
                last_non_comment = -neg_index
                break
        else:
            raise RuntimeError("No non-comments in comma_list_property")

        with self.indent(2):
            for value in values[:last_non_comment]:
                match value:
                    case Comment(c):
                        self.write(c)
                    case v:
                        self.write(f"{v}, ")

            self.write(f"{values[last_non_comment]}")

            if last_non_comment != -1:
                for comment in values[last_non_comment + 1 :]:
                    self.write(comment)

        self.write(";")

    def _measure(self, *strings: str):
        total_len = sum(len(s) for s in strings)
        total_len += self.current_indent
        if self.prefix is not None:
            total_len += len(self.prefix)

        print(dict(total_len=total_len))
        return total_len

    def overflows(self, *strings: str):
        return self._measure(strings) >= self.max_len

    def custom_property(self, name: str, value: str):
        self.write(f"--{name}: {value};")

    def define_property(
            self,
            name: str,
            syntax: str,
            inherits: bool = False,
            initial_value: str | None = None,
    ):
        with self.at_rule('property', name):
            self.property('syntax', syntax)
            if inherits:
                self.property('inherits', 'true')
            if initial_value is not None:
                self.property('initialValue', initial_value)

