"""TUI input and prompt utilities for interactive mode."""

from __future__ import annotations

import re

try:
    import readline
    HAS_READLINE = True
    # Check if we're using libedit (which has poor reverse search)
    INPUT_USES_LIBEDIT = getattr(readline, 'backend', None) == 'editline'

    if INPUT_USES_LIBEDIT:
        # Disable Ctrl-R entirely for libedit - it's not good enough
        try:
            readline.parse_and_bind("bind -r ^R")
        except Exception:
            pass
    else:
        # For GNU readline, disable Ctrl-R by default (will enable in shell prompt)
        try:
            readline.parse_and_bind("bind -r ^R")
        except Exception:
            pass
except ImportError:
    HAS_READLINE = False
    INPUT_USES_LIBEDIT = False
    readline = None  # type: ignore

from . import action_prompt_choices
from . import action_prompt_menu
from ..output.colors import Colors, format_hotkey
from ..i18n import _, pgettext
from ..utils.session_lock import temporarily_release_session_lock

# Module-level storage for shell command history
_shell_command_history: list[str] = []


def unlocked_input(prompt: str) -> str:
    """Read input without holding the repository session lock."""
    with temporarily_release_session_lock():
        return input(prompt)


def wrap_prompt_for_readline(prompt: str) -> str:
    """Wrap ANSI escape sequences in readline ignore markers.

    Readline needs escape sequences wrapped in \\001 and \\002 so it can
    calculate line length correctly for features like backspace and Ctrl-R.

    Args:
        prompt: Prompt string that may contain ANSI escape codes

    Returns:
        Prompt with escape codes wrapped if readline is available,
        otherwise unchanged
    """
    if not HAS_READLINE:
        return prompt

    # Pattern to match ANSI escape sequences (ESC[...m)
    # \x1b is ESC, then [, then any chars until m
    escape_pattern = re.compile(r'(\x1b\[[0-9;]*m)')

    # Wrap each escape sequence in \001 and \002
    return escape_pattern.sub(r'\001\1\002', prompt)


def prompt_action(use_color: bool = True, show_question: bool = True, has_hunk: bool = True) -> str:
    """
    Prompt user for an action choice.

    Displays primary and secondary actions with hotkeys and prompts for input.

    Args:
        use_color: Whether to use colored output
        show_question: Whether to display the "What do you want to do" question
        has_hunk: Whether a hunk is available (affects which actions are shown)

    Returns:
        Normalized choice (e.g., 'i', 's', 'd', 'q', 'a', 'l', 'x', '!', etc.)
    """
    option_groups = action_prompt_choices.action_prompt_option_groups(
        has_hunk=has_hunk,
        use_color=use_color,
    )

    if show_question:
        print()
        if has_hunk:
            print(_("What do you want to do with this hunk?"))
        else:
            print(_("What do you want to do?"))
        for text, hotkey, color in option_groups.primary:
            formatted = format_hotkey(text, hotkey, color)
            print(f"  {formatted}")

        # Grouped secondary menu sections
        print()

        rendered_sections: list[str] = []

        section_specs = [
                (pgettext("menu section label", "Other scope"), option_groups.scope),
                (pgettext("menu section label", "Flow"), option_groups.flow),
                (pgettext("menu section label", "More"), option_groups.more),
        ]

        for label, options in section_specs:
            if options:
                rendered_sections.extend(
                    action_prompt_menu.format_menu_section_lines(
                        label,
                        options,
                        use_color,
                    )
                )

        if rendered_sections:
            for rendered_section in rendered_sections:
                print(rendered_section)

        print()
    try:
        prompt_text: str = _("Action: ")
        if use_color and Colors.enabled():
            # Remove trailing space, add color, add space after reset
            prompt_text = f"{Colors.BOLD}{prompt_text.rstrip()}{Colors.RESET} "
        choice = unlocked_input(wrap_prompt_for_readline(prompt_text)).strip()
    except (KeyboardInterrupt, EOFError):
        return "q"  # Ctrl-C or Ctrl-D exits

    return action_prompt_choices.normalize_action_prompt_choice(choice)


def confirm_destructive_operation(_operation: str, message: str) -> bool:
    """
    Prompt for confirmation of a destructive operation.

    Args:
        _operation: Name of the operation (e.g., "discard", "block")
        message: Warning message to display

    Returns:
        True if user confirmed with "yes", False otherwise
    """
    use_color = Colors.enabled()

    print()
    warning_text = _("⚠️  {message}").format(message=message)
    if use_color:
        print(f"{Colors.RED}{warning_text}{Colors.RESET}")
    else:
        print(warning_text)

    try:
        yes_text = _("yes")
        no_text = _("NO")

        if use_color:
            yes_label = f"{Colors.GREEN}{yes_text}{Colors.RESET}"
            no_label = f"{Colors.BOLD}{no_text}{Colors.RESET}"
        else:
            yes_label = yes_text
            no_label = no_text

        prompt_text = _("Are you sure? [{yes}/{no}]: ").format(
            yes=yes_label,
            no=no_label,
        )
        response = unlocked_input(wrap_prompt_for_readline(prompt_text)).strip().lower()
    except (KeyboardInterrupt, EOFError):
        return False

    return response == yes_text.lower()


