"use client";
import React, { useCallback, useMemo } from "react";
import {
Save,
RefreshCw,
Download,
Hash,
AlertTriangle,
CheckCircle2,
UploadCloud,
Wand2,
Sparkles,
GitBranch,
Keyboard,
} from "lucide-react";
import { Button } from "~/components/ui/button";
import { Badge } from "~/components/ui/badge";
import { Separator } from "~/components/ui/separator";
import { cn } from "~/lib/utils";
import { useDesignerStore } from "../state/store";
/**
* BottomStatusBar
*
* Compact, persistent status + quick-action bar for the Experiment Designer.
* Shows:
* - Validation / drift / unsaved state
* - Short design hash & version
* - Aggregate counts (steps / actions)
* - Last persisted hash (if available)
* - Quick actions (Save, Validate, Export, Command Palette)
*
* The bar is intentionally UI-only: callback props are used so that higher-level
* orchestration (e.g. DesignerRoot / Shell) controls actual side effects.
*/
export interface BottomStatusBarProps {
onSave?: () => void;
onValidate?: () => void;
onExport?: () => void;
onOpenCommandPalette?: () => void;
onToggleVersionStrategy?: () => void;
className?: string;
saving?: boolean;
validating?: boolean;
exporting?: boolean;
/**
* Optional externally supplied last saved Date for relative display.
*/
lastSavedAt?: Date;
}
export function BottomStatusBar({
onSave,
onValidate,
onExport,
onOpenCommandPalette,
onToggleVersionStrategy,
className,
saving,
validating,
exporting,
lastSavedAt,
}: BottomStatusBarProps) {
/* ------------------------------------------------------------------------ */
/* Store Selectors */
/* ------------------------------------------------------------------------ */
const steps = useDesignerStore((s) => s.steps);
const lastPersistedHash = useDesignerStore((s) => s.lastPersistedHash);
const currentDesignHash = useDesignerStore((s) => s.currentDesignHash);
const lastValidatedHash = useDesignerStore((s) => s.lastValidatedHash);
const pendingSave = useDesignerStore((s) => s.pendingSave);
const versionStrategy = useDesignerStore((s) => s.versionStrategy);
const autoSaveEnabled = useDesignerStore((s) => s.autoSaveEnabled);
const actionCount = useMemo(
() => steps.reduce((sum, st) => sum + st.actions.length, 0),
[steps],
);
const hasUnsaved = useMemo(
() =>
Boolean(currentDesignHash) &&
currentDesignHash !== lastPersistedHash &&
!pendingSave,
[currentDesignHash, lastPersistedHash, pendingSave],
);
const validationStatus = useMemo<"unvalidated" | "valid" | "drift">(() => {
if (!currentDesignHash || !lastValidatedHash) return "unvalidated";
if (currentDesignHash !== lastValidatedHash) return "drift";
return "valid";
}, [currentDesignHash, lastValidatedHash]);
const shortHash = useMemo(
() => (currentDesignHash ? currentDesignHash.slice(0, 8) : "—"),
[currentDesignHash],
);
const lastPersistedShort = useMemo(
() => (lastPersistedHash ? lastPersistedHash.slice(0, 8) : null),
[lastPersistedHash],
);
/* ------------------------------------------------------------------------ */
/* Derived Display Helpers */
/* ------------------------------------------------------------------------ */
function formatRelative(date?: Date): string {
if (!date) return "—";
const now = Date.now();
const diffMs = now - date.getTime();
if (diffMs < 30_000) return "just now";
const mins = Math.floor(diffMs / 60_000);
if (mins < 60) return `${mins}m ago`;
const hrs = Math.floor(mins / 60);
if (hrs < 24) return `${hrs}h ago`;
const days = Math.floor(hrs / 24);
return `${days}d ago`;
}
const relSaved = formatRelative(lastSavedAt);
const validationBadge = (() => {
switch (validationStatus) {
case "valid":
return (