(function () {
  'use strict';

  const article = document.querySelector('[data-document-id]');
  if (!article) return;

  const documentId = article.dataset.documentId;
  const documentHash = article.dataset.documentHash;
  const canComment = article.dataset.canComment === 'true';
  const workspaceShell = article.closest('.workspace-shell--document');
  const commentsAsideList = document.querySelector('[data-comments-aside-list]');
  const commentsAsideListMobile = document.querySelector('[data-comments-aside-list-mobile]');
  const stripTrack = document.querySelector('[data-comments-strip-track]');
  const commentsAside = document.querySelector('[data-comments-aside]');
  const commentsSheetToggle = document.querySelector('[data-toggle-comments]');
  const commentsSheetClose = document.querySelector('[data-close-comments]');
  const commentToggle = document.querySelector('[data-comment-toggle]');
  const watchButton = document.querySelector('[data-document-watch]');
  const historyButtons = document.querySelectorAll('[data-comments-history]');
  const commentVisibilityKey = `cairnquire:document-comments:${documentId}`;
  const commentsByAnchor = new Map();
  const elementsByAnchor = new Map();
  let includeResolved = false;
  let watching = false;

  function readCommentVisibility() {
    try {
      return window.localStorage.getItem(commentVisibilityKey) === 'hidden';
    } catch (err) {
      return false;
    }
  }

  function commentsHidden() {
    return !workspaceShell?.classList.contains('comments-visible');
  }

  function setCommentsHidden(hidden) {
    article.classList.toggle('comments-hidden', hidden);
    workspaceShell?.classList.toggle('comments-visible', !hidden);
    try {
      window.localStorage.setItem(commentVisibilityKey, hidden ? 'hidden' : 'visible');
    } catch (err) {
      // Storage can be unavailable in private browsing contexts.
    }
    if (commentToggle) {
      commentToggle.setAttribute('aria-pressed', hidden ? 'false' : 'true');
      commentToggle.setAttribute('aria-label', hidden ? 'Show comments' : 'Hide comments');
      commentToggle.title = hidden ? 'Show comments' : 'Hide comments';
    }
    if (!hidden) {
      renderCommentsAside();
      updateStripMarkers();
    }
  }

  setCommentsHidden(readCommentVisibility());
  commentToggle?.addEventListener('click', () => setCommentsHidden(!commentsHidden()));

  function hashParagraph(el) {
    const text = (el.textContent || '').trim();
    if (!text) return null;
    let hash = 0;
    for (let i = 0; i < text.length; i++) {
      hash = ((hash << 5) - hash) + text.charCodeAt(i);
      hash &= hash;
    }
    return 'h' + Math.abs(hash).toString(36);
  }

  function addCommentButton(el, anchorHash) {
    const button = document.createElement('button');
    button.className = 'comment-anchor-btn';
    button.type = 'button';
    button.title = 'Add comment';
    button.setAttribute('aria-label', 'Add comment');
    button.innerHTML = `
      <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
        <path d="M21 15a2 2 0 0 1-2 2H8l-5 4V7a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2Z"></path>
        <path d="M12 8v4"></path><path d="M10 10h4"></path>
      </svg>
      <span class="comment-anchor-count" aria-hidden="true" hidden></span>
      <span class="sr-only">Add comment</span>`;
    button.addEventListener('click', () => createComment(anchorHash));
    el.appendChild(button);
  }

  function updateCommentButtonState(el, comments) {
    const button = el.querySelector('.comment-anchor-btn');
    const count = comments.filter(comment => !comment.resolvedAt).length;
    if (button) {
      const hasComments = count > 0;
      button.classList.toggle('comment-anchor-btn--has-comments', hasComments);
      const label = hasComments ? `${count} comment${count === 1 ? '' : 's'}` : 'Add comment';
      button.setAttribute('aria-label', label);
      button.title = label;
      const countNode = button.querySelector('.comment-anchor-count');
      if (countNode) {
        countNode.hidden = !hasComments;
        countNode.textContent = count > 99 ? '99+' : String(count);
      }
    }
    el.dataset.hasComments = comments.length > 0 ? 'true' : 'false';
  }

  async function createComment(anchorHash, parentId) {
    const promptText = parentId ? 'Reply to this comment:' : 'Comment on this section:';
    const content = window.prompt(promptText);
    if (!content || !content.trim()) return;
    try {
      const response = await fetch('/api/comments', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          documentId,
          versionHash: documentHash,
          content: content.trim(),
          anchorHash,
          parentId: parentId || undefined,
        }),
      });
      if (!response.ok) throw new Error(await response.text());
      await loadCommentsForAnchor(anchorHash);
    } catch (err) {
      window.alert('Failed to post comment: ' + err.message);
    }
  }

  async function resolveThread(comment, anchorHash) {
    if (!window.confirm('Resolve this comment thread?')) return;
    try {
      const response = await fetch(`/api/comments/${encodeURIComponent(comment.id)}/resolve`, { method: 'POST' });
      if (!response.ok) throw new Error(await response.text());
      await loadCommentsForAnchor(anchorHash);
    } catch (err) {
      window.alert('Failed to resolve comment: ' + err.message);
    }
  }

  async function loadCommentsForAnchor(anchorHash) {
    const element = elementsByAnchor.get(anchorHash);
    if (!element) return;
    const params = new URLSearchParams({ document_id: documentId, anchor_hash: anchorHash });
    if (includeResolved) params.set('include_resolved', 'true');
    try {
      const response = await fetch(`/api/comments?${params}`);
      if (!response.ok) return;
      const data = await response.json();
      const comments = data.comments || [];
      commentsByAnchor.set(anchorHash, comments);
      renderInlineComments(element, anchorHash, comments);
      renderCommentsAside();
      updateStripMarkers();
    } catch (err) {
      console.error('load comments', err);
    }
  }

  function buildCommentForest(comments) {
    const nodes = new Map(comments.map(comment => [comment.id, { comment, children: [] }]));
    const roots = [];
    comments.forEach(comment => {
      const node = nodes.get(comment.id);
      const parent = comment.parentId ? nodes.get(comment.parentId) : null;
      if (parent) parent.children.push(node);
      else roots.push(node);
    });
    return roots;
  }

  function renderThreadNode(node, anchorHash, variant) {
    const comment = node.comment;
    const wrapper = document.createElement('div');
    wrapper.className = `comment-thread${comment.resolvedAt ? ' is-resolved' : ''}`;
    wrapper.dataset.commentId = comment.id;

    const item = document.createElement('div');
    item.className = variant === 'aside' ? 'comments-aside-item__comment' : 'comment-item';

    const meta = document.createElement('div');
    meta.className = variant === 'aside' ? 'comments-aside-item__meta' : 'comment-meta';
    const author = document.createElement('strong');
    author.textContent = comment.authorName || 'Anonymous';
    const time = document.createElement('time');
    time.textContent = formatDate(comment.createdAt);
    meta.append(author, time);
    if (comment.resolvedAt) {
      const status = document.createElement('span');
      status.className = 'comment-status';
      status.textContent = 'Resolved';
      meta.appendChild(status);
    }

    const body = document.createElement('div');
    body.className = variant === 'aside' ? 'comments-aside-item__body' : 'comment-body';
    body.textContent = comment.content;
    item.append(meta, body);

    if (canComment && !comment.resolvedAt) {
      const actions = document.createElement('div');
      actions.className = 'comment-actions';
      const reply = document.createElement('button');
      reply.type = 'button';
      reply.className = 'comment-action';
      reply.textContent = 'Reply';
      reply.addEventListener('click', () => createComment(anchorHash, comment.id));
      actions.appendChild(reply);
      if (!comment.parentId) {
        const resolve = document.createElement('button');
        resolve.type = 'button';
        resolve.className = 'comment-action';
        resolve.textContent = 'Resolve thread';
        resolve.addEventListener('click', () => resolveThread(comment, anchorHash));
        actions.appendChild(resolve);
      }
      item.appendChild(actions);
    }
    wrapper.appendChild(item);

    if (node.children.length) {
      const replies = document.createElement('div');
      replies.className = 'comment-thread__replies';
      node.children.forEach(child => replies.appendChild(renderThreadNode(child, anchorHash, variant)));
      wrapper.appendChild(replies);
    }
    return wrapper;
  }

  function renderInlineComments(el, anchorHash, comments) {
    el.querySelector('.comment-list')?.remove();
    updateCommentButtonState(el, comments);
    if (!comments.length) return;
    const list = document.createElement('div');
    list.className = 'comment-list';
    buildCommentForest(comments).forEach(root => list.appendChild(renderThreadNode(root, anchorHash, 'inline')));
    el.appendChild(list);
  }

  function buildAsideItem(anchorHash, el, comments) {
    if (!comments.length) return null;
    const asideItem = document.createElement('div');
    asideItem.className = 'comments-aside-item';
    asideItem.dataset.anchorHash = anchorHash;

    const target = document.createElement('button');
    target.className = 'comments-aside-item__target';
    target.type = 'button';
    target.textContent = el.dataset.commentTarget || 'Commented section';
    target.addEventListener('click', () => el.scrollIntoView({ behavior: 'smooth', block: 'center' }));
    asideItem.appendChild(target);
    buildCommentForest(comments).forEach(root => asideItem.appendChild(renderThreadNode(root, anchorHash, 'aside')));
    return asideItem;
  }

  function renderCommentsInto(container) {
    if (!container) return;
    container.innerHTML = '';
    let rendered = 0;
    elementsByAnchor.forEach((el, anchorHash) => {
      const item = buildAsideItem(anchorHash, el, commentsByAnchor.get(anchorHash) || []);
      if (item) {
        container.appendChild(item);
        rendered++;
      }
    });
    if (!rendered) {
      container.innerHTML = `<p class="document-comments-aside__empty">${includeResolved ? 'No comments found.' : 'No active comments yet.'}</p>`;
    }
  }

  function renderCommentsAside() {
    renderCommentsInto(commentsAsideList);
    renderCommentsInto(commentsAsideListMobile);
  }

  function updateStripMarkers() {
    if (!stripTrack) return;
    stripTrack.innerHTML = '';
    const shell = document.querySelector('.document-shell');
    if (!shell) return;
    elementsByAnchor.forEach((el, anchorHash) => {
      if (!(commentsByAnchor.get(anchorHash) || []).length) return;
      const rect = el.getBoundingClientRect();
      const shellRect = shell.getBoundingClientRect();
      const relativeTop = rect.top - shellRect.top + shell.scrollTop;
      const percent = (relativeTop / shell.scrollHeight) * 100;
      const marker = document.createElement('button');
      marker.className = 'document-comments-strip__marker';
      marker.type = 'button';
      marker.style.top = Math.max(0, Math.min(95, percent)) + '%';
      marker.dataset.anchorHash = anchorHash;
      marker.title = 'Jump to comments';
      marker.addEventListener('click', () => {
        el.scrollIntoView({ behavior: 'smooth', block: 'center' });
        if (commentsHidden()) setCommentsHidden(false);
        commentsAsideList?.querySelector(`[data-anchor-hash="${anchorHash}"]`)?.scrollIntoView({ behavior: 'smooth', block: 'center' });
      });
      stripTrack.appendChild(marker);
    });
  }

  function initBidirectionalHover() {
    const body = document.querySelector('.markdown-body');
    if (!body || !commentsAsideList) return;
    body.addEventListener('mouseover', event => toggleHighlight(event.target.closest('.commentable'), true));
    body.addEventListener('mouseout', event => toggleHighlight(event.target.closest('.commentable'), false));
    commentsAsideList.addEventListener('mouseover', event => toggleAsideHighlight(event.target.closest('.comments-aside-item'), true));
    commentsAsideList.addEventListener('mouseout', event => toggleAsideHighlight(event.target.closest('.comments-aside-item'), false));
  }

  function toggleHighlight(el, active) {
    if (!el) return;
    const hash = el.dataset.anchorHash;
    el.classList.toggle('is-highlighted', active);
    commentsAsideList?.querySelector(`[data-anchor-hash="${hash}"]`)?.classList.toggle('is-highlighted', active);
    stripTrack?.querySelector(`[data-anchor-hash="${hash}"]`)?.classList.toggle('is-active', active);
  }

  function toggleAsideHighlight(item, active) {
    if (!item) return;
    toggleHighlight(elementsByAnchor.get(item.dataset.anchorHash), active);
  }

  function setCommentsSheetOpen(open) {
    if (!commentsAside) return;
    commentsAside.classList.toggle('is-open', open);
    commentsSheetToggle?.setAttribute('aria-expanded', String(open));
    if (open) {
      document.body.classList.add('drawer-open');
      renderCommentsAside();
    } else if (!document.querySelector('[data-picker-drawer].is-open, [data-meta-drawer].is-open')) {
      document.body.classList.remove('drawer-open');
    }
  }

  commentsSheetToggle?.addEventListener('click', () => setCommentsSheetOpen(!commentsAside?.classList.contains('is-open')));
  commentsSheetClose?.addEventListener('click', () => setCommentsSheetOpen(false));

  function setWatchState(nextWatching) {
    watching = nextWatching;
    if (!watchButton) return;
    watchButton.setAttribute('aria-pressed', String(watching));
    const label = watchButton.querySelector('[data-document-watch-label]');
    if (label) label.textContent = watching ? 'Unwatch document' : 'Watch document';
  }

  async function loadWatchStatus() {
    if (!watchButton) return;
    try {
      const response = await fetch(`/api/watch/status?document_id=${encodeURIComponent(documentId)}`);
      if (!response.ok) return;
      const data = await response.json();
      setWatchState(Boolean(data.watching));
    } catch (err) {
      console.error('load watch status', err);
    }
  }

  async function toggleWatch() {
    if (!watchButton) return;
    watchButton.disabled = true;
    try {
      const response = await fetch('/api/watch/document', {
        method: watching ? 'DELETE' : 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ documentId }),
      });
      if (!response.ok) throw new Error(await response.text());
      setWatchState(!watching);
    } catch (err) {
      window.alert('Failed to update watch: ' + err.message);
    } finally {
      watchButton.disabled = false;
    }
  }

  watchButton?.addEventListener('click', toggleWatch);

  function updateHistoryButtons() {
    historyButtons.forEach(button => {
      button.setAttribute('aria-pressed', String(includeResolved));
      button.textContent = includeResolved ? 'Hide resolved' : 'Show resolved';
    });
  }

  historyButtons.forEach(button => button.addEventListener('click', async () => {
    includeResolved = !includeResolved;
    updateHistoryButtons();
    await Promise.all(Array.from(elementsByAnchor.keys(), loadCommentsForAnchor));
  }));

  function formatDate(iso) {
    return new Date(iso).toLocaleString();
  }

  function init() {
    const body = document.querySelector('.markdown-body');
    if (!body) return;
    const commentables = body.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, blockquote, pre');
    commentables.forEach(el => {
      const targetText = (el.textContent || '').replace(/\s+/g, ' ').trim();
      const anchorHash = hashParagraph(el);
      if (!anchorHash) return;
      el.dataset.anchorHash = anchorHash;
      el.dataset.commentTarget = targetText.length > 120 ? targetText.slice(0, 120) + '…' : targetText;
      el.classList.add('commentable');
      elementsByAnchor.set(anchorHash, el);
      if (canComment) addCommentButton(el, anchorHash);
      loadCommentsForAnchor(anchorHash);
    });
    renderCommentsAside();
    initBidirectionalHover();
    loadWatchStatus();
    updateHistoryButtons();
    const shell = document.querySelector('.document-shell');
    shell?.addEventListener('scroll', () => requestAnimationFrame(updateStripMarkers));
    window.addEventListener('resize', () => requestAnimationFrame(updateStripMarkers));
    setTimeout(updateStripMarkers, 100);
  }

  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
  else init();
})();
