import { DataTable, withColumnResizing, withSearch, withSorting, type DataTableColumn, type DataTableSort } from './internal/DataTable'
import { useId, useMemo, useState, type JSX } from 'react'
import { DetailDrawer } from './DetailDrawer'
import { DeckTooltipLayer } from './DeckTooltip'
import { CiCoverageBanner } from './CiCoverageBanner'
import { resolvePrQueueRepoGap } from './ci-coverage'
import { formatDurationMs } from './format'
import { safeHttpUrl } from './safe-url'
import { StatusChip } from './StatusChip'

export type PrCheckBucket = 'pass' | 'fail' | 'pending' | 'skipping' | 'cancel'

export interface PrQueueCheck {
  name: string
  bucket: PrCheckBucket
}

export interface PrQueueItem {
  repo: string
  number: number
  title: string
  url: string
  branch: string
  headRefOid: string
  createdAt: string
  ageH: number
  mergeable: 'MERGEABLE' | 'CONFLICTING' | 'UNKNOWN'
  staleVsMain: boolean | null
  checksComplete: boolean
  checks: PrQueueCheck[]
  trainPr: number | null
}

export interface PrQueueRepoCompleteness {
  repo: string
  prsComplete: boolean
  pollDeferred?: boolean
  degraded?: Array<'budget' | 'pagination' | 'jobs'>
}

export interface PrQueueTableProps {
  prs: PrQueueItem[]
  repos: PrQueueRepoCompleteness[]
  allScope: boolean
}

function rowIdentity(pr: PrQueueItem): string {
  return `${pr.repo}\u0000${pr.number}\u0000${pr.headRefOid}`
}

function testIdentity(pr: PrQueueItem): string {
  return `${pr.repo.replace(/[^A-Za-z0-9]+/g, '-')}-${pr.number}`
}

function checksRollup(pr: PrQueueItem): string {
  if (!pr.checksComplete) return 'checks incomplete'
  if (pr.checks.some((check) => check.bucket === 'fail')) return 'checks failed'
  if (pr.checks.some((check) => check.bucket === 'pending')) return 'checks pending'
  if (pr.checks.some((check) => check.bucket === 'cancel')) return 'checks cancelled'
  if (pr.checks.length === 0) return 'no checks'
  if (pr.checks.every((check) => check.bucket === 'skipping')) return 'checks skipped'
  return 'checks passing'
}

function stableAgeSort(prs: PrQueueItem[], direction: 'asc' | 'desc'): PrQueueItem[] {
  const multiplier = direction === 'asc' ? 1 : -1
  return [...prs].sort((left, right) => (
    multiplier * (left.ageH - right.ageH)
    || left.repo.localeCompare(right.repo)
    || right.number - left.number
    || left.headRefOid.localeCompare(right.headRefOid)
  ))
}

function mergeability(pr: PrQueueItem): JSX.Element | null {
  if (pr.mergeable === 'CONFLICTING') return <StatusChip status="conflicting" />
  if (pr.mergeable === 'UNKNOWN') return <StatusChip status="mergeability unknown" />
  return null
}

function rebaseStatus(staleVsMain: boolean | null): string {
  if (staleVsMain === null) return 'rebase unknown'
  return staleVsMain ? 'needs rebase' : 'up to date'
}

