/**
 * UI controls — zoom, split ratios, topbar, recent menu, info panel,
 * notes, toast notifications, and reset/reload buttons.
 */
import { api } from './api';
import type { VideoItem } from './types';
import {
  library, currentIndex, prefs, setPrefs,
  clamp, fmtTime, fmtBytes, fmtDate, fmtBitrate,
  currentItem, cb,
} from './store';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';

// ---- DOM refs ----
let contentGrid: HTMLElement;
let divider: HTMLElement;
let dockGrid: HTMLElement;
let dockDivider: HTMLElement;
let zoomOutBtn: HTMLElement;
let zoomInBtn: HTMLElement;
let zoomResetBtn: HTMLElement;
let onTopChk: HTMLInputElement;
let autoplayChk: HTMLInputElement;
let chooseBtn: HTMLElement;
let chooseDropBtn: HTMLElement;
let recentMenu: HTMLElement;
let refreshBtn: HTMLElement;
let resetProgBtn: HTMLElement;
let notesBox: HTMLTextAreaElement;
let notesBackdrop: HTMLElement;
let notesSaved: HTMLElement;
let nowTitle: HTMLElement;
let nowSub: HTMLElement;
let overallBar: HTMLElement;
let overallPct: HTMLElement;
let toast: HTMLElement;
let toastMsg: HTMLElement;
let infoGridEl: HTMLElement;
let winMinBtn: HTMLElement;
let winMaxBtn: HTMLElement;
let winCloseBtn: HTMLElement;

// Info panel elements
let infoFolder: HTMLElement;
let infoNext: HTMLElement;
let infoStruct: HTMLElement;
let infoTitle: HTMLElement;
let infoRel: HTMLElement;
let infoPos: HTMLElement;
let infoFileBits: HTMLElement;
let infoVidBits: HTMLElement;
let infoAudBits: HTMLElement;
let infoSubsBits: HTMLElement;
let infoFinished: HTMLElement;
let infoRemaining: HTMLElement;
let infoEta: HTMLElement;
let infoVolume: HTMLElement;
let infoSpeed: HTMLElement;
let infoKnown: HTMLElement;
let infoTop: HTMLElement;

// ---- State ----
let toastTimer: ReturnType<typeof setTimeout> | null = null;
let draggingDivider = false;
let draggingDockDivider = false;
let saveSplitTimer: ReturnType<typeof setTimeout> | null = null;
let saveDockTimer: ReturnType<typeof setTimeout> | null = null;
let saveZoomTimer: ReturnType<typeof setTimeout> | null = null;
let noteSaveTimer: ReturnType<typeof setTimeout> | null = null;
let notesSavedTimer: ReturnType<typeof setTimeout> | null = null;
let recentOpen = false;
let resetConfirmTimer: ReturnType<typeof setTimeout> | null = null;
let resetConfirming = false;

// ---- Player ref for position info ----
let player: HTMLVideoElement;

