/**
 * Playlist rendering — list items, tree SVG connectors, custom scrollbar,
 * and drag-and-drop reorder.
 */
import { api } from './api';
import type { VideoItem } from './types';
import {
  library, currentIndex, setCurrentIndex,
  clamp, fmtTime, cb, currentItem, computeResumeTime,
  setLibrary,
} from './store';

// ---- DOM refs ----
let listEl: HTMLElement;
let emptyHint: HTMLElement;
let listScrollbar: HTMLElement;
let listScrollbarThumb: HTMLElement;
let plistSearch: HTMLInputElement;
let plistSearchClear: HTMLElement;
let plistStats: HTMLElement;
let scrollToCurrentBtn: HTMLElement;

// ---- Scroll-to-current observer ----
let activeRowObserver: IntersectionObserver | null = null;

// ---- Drag state ----
let dragFromIndex: number | null = null;
let dropTargetIndex: number | null = null;
let dropAfter = false;

/** True while a drag-reorder is in progress. Used to guard against renderList during drag. */
export function isDragging(): boolean { return dragFromIndex !== null; }

// ---- Scrollbar state ----
let scrollbarHideTimer: ReturnType<typeof setTimeout> | null = null;
let scrollbarDragging = false;
let scrollbarDragStartY = 0;
let scrollbarDragStartScrollTop = 0;

// ---- Scroll fades ----
let updateListFades: () => void = () => {};

export function initPlaylist(): void {
  listEl = document.getElementById('list')!;
  listEl.setAttribute('role', 'listbox');
  listEl.setAttribute('aria-label', 'Playlist');
  emptyHint = document.getElementById('emptyHint')!;
  listScrollbar = document.getElementById('listScrollbar')!;
  listScrollbarThumb = document.getElementById('listScrollbarThumb')!;

  // Scrollbar drag handlers
  if (listScrollbarThumb) {
    listScrollbarThumb.style.pointerEvents = 'auto';
    listScrollbar.style.pointerEvents = 'auto';

    const startDrag = (e: MouseEvent | TouchEvent) => {
      e.preventDefault();
      scrollbarDragging = true;
      scrollbarDragStartY = 'touches' in e ? e.touches[0].clientY : e.clientY;
      scrollbarDragStartScrollTop = listEl.scrollTop;
      listScrollbar.classList.add('active');
      document.body.style.userSelect = 'none';
    };

    const doDrag = (e: MouseEvent | TouchEvent) => {
      if (!scrollbarDragging) return;
      const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
      const deltaY = clientY - scrollbarDragStartY;
      const trackHeight = listEl.clientHeight - 24;
      const thumbHeight = Math.max(24, listEl.clientHeight * (listEl.clientHeight / listEl.scrollHeight));
      const scrollableTrack = trackHeight - thumbHeight;
      const maxScroll = listEl.scrollHeight - listEl.clientHeight;
      if (scrollableTrack > 0) {
        const scrollDelta = (deltaY / scrollableTrack) * maxScroll;
        listEl.scrollTop = scrollbarDragStartScrollTop + scrollDelta;
      }
    };

    const endDrag = () => {
      if (scrollbarDragging) {
        scrollbarDragging = false;
        document.body.style.userSelect = '';
        if (scrollbarHideTimer) clearTimeout(scrollbarHideTimer);
        scrollbarHideTimer = setTimeout(() => { listScrollbar.classList.remove('active'); }, 1200);
      }
    };

    listScrollbarThumb.addEventListener('mousedown', startDrag as any);
    listScrollbarThumb.addEventListener('touchstart', startDrag as any);
    window.addEventListener('mousemove', doDrag as any);
    window.addEventListener('touchmove', doDrag as any);
    window.addEventListener('mouseup', endDrag);
    window.addEventListener('touchend', endDrag);
  }

  // Allow internal DnD on the list container
  if (listEl) {
    listEl.addEventListener('dragenter', (e) => { e.preventDefault(); });
    listEl.addEventListener('dragover', (e) => { e.preventDefault(); });
    listEl.addEventListener('drop', (e) => { e.preventDefault(); });
  }

  if (listEl) {
    updateListFades = () => {
      const atTop = listEl.scrollTop < 5;
      const atBottom = listEl.scrollTop + listEl.clientHeight >= listEl.scrollHeight - 5;
      listEl.classList.toggle('at-top', atTop);
      listEl.classList.toggle('at-bottom', atBottom);
      updateScrollbar();
      if (listScrollbar && !scrollbarDragging) {
        listScrollbar.classList.add('active');
        if (scrollbarHideTimer) clearTimeout(scrollbarHideTimer);
        scrollbarHideTimer = setTimeout(() => { listScrollbar.classList.remove('active'); }, 1200);
      }
    };
    listEl.addEventListener('scroll', updateListFades);
    setTimeout(updateListFades, 100);
    setTimeout(updateListFades, 500);
  }

  // Live region for reorder announcements
  const liveRegion = document.createElement('div');
  liveRegion.id = 'playlistLive';
  liveRegion.setAttribute('aria-live', 'polite');
  liveRegion.setAttribute('aria-atomic', 'true');
  liveRegion.style.cssText = 'position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);';
  document.body.appendChild(liveRegion);

  // --- Playlist search ---
  plistSearch = document.getElementById('plistSearch') as HTMLInputElement;
  plistSearchClear = document.getElementById('plistSearchClear')!;
  plistStats = document.getElementById('plistStats')!;

  if (plistSearch) {
    plistSearch.addEventListener('input', () => {
      plistSearchClear.style.display = plistSearch.value ? 'flex' : 'none';
      renderList();
    });
    plistSearch.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') {
        plistSearch.value = '';
        plistSearchClear.style.display = 'none';
        renderList();
      }
    });
  }
  if (plistSearchClear) {
    plistSearchClear.onclick = () => {
      plistSearch.value = '';
      plistSearchClear.style.display = 'none';
      plistSearch.focus();
      renderList();
    };
  }

  // --- Scroll-to-current button ---
  scrollToCurrentBtn = document.getElementById('scrollToCurrent')!;
  if (scrollToCurrentBtn) {
    scrollToCurrentBtn.onclick = () => {
      const activeRow = listEl.querySelector('.row.active') as HTMLElement | null;
      if (activeRow) activeRow.scrollIntoView({ block: 'center', behavior: 'smooth' });
    };
  }
}

