import re
import sys
from enum import Enum
from typing import (
    NotRequired,
    List,
    Any,
    Optional,
    Iterable,
    Literal,
    Dict,
)

from debputy.lsp.vendoring._deb822_repro import (
    LIST_SPACE_SEPARATED_INTERPRETATION,
    LIST_COMMA_SEPARATED_INTERPRETATION,
)
from debputy.lsp.vendoring._deb822_repro.parsing import (
    LIST_UPLOADERS_INTERPRETATION,
    Deb822ParsedTokenList,
    Interpretation,
    _parse_whitespace_list_value,
    Deb822ParsedValueElement,
    _parsed_value_render_factory,
    ListInterpretation,
)
from debputy.lsp.vendoring._deb822_repro.tokens import (
    Deb822SpaceSeparatorToken,
    _value_line_tokenizer,
    _RE_WHITESPACE_SEPARATED_WORD_LIST,
    Deb822ValueToken,
    Deb822Token,
)
from debputy.manifest_parser.declarative_parser import ParserGenerator
from debputy.manifest_parser.tagging_types import DebputyParsedContent

_DEB822_REFERENCE_DATA_PARSER_GENERATOR = ParserGenerator()

# FIXME: should go into python3-debian
_RE_COMMA = re.compile("([^,]*),([^,]*)")

UsageHint = Literal["rare",]


@_value_line_tokenizer
def comma_or_space_split_tokenizer(v: str) -> Iterable[Deb822Token]:
    assert "\n" not in v
    for match in _RE_WHITESPACE_SEPARATED_WORD_LIST.finditer(v):
        space_before, word, space_after = match.groups()
        if space_before:
            yield Deb822SpaceSeparatorToken(sys.intern(space_before))
        if "," in word:
            for m in _RE_COMMA.finditer(word):
                word_before, word_after = m.groups()
                if word_before:
                    yield Deb822ValueToken(word_before)
                # ... not quite a whitespace, but it is too much pain to make it a non-whitespace token.
                yield Deb822SpaceSeparatorToken(",")
                if word_after:
                    yield Deb822ValueToken(word_after)
        else:
            yield Deb822ValueToken(word)
        if space_after:
            yield Deb822SpaceSeparatorToken(sys.intern(space_after))


# FIXME: should go into python3-debian
LIST_COMMA_OR_SPACE_SEPARATED_INTERPRETATION = ListInterpretation(
    comma_or_space_split_tokenizer,
    _parse_whitespace_list_value,
    Deb822ParsedValueElement,
    Deb822SpaceSeparatorToken,
    lambda: Deb822SpaceSeparatorToken(","),
    _parsed_value_render_factory,
)

_KEY2FIELD_VALUE_CLASS: Dict[str, "FieldValueClass"]


class FieldValueClass(Enum):
    SINGLE_VALUE = "single-value", LIST_SPACE_SEPARATED_INTERPRETATION
    SPACE_SEPARATED_LIST = "space-separated-list", LIST_SPACE_SEPARATED_INTERPRETATION
    BUILD_PROFILES_LIST = "build-profiles-list", None  # TODO
    COMMA_SEPARATED_LIST = "comma-separated-list", LIST_COMMA_SEPARATED_INTERPRETATION
    COMMA_SEPARATED_EMAIL_LIST = (
        "comma-separated-email-list",
        LIST_UPLOADERS_INTERPRETATION,
    )
    COMMA_OR_SPACE_SEPARATED_LIST = (
        "comma-or-space-separated-list",
        LIST_COMMA_OR_SPACE_SEPARATED_INTERPRETATION,
    )
    FREE_TEXT_FIELD = "free-text", None
    DEP5_FILE_LIST = "dep5-file-list", LIST_SPACE_SEPARATED_INTERPRETATION

    @classmethod
    def from_key(cls, key: str) -> "FieldValueClass":
        return _KEY2FIELD_VALUE_CLASS[key]

    @property
    def key(self) -> str:
        return self.value[0]

    def interpreter(self) -> Optional[Interpretation[Deb822ParsedTokenList[Any, Any]]]:
        return self.value[1]


# TODO: Have the parser generator support enums better than this hack.
FieldValueType = Literal[tuple(x.key for x in FieldValueClass)]
_KEY2FIELD_VALUE_CLASS = {x.key: x for x in FieldValueClass}


class Documentation(DebputyParsedContent):
    synopsis: NotRequired[str]
    long_description: NotRequired[str]
    uris: NotRequired[List[str]]


class DCtrlSubstvar(DebputyParsedContent):
    name: str
    defined_by: str
    dh_sequence: NotRequired[str]
    documentation: NotRequired[Documentation]


class DctrlSubstvarsReferenceData(DebputyParsedContent):
    substvars: List[DCtrlSubstvar]


class StaticValue(DebputyParsedContent):
    value: str
    documentation: NotRequired[Documentation]
    sort_key: NotRequired[str]
    usage_hint: NotRequired[UsageHint]


class Deb822Field(DebputyParsedContent):
    canonical_name: str
    field_value_type: FieldValueType
    unknown_value_authority: NotRequired[str]
    missing_field_authority: NotRequired[str]
    default_value: NotRequired[str]
    usage_hint: NotRequired[UsageHint]
    documentation: NotRequired[Documentation]
    values: NotRequired[List[StaticValue]]
    replaced_by: NotRequired[str]
    is_obsolete_without_replacement: NotRequired[Literal[True]]
    spellcheck_value: NotRequired[Literal[True]]
    supports_substvars: NotRequired[Literal[False]]


class StanzaType(DebputyParsedContent):
    stanza_name: str
    fields: List[Deb822Field]


class ReferenceVariable(DebputyParsedContent):
    name: str
    fallback: str


class ReferenceDefinition(DebputyParsedContent):
    variables: NotRequired[List[ReferenceVariable]]


class Deb822ReferenceData(DebputyParsedContent):
    definitions: NotRequired[ReferenceDefinition]
    stanza_types: List[StanzaType]


DEB822_REFERENCE_DATA_PARSER = _DEB822_REFERENCE_DATA_PARSER_GENERATOR.generate_parser(
    Deb822ReferenceData
)


DCTRL_SUBSTVARS_REFERENCE_DATA_PARSER = (
    _DEB822_REFERENCE_DATA_PARSER_GENERATOR.generate_parser(DctrlSubstvarsReferenceData)
)