export function initUI(): void {
  contentGrid = document.getElementById('contentGrid')!;
  divider = document.getElementById('divider')!;
  dockGrid = document.getElementById('dockGrid')!;
  dockDivider = document.getElementById('dockDivider')!;
  zoomOutBtn = document.getElementById('zoomOutBtn')!;
  zoomInBtn = document.getElementById('zoomInBtn')!;
  zoomResetBtn = document.getElementById('zoomResetBtn')!;
  onTopChk = document.getElementById('onTopChk') as HTMLInputElement;
  autoplayChk = document.getElementById('autoplayChk') as HTMLInputElement;
  chooseBtn = document.getElementById('chooseBtn')!;
  chooseDropBtn = document.getElementById('chooseDropBtn')!;
  chooseDropBtn.setAttribute('aria-haspopup', 'true');
  chooseDropBtn.setAttribute('aria-expanded', 'false');
  recentMenu = document.getElementById('recentMenu')!;
  refreshBtn = document.getElementById('refreshBtn')!;
  resetProgBtn = document.getElementById('resetProgBtn')!;
  notesBox = document.getElementById('notesBox') as HTMLTextAreaElement;
  notesSaved = document.getElementById('notesSaved')!;

  // Create notes highlight backdrop for timestamp coloring
  notesBackdrop = document.createElement('div');
  notesBackdrop.className = 'notesBackdrop';
  notesBackdrop.setAttribute('aria-hidden', 'true');
  notesBox.parentElement!.insertBefore(notesBackdrop, notesBox);

  // Scroll sync between textarea and backdrop
  notesBox.addEventListener('scroll', () => {
    notesBackdrop.scrollTop = notesBox.scrollTop;
    notesBackdrop.scrollLeft = notesBox.scrollLeft;
  });

  // Forward hover/focus state to backdrop for visual effects
  notesBox.addEventListener('mouseenter', () => notesBackdrop.classList.add('hover'));
  notesBox.addEventListener('mouseleave', () => notesBackdrop.classList.remove('hover'));
  notesBox.addEventListener('focus', () => notesBackdrop.classList.add('focus'));
  notesBox.addEventListener('blur', () => notesBackdrop.classList.remove('focus'));

  // Show pointer cursor when hovering over timestamps
  notesBox.addEventListener('mousemove', (e) => {
    notesBox.style.pointerEvents = 'none';
    const el = document.elementFromPoint(e.clientX, e.clientY);
    notesBox.style.pointerEvents = '';
    notesBox.style.cursor = (el && el.classList.contains('tsLink')) ? 'pointer' : '';
  });
  nowTitle = document.getElementById('nowTitle')!;
  nowSub = document.getElementById('nowSub')!;
  overallBar = document.getElementById('overallBar')!;
  overallPct = document.getElementById('overallPct')!;
  toast = document.getElementById('toast')!;
  toastMsg = document.getElementById('toastMsg')!;
  infoGridEl = document.getElementById('infoGrid')!;
  player = document.getElementById('player') as HTMLVideoElement;

  // --- Window controls ---
  winMinBtn = document.getElementById('winMinBtn')!;
  winMaxBtn = document.getElementById('winMaxBtn')!;
  winCloseBtn = document.getElementById('winCloseBtn')!;

  const appWindow = getCurrentWebviewWindow();

  winMinBtn.onclick = () => appWindow.minimize();
  winMaxBtn.onclick = () => appWindow.toggleMaximize();
  winCloseBtn.onclick = () => appWindow.close();

  // Update maximize icon based on window state
  const updateMaxIcon = async () => {
    try {
      const maximized = await appWindow.isMaximized();
      const icon = winMaxBtn.querySelector('i')!;
      icon.className = maximized ? 'fa-solid fa-clone' : 'fa-solid fa-square';
    } catch (_) {}
  };

  appWindow.onResized(() => { updateMaxIcon(); });
  updateMaxIcon();

  infoFolder = document.getElementById('infoFolder')!;
  infoNext = document.getElementById('infoNext')!;
  infoStruct = document.getElementById('infoStruct')!;
  infoTitle = document.getElementById('infoTitle')!;
  infoRel = document.getElementById('infoRel')!;
  infoPos = document.getElementById('infoPos')!;
  infoFileBits = document.getElementById('infoFileBits')!;
  infoVidBits = document.getElementById('infoVidBits')!;
  infoAudBits = document.getElementById('infoAudBits')!;
  infoSubsBits = document.getElementById('infoSubsBits')!;
  infoFinished = document.getElementById('infoFinished')!;
  infoRemaining = document.getElementById('infoRemaining')!;
  infoEta = document.getElementById('infoEta')!;
  infoVolume = document.getElementById('infoVolume')!;
  infoSpeed = document.getElementById('infoSpeed')!;
  infoKnown = document.getElementById('infoKnown')!;
  infoTop = document.getElementById('infoTop')!;

  // --- Info panel scroll fades ---
  if (infoGridEl) {
    const updateInfoFades = () => {
      const atTop = infoGridEl.scrollTop < 5;
      const atBottom = infoGridEl.scrollTop + infoGridEl.clientHeight >= infoGridEl.scrollHeight - 5;
      infoGridEl.classList.toggle('at-top', atTop);
      infoGridEl.classList.toggle('at-bottom', atBottom);
    };
    infoGridEl.addEventListener('scroll', updateInfoFades);
    setTimeout(updateInfoFades, 100);
    setTimeout(updateInfoFades, 500);
  }

  // --- Divider drag ---
  divider.tabIndex = 0;
  divider.setAttribute('role', 'separator');
  divider.setAttribute('aria-orientation', 'vertical');
  divider.setAttribute('aria-label', 'Resize panels');
  divider.addEventListener('mousedown', (e) => {
    draggingDivider = true;
    document.body.style.userSelect = 'none';
    e.preventDefault();
  });
  divider.addEventListener('keydown', (e) => {
    if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
      e.preventDefault(); e.stopPropagation();
      const delta = e.key === 'ArrowLeft' ? -0.02 : 0.02;
      const current = prefs?.split_ratio || 0.62;
      prefs!.split_ratio = applySplit(current + delta);
      savePrefsPatch({ split_ratio: prefs!.split_ratio });
    }
  });
  dockDivider.tabIndex = 0;
  dockDivider.setAttribute('role', 'separator');
  dockDivider.setAttribute('aria-orientation', 'vertical');
  dockDivider.setAttribute('aria-label', 'Resize dock panes');
  dockDivider.addEventListener('mousedown', (e) => {
    draggingDockDivider = true;
    document.body.style.userSelect = 'none';
    e.preventDefault();
  });
  dockDivider.addEventListener('keydown', (e) => {
    if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
      e.preventDefault(); e.stopPropagation();
      const delta = e.key === 'ArrowLeft' ? -0.02 : 0.02;
      const current = prefs?.dock_ratio || 0.62;
      prefs!.dock_ratio = applyDockSplit(current + delta);
      savePrefsPatch({ dock_ratio: prefs!.dock_ratio });
    }
  });

  window.addEventListener('mouseup', async () => {
    if (draggingDivider) {
      draggingDivider = false;
      document.body.style.userSelect = '';
      if (prefs && typeof prefs.split_ratio === 'number') {
        await savePrefsPatch({ split_ratio: prefs.split_ratio });
      }
    }
    if (draggingDockDivider) {
      draggingDockDivider = false;
      document.body.style.userSelect = '';
      if (prefs && typeof prefs.dock_ratio === 'number') {
        await savePrefsPatch({ dock_ratio: prefs.dock_ratio });
      }
    }
  });

  window.addEventListener('mousemove', (e) => {
    if (draggingDivider) {
      const rect = contentGrid.getBoundingClientRect();
      const x = e.clientX - rect.left;
      if (!prefs) setPrefs({});
      prefs!.split_ratio = applySplit(x / rect.width);
      if (saveSplitTimer) clearTimeout(saveSplitTimer);
      saveSplitTimer = setTimeout(() => {
        if (prefs) savePrefsPatch({ split_ratio: prefs.split_ratio });
      }, 400);
    }
    if (draggingDockDivider) {
      const rect = dockGrid.getBoundingClientRect();
      const x = e.clientX - rect.left;
      if (!prefs) setPrefs({});
      prefs!.dock_ratio = applyDockSplit(x / rect.width);
      if (saveDockTimer) clearTimeout(saveDockTimer);
      saveDockTimer = setTimeout(() => {
        if (prefs) savePrefsPatch({ dock_ratio: prefs.dock_ratio });
      }, 400);
    }
  });

  // --- Zoom controls ---
  zoomOutBtn.onclick = () => {
    prefs!.ui_zoom = applyZoom(Number(prefs?.ui_zoom || 1.0) - 0.1);
    if (saveZoomTimer) clearTimeout(saveZoomTimer);
    saveZoomTimer = setTimeout(() => savePrefsPatch({ ui_zoom: prefs!.ui_zoom }), 120);
    if (recentOpen) positionRecentMenu();
  };
  zoomInBtn.onclick = () => {
    prefs!.ui_zoom = applyZoom(Number(prefs?.ui_zoom || 1.0) + 0.1);
    if (saveZoomTimer) clearTimeout(saveZoomTimer);
    saveZoomTimer = setTimeout(() => savePrefsPatch({ ui_zoom: prefs!.ui_zoom }), 120);
    if (recentOpen) positionRecentMenu();
  };
  zoomResetBtn.onclick = () => {
    prefs!.ui_zoom = applyZoom(1.0);
    if (saveZoomTimer) clearTimeout(saveZoomTimer);
    saveZoomTimer = setTimeout(() => savePrefsPatch({ ui_zoom: prefs!.ui_zoom }), 120);
    if (recentOpen) positionRecentMenu();
  };

  // --- On Top toggle ---
  onTopChk.addEventListener('change', async () => {
    const enabled = !!onTopChk.checked;
    try {
      await api.setAlwaysOnTop(enabled);
      prefs!.always_on_top = enabled;
      await savePrefsPatch({ always_on_top: enabled });
      notify(enabled ? 'On top enabled.' : 'On top disabled.');
    } catch (_) {
      onTopChk.checked = !!prefs?.always_on_top;
    }
  });

  // --- Autoplay toggle ---
  autoplayChk.addEventListener('change', async () => {
    if (!library) return;
    const enabled = !!autoplayChk.checked;
    try {
      const res = await api.setFolderAutoplay(enabled);
      if (res && res.ok) {
        (library as any).folder_autoplay = enabled;
        updateInfoPanel();
        notify(enabled ? 'Autoplay: ON' : 'Autoplay: OFF');
      }
    } catch (_) {}
  });

  // --- Reset progress (two-click confirmation) ---
  resetProgBtn.addEventListener('click', async () => {
    if (!library) return;
    if (!resetConfirming) {
      resetConfirming = true;
      resetProgBtn.classList.add('confirming');
      const icon = resetProgBtn.querySelector('i');
      if (icon) icon.className = 'fa-solid fa-exclamation-triangle';
      resetProgBtn.setAttribute('aria-label', 'Confirm reset progress');
      resetConfirmTimer = setTimeout(() => {
        cancelResetConfirm();
      }, 3000);
      return;
    }
    // Second click — actually reset
    cancelResetConfirm();
    try {
      const res = await api.resetWatchProgress();
      if (res && res.ok) {
        notify('Progress reset for this folder.');
        const info = await api.getLibrary();
        if (info && info.ok) {
          await cb.onLibraryLoaded?.(info, false);
        }
      }
    } catch (_) {}
  });
  resetProgBtn.addEventListener('blur', () => { if (resetConfirming) cancelResetConfirm(); });

  // --- Open folder / Recent menu ---
  chooseBtn.onclick = async () => {
    closeRecentMenu();
    const info = await api.selectFolder();
    if (!info || !info.ok) return;
    await cb.onLibraryLoaded?.(info, true);
    notify('Folder loaded.');
  };

  chooseDropBtn.onclick = async (e) => {
    e.preventDefault();
    e.stopPropagation();
    if (recentOpen) closeRecentMenu();
    else await openRecentMenu();
  };

  chooseDropBtn.addEventListener('keydown', (e) => {
    if (e.key === 'Escape' && recentOpen) {
      closeRecentMenu();
      chooseDropBtn.focus();
    }
  });

  window.addEventListener('resize', () => { if (recentOpen) positionRecentMenu(); });
  window.addEventListener('scroll', () => { if (recentOpen) positionRecentMenu(); }, true);
  window.addEventListener('click', () => { if (recentOpen) closeRecentMenu(); });
  recentMenu.addEventListener('click', (e) => e.stopPropagation());

  // --- Reload ---
  refreshBtn.onclick = async () => {
    const info = await api.getLibrary();
    if (!info || !info.ok) return;
    await cb.onLibraryLoaded?.(info, false);
    notify('Reloaded.');
  };

  // --- Notes ---
  notesBox.addEventListener('input', () => {
    updateNotesHighlight();
    if (!library) return;
    const it = currentItem();
    if (!it) return;
    if (noteSaveTimer) clearTimeout(noteSaveTimer);
    if (notesSavedTimer) clearTimeout(notesSavedTimer);
    if (notesSaved) notesSaved.classList.remove('show');
    noteSaveTimer = setTimeout(async () => {
      try {
        await api.setNote(it.fid, notesBox.value || '');
        if (notesSaved) {
          notesSaved.classList.add('show');
          notesSavedTimer = setTimeout(() => { notesSaved.classList.remove('show'); }, 2000);
        }
      } catch (_) {}
    }, 350);
  });

  // --- Timestamp insertion ---
  const insertTimestampBtn = document.getElementById('insertTimestamp');
  if (insertTimestampBtn) {
    insertTimestampBtn.onclick = () => {
      const t = player?.currentTime || 0;
      const m = Math.floor(t / 60);
      const s = Math.floor(t % 60);
      const stamp = `[${m}:${String(s).padStart(2, '0')}] `;
      const pos = notesBox.selectionStart ?? notesBox.value.length;
      const before = notesBox.value.substring(0, pos);
      const after = notesBox.value.substring(pos);
      notesBox.value = before + stamp + after;
      notesBox.focus();
      const newPos = pos + stamp.length;
      notesBox.setSelectionRange(newPos, newPos);
      // Trigger note save
      notesBox.dispatchEvent(new Event('input'));
    };
  }

  // --- Clickable timestamps in notes ---
  notesBox.addEventListener('click', () => {
    const pos = notesBox.selectionStart;
    if (pos === null || pos === undefined) return;
    // Only act on single click without selection
    if (notesBox.selectionStart !== notesBox.selectionEnd) return;
    const text = notesBox.value;
    // Match [H:MM:SS] or [M:SS] patterns
    const re = /\[(\d{1,3}):(\d{2})(?::(\d{2}))?\]/g;
    let match;
    while ((match = re.exec(text)) !== null) {
      const start = match.index;
      const end = start + match[0].length;
      if (pos >= start && pos <= end) {
        let totalSeconds: number;
        if (match[3] !== undefined) {
          // [H:MM:SS]
          totalSeconds = parseInt(match[1], 10) * 3600 + parseInt(match[2], 10) * 60 + parseInt(match[3], 10);
        } else {
          // [M:SS]
          totalSeconds = parseInt(match[1], 10) * 60 + parseInt(match[2], 10);
        }
        if (player && Number.isFinite(player.duration) && player.duration > 0) {
          player.currentTime = clamp(totalSeconds, 0, player.duration);
          cb.notify?.(`Jumped to ${match[0]}`);
        }
        break;
      }
    }
  });

  // --- Collapsible dock panes ---
  const notesHeader = document.getElementById('notesHeader');
  const infoHeader = document.getElementById('infoHeader');
  if (notesHeader) {
    notesHeader.style.cursor = 'pointer';
    notesHeader.setAttribute('aria-expanded', 'true');
    notesHeader.addEventListener('click', (e) => {
      if ((e.target as HTMLElement).closest('.timestampBtn')) return;
      toggleDockPane(notesHeader, 'notes_collapsed');
    });
    notesHeader.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' || e.key === ' ') {
        e.preventDefault();
        toggleDockPane(notesHeader, 'notes_collapsed');
      }
    });
    notesHeader.tabIndex = 0;
  }
  if (infoHeader) {
    infoHeader.style.cursor = 'pointer';
    infoHeader.setAttribute('aria-expanded', 'true');
    infoHeader.addEventListener('click', () => {
      toggleDockPane(infoHeader, 'info_collapsed');
    });
    infoHeader.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' || e.key === ' ') {
        e.preventDefault();
        toggleDockPane(infoHeader, 'info_collapsed');
      }
    });
    infoHeader.tabIndex = 0;
  }

  // Restore collapsed state from prefs
  if (prefs?.notes_collapsed) collapsePane(notesHeader!, true);
  if (prefs?.info_collapsed) collapsePane(infoHeader!, true);
}

