"""Byte-safe conversion and parsing for pathnames emitted by Git."""

from __future__ import annotations

import json
import os
import shlex
from collections.abc import Iterable

from .exceptions import CommandError
from .i18n import _


_C_ESCAPES = {
    ord("a"): b"\a",
    ord("b"): b"\b",
    ord("t"): b"\t",
    ord("n"): b"\n",
    ord("v"): b"\v",
    ord("f"): b"\f",
    ord("r"): b"\r",
    ord('"'): b'"',
    ord("\\"): b"\\",
}


def encode_path(path: str) -> bytes:
    """Encode a filesystem path without losing undecodable bytes."""
    return os.fsencode(path)


def decode_path(path: bytes) -> str:
    """Decode a filesystem path without losing undecodable bytes."""
    return os.fsdecode(path)


def display_path(path: str) -> str:
    """Return a terminal-safe, reversible representation of a path."""
    raw_path = encode_path(path)
    try:
        decoded_path = raw_path.decode("utf-8")
    except UnicodeDecodeError:
        return quote_path_token(raw_path).decode("ascii")

    return terminal_safe_text(decoded_path)


def terminal_safe_text(value: str) -> str:
    """Return text without literal terminal controls or bidi formatting."""
    if value and all(character.isprintable() for character in value):
        return value
    # JSON leaves most non-ASCII format controls literal when ensure_ascii is
    # disabled. Escape those controls so quoted text cannot alter the
    # terminal's bidirectional state or hide characters, while keeping ordinary
    # printable Unicode readable beside C0 escapes such as ``\n``.
    has_non_ascii_control = any(
        ord(character) >= 0x20 and not character.isprintable()
        for character in value
    )
    return json.dumps(value, ensure_ascii=has_non_ascii_control)


def terminal_safe_shell_quote(value: str) -> str:
    """Quote one shell argument without emitting terminal control bytes.

    Ordinary printable arguments retain POSIX ``shlex.quote`` output. Arguments
    containing control or undecodable bytes use ANSI-C quoting, which keeps the
    displayed command on one harmless terminal line while preserving the exact
    filesystem bytes in shells that support that quoting form.
    """
    if all(character.isprintable() for character in value):
        return shlex.quote(value)

    escaped = []
    named_escapes = {
        ord("\a"): r"\a",
        ord("\b"): r"\b",
        ord("\t"): r"\t",
        ord("\n"): r"\n",
        ord("\v"): r"\v",
        ord("\f"): r"\f",
        ord("\r"): r"\r",
        0x1B: r"\e",
    }
    for byte in encode_path(value):
        if byte in named_escapes:
            escaped.append(named_escapes[byte])
        elif byte == ord("'"):
            escaped.append(r"\'")
        elif byte == ord("\\"):
            escaped.append(r"\\")
        elif 0x20 <= byte < 0x7F:
            escaped.append(chr(byte))
        else:
            escaped.append(f"\\{byte:03o}")
    return "$'" + "".join(escaped) + "'"


def terminal_safe_shell_join(arguments: Iterable[str]) -> str:
    """Render shell arguments without allowing terminal control bytes."""
    return " ".join(terminal_safe_shell_quote(argument) for argument in arguments)


def nul_records(output: bytes) -> list[bytes]:
    """Split NUL-delimited Git output, omitting its final empty record."""
    records = output.split(b"\0")
    if records and not records[-1]:
        records.pop()
    return records


def quote_path_token(path: bytes) -> bytes:
    """Encode raw pathname bytes as one Git C-style quoted token."""
    if path and all(
        32 < value < 127 and value not in (ord('"'), ord("\\"))
        for value in path
    ):
        return path

    escaped = bytearray(b'"')
    for value in path:
        if value == ord('"'):
            escaped.extend(b'\\"')
        elif value == ord("\\"):
            escaped.extend(b"\\\\")
        elif value == ord("\t"):
            escaped.extend(b"\\t")
        elif value == ord("\n"):
            escaped.extend(b"\\n")
        elif value == ord("\r"):
            escaped.extend(b"\\r")
        elif 32 <= value < 127:
            escaped.append(value)
        else:
            escaped.extend(f"\\{value:03o}".encode("ascii"))
    escaped.append(ord('"'))
    return bytes(escaped)


def unquote_path_token(token: bytes) -> bytes:
    """Decode one Git C-style quoted pathname token to raw bytes."""
    if not token.startswith(b'"'):
        return token
    if len(token) < 2 or not token.endswith(b'"'):
        raise CommandError(_("Unterminated quoted Git pathname"))

    result = bytearray()
    index = 1
    end = len(token) - 1
    while index < end:
        value = token[index]
        if value != ord("\\"):
            result.append(value)
            index += 1
            continue

        index += 1
        if index >= end:
            raise CommandError(_("Incomplete escape in Git pathname"))
        escaped = token[index]
        if escaped in _C_ESCAPES:
            result.extend(_C_ESCAPES[escaped])
            index += 1
            continue
        if ord("0") <= escaped <= ord("7"):
            octal_end = index
            while (
                octal_end < min(index + 3, end)
                and ord("0") <= token[octal_end] <= ord("7")
            ):
                octal_end += 1
            octal_value = int(token[index:octal_end], 8)
            if octal_value > 0xFF:
                raise CommandError(
                    _("Octal escape is outside byte range in Git pathname")
                )
            result.append(octal_value)
            index = octal_end
            continue
        raise CommandError(_("Invalid escape in Git pathname"))

    return bytes(result)


def quoted_token_end(data: bytes, start: int = 0) -> int:
    """Return the exclusive end of a C-quoted token in *data*."""
    if start >= len(data) or data[start] != ord('"'):
        raise CommandError(_("Expected quoted Git pathname"))
    index = start + 1
    while index < len(data):
        if data[index] == ord("\\"):
            index += 2
            continue
        if data[index] == ord('"'):
            return index + 1
        index += 1
    raise CommandError(_("Unterminated quoted Git pathname"))
