// <RefreshIntervalPicker> + `useRefreshInterval` — the shared "grafana-
// like" polling pattern from the design-language discussion: the
// operator picks a cadence, the page re-fetches on it, and the timer
// pauses while the tab is backgrounded rather than burning requests for
// no visible benefit. Deliberately small (per mara: "component should
// not be too big") — a preset cadence list and a pause-on-hidden timer,
// nothing more. The UI (picker) and the behaviour (hook) live in one
// file since neither is useful without the other and splitting them
// would be two files for one concern.
//
// The hook, not the picker, owns re-fetch safety: a caller passes its
// own fetch callback, and it's the CALLER's job to make sure that
// callback doesn't clobber an input the operator is mid-edit on — this
// hook only decides *when* to call it.
import { useEffect, useRef } from 'preact/hooks';
import { SelectField, type SelectOption } from '../select-field/SelectField.js';

// `null` means "off" throughout this module — no separate boolean, so
// there's exactly one way to represent "not polling".
export type RefreshIntervalMs = number | null;

const PRESETS: { value: RefreshIntervalMs; label: string }[] = [
  { value: null, label: 'off' },
  { value: 10_000, label: '10s' },
  { value: 30_000, label: '30s' },
  { value: 60_000, label: '1m' },
];

// `SelectField` only speaks strings, so presets round-trip through the
// label side of `PRESETS` rather than a second parallel list.
const OPTIONS: SelectOption[] = PRESETS.map((p) => ({ value: p.label, label: p.label }));

export function RefreshIntervalPicker({
  id,
  value,
  onChange,
}: {
  id: string;
  value: RefreshIntervalMs;
  onChange: (value: RefreshIntervalMs) => void;
}) {
  const current = PRESETS.find((p) => p.value === value) ?? PRESETS[0];
  return (
    <SelectField
      id={id}
      label="refresh"
      value={current.label}
      onChange={(label) => {
        const preset = PRESETS.find((p) => p.label === label);
        if (preset) onChange(preset.value);
      }}
      options={OPTIONS}
    />
  );
}

// Calls `onTick` immediately and then every `intervalMs`, paused while
// the document is hidden and resynced (an immediate call, not a stale
// leftover wait) on becoming visible again — same pattern
// `RelativeTime` already uses for the same reason. `intervalMs = null`
// means "off": no calls at all, not even the initial one — a caller
// that wants data on mount regardless of the picker's starting value
// should default its own `intervalMs` state to a real cadence, not
// `null`, and add a separate mount effect only if it genuinely wants
// "off" to still mean "load once."
export function useRefreshInterval(intervalMs: RefreshIntervalMs, onTick: () => void) {
  // Always-current via a ref rather than a `useEffect` dependency:
  // callers pass an inline closure that's a new value every render, and
  // depending on it directly would re-arm the timer (losing whatever's
  // left of the current interval) on every render instead of only when
  // the cadence itself changes. The ref keeps the *timer* stable while
  // still calling the *latest* callback, not a stale one captured at
  // mount.
  const onTickRef = useRef(onTick);
  onTickRef.current = onTick;

  useEffect(() => {
    if (intervalMs === null) return;
    let id: ReturnType<typeof setInterval> | undefined;

    const start = () => {
      onTickRef.current();
      id = setInterval(() => onTickRef.current(), intervalMs);
    };
    const stop = () => {
      if (id !== undefined) clearInterval(id);
      id = undefined;
    };

    if (document.visibilityState === 'visible') start();
    const onVisibility = () => {
      if (document.visibilityState === 'visible') start();
      else stop();
    };
    document.addEventListener('visibilitychange', onVisibility);
    return () => {
      stop();
      document.removeEventListener('visibilitychange', onVisibility);
    };
  }, [intervalMs]);
}