// ---- Exported functions ----

export function applyZoom(z: number): number {
  const zoom = clamp(Number(z || 1.0), 0.75, 2.0);
  document.documentElement.style.setProperty('--zoom', String(zoom));
  if (zoomResetBtn) zoomResetBtn.textContent = `${Math.round(zoom * 100)}%`;
  document.body.getBoundingClientRect(); // force reflow
  return zoom;
}

export function applySplit(ratio: number): number {
  const r = clamp(Number(ratio || 0.62), 0.35, 0.80);
  contentGrid.style.gridTemplateColumns = `calc(${(r * 100).toFixed(2)}% - 7px) 14px calc(${(100 - r * 100).toFixed(2)}% - 7px)`;
  return r;
}

export function applyDockSplit(ratio: number): number {
  const r = clamp(Number(ratio || 0.62), 0.35, 0.80);
  dockGrid.style.gridTemplateColumns = `calc(${(r * 100).toFixed(2)}% - 7px) 14px calc(${(100 - r * 100).toFixed(2)}% - 7px)`;
  return r;
}

export function notify(msg: string): void {
  if (!toastMsg || !toast) return;
  toastMsg.textContent = msg;
  toast.classList.add('show');
  if (toastTimer) clearTimeout(toastTimer);
  toastTimer = setTimeout(() => { toast.classList.remove('show'); }, 2600);
}