export function updateScrollbar(): void {
  if (!listEl || !listScrollbarThumb) return;
  const scrollHeight = listEl.scrollHeight;
  const clientHeight = listEl.clientHeight;
  if (scrollHeight <= clientHeight) {
    listScrollbar.style.display = 'none';
    return;
  }
  listScrollbar.style.display = 'block';
  const scrollRatio = clientHeight / scrollHeight;
  const thumbHeight = Math.max(24, clientHeight * scrollRatio);
  const maxScroll = scrollHeight - clientHeight;
  const scrollTop = listEl.scrollTop;
  const trackHeight = clientHeight - 24;
  const thumbTop = maxScroll > 0 ? (scrollTop / maxScroll) * (trackHeight - thumbHeight) : 0;
  listScrollbarThumb.style.height = thumbHeight + 'px';
  listScrollbarThumb.style.top = thumbTop + 'px';
}

function clearDropIndicators(): void {
  listEl.querySelectorAll('.row').forEach(r => r.classList.remove('drop-before', 'drop-after'));
}

async function reorderPlaylistByGap(fromIdx: number, targetIdx: number, after: boolean): Promise<void> {
  if (!library || !library.items) return;
  const base = library.items.slice().sort((a, b) => a.index - b.index).map(x => x.fid);
  if (fromIdx < 0 || fromIdx >= base.length) return;
  if (targetIdx < 0 || targetIdx >= base.length) return;

  const moving = base[fromIdx];
  base.splice(fromIdx, 1);

  let insertAt = targetIdx;
  if (fromIdx < targetIdx) insertAt -= 1;
  if (after) insertAt += 1;
  insertAt = clamp(insertAt, 0, base.length);
  base.splice(insertAt, 0, moving);

  try {
    const res = await api.setOrder(base);
    if (res && res.ok) {
      const info = await api.getLibrary();
      if (info && info.ok) {
        setLibrary(info);
        setCurrentIndex(info.current_index || 0);
        renderList();
        cb.updateOverall?.();
        cb.updateInfoPanel?.();
        await cb.refreshCurrentVideoMeta?.();
      }
    }
  } catch (err) { console.error('reorderPlaylistByGap failed:', err); }
}

