"""Block-file command implementation."""

from __future__ import annotations

import sys
from contextlib import nullcontext

from ..data.session import path_is_intent_to_add
from ..data.session_marker import session_is_active
from ..data.undo.checkpoints import undo_checkpoint
from ..data.ignore_files import (
    add_file_to_local_exclude,
    add_file_to_gitignore,
)
from ..exceptions import NoMoreHunks, exit_with_error
from ..git_paths import display_path, terminal_safe_shell_quote
from ..i18n import _
from ..utils.file_io import append_file_path_to_file, remove_file_path_from_file
from ..utils.git_command import run_git_command
from ..utils.git_worktree import git_remove_paths
from ..utils.git_repository import (
    require_git_repository,
)
from ..utils.repository_path import normalize_repository_path
from ..utils.paths import (
    ensure_state_directory_exists,
    get_auto_added_files_file_path,
    get_blocked_files_file_path,
)
from .selection.action_completion import advance_to_and_show_next_change


def _is_new_intent_to_add_file(file_path: str) -> bool:
    """Return True when file_path is an intent-to-add entry absent from HEAD."""
    stage_result = run_git_command(
        ["ls-files", "--stage", "--", file_path],
        check=False,
        requires_index_lock=False,
        literal_pathspecs=True,
    )
    stage_output = stage_result.stdout.strip()
    if not stage_output:
        return False

    if not path_is_intent_to_add(file_path):
        return False

    head_check = run_git_command(
        ["cat-file", "-e", f"HEAD:{file_path}"], check=False, requires_index_lock=False
    )
    return head_check.returncode != 0


def command_block_file(file_path_arg: str = "", local_only: bool = False) -> None:
    """Permanently exclude a file by adding it to .gitignore and blocked list."""
    require_git_repository()
    ensure_state_directory_exists()

    if not file_path_arg:
        from ..data.line_state import load_line_changes_from_state

        line_changes = load_line_changes_from_state()
        if line_changes is None:
            exit_with_error(
                _("No selected hunk. Run 'show' first or specify file path.")
            )
        file_path_arg = line_changes.path

    # Resolve to repo-relative path, normalizing directories to a trailing slash
    file_path = normalize_repository_path(file_path_arg).value
    session_active = session_is_active()
    checkpoint_paths = [] if local_only else [".gitignore"]
    checkpoint = (
        undo_checkpoint(
            f"block-file {terminal_safe_shell_quote(file_path)}",
            worktree_paths=checkpoint_paths,
            index_paths=[file_path] if not file_path.endswith("/") else [],
            repository_paths=["info/exclude"] if local_only else [],
        )
        if session_active
        else nullcontext()
    )

    with checkpoint:
        # Add to .git/info/exclude or .gitignore
        if local_only:
            add_file_to_local_exclude(file_path)
        else:
            add_file_to_gitignore(file_path)

        # Remove from index if session is active and the file is a new intent-to-add entry
        if (
            session_active
            and not file_path.endswith("/")
            and _is_new_intent_to_add_file(file_path)
        ):
            git_remove_paths(
                [file_path],
                cached=True,
                quiet=True,
                ignore_unmatch=True,
            )
            remove_file_path_from_file(get_auto_added_files_file_path(), file_path)

        # Add to blocked-files state
        append_file_path_to_file(get_blocked_files_file_path(), file_path)

    print(_("Blocked file: {}").format(display_path(file_path)), file=sys.stderr)

    if session_active:
        try:
            advance_to_and_show_next_change()
        except NoMoreHunks:
            pass
