use super::git::LocalGitState;
use super::git::Submodule;
use crate::ci::CiRun;
use crate::ci::OwnerRepo;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) enum Visibility {
#[default]
Visible,
Deleted,
Dismissed,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) enum WorktreeHealth {
#[default]
Normal,
Broken,
}
#[derive(Clone, Debug)]
pub(crate) struct GitHubInfo {
pub stars: u64,
pub description: Option<String>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) enum CiPagination {
#[default]
HasMore,
Exhausted,
}
impl CiPagination {
#[cfg(test)]
pub(crate) const fn is_exhausted(self) -> bool { matches!(self, Self::Exhausted) }
}
impl From<bool> for CiPagination {
fn from(exhausted: bool) -> Self {
if exhausted {
Self::Exhausted
} else {
Self::HasMore
}
}
}
#[derive(Clone)]
pub(crate) struct ProjectCiInfo {
pub runs: Vec<CiRun>,
pub github_total: u32,
pub ci_pagination: CiPagination,
}
#[derive(Clone, Default)]
pub(crate) enum ProjectCiData {
#[default]
Unfetched,
Loaded(ProjectCiInfo),
}
impl ProjectCiData {
pub(crate) const fn info(&self) -> Option<&ProjectCiInfo> {
match self {
Self::Unfetched => None,
Self::Loaded(info) => Some(info),
}
}
pub(crate) const fn github_total(&self) -> u32 {
match self {
Self::Unfetched => 0,
Self::Loaded(info) => info.github_total,
}
}
}
#[derive(Clone, Default)]
pub(crate) enum ProjectPrData {
#[default]
Unfetched,
Loading(Option<ProjectPrInfo>),
Loaded(ProjectPrInfo),
Unavailable(ProjectPrUnavailable),
}
impl ProjectPrData {
pub(crate) const fn info(&self) -> Option<&ProjectPrInfo> {
match self {
Self::Loaded(info) => Some(info),
Self::Loading(stale) => stale.as_ref(),
Self::Unavailable(unavailable) => unavailable.stale.as_ref(),
Self::Unfetched => None,
}
}
pub(crate) const fn needs_fetch(&self) -> bool {
matches!(self, Self::Unfetched | Self::Unavailable(_))
}
}
#[derive(Clone)]
pub(crate) struct ProjectPrInfo {
pub open: Vec<PullRequestInfo>,
pub default_branch: String,
pub fetched_at: String,
pub completeness: PullRequestCompleteness,
pub viewer_login: String,
pub owner_repo: OwnerRepo,
}
#[derive(Clone)]
pub(crate) struct ProjectPrUnavailable {
pub reason: PullRequestUnavailableReason,
pub stale: Option<ProjectPrInfo>,
pub fetched_at: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct PullRequestInfo {
pub number: u32,
pub title: String,
pub url: String,
pub state: PullRequestState,
pub head: String,
pub head_owner: Option<String>,
pub head_repo: Option<String>,
pub base: String,
}
impl PullRequestInfo {
pub(crate) fn branch_label(&self, base_default: &str) -> String {
let head = self.head_owner.as_ref().map_or_else(
|| self.head.clone(),
|owner| format!("{owner}:{}", self.head),
);
if self.base == base_default {
head
} else {
format!("{head} -> {}", self.base)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum PullRequestState {
Draft,
ChangesRequested,
ChecksFailing,
Blocked,
Behind,
ReviewRequired,
Approved,
Ready,
Unknown,
}
impl PullRequestState {
pub(crate) const fn label(self) -> &'static str {
match self {
Self::Draft => "draft",
Self::ChangesRequested => "changes",
Self::ChecksFailing => "checks",
Self::Blocked => "blocked",
Self::Behind => "behind",
Self::ReviewRequired => "review",
Self::Approved => "approved",
Self::Ready => "ready",
Self::Unknown => "unknown",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum PullRequestUnavailableReason {
Unauthenticated,
RateLimited,
Network,
Forbidden,
RepositoryMissing,
GraphQlError,
IncompletePagination,
}
impl PullRequestUnavailableReason {
pub(crate) const fn label(self) -> &'static str {
match self {
Self::Unauthenticated => "unauthenticated",
Self::RateLimited => "rate limited",
Self::Network => "network unavailable",
Self::Forbidden => "forbidden",
Self::RepositoryMissing => "repository missing",
Self::GraphQlError => "github query failed",
Self::IncompletePagination => "incomplete results",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum PullRequestGoneReason {
Merged { base: String },
Closed,
Missing,
Unknown,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum PullRequestCompleteness {
Complete,
Truncated { shown: usize },
}
#[derive(Clone, Debug)]
pub(crate) struct LangEntry {
pub language: String,
pub files: usize,
pub code: usize,
pub comments: usize,
pub blanks: usize,
pub children: Vec<Self>,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct LanguageStats {
pub entries: Vec<LangEntry>,
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct TestCounts {
pub unit: usize,
pub integration: usize,
pub doc: usize,
pub doc_ignored: usize,
}
impl TestCounts {
pub(crate) const fn merged(self, other: Self) -> Self {
Self {
unit: self.unit + other.unit,
integration: self.integration + other.integration,
doc: self.doc + other.doc,
doc_ignored: self.doc_ignored + other.doc_ignored,
}
}
}
#[derive(Clone, Default)]
pub(crate) struct ProjectInfo {
pub disk_usage_bytes: Option<u64>,
pub in_project_non_target: Option<u64>,
pub in_project_target: Option<u64>,
pub local_git_state: LocalGitState,
pub language_stats: Option<LanguageStats>,
pub test_counts: Option<TestCounts>,
pub visibility: Visibility,
pub worktree_health: WorktreeHealth,
pub submodules: Vec<Submodule>,
}
impl ProjectInfo {
#[cfg(test)]
#[expect(dead_code, reason = "Reserved for later-stage test helpers")]
pub(crate) fn for_tests() -> Self { Self::default() }
}