"""Live file change rendering without selected-state mutation."""

from __future__ import annotations

import subprocess
from typing import Optional

from ..core.diff_parser import acquire_unified_diff
from ..core.models import (
    BinaryFileChange,
    FileModeChange,
    GitlinkChange,
    RenameChange,
    TextFileDeletionChange,
)
from .file_tracking import auto_add_untracked_files
from .binary_identity import attach_live_binary_fingerprint
from .live_diff import stream_live_git_diff


def render_mode_change(
    file_path: str,
    *,
    paired_path: str | None = None,
) -> Optional[FileModeChange]:
    """Render one executable-mode action without caching state."""
    paths = [file_path]
    if paired_path is not None and paired_path != file_path:
        paths.append(paired_path)
    try:
        with acquire_unified_diff(
            stream_live_git_diff(full_index=True, paths=paths)
        ) as patches:
            return next(
                (
                    item
                    for item in patches
                    if isinstance(item, FileModeChange)
                    and item.path() == file_path
                ),
                None,
            )
    except subprocess.CalledProcessError:
        return None


def render_binary_file_change(
    file_path: str,
    *,
    base: str | None = None,
) -> Optional[BinaryFileChange]:
    """Render a binary change against the requested comparison base."""
    auto_add_untracked_files([file_path])
    try:
        with acquire_unified_diff(
            stream_live_git_diff(
                base=base,
                full_index=True,
                ignore_submodules="none",
                submodule_format="short",
                paths=[file_path],
            )
        ) as patches:
            for item in patches:
                if isinstance(item, BinaryFileChange):
                    return attach_live_binary_fingerprint(
                        item,
                        comparison_base=base,
                    )
    except subprocess.CalledProcessError:
        return None
    return None


def render_gitlink_change(
    file_path: str,
    *,
    base: str | None = None,
) -> Optional[GitlinkChange]:
    """Render a gitlink change against the requested comparison base."""
    auto_add_untracked_files([file_path])
    try:
        with acquire_unified_diff(
            stream_live_git_diff(
                base=base,
                full_index=True,
                ignore_submodules="none",
                submodule_format="short",
                paths=[file_path],
            )
        ) as patches:
            for item in patches:
                if isinstance(item, GitlinkChange):
                    return item
    except subprocess.CalledProcessError:
        return None
    return None


def render_rename_change(file_path: str) -> Optional[RenameChange]:
    """Render a rename change involving file_path without caching state."""
    auto_add_untracked_files([file_path])
    try:
        with acquire_unified_diff(
            stream_live_git_diff(
                full_index=True,
                ignore_submodules="none",
                submodule_format="short",
            )
        ) as patches:
            for item in patches:
                if (
                    isinstance(item, RenameChange)
                    and file_path in (item.old_path, item.new_path)
                ):
                    return item
    except subprocess.CalledProcessError:
        return None
    return None


def render_text_deletion_change(
    file_path: str,
) -> Optional[TextFileDeletionChange]:
    """Render a whole-text-file deletion without caching state."""
    try:
        with acquire_unified_diff(
            stream_live_git_diff(
                full_index=True,
                ignore_submodules="none",
                submodule_format="short",
                paths=[file_path],
            )
        ) as patches:
            for item in patches:
                if isinstance(item, TextFileDeletionChange):
                    return item
    except subprocess.CalledProcessError:
        return None
    return None
