"""Data provider ABC for TUI plan table."""

from abc import ABC, abstractmethod
from pathlib import Path

from erk.tui.data.types import FetchTimings, PlanFilters, PlanRowData
from erk.tui.sorting.types import BranchActivity
from erk_shared.gateway.browser.abc import BrowserLauncher
from erk_shared.gateway.clipboard.abc import Clipboard
from erk_shared.gateway.github.types import PRCheckRun, PRReviewThread


class PlanDataProvider(ABC):
    """Abstract base class for plan data providers.

    Defines the interface for fetching plan data for TUI display.
    """

    @property
    @abstractmethod
    def repo_root(self) -> Path:
        """Get the repository root path.

        Returns:
            Path to the repository root directory
        """
        ...

    @property
    @abstractmethod
    def clipboard(self) -> Clipboard:
        """Get the clipboard interface for copy operations.

        Returns:
            Clipboard interface for copying to system clipboard
        """
        ...

    @property
    @abstractmethod
    def browser(self) -> BrowserLauncher:
        """Get the browser launcher interface for opening URLs.

        Returns:
            BrowserLauncher interface for opening URLs in browser
        """
        ...

    @abstractmethod
    def fetch_plans(self, filters: PlanFilters) -> tuple[list[PlanRowData], FetchTimings | None]:
        """Fetch plans matching the given filters.

        Args:
            filters: Filter options for the query

        Returns:
            Tuple of (list of PlanRowData for display, optional FetchTimings breakdown)
        """
        ...

    @abstractmethod
    def close_plan(self, plan_id: int, plan_url: str) -> list[int]:
        """Close a plan and its linked PRs.

        Args:
            plan_id: The plan ID to close
            plan_url: The plan URL for PR linkage lookup

        Returns:
            List of PR numbers that were also closed
        """
        ...

    @abstractmethod
    def dispatch_to_queue(self, plan_id: int, plan_url: str) -> None:
        """Dispatch a plan to the implementation queue.

        Args:
            plan_id: The plan ID to dispatch
            plan_url: The plan URL for repository context
        """
        ...

    @abstractmethod
    def fetch_branch_activity(self, rows: list[PlanRowData]) -> dict[int, BranchActivity]:
        """Fetch branch activity for plans that exist locally.

        Examines commits on each local branch (not in trunk) to determine
        the most recent activity.

        Args:
            rows: List of plan rows to fetch activity for

        Returns:
            Mapping of plan_id to BranchActivity for plans with local worktrees.
            Plans without local worktrees are not included in the result.
        """
        ...

    @abstractmethod
    def fetch_plan_content(self, plan_id: int, plan_body: str) -> str | None:
        """Return plan content from the PR body.

        Plans are PR-based: plan_body is the already-extracted content.
        Return it directly, or None if empty.

        Args:
            plan_id: The GitHub PR number
            plan_body: The extracted plan content from the PR body

        Returns:
            The plan content, or None if empty
        """
        ...

    @abstractmethod
    def fetch_objective_content(self, plan_id: int, plan_body: str) -> str | None:
        """Fetch objective content from the first comment of an issue.

        Args:
            plan_id: The GitHub issue number
            plan_body: The issue body (to extract objective_comment_id from metadata)

        Returns:
            The extracted objective content, or None if not found
        """
        ...

    @abstractmethod
    def fetch_plans_by_ids(self, plan_ids: set[int]) -> list[PlanRowData]:
        """Fetch specific plans by their GitHub numbers.

        Works for both issue-backed and PR-backed plans via the
        issueOrPullRequest GraphQL query.

        Args:
            plan_ids: Set of plan numbers to fetch (issue or PR numbers)

        Returns:
            List of PlanRowData objects for the specified plans, sorted by plan_id
        """
        ...

    @abstractmethod
    def fetch_plans_for_objective(self, objective_issue: int) -> list[PlanRowData]:
        """Fetch plans associated with a specific objective.

        Args:
            objective_issue: The objective issue number to filter by

        Returns:
            List of PlanRowData objects for plans linked to this objective
        """
        ...

    @abstractmethod
    def get_branch_stack(self, branch: str) -> list[str] | None:
        """Get the Graphite stack containing a branch.

        Returns the ordered list of branch names in the same stack,
        or None if the branch is not part of a Graphite stack.

        Args:
            branch: The branch name to look up

        Returns:
            Ordered list of branch names in the stack, or None
        """
        ...

    @abstractmethod
    def fetch_check_runs(self, pr_number: int) -> list[PRCheckRun]:
        """Fetch failing check runs for a pull request.

        Args:
            pr_number: The PR number to fetch check runs for

        Returns:
            List of PRCheckRun for failing checks, sorted by name
        """
        ...

    @abstractmethod
    def fetch_unresolved_comments(self, pr_number: int) -> list[PRReviewThread]:
        """Fetch unresolved review threads for a pull request.

        Args:
            pr_number: The PR number to fetch threads for

        Returns:
            List of unresolved PRReviewThread objects sorted by (path, line)
        """
        ...

    @abstractmethod
    def fetch_ci_summaries(self, pr_number: int) -> dict[str, str]:
        """Fetch CI failure summaries for a pull request.

        Looks for the ci-summarize job in the latest CI workflow run for
        this PR, parses ERK-CI-SUMMARY markers from its logs, and returns
        a mapping of check name to summary text.

        Args:
            pr_number: The PR number to fetch summaries for

        Returns:
            Mapping of check name to summary text. Empty dict if no
            summaries are available (job hasn't run, no failures, etc.)
        """
        ...