export function PrQueueTable(props: PrQueueTableProps): JSX.Element {
  const [sort, setSort] = useState<DataTableSort | null>({ columnId: 'age', direction: 'desc' })
  const [selectedId, setSelectedId] = useState<string | null>(null)
  const titleId = useId()
  const sorted = useMemo(() => stableAgeSort(props.prs, sort?.direction ?? 'desc'), [props.prs, sort?.direction])
  const completeness = useMemo(
    () => new Map(props.repos.map((repo) => [repo.repo, repo.prsComplete])),
    [props.repos],
  )
  const gaps = props.repos.filter((repo) => !repo.prsComplete)
  const selected = props.prs.find((pr) => rowIdentity(pr) === selectedId) ?? null
  const allColumns: DataTableColumn<PrQueueItem>[] = [
    { id: 'repo', header: 'Repo', minWidth: 180, sortable: true, sortValue: (pr) => pr.repo.toLowerCase(), searchValue: (pr) => pr.repo, cell: (pr) => <span className="whitespace-nowrap text-fg-muted">{pr.repo}</span> },
    { id: 'number', header: '#', minWidth: 70, sortable: true, sortValue: (pr) => pr.number, searchValue: (pr) => `#${pr.number}`, cell: (pr) => <a href={safeHttpUrl(pr.url)} data-pr-identity={`${pr.repo}#${pr.number}`} data-pr-oid={pr.headRefOid} className="font-semibold tabular-nums text-accent no-underline hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent">#{pr.number}</a> },
    { id: 'title', header: 'Title', minWidth: 260, sortable: true, sortValue: (pr) => pr.title.toLowerCase(), searchValue: (pr) => `${pr.title} ${pr.branch}`, cell: (pr) => <div className="flex min-w-0 items-center gap-2" data-testid={`pr-queue-row-${testIdentity(pr)}`}><span className="min-w-0 truncate text-sm text-fg">{pr.title}</span>{mergeability(pr)}{completeness.get(pr.repo) === false && <StatusChip status="PR list may be incomplete" />}</div> },
    { id: 'age', header: 'Age', minWidth: 90, sortable: true, sortValue: (pr) => pr.ageH, cell: (pr) => <span className="block text-right tabular-nums text-fg-muted">{formatDurationMs(pr.ageH * 3_600_000)}</span> },
    { id: 'checks', header: 'Checks', minWidth: 160, sortable: true, sortValue: (pr) => checksRollup(pr).toLowerCase(), searchValue: (pr) => `${checksRollup(pr)} ${pr.checks.map((check) => check.name).join(' ')}`, cell: (pr) => <span className="flex items-center gap-2"><StatusChip status={checksRollup(pr)} /><button type="button" aria-label={`Details for checks for ${props.allScope ? `${pr.repo} ` : ''}#${pr.number}`} aria-expanded={selectedId === rowIdentity(pr)} onClick={() => setSelectedId(rowIdentity(pr))} className="inline-flex min-h-11 items-center rounded-md focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent">Details</button></span> },
    { id: 'train', header: 'Train', minWidth: 110, sortable: true, sortValue: (pr) => pr.trainPr ?? -1, searchValue: (pr) => pr.trainPr === null ? 'not in train' : `train #${pr.trainPr}`, cell: (pr) => <StatusChip status={pr.trainPr === null ? 'not in train' : `train #${pr.trainPr}`} /> },
    { id: 'rebase', header: 'Rebase', minWidth: 130, sortable: true, sortValue: (pr) => rebaseStatus(pr.staleVsMain).toLowerCase(), searchValue: (pr) => rebaseStatus(pr.staleVsMain), cell: (pr) => <StatusChip status={rebaseStatus(pr.staleVsMain)} /> },
  ]
  const columns = props.allScope ? allColumns : allColumns.filter((column) => column.id !== 'repo')
  const capabilities = [withSorting({ sort, onSortChange: setSort, manual: sort?.columnId === 'age' }), withSearch({ label: 'Search PR queue' }), withColumnResizing()]

  if (props.prs.length === 0 && gaps.length === 0) {
    return <p className="text-sm text-fg-muted">No open PRs</p>
  }

  return (
    <div className="space-y-2">
      {gaps.map((repo) => {
        const gap = resolvePrQueueRepoGap(repo)
        return gap ? <CiCoverageBanner key={gap.id} gaps={[gap]} repoPrefix={repo.repo} /> : null
      })}
      {props.prs.length > 0 && (
        <DataTable
          caption="PR queue"
          columns={columns}
          rows={sort?.columnId === 'age' ? sorted : props.prs}
          getRowId={rowIdentity}
          capabilities={capabilities}
        />
      )}
      {selected ? <DetailDrawer eyebrow={`${selected.repo} · #${selected.number}`} title={selected.title} titleId={titleId} onClose={() => setSelectedId(null)}><div className="flex flex-wrap gap-2 border-t border-border bg-surface-raised px-3 py-2">{selected.checks.map((check) => <span key={check.name} className="inline-flex items-center gap-1 text-xs text-fg-muted"><span>{check.name}</span><StatusChip status={check.bucket} /></span>)}{selected.checks.length === 0 && <span className="text-xs text-fg-muted">No returned checks</span>}</div></DetailDrawer> : null}
      <DeckTooltipLayer />
    </div>
  )
}
