{{ define "fragments/line-quote-button" }}
<button
  id="line-quote-btn"
  type="button"
  aria-label="Quote line in comment"
  class="hidden fixed z-50 p-0.5 rounded bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-500 dark:text-gray-400 hover:text-black dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-600 cursor-pointer shadow-sm transition-opacity opacity-0 flex flex-col items-start"
  style="pointer-events: none;"
>
  {{ i "message-square-quote" "w-3.5 h-3.5" }}
  <span id="line-quote-btn-end" class="hidden mt-auto rotate-180 opacity-50">
    {{ i "message-square-quote" "w-3.5 h-3.5" }}
  </span>
</button>
<script>
  (() => {
    const btn = document.getElementById('line-quote-btn');
    if (!btn) return;
    const btnEnd = document.getElementById('line-quote-btn-end');

    const textarea = () =>
      document.getElementById('comment-textarea');

    const lineOf = (el) =>
      el?.closest?.('span[id*="-O"]')
      || el?.closest?.('span[id*="-N"]');

    const anchorOf = (el) => {
      const link = el.querySelector('a[href^="#"]');
      return link ? link.getAttribute('href').slice(1) : el.id || null;
    };

    const fileOf = (el) => {
      const d = el.closest('details[id^="file-"]');
      return d ? d.id.replace(/^file-/, '') : null;
    };

    const lineNumOf = (el) => anchorOf(el)?.match(/(\d+)(?:-[ON]?\d+)?$/)?.[1];

    const columnOf = (el) => el.closest('.flex-col');

    const linesInColumn = (col) =>
      Array.from(col.querySelectorAll('span[id*="-O"], span[id*="-N"]'))
        .filter(s => s.querySelector('a[href^="#"]'));

    let dragLines = null;

    const rangeBetween = (a, b) => {
      const col = columnOf(a);
      if (!col || col !== columnOf(b)) return [];
      const all = dragLines || linesInColumn(col);
      const ai = all.indexOf(a);
      const bi = all.indexOf(b);
      if (ai === -1 || bi === -1) return [];
      return all.slice(Math.min(ai, bi), Math.max(ai, bi) + 1);
    };

    const clearHl = (cls) =>
      document.querySelectorAll(`.${cls}`).forEach(el => el.classList.remove(cls));

    const applyHl = (a, b, cls) => {
      clearHl(cls);
      const sel = rangeBetween(a, b);
      sel.forEach(el => el.classList.add(cls));
      return sel;
    };

    const highlightFromHash = () => {
      clearHl('line-range-hl');
      let hash;
      try { hash = decodeURIComponent(window.location.hash.slice(1)); } catch (e) { return; }
      if (!hash) return;
      if (hash.startsWith('comment-') || hash.startsWith('round-')) return;
      const parts = hash.split('~');
      const startEl = document.getElementById(parts[0]);

      if (!startEl) {
        const params = new URLSearchParams(window.location.search);
        const hasCombined = parts.some(p => /-O\d+-N\d+$/.test(p));
        if (hasCombined && params.get('diff') !== 'unified') {
          params.set('diff', 'unified');
          window.location.replace(
            `${window.location.pathname}?${params}${window.location.hash}`
          );
        }
        return;
      }

      const endEl = parts.length === 2 ? document.getElementById(parts[1]) : startEl;
      if (!endEl) return;

      const details = startEl.closest('details');
      if (details) details.open = true;

      applyHl(startEl, endEl, 'line-range-hl');
      requestAnimationFrame(() =>
        startEl.scrollIntoView({ behavior: 'smooth', block: 'center' }));
    };

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', highlightFromHash);
    } else {
      highlightFromHash();
    }
    window.addEventListener('hashchange', highlightFromHash);

    let dragging = false;
    let dragAnchor = null;
    let dragCurrent = null;
    let hoverTarget = null;

    const commentBtn = () =>
      document.querySelector('[hx-get$="/comment"]:not(form *)');

    const openCommentForm = () => {
      const ta = textarea();
      if (ta) return Promise.resolve(ta);
      const trigger = commentBtn();
      if (!trigger) return Promise.resolve(null);
      trigger.click();
      return new Promise(resolve => {
        const handler = () => {
          const ta = textarea();
          if (!ta) return;
          document.body.removeEventListener('htmx:afterSettle', handler);
          clearTimeout(timer);
          resolve(ta);
        };
        const timer = setTimeout(() => {
          document.body.removeEventListener('htmx:afterSettle', handler);
          resolve(null);
        }, 5000);
        document.body.addEventListener('htmx:afterSettle', handler);
      });
    };

    const showBtn = (lineEl) => {
      if ((!textarea() && !commentBtn()) || !anchorOf(lineEl)) return;
      const rect = lineEl.getBoundingClientRect();
      Object.assign(btn.style, {
        top: `${rect.top + rect.height / 2 - btn.offsetHeight / 2}px`,
        left: `${rect.left + 4}px`,
        height: '',
        opacity: '1',
        pointerEvents: 'auto',
      });
      btn.classList.remove('hidden');
    };

    const hideBtn = () => {
      if (dragging) return;
      Object.assign(btn.style, { opacity: '0', pointerEvents: 'none', height: '' });
      btnEnd.classList.add('hidden');
      setTimeout(() => { if (btn.style.opacity === '0') btn.classList.add('hidden'); }, 150);
    };

    const stretchBtn = (a, b) => {
      const aRect = a.getBoundingClientRect();
      const bRect = b.getBoundingClientRect();
      const top = Math.min(aRect.top, bRect.top);
      const bottom = Math.max(aRect.bottom, bRect.bottom);
      const multiLine = a !== b;
      Object.assign(btn.style, {
        top: `${top}px`,
        left: `${aRect.left + 4}px`,
        height: `${bottom - top}px`,
      });
      if (multiLine) { btnEnd.classList.remove('hidden'); }
      else { btnEnd.classList.add('hidden'); }
    };

    document.addEventListener('mouseover', (e) => {
      if (dragging || e.target === btn || btn.contains(e.target)) return;
      const el = lineOf(e.target);
      if (el && el !== hoverTarget) { hoverTarget = el; showBtn(el); }
    });

    document.addEventListener('mouseout', (e) => {
      if (dragging) return;
      const el = lineOf(e.target);
      if (!el || lineOf(e.relatedTarget) === el || e.relatedTarget === btn || btn.contains(e.relatedTarget)) return;
      hoverTarget = null;
      hideBtn();
    });

    btn.addEventListener('mouseleave', (e) => {
      if (!dragging && !lineOf(e.relatedTarget)) { hoverTarget = null; hideBtn(); }
    });

    btn.addEventListener('mousedown', (e) => {
      if (e.button !== 0 || !hoverTarget) return;
      e.preventDefault();
      dragging = true;
      dragAnchor = dragCurrent = hoverTarget;
      const col = columnOf(hoverTarget);
      dragLines = col ? linesInColumn(col) : null;
      applyHl(dragAnchor, dragCurrent, 'line-quote-hl');
      btn.style.pointerEvents = 'none';
      document.body.style.userSelect = 'none';
    });

    document.addEventListener('mousemove', (e) => {
      if (!dragging) return;
      const el = lineOf(document.elementFromPoint(e.clientX, e.clientY));
      if (!el || el === dragCurrent) return;
      if (columnOf(el) !== columnOf(dragAnchor)) return;
      dragCurrent = el;
      applyHl(dragAnchor, dragCurrent, 'line-quote-hl');
      stretchBtn(dragAnchor, dragCurrent);
    });

    const insertIntoTextarea = (ta, selected) => {
      const first = selected[0];
      const last = selected[selected.length - 1];
      const fNum = lineNumOf(first);
      const firstAnchor = anchorOf(first);

      if (!fNum || !firstAnchor) return;

      const file = fileOf(first);
      const lNum = lineNumOf(last);
      const lastAnchor = anchorOf(last);

      const label = selected.length === 1
        ? (file ? `${file}:${fNum}` : `L${fNum}`)
        : (file ? `${file}:${fNum}-${lNum}` : `L${fNum}-${lNum}`);

      const fragment = selected.length === 1
        ? firstAnchor
        : `${firstAnchor}~${lastAnchor}`;

      const linkBase = document.getElementById('round-link-base')?.value
        || (window.location.pathname + window.location.search);
      const md = `[\`${label}\`](${linkBase}#${fragment})`;

      const { selectionStart: s, selectionEnd: end, value } = ta;
      const before = value.slice(0, s);
      const after = value.slice(end);
      let pre = '', suf = '';
      if (s === end && before.length > 0) {
        const cur = before.slice(before.lastIndexOf('\n') + 1);
        if (cur.length > 0) {
          const nextNl = after.indexOf('\n');
          const rest = nextNl === -1 ? after : after.slice(0, nextNl);
          if (rest.trim().length === 0) { pre = '\n'; }
          else { pre = before.endsWith(' ') ? '' : ' '; suf = after.startsWith(' ') ? '' : ' '; }
        }
      }
      ta.value = before + pre + md + suf + after;
      ta.selectionStart = ta.selectionEnd = s + pre.length + md.length + suf.length;
      ta.focus();
      ta.dispatchEvent(new Event('input', { bubbles: true }));
    };

    document.addEventListener('mouseup', () => {
      if (!dragging) return;
      dragging = false;
      document.body.style.userSelect = '';

      const selected = rangeBetween(dragAnchor, dragCurrent);
      if (selected.length > 0) {
        openCommentForm().then(ta => {
          if (ta) insertIntoTextarea(ta, selected);
        });
      }

      clearHl('line-quote-hl');
      dragLines = null;
      dragAnchor = dragCurrent = hoverTarget = null;
      hideBtn();
    });

    btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); });

    const cancelDrag = () => {
      if (!dragging) return;
      dragging = false;
      document.body.style.userSelect = '';
      clearHl('line-quote-hl');
      dragLines = null;
      dragAnchor = dragCurrent = hoverTarget = null;
      hideBtn();
    };
    window.addEventListener('blur', cancelDrag);
  })();
</script>
{{ end }}
