import dataclasses
import textwrap
from typing import (
    Optional,
    Union,
    Mapping,
    Sequence,
    Callable,
    Iterable,
    Any,
    Self,
    TYPE_CHECKING,
)

from debputy.lsp.ref_models.deb822_reference_parse_models import UsageHint
from debputy.lsp.vendoring._deb822_repro import Deb822ParagraphElement

if TYPE_CHECKING:
    from debputy.linting.lint_util import LintState
    from debputy.lsp.debputy_ls import DebputyLanguageServer

LSP_DATA_DOMAIN = "debputy-lsp-data"


def format_comp_item_synopsis_doc(
    usage_hint: Optional[UsageHint],
    synopsis_doc: Optional[str],
    is_deprecated: bool,
) -> str:
    if is_deprecated:
        return (
            f"[OBSOLETE]: {synopsis_doc}"
            if synopsis_doc is not None and not synopsis_doc.isspace()
            else f"[OBSOLETE]"
        )
    if usage_hint is not None:
        return (
            f"[{usage_hint.upper()}]: {synopsis_doc}"
            if synopsis_doc is not None and not synopsis_doc.isspace()
            else f"[{usage_hint.upper()}]"
        )
    return synopsis_doc


@dataclasses.dataclass(slots=True, frozen=True)
class Keyword:
    value: str
    synopsis: Optional[str] = None
    long_description: Optional[str] = None
    translation_context: str = ""
    is_obsolete: bool = False
    replaced_by: Optional[str] = None
    is_exclusive: bool = False
    sort_text: Optional[
        Union[
            str,
            Callable[
                ["Keyword", "LintState", Sequence[Deb822ParagraphElement], str], str
            ],
        ]
    ] = None
    usage_hint: Optional[UsageHint] = None
    can_complete_keyword_in_stanza: Optional[
        Callable[[Iterable[Deb822ParagraphElement]], bool]
    ] = None
    """For keywords in fields that allow multiple keywords, the `is_exclusive` can be
    used for keywords that cannot be used with other keywords. As an example, the `all`
    value in `Architecture` of `debian/control` cannot be used with any other architecture.
    """

    @property
    def is_deprecated(self) -> bool:
        return self.is_obsolete or self.replaced_by is not None

    def resolve_sort_text(
        self,
        lint_state: "LintState",
        stanza_parts: Sequence[Deb822ParagraphElement],
        value_being_completed: str,
    ) -> Optional[str]:
        sort_text = self.sort_text
        if sort_text is None:
            return None
        if not isinstance(sort_text, str):
            return sort_text(
                self,
                lint_state,
                stanza_parts,
                value_being_completed,
            )
        return sort_text

    def is_keyword_valid_completion_in_stanza(
        self,
        stanza_parts: Sequence[Deb822ParagraphElement],
    ) -> bool:
        return (
            self.can_complete_keyword_in_stanza is None
            or self.can_complete_keyword_in_stanza(stanza_parts)
        )

    def replace(self, **changes: Any) -> "Self":
        return dataclasses.replace(self, **changes)

    def synopsis_translated(
        self, translation_provider: Union["DebputyLanguageServer", "LintState"]
    ) -> str:
        return translation_provider.translation(LSP_DATA_DOMAIN).pgettext(
            self.translation_context,
            self.synopsis,
        )

    def long_description_translated(
        self, translation_provider: Union["DebputyLanguageServer", "LintState"]
    ) -> str:
        return translation_provider.translation(LSP_DATA_DOMAIN).pgettext(
            self.translation_context,
            self.long_description,
        )


def allowed_values(*values: Union[str, Keyword]) -> Mapping[str, Keyword]:
    as_keywords = [k if isinstance(k, Keyword) else Keyword(k) for k in values]
    as_mapping = {k.value: k for k in as_keywords if k.value}
    # Simple bug check
    assert len(as_keywords) == len(as_mapping)
    return as_mapping


# This is the set of styles that `debputy` explicitly supports, which is more narrow than
# the ones in the config file.
ALL_PUBLIC_NAMED_STYLES = allowed_values(
    Keyword(
        "black",
        long_description=textwrap.dedent(
            """\
            Uncompromising file formatting of Debian packaging files

            By using it, you  agree to cede control over minutiae of hand-formatting. In
            return, the formatter gives you speed, determinism, and freedom from style
            discussions about formatting.

            The `black` style is inspired by the `black` Python code formatter. Like with
            `black`, the style will evolve over time.
    """
        ),
    ),
)
