import React from 'react';
import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Copy, ExternalLink, Link2, RefreshCcw, RotateCcw, Settings, Share2 } from 'lucide-react';
import { YStack, XStack } from '@tamagui/stacks';
import { Slider } from 'tamagui';
import { SizableText as Text } from '@tamagui/text';
import { Pressable } from '@tamagui/react-native-web-lite';
import toast from 'react-hot-toast';
import { HeaderActionButton, MobileShell } from './components/MobileShell';
import { MobileCard, CTAButton, SkeletonCard } from './components/Primitives';
import { MobileField, MobileInput, MobileSelect } from './components/FormControls';
import { getEvent, getLiveShowLink, rotateLiveShowLink, updateEvent, LiveShowLink, LiveShowSettings, TenantEvent } from '../api';
import { isAuthError } from '../auth/tokens';
import { getApiErrorMessage } from '../lib/apiError';
import { resolveEventDisplayName } from '../lib/events';
import { adminPath } from '../constants';
import { useBackNavigation } from './hooks/useBackNavigation';
import { useAdminTheme } from './theme';

type LiveShowFormState = {
  moderation_mode: NonNullable<LiveShowSettings['moderation_mode']>;
  retention_window_hours: number;
  playback_mode: NonNullable<LiveShowSettings['playback_mode']>;
  pace_mode: NonNullable<LiveShowSettings['pace_mode']>;
  fixed_interval_seconds: number;
  layout_mode: NonNullable<LiveShowSettings['layout_mode']>;
  effect_preset: NonNullable<LiveShowSettings['effect_preset']>;
  effect_intensity: number;
  background_mode: NonNullable<LiveShowSettings['background_mode']>;
};

const DEFAULT_LIVE_SHOW_SETTINGS: LiveShowFormState = {
  moderation_mode: 'manual',
  retention_window_hours: 12,
  playback_mode: 'newest_first',
  pace_mode: 'auto',
  fixed_interval_seconds: 8,
  layout_mode: 'single',
  effect_preset: 'film_cut',
  effect_intensity: 70,
  background_mode: 'blur_last',
};

const MODERATION_VALUES: LiveShowFormState['moderation_mode'][] = ['off', 'manual', 'trusted_only'];
const PLAYBACK_VALUES: LiveShowFormState['playback_mode'][] = ['newest_first', 'balanced', 'curated'];
const PACE_VALUES: LiveShowFormState['pace_mode'][] = ['auto', 'fixed'];
const LAYOUT_VALUES: LiveShowFormState['layout_mode'][] = ['single', 'split', 'grid_burst'];
const BACKGROUND_VALUES: LiveShowFormState['background_mode'][] = ['blur_last', 'gradient', 'solid', 'brand'];
const EFFECT_VALUES: LiveShowFormState['effect_preset'][] = [
  'film_cut',
  'shutter_flash',
  'polaroid_toss',
  'parallax_glide',
  'light_effects',
];

const MODERATION_OPTIONS: Array<{ value: LiveShowFormState['moderation_mode']; labelKey: string; fallback: string }> = [
  { value: 'off', labelKey: 'liveShowSettings.moderation.off', fallback: 'No moderation' },
  { value: 'manual', labelKey: 'liveShowSettings.moderation.manual', fallback: 'Manual approval' },
  { value: 'trusted_only', labelKey: 'liveShowSettings.moderation.trustedOnly', fallback: 'Trusted sources only' },
];

const PLAYBACK_OPTIONS: Array<{ value: LiveShowFormState['playback_mode']; labelKey: string; fallback: string }> = [
  { value: 'newest_first', labelKey: 'liveShowSettings.playback.newest', fallback: 'Newest first' },
  { value: 'balanced', labelKey: 'liveShowSettings.playback.balanced', fallback: 'Balanced mix' },
  { value: 'curated', labelKey: 'liveShowSettings.playback.curated', fallback: 'Curated' },
];

const PACE_OPTIONS: Array<{ value: LiveShowFormState['pace_mode']; labelKey: string; fallback: string }> = [
  { value: 'auto', labelKey: 'liveShowSettings.pace.auto', fallback: 'Auto' },
  { value: 'fixed', labelKey: 'liveShowSettings.pace.fixed', fallback: 'Fixed' },
];