export function renderTreeSvg(it: VideoItem): SVGSVGElement {
  const depth = Number(it.depth || 0);
  const pipes = Array.isArray(it.pipes) ? it.pipes : [];
  const isLast = !!it.is_last;
  const hasPrev = !!it.has_prev_in_parent;

  const unit = 14;
  const pad = 8;
  const height = 28;
  const mid = Math.round(height / 2);
  const extend = 20;
  const width = pad + Math.max(1, depth) * unit + 18;

  const ns = 'http://www.w3.org/2000/svg';
  const svg = document.createElementNS(ns, 'svg');
  svg.setAttribute('class', 'treeSvg');
  svg.setAttribute('width', String(width));
  svg.setAttribute('height', String(height));
  svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
  svg.style.overflow = 'visible';

  const xCol = (j: number) => pad + j * unit + 0.5;

  for (let j = 0; j < depth - 1; j++) {
    if (pipes[j]) {
      const ln = document.createElementNS(ns, 'line');
      ln.setAttribute('x1', String(xCol(j)));
      ln.setAttribute('y1', String(-extend));
      ln.setAttribute('x2', String(xCol(j)));
      ln.setAttribute('y2', String(height + extend));
      svg.appendChild(ln);
    }
  }

  if (depth <= 0) {
    const c = document.createElementNS(ns, 'circle');
    c.setAttribute('cx', String(pad + 3));
    c.setAttribute('cy', String(mid));
    c.setAttribute('r', '3.2');
    c.setAttribute('opacity', '0.40');
    svg.appendChild(c);
    return svg;
  }

  const parentCol = depth - 1;
  const px = xCol(parentCol);

  if (hasPrev || !isLast) {
    const vln = document.createElementNS(ns, 'line');
    vln.setAttribute('x1', String(px));
    vln.setAttribute('y1', String(hasPrev ? -extend : mid));
    vln.setAttribute('x2', String(px));
    vln.setAttribute('y2', String(isLast ? mid : String(height + extend)));
    svg.appendChild(vln);
  }

  const hx1 = px;
  const hx2 = px + unit;
  const h = document.createElementNS(ns, 'line');
  h.setAttribute('x1', String(hx1));
  h.setAttribute('y1', String(mid));
  h.setAttribute('x2', String(hx2));
  h.setAttribute('y2', String(mid));
  svg.appendChild(h);

  const node = document.createElementNS(ns, 'circle');
  node.setAttribute('cx', String(hx2));
  node.setAttribute('cy', String(mid));
  node.setAttribute('r', '3.4');
  svg.appendChild(node);

  return svg;
}

