import { Button, Icon } from "@nodedc/ui-react";

import type { ObservationTimelineMode } from "../core/runtime/contracts";

export function ObservationTimeline({
  active,
  sourceCount,
  mode = "live-only",
  seekable = false,
  synchronization = "host-arrival-best-effort",
  rangeNs = null,
  currentNs = null,
  playing = false,
  onSeek,
  onPlayingChange,
  onJumpToEnd,
  accumulationSeconds,
  onAccumulationChange,
  onAccumulationCommit,
  className = "",
}: {
  active: boolean;
  sourceCount: number;
  mode?: ObservationTimelineMode;
  seekable?: boolean;
  synchronization?: "host-arrival-best-effort" | "shared-clock" | "frame-accurate";
  rangeNs?: { min: number; max: number } | null;
  currentNs?: number | null;
  playing?: boolean;
  onSeek?: (timeNs: number) => void;
  onPlayingChange?: (playing: boolean) => void;
  onJumpToEnd?: () => void;
  accumulationSeconds?: number;
  onAccumulationChange?: (value: number) => void;
  onAccumulationCommit?: () => void;
  className?: string;
}) {
  const playbackRange = normalizeTimelineRange(rangeNs);
  const buffered = Boolean(
    seekable &&
    (mode === "buffered" || mode === "recorded") &&
    playbackRange,
  );
  const elapsedSeconds = playbackRange
    ? timelineOffsetSeconds(playbackRange, currentNs ?? playbackRange.min)
    : 0;
  const durationSeconds = playbackRange
    ? Math.max(0, (playbackRange.max - playbackRange.min) / 1_000_000_000)
    : 0;
  const synchronizationLabel = {
    "host-arrival-best-effort": "Синхронизация по приходу",
    "shared-clock": "Общие часы",
    "frame-accurate": "Покадровая синхронизация",
  }[synchronization];
  const accumulationValue = accumulationSeconds === undefined
    ? null
    : normalizeAccumulationSeconds(accumulationSeconds);
  return (
    <div
      className={`observation-timeline ${className}`.trim()}
      data-active={active ? "true" : undefined}
      data-mode={mode}
      data-accumulation={accumulationValue !== null ? "true" : undefined}
    >
      {accumulationValue !== null ? (
        <div className="observation-timeline__accumulation">
          <span>Накопление</span>
          <input
            className="observation-timeline__track"
            type="range"
            min={0}
            max={120}
            step={1}
            value={accumulationValue}
            aria-label="Окно накопления облака точек"
            aria-valuetext={formatAccumulationDuration(accumulationValue)}
            onChange={(event) =>
              onAccumulationChange?.(normalizeAccumulationSeconds(Number(event.target.value)))}
            onPointerUp={onAccumulationCommit}
            onTouchEnd={onAccumulationCommit}
            onKeyUp={onAccumulationCommit}
            onBlur={onAccumulationCommit}
          />
          <code>{formatAccumulationDuration(accumulationValue)}</code>
        </div>
      ) : null}
      <div className="observation-timeline__playback">
        <Button
          size="compact"
          variant="ghost"
          disabled={!buffered || !onSeek}
          aria-label="Перейти к началу"
          onClick={() => playbackRange && onSeek?.(playbackRange.min)}
        >
          <Icon name="chevron-left" />
        </Button>
        <Button
          size="compact"
          variant="secondary"
          disabled={!buffered || !onPlayingChange}
          onClick={() => onPlayingChange?.(!playing)}
        >
          {buffered ? (playing ? "Пауза" : "Воспроизвести") : "Только эфир"}
        </Button>
        <input
          className="observation-timeline__track"
          type="range"
          min={0}
          max={Math.max(durationSeconds, 0.001)}
          step={0.01}
          value={Math.min(elapsedSeconds, durationSeconds)}
          disabled={!buffered || !onSeek}
          aria-label="Позиция воспроизведения"
          onChange={(event) => {
            if (!playbackRange) return;
            onSeek?.(playbackRange.min + Number(event.target.value) * 1_000_000_000);
          }}
        />
        <div className="observation-timeline__meta">
          <code>
            {buffered
              ? `${formatTimelineDuration(elapsedSeconds)} / ${formatTimelineDuration(durationSeconds)}`
              : active ? "LIVE" : "—:—:—.———"}
          </code>
          <small>
            {buffered ? `${sourceCount} каналов` : `${synchronizationLabel} · буфер не включён`}
          </small>
        </div>
        <button
          type="button"
          className="observation-timeline__follow"
          data-active={!buffered && active ? "true" : undefined}
          disabled={buffered ? !onJumpToEnd : true}
          onClick={onJumpToEnd}
        >
          {buffered ? "К КОНЦУ" : "ЭФИР"}
        </button>
      </div>
    </div>
  );
}

export function normalizeAccumulationSeconds(value: number): number {
  if (!Number.isFinite(value)) return 0;
  return Math.min(120, Math.max(0, Math.round(value)));
}

export function formatAccumulationDuration(value: number): string {
  const seconds = normalizeAccumulationSeconds(value);
  return seconds === 0 ? "Кадр" : `${seconds} с`;
}

export function normalizeTimelineRange(
  range: { min: number; max: number } | null | undefined,
): { min: number; max: number } | null {
  if (!range || !Number.isFinite(range.min) || !Number.isFinite(range.max)) return null;
  if (range.max <= range.min) return null;
  return range;
}

export function timelineOffsetSeconds(
  range: { min: number; max: number },
  currentNs: number,
): number {
  const clamped = Math.min(Math.max(currentNs, range.min), range.max);
  return Math.max(0, (clamped - range.min) / 1_000_000_000);
}

export function formatTimelineDuration(seconds: number): string {
  if (!Number.isFinite(seconds) || seconds < 0) return "00:00.000";
  const wholeMinutes = Math.floor(seconds / 60);
  const remaining = seconds - wholeMinutes * 60;
  return `${String(wholeMinutes).padStart(2, "0")}:${remaining.toFixed(3).padStart(6, "0")}`;
}
