// The upcoming-photos grid: rendering plus drag-to-reorder. Ported
// intact from the original single-page UI -- the Pointer Events state
// machine below (hold-to-arm on touch so page scrolling still works) is
// battle-tested; treat changes with suspicion.
//
// Expects window.FRAME_API = '/api/frames/<id>' set by the page, and a
// loadQueue() global (frame_photos.js) to refetch authoritative state.

let upcomingItems = [];

// Pointer Events (not the native HTML5 Drag-and-Drop API) so the same
// code drives mouse, touch, and pen -- native drag-and-drop is
// mouse-only by spec and never fires at all on phones/tablets.
//
// On touch specifically, a card only "arms" for dragging after a
// brief hold (DRAG_HOLD_MS) with the finger roughly stationary --
// .photo-card's touch-action stays "pan-y" (native scroll allowed)
// the whole time up to that point, so a normal touch-and-swipe to
// scroll the page still works even though it starts on a card. Once
// armed, touch-action switches to "none" for the rest of that touch
// so drag tracking gets every pointermove reliably. Mouse skips the
// hold entirely (no scroll-vs-drag ambiguity with a mouse).
let dragState = null; // { pointerId, fromIndex, toIndex, moved, armed, holdTimer }
const DRAG_START_THRESHOLD_PX = 6; // ignore tiny jitter once armed, before committing to a drag
const DRAG_HOLD_MS = 250;
const HOLD_CANCEL_THRESHOLD_PX = 10; // movement before the hold timer fires -> treat as a scroll, not a drag

function vibrate(ms) {
  // Chrome/Android only -- iOS Safari has no Vibration API. Wrapped so
  // it's a harmless no-op everywhere else rather than needing a
  // feature check at every call site.
  try { navigator.vibrate?.(ms); } catch (e) { /* ignore */ }
}

function clearDragOverStyling() {
  document.querySelectorAll('.photo-card.drag-over').forEach((el) => el.classList.remove('drag-over'));
}

function endDrag(card) {
  if (dragState && dragState.holdTimer) {
    clearTimeout(dragState.holdTimer);
  }
  card.style.touchAction = '';
  card.style.transform = '';
  card.classList.remove('dragging', 'drag-armed');
  clearDragOverStyling();
  if (dragState && dragState.moved && dragState.toIndex !== undefined && dragState.toIndex !== dragState.fromIndex) {
    vibrate(15);
    moveItem(dragState.fromIndex, dragState.toIndex);
  }
  dragState = null;
}

