import textwrap
from argparse import BooleanOptionalAction

from debputy.commands.debputy_cmd.context import ROOT_COMMAND, CommandContext, add_arg
from debputy.util import _error


_EDITOR_SNIPPETS = {
    "emacs": "emacs+eglot",
    "emacs+eglot": textwrap.dedent(
        """\
        ;; `deputy lsp server` glue for emacs eglot (eglot is built-in these days)
        ;;
        ;; Add to ~/.emacs or ~/.emacs.d/init.el and then activate via `M-x eglot`.
        ;;
        ;; Requires: apt install elpa-dpkg-dev-el
        ;; Recommends: apt install elpa-markdown-mode

        ;; Make emacs recognize debian/debputy.manifest as a YAML file
        (add-to-list 'auto-mode-alist '("/debian/debputy.manifest\\'" . yaml-mode))
        ;; Inform eglot about the debputy LSP
        (with-eval-after-load 'eglot
            (add-to-list 'eglot-server-programs
                    '(debian-control-mode . ("debputy" "lsp" "server")))
            (add-to-list 'eglot-server-programs
                    '(debian-changelog-mode . ("debputy" "lsp" "server")))
            (add-to-list 'eglot-server-programs
                    '(debian-copyright-mode . ("debputy" "lsp" "server")))
            ;; The debian/rules file uses the qmake mode.
            (add-to-list 'eglot-server-programs
                    '(makefile-gmake-mode . ("debputy" "lsp" "server")))
        )

        ;; Auto-start eglot for the relevant modes.
        (add-hook 'debian-control-mode-hook 'eglot-ensure)
        ;; NOTE: changelog disabled by default because for some reason it
        ;;       this hook causes perceivable delay (several seconds) when
        ;;       opening the first changelog. It seems to be related to imenu.
        ;; (add-hook 'debian-changelog-mode-hook 'eglot-ensure)
        (add-hook 'debian-copyright-mode-hook 'eglot-ensure)
        (add-hook 'makefile-gmake-mode-hook 'eglot-ensure)
    """
    ),
    "vim": "vim+youcompleteme",
    "vim+youcompleteme": textwrap.dedent(
        """\
        # debputy lsp server glue for vim with vim-youcompleteme. Add to ~/.vimrc
        #
        # Requires: apt install vim-youcompleteme

        # Make vim recognize debputy.manifest as YAML file
        au BufNewFile,BufRead debputy.manifest          setf yaml
        # Inform vim/ycm about the debputy LSP
        let g:ycm_language_server = [
          \\   { 'name': 'debputy',
          \\     'filetypes': [ 'debcontrol', 'debcopyright', 'debchangelog', 'make', 'yaml'],
          \\     'cmdline': [ 'debputy', 'lsp', 'server' ]
          \\   },
          \\ ]

        packadd! youcompleteme
        # Add relevant ycm keybinding such as:
        # nmap <leader>d <plug>(YCMHover)
    """
    ),
}


lsp_command = ROOT_COMMAND.add_dispatching_subcommand(
    "lsp",
    dest="lsp_command",
    help_description="Language server related subcommands",
)


@lsp_command.register_subcommand(
    "server",
    log_only_to_stderr=True,
    help_description="Start the language server",
    argparser=[
        add_arg(
            "--tcp",
            action="store_true",
            help="Use TCP server",
        ),
        add_arg(
            "--ws",
            action="store_true",
            help="Use WebSocket server",
        ),
        add_arg(
            "--host",
            default="127.0.0.1",
            help="Bind to this address (Use with --tcp / --ws)",
        ),
        add_arg(
            "--port",
            type=int,
            default=2087,
            help="Bind to this port (Use with --tcp / --ws)",
        ),
    ],
)
def lsp_server_cmd(context: CommandContext) -> None:
    parsed_args = context.parsed_args

    try:
        import lsprotocol
        import pygls
    except ImportError:
        _error(
            "This feature requires lsprotocol and pygls (apt-get install python3-lsprotocol python3-pygls)"
        )

    from debputy.lsp.lsp_features import ensure_lsp_features_are_loaded
    from debputy.lsp.lsp_dispatch import DEBPUTY_LANGUAGE_SERVER

    ensure_lsp_features_are_loaded()
    debputy_language_server = DEBPUTY_LANGUAGE_SERVER

    if parsed_args.tcp:
        debputy_language_server.start_tcp(parsed_args.host, parsed_args.port)
    elif parsed_args.ws:
        debputy_language_server.start_ws(parsed_args.host, parsed_args.port)
    else:
        debputy_language_server.start_io()


@lsp_command.register_subcommand(
    "editor-config",
    help_description="Provide editor configuration snippets",
    argparser=[
        add_arg(
            "editor_name",
            metavar="editor",
            choices=_EDITOR_SNIPPETS,
            help="The editor to provide a snippet for",
        ),
    ],
)
def lsp_editor_glue(context: CommandContext) -> None:
    editor_name = context.parsed_args.editor_name
    result = _EDITOR_SNIPPETS[editor_name]
    while result in _EDITOR_SNIPPETS:
        result = _EDITOR_SNIPPETS[result]
    print(result)


@lsp_command.register_subcommand(
    "features",
    help_description="Describe language ids and features",
)
def lsp_editor_glue(_context: CommandContext) -> None:
    try:
        import lsprotocol
        import pygls
    except ImportError:
        _error(
            "This feature requires lsprotocol and pygls (apt-get install python3-lsprotocol python3-pygls)"
        )

    from debputy.lsp.lsp_features import describe_lsp_features

    describe_lsp_features()


@ROOT_COMMAND.register_subcommand(
    "lint",
    log_only_to_stderr=True,
    argparser=[
        add_arg(
            "--spellcheck",
            dest="spellcheck",
            action="store_true",
            shared=True,
            help="Enable spellchecking",
        ),
        add_arg(
            "--auto-fix",
            dest="auto_fix",
            action="store_true",
            shared=True,
            help="Automatically fix problems with trivial or obvious corrections.",
        ),
        add_arg(
            "--linter-exit-code",
            dest="linter_exit_code",
            default=True,
            action=BooleanOptionalAction,
            help='Enable or disable the "linter" convention of exiting with an error if severe issues were found',
        ),
    ],
)
def lint_cmd(context: CommandContext) -> None:
    try:
        import lsprotocol
    except ImportError:
        _error("This feature requires lsprotocol (apt-get install python3-lsprotocol)")

    from debputy.linting.lint_impl import perform_linting

    context.must_be_called_in_source_root()
    perform_linting(context)


def ensure_lint_and_lsp_commands_are_loaded():
    # Loading the module does the heavy lifting
    # However, having this function means that we do not have an "unused" import that some tool
    # gets tempted to remove
    assert ROOT_COMMAND.has_command("lsp")
    assert ROOT_COMMAND.has_command("lint")
