"""
This type stub file was generated by pyright.
"""

from abc import ABC, abstractmethod
from collections.abc import Generator
from functools import cached_property
from typing import TYPE_CHECKING
from git import Commit as GitCommit, Diff, Remote, Repo as GitCLI
from git.remote import PushInfoList
from graph_sitter.perf import stopwatch
from .schemas.config import BaseRepoConfig
from .schemas.enums import CheckoutResult, FetchResult
from codeowners import CodeOwners as CodeOwnersParser

if TYPE_CHECKING:
    ...
logger = ...
class RepoOperator(ABC):
    """A wrapper around GitPython to make it easier to interact with a repo."""
    repo_config: BaseRepoConfig
    base_dir: str
    _codeowners_parser: CodeOwnersParser | None = ...
    _default_branch: str | None = ...
    bot_commit: bool = ...
    def __init__(self, repo_config: BaseRepoConfig, base_dir: str = ..., bot_commit: bool = ...) -> None:
        ...
    
    @property
    def repo_name(self) -> str:
        ...
    
    @property
    def repo_path(self) -> str:
        ...
    
    @property
    def viz_path(self) -> str:
        ...
    
    @property
    def viz_file_path(self) -> str:
        ...
    
    @cached_property
    def git_cli(self) -> GitCLI:
        """Note: this is recursive, may want to look out"""
        ...
    
    @property
    def head_commit(self) -> GitCommit:
        ...
    
    @property
    def git_diff(self) -> str:
        """Get the diff of the repo. Useful for checking if there are any changes."""
        ...
    
    @property
    def default_branch(self) -> str:
        ...
    
    @abstractmethod
    def codeowners_parser(self) -> CodeOwnersParser | None:
        ...
    
    def repo_exists(self) -> bool:
        ...
    
    def clean_repo(self) -> None:
        """Cleans the repo by:
        1. Discards any changes (tracked/untracked)
        2. Checks out the default branch (+ makes sure it's up to date with the remote)
        3. Deletes all branches except the default branch
        4. Deletes all remotes except origin

        Used in SetupOption.PULL_OR_CLONE to allow people to re-use existing repos and start from a clean state.
        """
        ...
    
    @stopwatch
    def discard_changes(self) -> None:
        """Cleans repo dir by discarding any changes in staging/working directory and removes untracked files/dirs. Use with .is_dirty()."""
        ...
    
    @stopwatch
    def clean_remotes(self) -> None:
        ...
    
    @stopwatch
    def clean_branches(self) -> None:
        ...
    
    @abstractmethod
    def pull_repo(self) -> None:
        """Pull the latest commit down to an existing local repo"""
        ...
    
    def safe_get_commit(self, commit: str) -> GitCommit | None:
        """Gets commit if it exists, else returns None"""
        ...
    
    @abstractmethod
    def fetch_remote(self, remote_name: str = ..., refspec: str | None = ..., force: bool = ...) -> FetchResult:
        """Fetches and updates a ref from a remote repository.

        Args:
            remote_name (str): Name of the remote to fetch from. Defaults to "origin".
            refspec (str | None): The refspec to fetch. If None, fetches all refs. Defaults to None.
            force (bool): If True, forces the fetch operation. Defaults to True.

        Returns:
            FetchResult: An enum indicating the result of the fetch operation.
                - SUCCESS: Fetch was successful.
                - REFSPEC_NOT_FOUND: The specified refspec doesn't exist in the remote.

        Raises:
            GitCommandError: If the fetch operation fails for reasons other than a missing refspec.

        Note:
            This force fetches by default b/c by default we prefer the remote branch over our local branch.
        """
        ...
    
    def delete_remote(self, remote_name: str) -> None:
        ...
    
    def create_remote(self, remote_name: str, remote_url: str) -> None:
        """Creates a remote. Skips if the remote already exists."""
        ...
    
    @stopwatch
    def checkout_commit(self, commit_hash: str | GitCommit, remote_name: str = ...) -> CheckoutResult:
        """Checks out the relevant commit
        TODO: handle the environment being dirty
        """
        ...
    
    def get_active_branch_or_commit(self) -> str:
        """Returns the current active branch, or commit hexsha if head is detached"""
        ...
    
    def is_branch_checked_out(self, branch_name: str) -> bool:
        ...
    
    def delete_local_branch(self, branch_name: str) -> None:
        ...
    
    def checkout_branch(self, branch_name: str | None, *, remote: bool = ..., remote_name: str = ..., create_if_missing: bool = ...) -> CheckoutResult:
        """Attempts to check out the branch in the following order:
        - Check out the local branch by name
        - Check out the remote branch if it's been fetched
        - Creates a new branch from the current commit (with create=True)

        NOTE: if branch is already checked out this does nothing.
        TIP: Use remote=True if you want to always try to checkout the branch from a remote

        Args:
        ----
            branch_name (str): Name of the branch to checkout.
            create_if_missing: If the branch doesn't exist, create one
            remote: Checks out a branch from a Remote + tracks the Remote
            force (bool): If True, force checkout by resetting the current branch to HEAD.
                          If False, raise an error if the branch is dirty.

        Raises:
        ------
            GitCommandError: If there's an error with Git operations.
            RuntimeError: If the branch is dirty and force is not set.
        """
        ...
    
    def get_diff_files_from_ref(self, ref: str): # -> Any:
        ...
    
    def get_diffs(self, ref: str | GitCommit, reverse: bool = ...) -> list[Diff]:
        """Gets all staged diffs"""
        ...
    
    @stopwatch
    def stage_and_commit_all_changes(self, message: str, verify: bool = ...) -> bool:
        """TODO: rename to stage_and_commit_changes
        Stage all changes and commit them with the given message.
        Returns True if a commit was made and False otherwise.
        """
        ...
    
    def commit_changes(self, message: str, verify: bool = ...) -> bool:
        """Returns True if a commit was made and False otherwise."""
        ...
    
    def stage_and_commit_file(self, message: str, filepath: str) -> None:
        """Stage all changes and commit them with the given message."""
        ...
    
    @abstractmethod
    def push_changes(self, remote: Remote | None = ..., refspec: str | None = ..., force: bool = ...) -> PushInfoList:
        """Push the changes to the given refspec of the remote repository.

        Args:
            refspec (str | None): refspec to push. If None, the current active branch is used.
            remote (Remote | None): Remote to push too. Defaults to 'origin'.
        """
        ...
    
    def relpath(self, abspath) -> str:
        ...
    
    def abspath(self, relpath) -> str:
        ...
    
    def file_exists(self, path: str) -> bool:
        ...
    
    def folder_exists(self, path: str) -> bool:
        ...
    
    def mkdir(self, path: str) -> None:
        ...
    
    def emptydir(self, path: str) -> None:
        """Removes all files within the specified directory."""
        ...
    
    def get_file(self, path: str) -> str:
        """Returns the contents of a file"""
        ...
    
    def write_file(self, relpath: str, content: str) -> None:
        """Writes file content to disk"""
        ...
    
    def delete_file(self, path: str) -> None:
        """Deletes a file from the repo"""
        ...
    
    def iter_files(self, subdirs: list[str] | None = ..., extensions: list[str] | None = ..., ignore_list: list[str] | None = ...) -> Generator[tuple[str, str]]:
        """Iterates over all files in the codebase, yielding the filepath and its content.

        Args:
        ----
            subdirs (list[str], optional): List of subdirectories to include. Defaults to None. Can include full filenames.
            codeowners (list[str], optional): List of codeowners to iter files for. Defaults to None. Ex: if codeowners=["@group"], only files owned by @group will be included.
            extensions (list[str], optional): List of file extensions to include. Defaults to None.

        Yields:
        ------
            tuple: A tuple containing the relative filepath and the content of the file.

        """
        ...
    
    def list_files(self, subdirs: list[str] | None = ..., extensions: list[str] | None = ...) -> list[str]:
        """List files matching subdirs + extensions in a repo.

        Args:
        ----
            subdirs (list[str], optional): List of subdirectories to include. Defaults to None.
            codeowners (list[str], optional): List of codeowners to iter files for. Defaults to None. Ex: if codeowners=["@group"], only files owned by @group will be included.
            extensions (list[str], optional): List of file extensions to include. Defaults to None.

        Yields:
        ------
            str: filepath

        """
        ...
    
    def get_commits_in_last_n_days(self, days: int = ...) -> list[str]:
        """Returns a list of commits in the last n days"""
        ...
    
    def get_modified_files_in_last_n_days(self, days: int = ...) -> tuple[list[str], list[str]]:
        """Returns a list of files modified and deleted in the last n days"""
        ...
    
    @abstractmethod
    def base_url(self) -> str | None:
        ...
    
    def stash_push(self) -> None:
        ...
    
    def stash_pop(self) -> None:
        ...
    


