"""Start command implementation."""

from __future__ import annotations

from typing import Optional

from ..data.auto_advance import DEFAULT_AUTO_ADVANCE, write_auto_advance_default
from ..data.hunk_tracking import fetch_next_change
from ..data.selected_change.lifecycle import clear_selected_change_state_files
from ..data.file_tracking import auto_add_untracked_files
from ..data.session import (
    clear_session_state,
    get_iteration_count,
    initialize_abort_state,
)
from ..data.session_marker import session_is_active
from ..data.session_ownership import (
    claim_session_ownership,
    require_no_foreign_session_owner,
)
from ..data.start_time_changes import (
    normalize_start_time_staged_deletions,
    normalize_start_time_staged_renames,
)
from ..exceptions import CommandError, NoMoreHunks
from ..i18n import _
from ..utils.file_io import write_text_file_contents
from ..utils.git_repository import require_git_repository
from ..utils.paths import ensure_state_directory_exists, get_context_lines_file_path
from .selection.selected_change_display import show_selected_change
from .session.iteration import restart_iteration_pass


def command_start(
    *,
    context_lines: Optional[int] = None,
    quiet: bool = False,
    auto_advance: bool | None = None,
) -> None:
    """Start a new batch staging session.

    Args:
        context_lines: Number of context lines to use in diffs (default: 3)
        quiet: If True, suppress "No more hunks" message when no changes exist
        auto_advance: Whether later actions should select the next hunk
    """
    require_git_repository()
    ensure_state_directory_exists()
    require_no_foreign_session_owner()

    # If session already exists, run again logic instead
    if session_is_active():
        current_iteration = get_iteration_count()
        claim_session_ownership()
        if auto_advance is not None:
            write_auto_advance_default(auto_advance)
        restart_iteration_pass(
            current_iteration=current_iteration,
            quiet=quiet,
        )
        return

    # Batch reviews may be shown outside an active session. A new session must
    # not inherit that selected/review cache before selecting the first live hunk.
    clear_selected_change_state_files()

    # Initialize abort state for new session
    initialize_abort_state()
    try:
        claim_session_ownership()
    except BaseException:
        # The snapshot is worktree-local and has not been published as an
        # active shared session. Remove it if ownership publication fails.
        clear_session_state()
        raise

    try:
        # Staged renames and text deletions are already in the index, so a plain
        # worktree-vs-index pass would otherwise never offer them as workflow
        # choices. Preserve the exact start state in abort metadata first, then
        # expose those staged entries as unstaged choices for this session.
        normalize_start_time_staged_renames()
        normalize_start_time_staged_deletions()

        write_auto_advance_default(
            DEFAULT_AUTO_ADVANCE
            if auto_advance is None else
            auto_advance
        )

        if context_lines is not None:
            write_text_file_contents(get_context_lines_file_path(), str(context_lines))

        # Make untracked files visible to git diff so they can be staged,
        # blocked by .gitignore, or deleted.
        auto_add_untracked_files(show_progress=not quiet)

        try:
            fetch_next_change()
        except NoMoreHunks as error:
            if error.all_changes_already_batched and error.batch_names:
                raise CommandError(
                    _already_batched_changes_message(error.batch_names),
                    exit_code=2,
                )
            raise CommandError(_("No changes to process."), exit_code=2)

        if not quiet:
            show_selected_change()
    except BaseException as start_error:
        from .abort import command_abort

        try:
            command_abort(quiet=True)
        except BaseException as abort_error:
            raise start_error from abort_error
        raise


def _already_batched_changes_message(batch_names: frozenset[str]) -> str:
    """Explain that live changes were hidden by persistent batch ownership."""
    locations = ", ".join(
        _("batch {name}").format(name=repr(name))
        for name in sorted(batch_names)
    )
    return _(
        "All working tree changes are currently saved in {locations}."
    ).format(locations=locations)