function renderUpcoming(items) {
  upcomingItems = items;
  const grid = document.getElementById('upcoming-grid');
  grid.innerHTML = '';

  items.forEach((item, i) => {
    const card = document.createElement('div');
    card.className = 'photo-card';
    card.dataset.index = String(i);

    const img = document.createElement('img');
    img.src = item.thumbnail_url;
    img.alt = '';
    card.appendChild(img);

    const badge = document.createElement('span');
    badge.className = 'badge';
    badge.textContent = String(i + 1);
    card.appendChild(badge);

    const removeBtn = document.createElement('button');
    removeBtn.type = 'button';
    removeBtn.className = 'remove-btn';
    removeBtn.title = 'Remove from rotation';
    removeBtn.textContent = '×';
    removeBtn.addEventListener('click', (e) => {
      e.stopPropagation();
      removeAsset(item.id);
    });
    card.appendChild(removeBtn);

    const nextBtn = document.createElement('button');
    nextBtn.type = 'button';
    nextBtn.className = 'show-next';
    nextBtn.textContent = 'Show next';
    nextBtn.addEventListener('click', (e) => {
      e.stopPropagation();
      showNext(i);
    });
    card.appendChild(nextBtn);

    card.addEventListener('pointerdown', (e) => {
      if (e.target.closest('.show-next') || e.target.closest('.remove-btn')) {
        return; // let the button's own click handler run, don't start a drag
      }
      if (e.pointerType === 'mouse' && e.button !== 0) {
        return; // left button only
      }

      dragState = {
        pointerId: e.pointerId, fromIndex: i, toIndex: undefined, moved: false,
        armed: e.pointerType !== 'touch', startX: e.clientX, startY: e.clientY, holdTimer: null,
      };

      if (e.pointerType === 'touch') {
        dragState.holdTimer = setTimeout(() => {
          if (dragState && dragState.pointerId === e.pointerId && !dragState.armed) {
            dragState.armed = true;
            card.classList.add('drag-armed');
            card.style.touchAction = 'none';
            vibrate(10);
            try { card.setPointerCapture(e.pointerId); } catch (err) { /* pointer may have already left */ }
          }
        }, DRAG_HOLD_MS);
      } else {
        card.setPointerCapture(e.pointerId);
      }
    });

    card.addEventListener('pointermove', (e) => {
      if (!dragState || dragState.pointerId !== e.pointerId) {
        return;
      }

      if (!dragState.armed) {
        // Still deciding whether this is a hold-to-drag or a scroll --
        // moving this much before the hold timer fires means scroll;
        // bail out and let the browser's native pan-y handle it.
        const dx0 = e.clientX - dragState.startX;
        const dy0 = e.clientY - dragState.startY;
        if (Math.hypot(dx0, dy0) > HOLD_CANCEL_THRESHOLD_PX) {
          clearTimeout(dragState.holdTimer);
          dragState = null;
        }
        return;
      }

      const dx = e.clientX - dragState.startX;
      const dy = e.clientY - dragState.startY;

      if (!dragState.moved) {
        if (Math.hypot(dx, dy) < DRAG_START_THRESHOLD_PX) {
          return;
        }
        dragState.moved = true;
        card.classList.remove('drag-armed');
        card.classList.add('dragging');
      }

      // Follows the finger 1:1 -- the actual "pick it up and carry it"
      // feedback.
      card.style.transform = `translate(${dx}px, ${dy}px) scale(1.06)`;

      const overCard = document.elementFromPoint(e.clientX, e.clientY)?.closest('.photo-card');
      clearDragOverStyling();
      if (overCard && overCard !== card) {
        overCard.classList.add('drag-over');
        dragState.toIndex = Number(overCard.dataset.index);
      } else {
        dragState.toIndex = undefined;
      }
    });

    card.addEventListener('pointerup', (e) => {
      if (dragState && dragState.pointerId === e.pointerId) {
        endDrag(card);
      }
    });
    card.addEventListener('pointercancel', (e) => {
      if (dragState && dragState.pointerId === e.pointerId) {
        endDrag(card);
      }
    });

    grid.appendChild(card);
  });
}

function moveItem(fromIndex, toIndex) {
  const items = upcomingItems.slice();
  const [moved] = items.splice(fromIndex, 1);
  items.splice(toIndex, 0, moved);
  renderUpcoming(items);
  persistOrder(items);
}

async function showNext(index) {
  const assetId = upcomingItems[index].id;
  try {
    const resp = await fetch(`${window.FRAME_API}/queue/promote`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ asset_id: assetId }),
    });
    if (!resp.ok) {
      throw new Error(await apiError(resp));
    }
  } catch (e) {
    showStatus(false, e.message);
  }
  loadQueue(); // always refetch the authoritative order rather than guessing locally
}

async function removeAsset(assetId) {
  if (!confirm('Remove this photo from the rotation? It stays in Immich -- this frame just won\'t show it again.')) {
    return;
  }
  try {
    const resp = await fetch(`${window.FRAME_API}/queue/remove`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ asset_id: assetId }),
    });
    if (!resp.ok) {
      throw new Error(await apiError(resp));
    }
  } catch (e) {
    showStatus(false, e.message);
  }
  loadQueue();
}

async function persistOrder(items) {
  try {
    const resp = await fetch(`${window.FRAME_API}/queue/reorder`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ queue: items.map((item) => item.id) }),
    });
    if (!resp.ok) {
      throw new Error(await apiError(resp));
    }
  } catch (e) {
    showStatus(false, e.message);
    loadQueue();
  }
}