export function updateNowHeader(it: VideoItem | null): void {
  nowTitle.textContent = it ? (it.title || it.name) : 'No video loaded';
  nowSub.textContent = it ? it.relpath : '-';
}

export function updateOverall(): void {
  if (!library) {
    overallBar.style.width = '0%'; overallPct.textContent = '-';
    const pb = overallBar.parentElement;
    if (pb) pb.setAttribute('aria-valuenow', '0');
    return;
  }
  if (library.overall_progress === null || library.overall_progress === undefined) {
    overallBar.style.width = '0%'; overallPct.textContent = '-';
    const pb = overallBar.parentElement;
    if (pb) pb.setAttribute('aria-valuenow', '0');
    return;
  }
  const p = clamp(library.overall_progress, 0, 100);
  overallBar.style.width = `${p.toFixed(1)}%`;
  overallPct.textContent = `${p.toFixed(1)}%`;
  const progressBarEl = overallBar.parentElement;
  if (progressBarEl) progressBarEl.setAttribute('aria-valuenow', String(Math.round(p)));
}

export function updateInfoPanel(): void {
  const it = currentItem();
  infoFolder.textContent = library?.folder || '-';
  infoNext.textContent = library?.next_up ? library.next_up.title : '-';
  infoStruct.textContent = library ? (library.has_subdirs ? 'Subfolders detected' : 'Flat folder') : '-';
  infoTitle.textContent = it?.title || '-';
  infoRel.textContent = it?.relpath || '-';

  const t = player?.currentTime || 0;
  const d = player && Number.isFinite(player.duration) ? player.duration : 0;
  infoPos.textContent = d > 0 ? `${fmtTime(t)} / ${fmtTime(d)}` : fmtTime(t);

  if (library) {
    infoFinished.textContent = `${library.finished_count ?? 0}`;
    infoRemaining.textContent = `${library.remaining_count ?? 0}`;
    infoEta.textContent = (library.remaining_seconds_known != null) ? fmtTime(library.remaining_seconds_known) : '-';
    infoKnown.textContent = `${library.durations_known || 0}/${library.count || 0}`;
    infoTop.textContent = (library.top_folders || []).map((f: any) => `${f.name}: ${f.finished}/${f.total}`).join('  \u2022  ') || '-';
    infoVolume.textContent = `${Math.round(clamp(Number(library.folder_volume ?? 1), 0, 1) * 100)}%`;
    infoSpeed.textContent = `${Number(library.folder_rate ?? 1).toFixed(2)}x`;
  } else {
    infoFinished.textContent = '-';
    infoRemaining.textContent = '-';
    infoEta.textContent = '-';
    infoKnown.textContent = '-';
    infoTop.textContent = '-';
    infoVolume.textContent = '-';
    infoSpeed.textContent = '-';
  }
}