const LAYOUT_OPTIONS: Array<{ value: LiveShowFormState['layout_mode']; labelKey: string; fallback: string }> = [
  { value: 'single', labelKey: 'liveShowSettings.layout.single', fallback: 'Single' },
  { value: 'split', labelKey: 'liveShowSettings.layout.split', fallback: 'Split' },
  { value: 'grid_burst', labelKey: 'liveShowSettings.layout.gridBurst', fallback: 'Grid burst' },
];

const BACKGROUND_OPTIONS: Array<{ value: LiveShowFormState['background_mode']; labelKey: string; fallback: string }> = [
  { value: 'blur_last', labelKey: 'liveShowSettings.background.blurLast', fallback: 'Blur last photo' },
  { value: 'gradient', labelKey: 'liveShowSettings.background.gradient', fallback: 'Soft gradient' },
  { value: 'solid', labelKey: 'liveShowSettings.background.solid', fallback: 'Solid color' },
  { value: 'brand', labelKey: 'liveShowSettings.background.brand', fallback: 'Brand colors' },
];

const EFFECT_PRESETS: Array<{
  value: LiveShowFormState['effect_preset'];
  labelKey: string;
  descriptionKey: string;
  fallbackLabel: string;
  fallbackDescription: string;
}> = [
  {
    value: 'film_cut',
    labelKey: 'liveShowSettings.effects.filmCut.title',
    descriptionKey: 'liveShowSettings.effects.filmCut.description',
    fallbackLabel: 'Film cut',
    fallbackDescription: 'Clean fades with a subtle cinematic feel.',
  },
  {
    value: 'shutter_flash',
    labelKey: 'liveShowSettings.effects.shutterFlash.title',
    descriptionKey: 'liveShowSettings.effects.shutterFlash.description',
    fallbackLabel: 'Shutter flash',
    fallbackDescription: 'Snappy flash and slide transitions.',
  },
  {
    value: 'polaroid_toss',
    labelKey: 'liveShowSettings.effects.polaroidToss.title',
    descriptionKey: 'liveShowSettings.effects.polaroidToss.description',
    fallbackLabel: 'Polaroid toss',
    fallbackDescription: 'Card-like frames with a gentle rotation settle.',
  },
  {
    value: 'parallax_glide',
    labelKey: 'liveShowSettings.effects.parallaxGlide.title',
    descriptionKey: 'liveShowSettings.effects.parallaxGlide.description',
    fallbackLabel: 'Parallax glide',
    fallbackDescription: 'Slow zoom with a soft depth backdrop.',
  },
  {
    value: 'light_effects',
    labelKey: 'liveShowSettings.effects.light.title',
    descriptionKey: 'liveShowSettings.effects.light.description',
    fallbackLabel: 'Light effects',
    fallbackDescription: 'Minimal transitions for lower-powered devices.',
  },
];

