import { format, formatDistanceToNow } from "date-fns";
import { ChevronRight, ChessPawn, Hourglass, LayoutGrid, Moon } from "lucide-react";
import { Link } from "react-router";
import { Button } from "~/components/ui/button";
import { Card, CardContent, CardHeader } from "~/components/ui/card";
import { GradientIcon } from "~/components/ui/GradientIcon";
// ─── Types ────────────────────────────────────────────────────────────────────
export interface DraftInfoCardProps {
draftRounds: number;
draftTimerMode: "standard" | "chess_clock";
/** Per-pick time for standard; initial bank for chess clock (seconds) */
draftInitialTime: number;
/** Bonus seconds added after each pick (chess clock only; ignored in standard) */
draftIncrementTime: number;
sportsCount: number;
isDraftOrderSet: boolean;
/** true when season status is pre_draft or draft */
isDraftOrPreDraft: boolean;
draftDateTime?: string | Date | null;
draftBoardHref: string;
draftRoomHref: string;
/** Link to the pre-draft queue builder; shown only when draft order is not yet set and user has a team */
queueBuilderHref?: string;
/** 1-based draft position for the current user; omit or undefined to hide the panel */
userDraftPosition?: number;
/** IANA timezone used to display the draft date and time. */
draftTimezone?: string | null;
overnightPauseMode?: "none" | "league" | "per_user";
overnightPauseStart?: string | null;
overnightPauseEnd?: string | null;
overnightPauseTimezone?: string | null;
}
// ─── Sub-components ───────────────────────────────────────────────────────────
function InfoPanel({ label, value, sub }: { label: string; value: React.ReactNode; sub?: React.ReactNode }) {
return (
{label}
{value}
{sub && {sub} }
);
}
// ─── Card variant (pre_draft / draft) ─────────────────────────────────────────
function DraftInfoCardVariant({
draftRounds,
draftTimerMode,
draftInitialTime,
draftIncrementTime,
sportsCount,
isDraftOrderSet,
draftDateTime,
draftTimezone,
draftRoomHref,
queueBuilderHref,
userDraftPosition,
overnightPauseMode,
overnightPauseStart,
overnightPauseEnd,
overnightPauseTimezone,
}: DraftInfoCardProps) {
const flexSpots = Math.max(0, draftRounds - sportsCount);
const timerLabel = draftTimerMode === "chess_clock" ? "Chess Clock" : "Standard Timer";
const draftDate = draftDateTime ? new Date(draftDateTime) : null;
const timerValue = draftTimerMode === "chess_clock"
? `${formatHumanTime(draftInitialTime, { attributive: true })} time bank`
: formatHumanTime(draftInitialTime);
const timerSub = draftTimerMode === "chess_clock"
? `+${formatHumanTime(draftIncrementTime, { attributive: true })} increment`
: "per pick";
const roundsSub = [
`${sportsCount} sport${sportsCount !== 1 ? "s" : ""}`,
flexSpots > 0 ? `${flexSpots} flex` : null,
].filter(Boolean).join(" + ");
const hasPosition = userDraftPosition !== null && userDraftPosition !== undefined;
const hasOvernight = !!(overnightPauseMode && overnightPauseMode !== "none" && overnightPauseStart && overnightPauseEnd);
// Mechanics row: Rounds + (Your Pick?) + Timer
// When overnight is shown, Draft Date moves to the top row so mechanics has 2–3 items.
// When no overnight, Draft Date stays in this row giving 3–4 items.
const mechanicsColsClass = hasOvernight
? (hasPosition ? "grid-cols-2 sm:grid-cols-3" : "grid-cols-2")
: (hasPosition ? "grid-cols-2 sm:grid-cols-4" : "grid-cols-2 sm:grid-cols-3");
const timerPanel = (
{draftTimerMode === "chess_clock"
?
: }
{timerValue}
}
sub={timerSub}
/>
);
return (
Draft Info
{isDraftOrderSet ? (
Enter Draft Room
) : queueBuilderHref ? (
Set Pre-Draft Queue
) : null}
{/* Scheduling row: Draft Date + Overnight Pause side-by-side when overnight is configured */}
{hasOvernight && (
{formatDraftDateTime(draftDate, draftTimezone)} : "TBD"}
sub={draftDate ? {formatRelativeDate(draftDate)} : undefined}
/>
{formatHHMM(overnightPauseStart ?? "")} – {formatHHMM(overnightPauseEnd ?? "")}
}
sub={overnightPauseMode === "league" && overnightPauseTimezone
? overnightPauseTimezone
: "per-user timezone"}
/>
)}
{/* Mechanics row: Rounds + Your Pick? + Timer (+ Draft Date when no overnight) */}
{!hasOvernight && (
{formatDraftDateTime(draftDate, draftTimezone)} : "TBD"}
sub={draftDate ? {formatRelativeDate(draftDate)} : undefined}
/>
)}
{hasPosition && (
)}
{timerPanel}
{/* placeholder to keep grid balanced on mobile when only 2 items */}
{hasOvernight && !hasPosition &&
}
);
}
// ─── Helpers ──────────────────────────────────────────────────────────────────
/** Formats "23:00" → "11 PM", "07:00" → "7 AM". */
function formatHHMM(hhmm: string): string {
const [hStr, mStr] = hhmm.split(":");
const h = parseInt(hStr, 10);
const m = parseInt(mStr, 10);
const period = h < 12 ? "AM" : "PM";
const h12 = h % 12 === 0 ? 12 : h % 12;
return m === 0 ? `${h12} ${period}` : `${h12}:${String(m).padStart(2, "0")} ${period}`;
}
function formatDraftDateTime(date: Date, timezone: string | null | undefined): string {
const parts = new Intl.DateTimeFormat("en-US", {
timeZone: getDisplayTimezone(timezone),
month: "long",
day: "numeric",
year: "numeric",
hour: "numeric",
minute: "2-digit",
hour12: true,
timeZoneName: "shortGeneric",
}).formatToParts(date);
const getPart = (type: Intl.DateTimeFormatPartTypes) => (
parts.find((part) => part.type === type)?.value ?? ""
);
return `${getPart("month")} ${getPart("day")}, ${getPart("year")} ${getPart("hour")}:${getPart("minute")}${getPart("dayPeriod")} ${getPart("timeZoneName")}`;
}
function getDisplayTimezone(timezone: string | null | undefined): string {
if (!timezone) return "UTC";
try {
new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format();
return timezone;
} catch {
return "UTC";
}
}
function formatRelativeDate(date: Date): string {
const value = formatDistanceToNow(date, { addSuffix: true });
return value.charAt(0).toUpperCase() + value.slice(1);
}
/** Converts a seconds value to a short human-readable string, e.g. "1 minute", "10 seconds", "2 hours". */
function formatHumanTime(seconds: number, options: { attributive?: boolean } = {}): string {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
const parts: string[] = [];
const plural = (value: number) => (!options.attributive && value !== 1 ? "s" : "");
if (h > 0) parts.push(`${h} hour${plural(h)}`);
if (m > 0) parts.push(`${m} minute${plural(m)}`);
if (s > 0) parts.push(`${s} second${plural(s)}`);
return parts.join(" ") || "0 seconds";
}
// ─── Bare variant (active / completed) ────────────────────────────────────────
function DraftInfoBareVariant({
draftRounds,
draftTimerMode,
draftInitialTime,
draftIncrementTime,
sportsCount,
draftDateTime,
draftBoardHref,
}: DraftInfoCardProps) {
const flexSpots = Math.max(0, draftRounds - sportsCount);
return (
{/* Mode icon */}
{draftTimerMode === "chess_clock"
?
: }
{draftDateTime && (
{format(new Date(draftDateTime), "MMM d, yyyy")}
|
{format(new Date(draftDateTime), "p")}
)}
{draftRounds} rounds ({sportsCount} sport{sportsCount !== 1 ? "s" : ""}
{flexSpots > 0 && ` + ${flexSpots} flex`})
{draftTimerMode === "standard"
? `Standard — ${formatHumanTime(draftInitialTime)}/pick`
: `Chess Clock — ${formatHumanTime(draftInitialTime)} + ${formatHumanTime(draftIncrementTime)} increment`}
);
}
// ─── Public API ───────────────────────────────────────────────────────────────
export function DraftInfoCard(props: DraftInfoCardProps) {
if (props.isDraftOrPreDraft) {
return ;
}
return ;
}