export async function refreshCurrentVideoMeta(): Promise<void> {
  const it = currentItem();
  if (!it) {
    infoFileBits.textContent = '-'; infoVidBits.textContent = '-';
    infoAudBits.textContent = '-'; infoSubsBits.textContent = '-';
    return;
  }
  try {
    const res = await api.getCurrentVideoMeta();
    if (!res || !res.ok) {
      infoFileBits.textContent = '-'; infoVidBits.textContent = '-';
      infoAudBits.textContent = '-'; infoSubsBits.textContent = '-';
      return;
    }
    const b = res.basic || ({} as any);
    const p = res.probe || null;
    const ffFound = !!res.ffprobe_found;

    const bits: string[] = [];
    if (b.ext) bits.push(String(b.ext).toUpperCase());
    if (b.size) bits.push(fmtBytes(b.size));
    if (b.mtime) bits.push(`modified ${fmtDate(b.mtime)}`);
    if (b.folder) bits.push(`folder ${b.folder}`);
    infoFileBits.textContent = bits.join('  \u2022  ') || '-';

    if (p) {
      const v: string[] = [];
      if (p.v_codec) v.push(String(p.v_codec).toUpperCase());
      if (p.width && p.height) v.push(`${p.width}\u00d7${p.height}`);
      if (p.fps) v.push(`${Number(p.fps).toFixed(2)} fps`);
      if (p.pix_fmt) v.push(p.pix_fmt);
      const vb = fmtBitrate(p.v_bitrate || 0); if (vb) v.push(vb);
      infoVidBits.textContent = v.join('  \u2022  ') || '-';

      const a: string[] = [];
      if (p.a_codec) a.push(String(p.a_codec).toUpperCase());
      if (p.channels) a.push(`${p.channels} ch`);
      if (p.sample_rate) a.push(`${(Number(p.sample_rate) / 1000).toFixed(1)} kHz`);
      const ab = fmtBitrate(p.a_bitrate || 0); if (ab) a.push(ab);
      infoAudBits.textContent = a.join('  \u2022  ') || '-';

      const subs = p.subtitle_tracks || [];
      if (subs.length > 0) {
        const subInfo = subs.map(s => {
          const lang = s.language?.toUpperCase() || '';
          const title = s.title || '';
          return title || lang || s.codec || 'Track';
        }).join(', ');
        infoSubsBits.textContent = `${subs.length} embedded (${subInfo})`;
      } else if (it.has_sub) {
        infoSubsBits.textContent = 'External file loaded';
      } else {
        infoSubsBits.textContent = 'None';
      }
    } else {
      infoVidBits.textContent = ffFound
        ? '(ffprobe available, metadata not read for this file)'
        : '(ffprobe not found)';
      infoAudBits.textContent = '-';
      infoSubsBits.textContent = it.has_sub ? 'External file loaded' : '-';
    }
  } catch (_) {
    infoFileBits.textContent = '-'; infoVidBits.textContent = '-';
    infoAudBits.textContent = '-'; infoSubsBits.textContent = '-';
  }
}

