import type { RepoCiDegradation } from './RepoCiTile'

export interface CiCoverageGap {
  id: string
  headline: string
  detail: string
  action: string
}

export interface CiRepoCompleteness {
  repo: string
  queueComplete: boolean
  historyComplete: boolean
  trainRunsComplete: boolean
  runnersComplete: boolean
  refsComplete: boolean
  prsComplete: boolean
  trainsComplete: boolean
  pollDeferred?: boolean
  degraded?: RepoCiDegradation[]
}

function hasDegradation(repo: CiRepoCompleteness, reason: RepoCiDegradation): boolean {
  return repo.degraded?.includes(reason) ?? false
}

function gap(id: string, headline: string, detail: string, action: string): CiCoverageGap {
  return { id, headline, detail, action }
}

function dedupeGaps(gaps: CiCoverageGap[]): CiCoverageGap[] {
  const seen = new Set<string>()
  return gaps.filter((entry) => {
    if (seen.has(entry.id)) return false
    seen.add(entry.id)
    return true
  })
}

/** Resolves repo-level CI data gaps into what happened, why, and what to do. */
export function resolveCiRepoGaps(repo: CiRepoCompleteness): CiCoverageGap[] {
  // Poll rotation serves the last successful snapshot; deferral is internal, not operator work.
  if (repo.pollDeferred) return []

  const gaps: CiCoverageGap[] = []

  if (!repo.queueComplete) {
    gaps.push(
      gap(
        `${repo.repo}:queue-incomplete`,
        'Actions queue unavailable',
        'Queued and in-progress run counts could not be fetched.',
        'Check GitHub API access. Queue depth is hidden until the next successful poll.',
      ),
    )
  }

  if (!repo.runnersComplete) {
    gaps.push(
      hasDegradation(repo, 'pagination')
        ? gap(
            `${repo.repo}:runners-truncated`,
            'Runner list truncated',
            'This repo has more than 100 self-hosted runners; occupancy totals are hidden.',
            'Remove offline runners in GitHub or accept partial runner metrics.',
          )
        : gap(
            `${repo.repo}:runners-incomplete`,
            'Runner occupancy unavailable',
            'Runner status could not be loaded this poll.',
            'Check GitHub API access and self-hosted runner registration.',
          ),
    )
  }

  if (!repo.refsComplete) {
    gaps.push(
      gap(
        `${repo.repo}:refs-incomplete`,
        'Train branch list truncated',
        'More than 100 integration/batch-train-* branches exist, or refs could not be loaded. Train state is unknown.',
        'Delete merged or stale train branches in GitHub. Train actions stay disabled until refs are complete.',
      ),
    )
  }

  if (!repo.prsComplete) {
    gaps.push(
      gap(
        `${repo.repo}:prs-incomplete`,
        'Open PR list truncated',
        'More than 100 open pull requests, or the PR query failed. Queue depth and oldest-queued age are hidden.',
        'Close or merge stale PRs. Per-PR rows may still appear but are not guaranteed complete.',
      ),
    )
  }

  if (repo.refsComplete && repo.prsComplete && !repo.trainRunsComplete) {
    gaps.push(
      gap(
        `${repo.repo}:train-runs-incomplete`,
        'Train run history truncated',
        'Exact workflow runs for active trains could not be fully loaded (pagination or API failure).',
        'Wait for the next poll. If it persists, check GitHub Actions API limits.',
      ),
    )
  }

  if (repo.refsComplete && repo.prsComplete && repo.trainRunsComplete && !repo.trainsComplete) {
    gaps.push(
      gap(
        `${repo.repo}:trains-incomplete`,
        'Train data incomplete',
        'Train lifecycle could not be derived from complete refs, PRs, and run data.',
        'Resolve the other coverage gaps above first.',
      ),
    )
  }

  if (
    hasDegradation(repo, 'pagination')
    && gaps.every((entry) => !entry.id.includes('truncated') && !entry.id.includes('prs-incomplete') && !entry.id.includes('refs-incomplete'))
  ) {
    gaps.push(
      gap(
        `${repo.repo}:pagination`,
        'GitHub pagination limit hit',
        'At least one API response returned a partial page (100-item cap) this poll.',
        'Usually harmless for recent CI status. Clean up stale train branches or large PR backlogs if warnings persist.',
      ),
    )
  }

  if (hasDegradation(repo, 'budget') && !repo.pollDeferred) {
    gaps.push(
      gap(
        `${repo.repo}:budget`,
        'Poll budget exhausted',
        'The collector stopped fetching before all repos or job logs were refreshed.',
        'Wait for the next poll cycle. Reduce monitored repos if this is frequent.',
      ),
    )
  }

  if (hasDegradation(repo, 'jobs')) {
    gaps.push(
      gap(
        `${repo.repo}:jobs`,
        'Train job details truncated',
        'Some train jobs or failure logs could not be fetched within the poll budget.',
        'Wait for the next poll. Infra classification may be delayed.',
      ),
    )
  }

  return dedupeGaps(gaps)
}

/** First train-specific gap for a repo, or null when train data is complete. */
export function resolveTrainRepoGap(repo: CiRepoCompleteness): CiCoverageGap | null {
  if (repo.trainsComplete) return null
  const gaps = resolveCiRepoGaps(repo)
  const trainGap = gaps.find((entry) =>
    entry.id.endsWith(':refs-incomplete')
    || entry.id.endsWith(':prs-incomplete')
    || entry.id.endsWith(':train-runs-incomplete')
    || entry.id.endsWith(':trains-incomplete')
    || entry.id.endsWith(':poll-deferred'),
  )
  return trainGap ?? gaps[0] ?? null
}

/** PR-queue-specific gap for a repo. */
export function resolvePrQueueRepoGap(
  repo: Pick<CiRepoCompleteness, 'repo' | 'prsComplete' | 'pollDeferred' | 'degraded'>,
): CiCoverageGap | null {
  if (repo.prsComplete) return null
  return resolveCiRepoGaps({
    repo: repo.repo,
    queueComplete: true,
    historyComplete: true,
    trainRunsComplete: true,
    runnersComplete: true,
    refsComplete: true,
    prsComplete: false,
    trainsComplete: true,
    pollDeferred: repo.pollDeferred,
    degraded: repo.degraded,
  }).find((entry) => entry.id.endsWith(':prs-incomplete') || entry.id.endsWith(':poll-deferred')) ?? null
}
