import re
from typing import (
    Union,
    Sequence,
    Optional,
)

from debputy.linting.lint_util import LintState, with_range_in_continuous_parts
from debputy.lsp.debputy_ls import DebputyLanguageServer
from debputy.lsp.lsp_debian_control_reference_data import (
    DTestsCtrlFileMetadata,
)
from debputy.lsp.lsp_features import (
    lint_diagnostics,
    lsp_completer,
    lsp_hover,
    lsp_standard_handler,
    lsp_folding_ranges,
    lsp_semantic_tokens_full,
    lsp_will_save_wait_until,
    lsp_format_document,
    SecondaryLanguage,
    LanguageDispatchRule,
    lsp_cli_reformat_document,
)
from debputy.lsp.lsp_generic_deb822 import (
    deb822_completer,
    deb822_hover,
    deb822_folding_ranges,
    deb822_semantic_tokens_full,
    deb822_format_file,
    scan_for_syntax_errors_and_token_level_diagnostics,
)
from debputy.lsp.vendoring._deb822_repro import Deb822ParagraphElement
from debputy.lsprotocol.types import (
    CompletionItem,
    CompletionList,
    CompletionParams,
    HoverParams,
    Hover,
    TEXT_DOCUMENT_CODE_ACTION,
    SemanticTokens,
    SemanticTokensParams,
    FoldingRangeParams,
    FoldingRange,
    WillSaveTextDocumentParams,
    TextEdit,
    DocumentFormattingParams,
)

try:
    from debputy.lsp.vendoring._deb822_repro.locatable import (
        Position as TEPosition,
        Range as TERange,
        START_POSITION,
    )

    from pygls.server import LanguageServer
    from pygls.workspace import TextDocument
except ImportError:
    pass


_CONTAINS_SPACE_OR_COLON = re.compile(r"[\s:]")
_DISPATCH_RULE = LanguageDispatchRule.new_rule(
    "debian/tests/control",
    None,
    "debian/tests/control",
    [
        # emacs's name - from elpa-dpkg-dev-el (>> 37.11)
        SecondaryLanguage("debian-autopkgtest-control-mode"),
        # vim-debian's id: https://salsa.debian.org/vim-team/vim-debian/-/commit/14776de4f28f82177ef6e2397510d01b266f3b41
        SecondaryLanguage("autopkgtest"),
    ],
)

_DTESTS_CTRL_FILE_METADATA = DTestsCtrlFileMetadata()

lsp_standard_handler(_DISPATCH_RULE, TEXT_DOCUMENT_CODE_ACTION)


@lsp_hover(_DISPATCH_RULE)
def debian_tests_control_hover(
    ls: "DebputyLanguageServer",
    params: HoverParams,
) -> Optional[Hover]:
    return deb822_hover(ls, params, _DTESTS_CTRL_FILE_METADATA)


@lsp_completer(_DISPATCH_RULE)
def debian_tests_control_completions(
    ls: "DebputyLanguageServer",
    params: CompletionParams,
) -> Optional[Union[CompletionList, Sequence[CompletionItem]]]:
    return deb822_completer(ls, params, _DTESTS_CTRL_FILE_METADATA)


@lsp_folding_ranges(_DISPATCH_RULE)
def debian_tests_control_folding_ranges(
    ls: "DebputyLanguageServer",
    params: FoldingRangeParams,
) -> Optional[Sequence[FoldingRange]]:
    return deb822_folding_ranges(ls, params, _DTESTS_CTRL_FILE_METADATA)


@lint_diagnostics(_DISPATCH_RULE)
async def _lint_debian_tests_control(lint_state: LintState) -> None:
    deb822_file = lint_state.parsed_deb822_file_content

    if not _DTESTS_CTRL_FILE_METADATA.file_metadata_applies_to_file(deb822_file):
        return

    first_error = await scan_for_syntax_errors_and_token_level_diagnostics(
        deb822_file,
        lint_state,
    )

    stanza_no = 0

    async for stanza_range, stanza in lint_state.slow_iter(
        with_range_in_continuous_parts(deb822_file.iter_parts())
    ):
        if not isinstance(stanza, Deb822ParagraphElement):
            continue
        stanza_position = stanza_range.start_pos
        if stanza_position.line_position >= first_error:
            break
        stanza_no += 1
        stanza_metadata = _DTESTS_CTRL_FILE_METADATA.classify_stanza(
            stanza,
            stanza_no,
        )
        await stanza_metadata.stanza_diagnostics(
            deb822_file,
            stanza,
            stanza_position,
            lint_state,
        )


@lsp_will_save_wait_until(_DISPATCH_RULE)
def _debian_tests_control_on_save_formatting(
    ls: "DebputyLanguageServer",
    params: WillSaveTextDocumentParams,
) -> Optional[Sequence[TextEdit]]:
    doc = ls.workspace.get_text_document(params.text_document.uri)
    lint_state = ls.lint_state(doc)
    return deb822_format_file(lint_state, _DTESTS_CTRL_FILE_METADATA)


@lsp_cli_reformat_document(_DISPATCH_RULE)
def _reformat_debian_tests_control(
    lint_state: LintState,
) -> Optional[Sequence[TextEdit]]:
    return deb822_format_file(lint_state, _DTESTS_CTRL_FILE_METADATA)


@lsp_format_document(_DISPATCH_RULE)
def _debian_tests_control_on_save_formatting(
    ls: "DebputyLanguageServer",
    params: DocumentFormattingParams,
) -> Optional[Sequence[TextEdit]]:
    doc = ls.workspace.get_text_document(params.text_document.uri)
    lint_state = ls.lint_state(doc)
    return deb822_format_file(lint_state, _DTESTS_CTRL_FILE_METADATA)


@lsp_semantic_tokens_full(_DISPATCH_RULE)
async def _debian_tests_control_semantic_tokens_full(
    ls: "DebputyLanguageServer",
    request: SemanticTokensParams,
) -> Optional[SemanticTokens]:
    return await deb822_semantic_tokens_full(
        ls,
        request,
        _DTESTS_CTRL_FILE_METADATA,
    )