export default function MobileEventLiveShowSettingsPage() {
  const { slug: slugParam } = useParams<{ slug?: string }>();
  const slug = slugParam ?? null;
  const { t, i18n } = useTranslation('management');
  const { textStrong, text, muted, danger, border, surface } = useAdminTheme();
  const [event, setEvent] = React.useState<TenantEvent | null>(null);
  const [form, setForm] = React.useState<LiveShowFormState>(DEFAULT_LIVE_SHOW_SETTINGS);
  const [liveShowLink, setLiveShowLink] = React.useState<LiveShowLink | null>(null);
  const [loading, setLoading] = React.useState(true);
  const [linkLoading, setLinkLoading] = React.useState(false);
  const [linkBusy, setLinkBusy] = React.useState(false);
  const [saving, setSaving] = React.useState(false);
  const [error, setError] = React.useState<string | null>(null);
  const back = useBackNavigation(slug ? adminPath(`/mobile/events/${slug}`) : adminPath('/mobile/events'));

  const locale = i18n.language?.startsWith('en') ? 'en-GB' : 'de-DE';

  const loadLink = React.useCallback(async () => {
    if (!slug) return;
    setLinkLoading(true);
    try {
      const link = await getLiveShowLink(slug);
      setLiveShowLink(link);
    } catch (err) {
      if (!isAuthError(err)) {
        toast.error(getApiErrorMessage(err, t('liveShowSettings.link.loadFailed', 'Live Show link could not be loaded.')));
      }
    } finally {
      setLinkLoading(false);
    }
  }, [slug, t]);

  const load = React.useCallback(async () => {
    if (!slug) return;
    setLoading(true);
    setError(null);
    try {
      const data = await getEvent(slug);
      setEvent(data);
      setForm(extractSettings(data));
      void loadLink();
    } catch (err) {
      if (!isAuthError(err)) {
        setError(getApiErrorMessage(err, t('liveShowSettings.loadFailed', 'Live Show settings could not be loaded.')));
      }
    } finally {
      setLoading(false);
    }
  }, [slug, t, loadLink]);

  React.useEffect(() => {
    void load();
  }, [load]);

  async function handleSave() {
    if (!event?.slug || saving) return;
    const eventTypeId = event.event_type_id ?? event.event_type?.id ?? null;
    if (!eventTypeId) {
      const msg = t('events.errors.missingType', 'Event type fehlt. Speichere das Event erneut im Admin.');
      setError(msg);
      toast.error(msg);
      return;
    }

    setSaving(true);
    setError(null);
    try {
      const payload = {
        name: typeof event.name === 'string' ? event.name : resolveName(event.name),
        slug: event.slug,
        event_type_id: eventTypeId,
        event_date: event.event_date ?? undefined,
        status: event.status ?? 'draft',
        is_active: event.is_active ?? undefined,
      };

      const updated = await updateEvent(event.slug, {
        ...payload,
        settings: {
          ...(event.settings ?? {}),
          live_show: {
            moderation_mode: form.moderation_mode,
            retention_window_hours: Math.round(form.retention_window_hours),
            playback_mode: form.playback_mode,
            pace_mode: form.pace_mode,
            fixed_interval_seconds: Math.round(form.fixed_interval_seconds),
            layout_mode: form.layout_mode,
            effect_preset: form.effect_preset,
            effect_intensity: Math.round(form.effect_intensity),
            background_mode: form.background_mode,
          },
        },
      });

      setEvent(updated);
      setForm(extractSettings(updated));
      toast.success(t('liveShowSettings.saveSuccess', 'Live Show settings updated.'));
    } catch (err) {
      if (!isAuthError(err)) {
        const message = getApiErrorMessage(err, t('liveShowSettings.saveFailed', 'Live Show settings could not be saved.'));
        setError(message);
        toast.error(message);
      }
    } finally {
      setSaving(false);
    }
  }

  async function handleRotateLink() {
    if (!slug || linkBusy) return;

    if (!window.confirm(t('liveShowSettings.link.rotateConfirm', 'Rotate the Live Show link? The current link will stop working.'))) {
      return;
    }

    setLinkBusy(true);
    try {
      const link = await rotateLiveShowLink(slug);
      setLiveShowLink(link);
      toast.success(t('liveShowSettings.link.rotateSuccess', 'Live Show link rotated.'));
    } catch (err) {
      if (!isAuthError(err)) {
        toast.error(getApiErrorMessage(err, t('liveShowSettings.link.rotateFailed', 'Live Show link could not be rotated.')));
      }
    } finally {
      setLinkBusy(false);
    }
  }

  const presetMeta = EFFECT_PRESETS.find((preset) => preset.value === form.effect_preset) ?? EFFECT_PRESETS[0];

  return (
    <MobileShell
      activeTab="home"
      title={t('liveShowSettings.title', 'Live Show settings')}
      onBack={back}
      headerActions={
        <HeaderActionButton onPress={() => load()} ariaLabel={t('common.refresh', 'Refresh')}>
          <RefreshCcw size={18} color={text} />
        </HeaderActionButton>
      }
    >
      {error ? (
        <MobileCard>
          <Text fontWeight="700" color={danger}>
            {error}
          </Text>
        </MobileCard>
      ) : null}

      {loading ? (
        <YStack space="$2">
          {Array.from({ length: 3 }).map((_, idx) => (
            <SkeletonCard key={`ls-skel-${idx}`} height={110} />
          ))}
        </YStack>
      ) : (
        <YStack space="$2">
          <MobileCard space="$2">
            <XStack alignItems="center" space="$2">
              <Link2 size={18} color={text} />
              <Text fontSize="$md" fontWeight="800" color={text}>
                {t('liveShowSettings.link.title', 'Live Show link')}
              </Text>
            </XStack>
            <Text fontSize="$xs" color={muted}>
              {t('liveShowSettings.link.subtitle', 'Open this link on a screen to run the Live Show.')}
            </Text>
            {linkLoading ? (
              <SkeletonCard height={96} />
            ) : liveShowLink?.url ? (
              <Text fontSize="$sm" color={textStrong} selectable>
                {liveShowLink.url}
              </Text>
            ) : (
              <Text fontSize="$sm" color={muted}>
                {t('liveShowSettings.link.empty', 'No Live Show link available.')}
              </Text>
            )}
            <XStack space="$2" marginTop="$2" alignItems="center" flexWrap="nowrap">
              <IconAction
                label={t('liveShowSettings.link.copy', 'Copy')}
                disabled={!liveShowLink?.url}
                onPress={() => liveShowLink?.url && copyToClipboard(liveShowLink.url, t)}
              >
                <Copy size={18} />
              </IconAction>
              <IconAction
                label={t('liveShowSettings.link.share', 'Share')}
                disabled={!liveShowLink?.url}
                onPress={() => liveShowLink?.url && shareLink(liveShowLink.url, event, t)}
              >
                <Share2 size={18} />
              </IconAction>
              <IconAction
                label={t('liveShowSettings.link.open', 'Open')}
                disabled={!liveShowLink?.url}
                onPress={() => liveShowLink?.url && openLink(liveShowLink.url)}
              >
                <ExternalLink size={18} />
              </IconAction>
              <IconAction
                label={t('liveShowSettings.link.rotate', 'Rotate')}
                disabled={linkBusy}
                onPress={() => handleRotateLink()}
              >
                <RotateCcw size={18} />
              </IconAction>
            </XStack>
            {liveShowLink?.qr_code_data_url ? (
              <XStack space="$2" alignItems="center" marginTop="$2" flexWrap="wrap">
                <Pressable
                  onPress={() => downloadQr(liveShowLink.qr_code_data_url!, 'live-show-qr.png')}
                  title={t('liveShowSettings.link.downloadQr', 'Download QR')}
                  aria-label={t('liveShowSettings.link.downloadQr', 'Download QR')}
                  style={{ borderRadius: 12, cursor: 'pointer' }}
                >
                  <img
                    src={liveShowLink.qr_code_data_url}
                    alt={t('liveShowSettings.link.qrAlt', 'Live Show QR code')}
                    style={{ width: 140, height: 140, borderRadius: 12, border: `1px solid ${border}` }}
                  />
                </Pressable>
              </XStack>
            ) : null}
            {liveShowLink ? (
              <Text fontSize="$xs" color={muted} marginTop="$1.5">
                {t('liveShowSettings.link.noExpiry', 'No expiry date set.')}
              </Text>
            ) : null}
            {liveShowLink?.rotated_at ? (
              <Text fontSize="$xs" color={muted} marginTop="$1.5">
                {t('liveShowSettings.link.rotatedAt', 'Last rotated {{time}}', {
                  time: formatTimestamp(liveShowLink.rotated_at, locale),
                })}
              </Text>
            ) : null}
          </MobileCard>

          <MobileCard space="$2">
            <XStack alignItems="center" space="$2">
              <Settings size={18} color={text} />
              <Text fontSize="$md" fontWeight="800" color={text}>
                {t('liveShowSettings.title', 'Live Show settings')}
              </Text>
            </XStack>
            <Text fontSize="$xs" color={muted}>
              {t('liveShowSettings.subtitle', 'Tune the playback, pacing, and effects shown on the screen.')}
            </Text>
          </MobileCard>

          <MobileCard space="$3">
            <Text fontSize="$sm" fontWeight="800" color={text}>
              {t('liveShowSettings.sections.moderation', 'Moderation')}
            </Text>
            <MobileField
              label={t('liveShowSettings.fields.moderationMode', 'Moderation mode')}
              hint={t('liveShowSettings.hints.moderation', 'Trusted sources include admin uploads and photobooths.')}
            >
              <MobileSelect
                value={form.moderation_mode}
                onChange={(event) => setForm((prev) => ({ ...prev, moderation_mode: event.target.value as LiveShowFormState['moderation_mode'] }))}
              >
                {MODERATION_OPTIONS.map((option) => (
                  <option key={option.value} value={option.value}>
                    {t(option.labelKey, option.fallback)}
                  </option>
                ))}
              </MobileSelect>
            </MobileField>

            <MobileField
              label={t('liveShowSettings.fields.retention', 'Retention window (hours)')}
              hint={t('liveShowSettings.hints.retention', 'Controls how long approved photos remain in rotation.')}
            >
              <MobileInput
                type="number"
                inputMode="numeric"
                min={1}
                max={72}
                value={String(form.retention_window_hours)}
                onChange={(event) => setForm((prev) => ({
                  ...prev,
                  retention_window_hours: clampNumber(event.target.value, prev.retention_window_hours, 1, 72),
                }))}
              />
            </MobileField>
          </MobileCard>

          <MobileCard space="$3">
            <Text fontSize="$sm" fontWeight="800" color={text}>
              {t('liveShowSettings.sections.playback', 'Playback')}
            </Text>
            <MobileField
              label={t('liveShowSettings.fields.playbackMode', 'Playback mode')}
              hint={t('liveShowSettings.hints.playback', 'Balance newer uploads with highlights when the queue is busy.')}
            >
              <MobileSelect
                value={form.playback_mode}
                onChange={(event) => setForm((prev) => ({ ...prev, playback_mode: event.target.value as LiveShowFormState['playback_mode'] }))}
              >
                {PLAYBACK_OPTIONS.map((option) => (
                  <option key={option.value} value={option.value}>
                    {t(option.labelKey, option.fallback)}
                  </option>
                ))}
              </MobileSelect>
            </MobileField>

            <MobileField
              label={t('liveShowSettings.fields.paceMode', 'Pace')}
              hint={t('liveShowSettings.hints.pace', 'Auto adapts to new uploads; fixed keeps a steady rhythm.')}
            >
              <MobileSelect
                value={form.pace_mode}
                onChange={(event) => setForm((prev) => ({ ...prev, pace_mode: event.target.value as LiveShowFormState['pace_mode'] }))}
              >
                {PACE_OPTIONS.map((option) => (
                  <option key={option.value} value={option.value}>
                    {t(option.labelKey, option.fallback)}
                  </option>
                ))}
              </MobileSelect>
            </MobileField>

            {form.pace_mode === 'fixed' ? (
              <MobileField
                label={t('liveShowSettings.fields.fixedInterval', 'Fixed interval (seconds)')}
              >
                <MobileInput
                  type="number"
                  inputMode="numeric"
                  min={3}
                  max={20}
                  value={String(form.fixed_interval_seconds)}
                  onChange={(event) => setForm((prev) => ({
                    ...prev,
                    fixed_interval_seconds: clampNumber(event.target.value, prev.fixed_interval_seconds, 3, 20),
                  }))}
                />
              </MobileField>
            ) : null}
          </MobileCard>

          <MobileCard space="$3">
            <Text fontSize="$sm" fontWeight="800" color={text}>
              {t('liveShowSettings.sections.effects', 'Effects & layout')}
            </Text>
            <MobileField
              label={t('liveShowSettings.fields.layoutMode', 'Layout')}
              hint={t('liveShowSettings.hints.layout', 'Split and grid layouts are used when enough photos are available.')}
            >
              <MobileSelect
                value={form.layout_mode}
                onChange={(event) => setForm((prev) => ({ ...prev, layout_mode: event.target.value as LiveShowFormState['layout_mode'] }))}
              >
                {LAYOUT_OPTIONS.map((option) => (
                  <option key={option.value} value={option.value}>
                    {t(option.labelKey, option.fallback)}
                  </option>
                ))}
              </MobileSelect>
            </MobileField>

            <MobileField
              label={t('liveShowSettings.fields.backgroundMode', 'Background')}
            >
              <MobileSelect
                value={form.background_mode}
                onChange={(event) => setForm((prev) => ({ ...prev, background_mode: event.target.value as LiveShowFormState['background_mode'] }))}
              >
                {BACKGROUND_OPTIONS.map((option) => (
                  <option key={option.value} value={option.value}>
                    {t(option.labelKey, option.fallback)}
                  </option>
                ))}
              </MobileSelect>
            </MobileField>

            <MobileField
              label={t('liveShowSettings.fields.effectPreset', 'Effect preset')}
            >
              <MobileSelect
                value={form.effect_preset}
                onChange={(event) => setForm((prev) => ({ ...prev, effect_preset: event.target.value as LiveShowFormState['effect_preset'] }))}
              >
                {EFFECT_PRESETS.map((preset) => (
                  <option key={preset.value} value={preset.value}>
                    {t(preset.labelKey, preset.fallbackLabel)}
                  </option>
                ))}
              </MobileSelect>
            </MobileField>

            <MobileCard borderColor={border} backgroundColor={surface}>
              <Text fontSize="$sm" fontWeight="700" color={text}>
                {t(presetMeta.labelKey, presetMeta.fallbackLabel)}
              </Text>
              <Text fontSize="$xs" color={muted}>
                {t(presetMeta.descriptionKey, presetMeta.fallbackDescription)}
              </Text>
            </MobileCard>

            <EffectSlider
              label={t('liveShowSettings.fields.effectIntensity', 'Effect intensity')}
              value={form.effect_intensity}
              min={0}
              max={100}
              step={5}
              onChange={(value) => setForm((prev) => ({ ...prev, effect_intensity: value }))}
              suffix="%"
            />
          </MobileCard>

          <CTAButton
            label={saving ? t('common.processing', '...') : t('liveShowSettings.save', 'Save settings')}
            onPress={() => handleSave()}
            disabled={saving}
          />
        </YStack>
      )}
    </MobileShell>
  );
}