export async function loadNoteForCurrent(): Promise<void> {
  const it = currentItem();
  if (!it) { notesBox.value = ''; updateNotesHighlight(); return; }
  try {
    const res = await api.getNote(it.fid);
    notesBox.value = (res && res.ok) ? (res.note || '') : '';
  } catch (_) { notesBox.value = ''; }
  updateNotesHighlight();
}

export function setOnTopChecked(v: boolean): void { onTopChk.checked = v; }
export function setAutoplayChecked(v: boolean): void { autoplayChk.checked = v; }

// ---- Recent menu ----

function ensureDropdownPortal(): void {
  try {
    if (recentMenu && recentMenu.parentElement !== document.body) {
      document.body.appendChild(recentMenu);
    }
    recentMenu.classList.add('dropdownPortal');
  } catch (_) {}
}

function positionRecentMenu(): void {
  const r = chooseDropBtn.getBoundingClientRect();
  const zoom = clamp(Number(prefs?.ui_zoom || 1), 0.75, 2.0);
  const baseW = 460, baseH = 360;
  const effW = baseW * zoom, effH = baseH * zoom;
  const left = clamp(r.right - effW, 10, window.innerWidth - effW - 10);
  const top = clamp(r.bottom + 8, 10, window.innerHeight - effH - 10);
  recentMenu.style.left = `${left}px`;
  recentMenu.style.top = `${top}px`;
  recentMenu.style.width = `${baseW}px`;
  recentMenu.style.maxHeight = `${baseH}px`;
}

