import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ClockCounterClockwiseIcon, CaretLeftIcon, CaretRightIcon, XIcon } from '@phosphor-icons/react'
import { Dialog, DialogContent, DialogTitle, DialogDescription } from '@/ui'
import type { DictionaryDetail } from '@/api/client'
import { cn } from '@/lib/utils'
/**
* TimeTravelModal — fullscreen-y modal per redesign prototype.
*
*
Layout:
*
*
Header: clock icon + TIME-TRAVEL · {DICT} cap + h1 title + ×
*
Compare panels: СЕЙЧАС vs ТОЧКА ВО ВРЕМЕНИ side-by-side.
* Right panel highlighted (`tint`) когда target != current.
*
Quick presets: сейчас / неделя / месяц / год / при создании
*
Step toggle: версия | день + ‹ › nav arrows
*
Slider: HTML range + overlay version markers (top) + date
* markers (bottom). Cursor — dark circle.
*
Tabs: Что изменилось / Записи на момент / Структура и поля.
* Backend diff endpoints отсутствуют — показываем empty/placeholder
* состояния с подсказкой backend pending.
*
Footer: disclaimer + Закрыть / "Открыть v{X} как readonly" CTA
*
*
*
Backend coverage: findActiveAt(at) уже работает (read-api). Нет
* snapshots endpoint, нет diff. Поэтому version labels на slider — выводятся
* из schemaVersion текущей версии (single point), а исторические marks из
* record validFrom timestamps. "Открыть как readonly" просто применяет
* ?at=ISO в URL — route обрабатывает.
*/
export type TimeTravelModalProps = {
open: boolean
onClose: () => void
detail: DictionaryDetail
/** Currently applied at-ISO (если активен readonly snapshot). */
value: string | undefined
/** Apply the picked moment: opens dict as readonly (route uses ?at=). */
onApply: (iso: string) => void
/** Clear at — return к live view. */
onClear: () => void
/** Unique record validFrom timestamps (ms) — для slider tick marks. */
marks: number[]
/** Total records на текущий момент — для tab counter. */
totalRecords: number
}
type StepMode = 'version' | 'day'
export function TimeTravelModal({
open,
onClose,
detail,
value,
onApply,
onClear,
marks,
totalRecords,
}: TimeTravelModalProps) {
const { t } = useTranslation()
const now = Date.now()
const valueMs = useMemo(() => {
if (!value) return now
const ms = Date.parse(value)
return Number.isNaN(ms) ? now : ms
}, [value, now])
const [picked, setPicked] = useState(valueMs)
const [step, setStep] = useState('version')
const [tab, setTab] = useState<'changes' | 'records' | 'schema'>('changes')
// Range: от min(marks, createdAt) до now+padding.
const createdMs = Date.parse(detail.createdAt)
const range = useMemo(() => {
const minMark = marks.length > 0 ? Math.min(...marks, createdMs) : createdMs - 86_400_000
const span = now - minMark
const pad = Math.max(span * 0.04, 86_400_000)
return { min: minMark - pad, max: now + pad }
}, [marks, createdMs, now])
// Sync picked when value prop changes while modal open.
// (Don't useEffect — пока explicitly value control.)
const pickedDate = new Date(picked)
const isAtNow = Math.abs(picked - now) < 60_000 // < 1 min ≈ now
const targetVersion = detail.schemaVersion // backend doesn't expose snapshots
const targetDiffers = !isAtNow
const handlePreset = (deltaMs: number | 'create') => {
if (deltaMs === 'create') {
setPicked(createdMs)
} else if (deltaMs === 0) {
setPicked(now)
} else {
setPicked(now + deltaMs)
}
}
const handleStepNav = (direction: -1 | 1) => {
if (step === 'day') {
setPicked((p) => Math.min(range.max, Math.max(range.min, p + direction * 86_400_000)))
} else {
// Version step: jump to nearest mark
const sorted = [...marks].sort((a, b) => a - b)
if (sorted.length === 0) return
if (direction === -1) {
const prev = [...sorted].reverse().find((m) => m < picked - 1000)
if (prev !== undefined) setPicked(prev)
} else {
const next = sorted.find((m) => m > picked + 1000)
if (next !== undefined) setPicked(next)
}
}
}
const handleApply = () => {
if (isAtNow) {
onClear()
} else {
onApply(new Date(picked).toISOString())
}
onClose()
}
return (
)
}
function ComparePanel({
label,
version,
ms,
records,
author,
note,
highlight,
}: {
label: React.ReactNode
version: string
ms: number
records: number
author?: string
note?: string
highlight?: boolean
}) {
const d = new Date(ms)
return (