function extractSettings(event: TenantEvent | null): LiveShowFormState {
  const liveShow = (event?.settings?.live_show ?? {}) as LiveShowSettings;

  return {
    moderation_mode: resolveOption(liveShow.moderation_mode, MODERATION_VALUES, DEFAULT_LIVE_SHOW_SETTINGS.moderation_mode),
    retention_window_hours: resolveNumber(liveShow.retention_window_hours, DEFAULT_LIVE_SHOW_SETTINGS.retention_window_hours, 1, 72),
    playback_mode: resolveOption(liveShow.playback_mode, PLAYBACK_VALUES, DEFAULT_LIVE_SHOW_SETTINGS.playback_mode),
    pace_mode: resolveOption(liveShow.pace_mode, PACE_VALUES, DEFAULT_LIVE_SHOW_SETTINGS.pace_mode),
    fixed_interval_seconds: resolveNumber(liveShow.fixed_interval_seconds, DEFAULT_LIVE_SHOW_SETTINGS.fixed_interval_seconds, 3, 20),
    layout_mode: resolveOption(liveShow.layout_mode, LAYOUT_VALUES, DEFAULT_LIVE_SHOW_SETTINGS.layout_mode),
    effect_preset: resolveOption(liveShow.effect_preset, EFFECT_VALUES, DEFAULT_LIVE_SHOW_SETTINGS.effect_preset),
    effect_intensity: resolveNumber(liveShow.effect_intensity, DEFAULT_LIVE_SHOW_SETTINGS.effect_intensity, 0, 100),
    background_mode: resolveOption(liveShow.background_mode, BACKGROUND_VALUES, DEFAULT_LIVE_SHOW_SETTINGS.background_mode),
  };
}