export function closeRecentMenu(): void {
  recentOpen = false;
  recentMenu.style.display = 'none';
  chooseDropBtn?.setAttribute('aria-expanded', 'false');
}

export async function openRecentMenu(): Promise<void> {
  ensureDropdownPortal();
  try {
    const res = await api.getRecents();
    recentMenu.innerHTML = '';
    if (!res || !res.ok || !res.items || res.items.length === 0) {
      const div = document.createElement('div');
      div.className = 'dropEmpty';
      div.textContent = 'No recent folders yet.';
      recentMenu.appendChild(div);
    } else {
      for (const it of res.items) {
        const row = document.createElement('div');
        row.className = 'dropItem';
        row.setAttribute('role', 'menuitem');
        row.tabIndex = -1;
        row.dataset.tooltip = it.name;
        row.dataset.tooltipDesc = it.path;

        const icon = document.createElement('div');
        icon.className = 'dropIcon';
        icon.innerHTML = '<i class="fa-solid fa-folder"></i>';

        const name = document.createElement('div');
        name.className = 'dropName';
        name.textContent = it.name;

        const removeBtn = document.createElement('button');
        removeBtn.className = 'dropRemove';
        removeBtn.setAttribute('aria-label', `Remove ${it.name}`);
        removeBtn.innerHTML = '<i class="fa-solid fa-xmark" aria-hidden="true"></i>';
        removeBtn.onclick = async (e) => {
          e.stopPropagation();
          try {
            await api.removeRecent(it.path);
            row.remove();
            if (recentMenu.querySelectorAll('.dropItem').length === 0) {
              const div = document.createElement('div');
              div.className = 'dropEmpty';
              div.textContent = 'No recent folders yet.';
              recentMenu.innerHTML = '';
              recentMenu.appendChild(div);
            }
          } catch (_) {}
        };

        row.appendChild(icon); row.appendChild(name); row.appendChild(removeBtn);
        row.onclick = async () => {
          closeRecentMenu();
          const info = await api.openFolderPath(it.path);
          if (!info || !info.ok) { notify('Folder not available.'); return; }
          await cb.onLibraryLoaded?.(info, true);
        };
        row.addEventListener('keydown', (e) => {
          if (e.key === 'ArrowDown') {
            e.preventDefault(); e.stopPropagation();
            const next = row.nextElementSibling as HTMLElement | null;
            if (next && next.classList.contains('dropItem')) next.focus();
          } else if (e.key === 'ArrowUp') {
            e.preventDefault(); e.stopPropagation();
            const prev = row.previousElementSibling as HTMLElement | null;
            if (prev && prev.classList.contains('dropItem')) prev.focus();
          } else if (e.key === 'Escape' || e.key === 'Tab') {
            e.preventDefault(); e.stopPropagation();
            closeRecentMenu();
            chooseDropBtn.focus();
          } else if (e.key === 'Enter' || e.key === ' ') {
            e.preventDefault(); e.stopPropagation();
            row.click();
          }
        });
        recentMenu.appendChild(row);
      }
    }
    positionRecentMenu();
    recentMenu.style.display = 'block';
    recentOpen = true;
    chooseDropBtn.setAttribute('aria-expanded', 'true');
    const first = recentMenu.querySelector('.dropItem') as HTMLElement | null;
    if (first) first.focus();
  } catch (_) { closeRecentMenu(); }
}