export function renderList(): void {
  listEl.innerHTML = '';
  if (!library || !library.items || library.items.length === 0) {
    emptyHint.style.display = 'block';
    if (plistStats) plistStats.textContent = '';
    return;
  }
  emptyHint.style.display = 'none';

  const tree = !!library.has_subdirs;
  const padN = String(library.items.length).length;

  // Search filter
  const filterText = plistSearch?.value?.trim().toLowerCase() || '';
  const allItems = library.items;
  const filteredItems: { it: typeof allItems[0]; displayIndex: number }[] = [];
  for (let i = 0; i < allItems.length; i++) {
    const it = allItems[i];
    if (filterText) {
      const haystack = `${it.title || ''} ${it.name || ''} ${it.relpath || ''}`.toLowerCase();
      if (!haystack.includes(filterText)) continue;
    }
    filteredItems.push({ it, displayIndex: i });
  }

  // Stats
  const totalCount = allItems.length;
  const doneCount = allItems.filter(it => it.finished).length;
  if (plistStats) {
    if (filterText) {
      plistStats.textContent = `${filteredItems.length} of ${totalCount}`;
    } else {
      plistStats.textContent = `${totalCount} videos  \u00b7  ${doneCount} done`;
    }
  }

  // Disconnect old observer
  if (activeRowObserver) { activeRowObserver.disconnect(); activeRowObserver = null; }

  for (const { it, displayIndex } of filteredItems) {

    const row = document.createElement('div');
    row.className = 'row' + (it.index === currentIndex ? ' active' : '');
    row.draggable = true;
    row.dataset.index = String(it.index);
    row.setAttribute('role', 'option');
    row.setAttribute('aria-selected', it.index === currentIndex ? 'true' : 'false');
    row.tabIndex = 0;

    // Computed aria-label
    const durStr = it.duration ? `${fmtTime(it.watched || 0)} / ${fmtTime(it.duration)}` : `${fmtTime(it.watched || 0)} watched`;
    const statusStr = it.index === currentIndex ? 'Now playing' : it.finished ? 'Done' : '';
    row.setAttribute('aria-label', `${String(displayIndex + 1).padStart(padN, '0')}. ${it.title || it.name} - ${durStr}${statusStr ? ' - ' + statusStr : ''}`);

    row.onclick = () => {
      if (dragFromIndex !== null) return;
      cb.loadIndex?.(it.index, computeResumeTime(it), true);
    };

    row.addEventListener('keydown', (e) => {
      const key = e.key;
      if (key === 'Enter' || key === ' ') {
        e.preventDefault(); e.stopPropagation();
        cb.loadIndex?.(it.index, computeResumeTime(it), true);
      } else if (key === 'ArrowDown' && !e.altKey) {
        e.preventDefault(); e.stopPropagation();
        const next = row.nextElementSibling as HTMLElement | null;
        if (next && next.classList.contains('row')) next.focus();
      } else if (key === 'ArrowUp' && !e.altKey) {
        e.preventDefault(); e.stopPropagation();
        const prev = row.previousElementSibling as HTMLElement | null;
        if (prev && prev.classList.contains('row')) prev.focus();
      } else if (e.altKey && key === 'ArrowDown' && displayIndex < library!.items!.length - 1) {
        e.preventDefault(); e.stopPropagation();
        reorderPlaylistByGap(it.index, library!.items![displayIndex + 1].index, true).then(() => {
          setTimeout(() => {
            const moved = listEl.querySelector(`[data-index="${it.index}"]`) as HTMLElement | null;
            if (moved) moved.focus();
            const lr = document.getElementById('playlistLive');
            if (lr) lr.textContent = `Moved to position ${displayIndex + 2}`;
          }, 100);
        });
      } else if (e.altKey && key === 'ArrowUp' && displayIndex > 0) {
        e.preventDefault(); e.stopPropagation();
        reorderPlaylistByGap(it.index, library!.items![displayIndex - 1].index, false).then(() => {
          setTimeout(() => {
            const moved = listEl.querySelector(`[data-index="${it.index}"]`) as HTMLElement | null;
            if (moved) moved.focus();
            const lr = document.getElementById('playlistLive');
            if (lr) lr.textContent = `Moved to position ${displayIndex}`;
          }, 100);
        });
      }
    });

    row.addEventListener('dragstart', (e) => {
      dragFromIndex = Number(row.dataset.index);
      row.classList.add('dragging');
      e.dataTransfer!.effectAllowed = 'move';
      try { e.dataTransfer!.setData('text/plain', String(dragFromIndex)); } catch (_) {}
    });

    row.addEventListener('dragend', async () => {
      row.classList.remove('dragging');
      if (dragFromIndex !== null && dropTargetIndex !== null) {
        await reorderPlaylistByGap(dragFromIndex, dropTargetIndex, dropAfter);
      }
      dragFromIndex = null; dropTargetIndex = null; dropAfter = false;
      clearDropIndicators();
    });

    row.addEventListener('dragenter', (e) => {
      e.preventDefault();
      e.stopPropagation();
    });

    row.addEventListener('dragover', (e) => {
      e.preventDefault();
      e.stopPropagation();
      e.dataTransfer!.dropEffect = 'move';
      const rect = row.getBoundingClientRect();
      const y = e.clientY - rect.top;
      const after = y > rect.height / 2;
      dropTargetIndex = Number(row.dataset.index);
      dropAfter = after;
      clearDropIndicators();
      row.classList.add(after ? 'drop-after' : 'drop-before');
    });

    row.addEventListener('drop', (e) => { e.preventDefault(); e.stopPropagation(); });

    const left = document.createElement('div');
    left.className = 'left';

    const num = document.createElement('div');
    num.className = 'numBadge';
    num.textContent = String(displayIndex + 1).padStart(padN, '0');
    left.appendChild(num);

    if (tree) left.appendChild(renderTreeSvg(it));

    const textWrap = document.createElement('div');
    textWrap.className = 'textWrap';

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

    const small = document.createElement('div');
    small.className = 'small';
    const d = it.duration, w = it.watched || 0;
    const note = it.note_len ? '  \u2022  note' : '';
    const sub = it.has_sub ? '  \u2022  subs' : '';
    small.textContent = (d ? `${fmtTime(w)} / ${fmtTime(d)}` : `${fmtTime(w)} watched`) + note + sub + `  -  ${it.relpath}`;

    textWrap.appendChild(name);
    textWrap.appendChild(small);
    left.appendChild(textWrap);

    const tag = document.createElement('div');
    tag.className = 'tag';
    if (it.index === currentIndex) { tag.classList.add('now'); tag.textContent = 'Now'; }
    else if (it.finished) { tag.classList.add('done'); tag.textContent = 'Done'; }
    else { tag.classList.add('hidden'); tag.textContent = ''; }

    // Move buttons for keyboard reorder alternative
    const moveWrap = document.createElement('div');
    moveWrap.className = 'moveWrap';

    if (displayIndex > 0) {
      const moveUp = document.createElement('button');
      moveUp.className = 'moveBtn';
      moveUp.setAttribute('aria-label', 'Move up');
      moveUp.innerHTML = '<i class="fa-solid fa-chevron-up" aria-hidden="true"></i>';
      moveUp.tabIndex = -1;
      moveUp.addEventListener('click', (e) => {
        e.stopPropagation();
        reorderPlaylistByGap(it.index, library!.items![displayIndex - 1].index, false).then(() => {
          const lr = document.getElementById('playlistLive');
          if (lr) lr.textContent = `Moved to position ${displayIndex}`;
        });
      });
      moveWrap.appendChild(moveUp);
    }

    if (displayIndex < library!.items!.length - 1) {
      const moveDown = document.createElement('button');
      moveDown.className = 'moveBtn';
      moveDown.setAttribute('aria-label', 'Move down');
      moveDown.innerHTML = '<i class="fa-solid fa-chevron-down" aria-hidden="true"></i>';
      moveDown.tabIndex = -1;
      moveDown.addEventListener('click', (e) => {
        e.stopPropagation();
        reorderPlaylistByGap(it.index, library!.items![displayIndex + 1].index, true).then(() => {
          const lr = document.getElementById('playlistLive');
          if (lr) lr.textContent = `Moved to position ${displayIndex + 2}`;
        });
      });
      moveWrap.appendChild(moveDown);
    }

    // Mini progress bar (Enhancement 14)
    if (it.duration && it.duration > 0) {
      const rowProgress = document.createElement('div');
      rowProgress.className = 'rowProgress';
      rowProgress.setAttribute('aria-hidden', 'true');
      const pct = clamp(((it.watched || 0) / it.duration) * 100, 0, 100);
      rowProgress.style.width = pct + '%';
      if (it.finished) rowProgress.classList.add('done');
      row.appendChild(rowProgress);
    }

    row.appendChild(left);
    row.appendChild(moveWrap);
    row.appendChild(tag);
    listEl.appendChild(row);
  }

  // Scroll-to-current observer
  const activeRow = listEl.querySelector('.row.active') as HTMLElement | null;
  if (activeRow && scrollToCurrentBtn) {
    activeRowObserver = new IntersectionObserver((entries) => {
      const visible = entries[0]?.isIntersecting;
      scrollToCurrentBtn.style.display = visible ? 'none' : 'inline-flex';
    }, { root: listEl, threshold: 0.5 });
    activeRowObserver.observe(activeRow);
  } else if (scrollToCurrentBtn) {
    scrollToCurrentBtn.style.display = 'none';
  }

  setTimeout(updateListFades, 50);
}
