"""Fish completion generation for tyro using embedded Python logic."""

from __future__ import annotations

from ... import _parsers
from ._base import CompletionGenerator
from ._python_completer import get_embedded_code
from ._spec import build_completion_spec


def _fish_escape_double_quoted(text: str) -> str:
    """Escape text for embedding inside a fish double-quoted string.

    Inside fish double quotes, only ``\\``, ``"``, and ``$`` are special.
    Backslash must be escaped first so the escapes we add aren't re-escaped.
    """
    return text.replace("\\", "\\\\").replace('"', r"\"").replace("$", r"\$")


def _fish_escape_single_quoted(text: str) -> str:
    """Quote text as a fish single-quoted string literal.

    Inside fish single quotes, only ``\\`` and ``'`` are special. This lets us
    interpolate an arbitrary program name (which may contain spaces or fish
    metacharacters) into the script as a single, safe token.
    """
    escaped = text.replace("\\", "\\\\").replace("'", "\\'")
    return f"'{escaped}'"


class TyroFishCompletionGenerator(CompletionGenerator):
    """Generates Fish completion scripts with embedded Python logic."""

    def generate(
        self,
        parser_spec: _parsers.ParserSpecification,
        prog: str,
        root_prefix: str,
    ) -> str:
        """Generate a Fish completion script.

        Args:
            parser_spec: Parser specification to generate completion for.
            prog: Program name.
            root_prefix: Prefix for completion function names.

        Returns:
            Fish completion script as a string.
        """
        # Build completion spec.
        spec = build_completion_spec(parser_spec, prog)
        spec_repr = repr(spec)

        # Get embedded Python code.
        python_code = get_embedded_code()

        # The spec and Python code are embedded inside a fish double-quoted
        # string (`set -l pycode "..."`). A default value or help string
        # containing `"`, `\`, or `$` would otherwise prematurely close the
        # string or trigger variable expansion, so escape the whole body.
        pycode_body = _fish_escape_double_quoted(
            f"# Hardcoded completion spec.\nCOMPLETION_SPEC = {spec_repr}\n{python_code}"
        )

        # Quote prog so a program name with spaces or fish metacharacters is
        # interpolated as a single safe token everywhere it appears below.
        prog_q = _fish_escape_single_quoted(prog)

        # Generate the fish completion script.
        script = f"""# AUTOMATICALLY GENERATED by tyro

# Main completion function.

function _{root_prefix}
    if command -sq python3
        set python_cmd python3
    else if command -sq python
        set python_cmd python
    else
        return
    end

    # Collect commandline tokens for completion.
    #
    # Newer fish versions support `commandline -xpc`, which returns tokens
    # with proper cursor-aware parsing and expansion semantics.
    #
    # Older fish versions do not support `-x`, and only provide `-opc`.
    # Although `-o` is deprecated in newer fish, it is still required as
    # a fallback for compatibility.
    #
    # Therefore:
    #   - Prefer `-xpc` when available.
    #   - Fall back to `-opc` on older fish versions.

    set -l words
    if commandline -x >/dev/null 2>&1
        set words (commandline -xpc)
    else
        set words (commandline -opc)
    end

    set -l current (count $words)
    set -l pycode "{pycode_body}"
    set -l results ($python_cmd -c "$pycode" $words $current)
    if test "$results" = __TYRO_COMPLETE_FILES__
        __fish_complete_path (commandline -ct)
    else
        for line in $results
            printf "%s\\n" $line
        end
    end
end

# Register completion for the full program name.
complete --no-files --command {prog_q} --arguments "(_{root_prefix})"

# Also register for the basename to handle ./script invocations. Guard against
# re-registering the same command name (when prog is already a bare name,
# basename == prog), which would otherwise produce duplicate candidates.
set -l _prog_basename (basename {prog_q})
if test "$_prog_basename" != {prog_q}
    complete --no-files --command $_prog_basename --arguments "(_{root_prefix})"
end
"""
        return script