def prompt_line_ids() -> str:
    """
    Prompt for line IDs with validation hint.

    Returns:
        User input string (e.g., "1,3,5-7")
    """
    try:
        return unlocked_input(_("Enter line IDs (e.g., 1,3,5-7): ")).strip()
    except (KeyboardInterrupt, EOFError):
        return ""


def prompt_quit_session() -> str:
    """
    Prompt whether to keep staged changes when quitting.

    Returns:
        'keep', 'undo', or 'cancel'
    """
    use_color = Colors.enabled()

    print()
    try:
        y_text = pgettext("yes hotkey", "y")
        n_text = pgettext("no hotkey", "n")
        yes_text = _("yes")
        no_text = _("no")

        if use_color:
            y_label = f"{Colors.GREEN}{y_text}{Colors.RESET}"
            n_label = f"{Colors.RED}{n_text}{Colors.RESET}"
        else:
            y_label = y_text
            n_label = n_text

        prompt_text = _(
            "Keep staged changes? [{yes_key}] {yes} / [{no_key}] {no}: "
        ).format(
            yes_key=y_label,
            yes=yes_text,
            no_key=n_label,
            no=no_text,
        )
        response = unlocked_input(wrap_prompt_for_readline(prompt_text)).strip()
    except (KeyboardInterrupt, EOFError):
        return "cancel"

    normalized = action_prompt_choices.normalize_localized_choice(
        response,
        stable_codes=frozenset({"y", "n"}),
        legacy_words={"yes": "y", "no": "n"},
        localized_words=action_prompt_choices.localized_word_aliases(
            (
                (str(y_text), "y"),
                (str(n_text), "n"),
                (str(yes_text), "y"),
                (str(no_text), "n"),
            )
        ),
    )
    if normalized == "y":
        return "keep"
    if normalized == "n":
        return "undo"
    return "cancel"


def prompt_shell_command() -> str:
    """
    Prompt for a shell command to execute.

    Uses readline for command history and editing if available.
    Ctrl-R reverse search works here to search previous commands.

    Returns:
        Command string entered by user, or empty string if cancelled
    """
    global _shell_command_history

    # Load shell command history and enable Ctrl-R for GNU readline only
    if HAS_READLINE:
        for cmd in _shell_command_history:
            readline.add_history(cmd)
        # Only enable Ctrl-R for GNU readline (libedit's reverse search is too poor)
        if not INPUT_USES_LIBEDIT:
            try:
                readline.parse_and_bind("bind ^R em-inc-search-prev")  # Re-bind Ctrl-R
            except Exception:
                pass

    try:
        command = unlocked_input("❯ ").strip()
        if command:
            # Save to module-level history
            _shell_command_history.append(command)
        return command
    except (KeyboardInterrupt, EOFError):
        # Ctrl-C during reverse search should just cancel, not exit
        return ""
    finally:
        # Clear readline history and disable Ctrl-R again (only for GNU readline)
        if HAS_READLINE:
            readline.clear_history()
            if not INPUT_USES_LIBEDIT:
                try:
                    readline.parse_and_bind("bind -r ^R")  # Unbind Ctrl-R
                except Exception:
                    pass


def prompt_fixup_action(use_color: bool = True) -> str:
    """
    Prompt for suggest-fixup submenu action.

    Args:
        use_color: Whether to use colored output

    Returns:
        Normalized choice ('y', 'n', 'r', or other input)
    """
    try:
        y_text = pgettext("yes hotkey", "y")
        n_text = pgettext("next hotkey", "n")
        r_text = pgettext("reset hotkey", "r")
        yes_text = _("yes")
        next_text = _("next")
        reset_text = _("reset")

        if use_color and Colors.enabled():
            y_label = f"{Colors.GREEN}{y_text}{Colors.RESET}"
            n_label = n_text
            r_label = r_text
        else:
            y_label = y_text
            n_label = n_text
            r_label = r_text

        prompt_text = _(
            "[{yes_key}] {yes} / [{next_key}] {next} / [{reset_key}] {reset}: "
        ).format(
            yes_key=y_label,
            yes=yes_text,
            next_key=n_label,
            next=next_text,
            reset_key=r_label,
            reset=reset_text,
        )
        choice = unlocked_input(wrap_prompt_for_readline(prompt_text)).strip()
    except (KeyboardInterrupt, EOFError):
        return "q"  # Ctrl-C or Ctrl-D cancels

    return action_prompt_choices.normalize_localized_choice(
        choice,
        stable_codes=frozenset({"y", "n", "r", "q"}),
        legacy_words={
            "yes": "y",
            "next": "n",
            "reset": "r",
            "cancel": "q",
            "quit": "q",
        },
        localized_words=action_prompt_choices.localized_word_aliases(
            (
                (str(y_text), "y"),
                (str(yes_text), "y"),
                (str(n_text), "n"),
                (str(next_text), "n"),
                (str(r_text), "r"),
                (str(reset_text), "r"),
                (str(_("cancel")), "q"),
                (str(_("quit")), "q"),
            )
        ),
    )