// ---- Private helpers ----

async function savePrefsPatch(patch: Record<string, unknown>): Promise<void> {
  await api.setPrefs(patch);
}

function cancelResetConfirm(): void {
  resetConfirming = false;
  if (resetConfirmTimer) { clearTimeout(resetConfirmTimer); resetConfirmTimer = null; }
  resetProgBtn.classList.remove('confirming');
  const icon = resetProgBtn.querySelector('i');
  if (icon) icon.className = 'fa-solid fa-clock-rotate-left';
  resetProgBtn.setAttribute('aria-label', 'Reset progress');
}

function toggleDockPane(header: HTMLElement, prefKey: string): void {
  const pane = header.closest('.dockPane');
  if (!pane) return;
  const isCollapsed = pane.classList.toggle('collapsed');
  header.setAttribute('aria-expanded', String(!isCollapsed));
  const chevron = header.querySelector('.dockChevron') as HTMLElement | null;
  if (chevron) chevron.style.transform = isCollapsed ? 'rotate(-90deg)' : '';
  savePrefsPatch({ [prefKey]: isCollapsed });
}

function updateNotesHighlight(): void {
  if (!notesBackdrop) return;
  const text = notesBox.value;
  const escaped = text
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;');
  const highlighted = escaped.replace(
    /\[(\d{1,3}:\d{2}(?::\d{2})?)\]/g,
    '<span class="tsLink">[$1]</span>'
  );
  // Trailing newline matches textarea's extra line space
  notesBackdrop.innerHTML = highlighted + '\n';
}

function collapsePane(header: HTMLElement, collapsed: boolean): void {
  const pane = header.closest('.dockPane');
  if (!pane) return;
  if (collapsed) {
    pane.classList.add('collapsed');
    header.setAttribute('aria-expanded', 'false');
    const chevron = header.querySelector('.dockChevron') as HTMLElement | null;
    if (chevron) chevron.style.transform = 'rotate(-90deg)';
  }
}