function resolveName(name: TenantEvent['name']): string {
  if (typeof name === 'string' && name.trim()) return name;
  if (name && typeof name === 'object') {
    return name.de ?? name.en ?? Object.values(name)[0] ?? 'Event';
  }
  return 'Event';
}

function copyToClipboard(value: string, t: any) {
  navigator.clipboard
    .writeText(value)
    .then(() => toast.success(t('liveShowSettings.link.copySuccess', 'Link copied')))
    .catch(() => toast.error(t('liveShowSettings.link.copyFailed', 'Link could not be copied')));
}

async function shareLink(value: string, event: TenantEvent | null, t: any) {
  if (navigator.share) {
    try {
      await navigator.share({
        title: event ? resolveEventDisplayName(event) : t('liveShowSettings.link.shareTitle', 'Live Show'),
        text: t('liveShowSettings.link.shareText', 'Live Show link'),
        url: value,
      });
      return;
    } catch {
      // ignore
    }
  }
  copyToClipboard(value, t);
}

function openLink(value: string) {
  window.open(value, '_blank', 'noopener,noreferrer');
}

function downloadQr(dataUrl: string, filename: string) {
  const link = document.createElement('a');
  link.href = dataUrl;
  link.download = filename;
  link.click();
}

function formatTimestamp(value: string, locale: string): string {
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return value;
  return date.toLocaleString(locale, { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' });
}

function resolveOption<T extends string>(value: unknown, options: T[], fallback: T): T {
  if (typeof value !== 'string') return fallback;
  return options.includes(value as T) ? (value as T) : fallback;
}

function resolveNumber(value: unknown, fallback: number, min: number, max: number): number {
  if (typeof value !== 'number' || Number.isNaN(value)) return fallback;
  return Math.min(max, Math.max(min, value));
}

function clampNumber(value: string, fallback: number, min: number, max: number): number {
  const parsed = Number(value);
  if (Number.isNaN(parsed)) return fallback;
  return Math.min(max, Math.max(min, parsed));
}

function EffectSlider({
  label,
  value,
  min,
  max,
  step,
  onChange,
  disabled,
  suffix,
}: {
  label: string;
  value: number;
  min: number;
  max: number;
  step: number;
  onChange: (value: number) => void;
  disabled?: boolean;
  suffix?: string;
}) {
  const { text, muted, primary, border, surface } = useAdminTheme();

  return (
    <YStack space="$1.5">
      <XStack alignItems="center" justifyContent="space-between">
        <Text fontSize="$sm" fontWeight="700" color={text}>
          {label}
        </Text>
        <Text fontSize="$xs" color={muted}>
          {value}
          {suffix ? ` ${suffix}` : ''}
        </Text>
      </XStack>
      <Slider
        width="100%"
        min={min}
        max={max}
        step={step}
        value={[value]}
        onValueChange={(next) => onChange(next[0] ?? value)}
        disabled={disabled}
      >
        <Slider.Track height={6} borderRadius={999} backgroundColor={border}>
          <Slider.TrackActive backgroundColor={primary} />
        </Slider.Track>
        <Slider.Thumb index={0} size="$2" backgroundColor={surface} borderColor={primary} borderWidth={2} />
      </Slider>
    </YStack>
  );
}

function IconAction({
  label,
  onPress,
  disabled,
  children,
}: {
  label: string;
  onPress: () => void;
  disabled?: boolean;
  children: React.ReactNode;
}) {
  const { surface, border, text, muted } = useAdminTheme();
  const color = disabled ? muted : text;

  return (
    <Pressable
      onPress={disabled ? undefined : onPress}
      disabled={disabled}
      title={label}
      aria-label={label}
      style={{
        width: 44,
        height: 44,
        borderRadius: 14,
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: surface,
        borderWidth: 1,
        borderColor: border,
        opacity: disabled ? 0.6 : 1,
      }}
    >
      <XStack alignItems="center" justifyContent="center">
        {React.isValidElement(children) ? React.cloneElement(children as any, { color }) : children}
      </XStack>
    </Pressable>
  );
}
