(function() {
  'use strict';

  const DOC_EXT_RE = /\.(?:md|markdown|mdown|txt)$/i;
  const DOC_TOKEN_RE = /(^|[\s([{<"'`])([^\s<>"'`]*\.(?:md|markdown|mdown|txt)(?::\d+)?)(?=$|[\s)\]}>.,;!?])/gi;
  const SKIP_SELECTOR = 'a,code,pre,kbd,samp,textarea,input,select,button,script,style';

  function _state() {
    return (typeof window !== 'undefined' && (window._ctmState || window.state)) || {};
  }

  function _recentSessions() {
    try {
      return Array.isArray(window.allRecentSessions) ? window.allRecentSessions : [];
    } catch {
      return [];
    }
  }

  function splitReference(raw, fallbackLine) {
    let value = String(raw || '').trim();
    while (/[.,;!?]$/.test(value)) value = value.slice(0, -1);
    const match = value.match(/^(.*):(\d+)$/);
    const line = match ? Math.max(1, Number(match[2]) || 1) : Math.max(1, Number(fallbackLine) || 1);
    const path = match ? match[1] : value;
    return { raw: value, path, line, label: value };
  }

  function _looksLikeDocumentReference(raw) {
    const ref = splitReference(raw);
    if (!ref.path || !DOC_EXT_RE.test(ref.path)) return false;
    if (/^[a-z][a-z0-9+.-]*:\/\//i.test(ref.path)) return false;
    if (/^data:/i.test(ref.path)) return false;
    return true;
  }

  function findReferences(text) {
    const source = String(text || '');
    const refs = [];
    DOC_TOKEN_RE.lastIndex = 0;
    let match;
    while ((match = DOC_TOKEN_RE.exec(source))) {
      const prefix = match[1] || '';
      const token = match[2] || '';
      if (!_looksLikeDocumentReference(token)) continue;
      const start = match.index + prefix.length;
      const end = start + token.length;
      refs.push({ ...splitReference(token), start, end });
    }
    return refs;
  }

  function _rowMatchesSession(row, sessionId) {
    if (!row || !sessionId) return false;
    const ids = [
      row.sessionId, row.session_id, row.id,
      row.provisionalId, row.provisional_id,
      row.agentSessionId, row.agent_session_id,
      row.claudeSessionId, row.claude_session_id,
    ];
    if (Array.isArray(row.agentSessionIds)) ids.push(...row.agentSessionIds);
    if (Array.isArray(row.agent_session_ids)) ids.push(...row.agent_session_ids);
    return ids.map(String).includes(String(sessionId));
  }

  function contextForSession(sessionId) {
    const state = _state();
    const id = sessionId || state.reviewingSessionId || state.activeTab || '';
    const live = id && state.sessions && typeof state.sessions.get === 'function'
      ? state.sessions.get(id)
      : null;
    const meta = live && live.meta || {};
    const recent = _recentSessions().find(row => _rowMatchesSession(row, id)) || null;
    const cwd = meta.cwd || live?.cwd || meta.project || live?.project || recent?.cwd || recent?.project || '';
    return { sessionId: id || '', cwd: cwd || '' };
  }

  function defaultContext() {
    return contextForSession();
  }

  function hashForReference(raw, opts) {
    const ref = splitReference(raw, opts && opts.line);
    const params = new URLSearchParams();
    params.set('type', 'doc');
    params.set('path', ref.path);
    params.set('line', String(ref.line));
    if (opts && opts.cwd) params.set('cwd', opts.cwd);
    if (opts && opts.sessionId) params.set('session', opts.sessionId);
    return '#review&' + params.toString();
  }

  function _isMobileShell() {
    try { return /^\/m(?:\/|$)/.test(window.location.pathname || ''); }
    catch { return false; }
  }

  function hrefForReference(raw, opts) {
    const hash = hashForReference(raw, opts);
    if (_isMobileShell()) return '/index.html?desktop=1' + hash;
    return hash;
  }

  function openReference(raw, opts) {
    const context = Object.assign({}, opts || {});
    const ref = splitReference(raw, context.line);
    if (typeof window.CR !== 'undefined' && typeof window.CR.openDocumentReference === 'function') {
      window.CR.openDocumentReference(ref.label, context);
      return;
    }
    if (_isMobileShell()) {
      window.location.href = hrefForReference(ref.label, context);
      return;
    }
    window.location.hash = hashForReference(ref.label, context);
  }

  function _makeAnchor(ref, context) {
    const a = document.createElement('a');
    a.className = 'ctm-doc-review-link';
    a.href = hrefForReference(ref.label, context);
    a.textContent = ref.label;
    a.title = 'Open document in Review';
    a.dataset.ctmDocPath = ref.path;
    a.dataset.ctmDocLine = String(ref.line);
    if (context && context.cwd) a.dataset.ctmDocCwd = context.cwd;
    if (context && context.sessionId) a.dataset.ctmDocSessionId = context.sessionId;
    a.addEventListener('click', (event) => {
      event.preventDefault();
      openReference(ref.label, context);
    });
    return a;
  }

  function _stripHrefDecoration(rawHref) {
    let value = String(rawHref || '').trim();
    if (!value || /^#/.test(value)) return '';
    if (/^(?:javascript|mailto|tel|data):/i.test(value)) return '';
    if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) {
      try {
        const parsed = new URL(value);
        if (parsed.origin !== window.location.origin) return '';
        value = decodeURIComponent(parsed.pathname || '');
      } catch {
        return '';
      }
    }
    const hashIndex = value.indexOf('#');
    if (hashIndex >= 0) value = value.slice(0, hashIndex);
    const queryIndex = value.indexOf('?');
    if (queryIndex >= 0) value = value.slice(0, queryIndex);
    try { value = decodeURIComponent(value); } catch {}
    return value.trim();
  }

  function _looksLikeAbsoluteUserPath(value) {
    return /^\/(?:Users|private|tmp|var|Volumes|opt|home)\//.test(String(value || ''));
  }

  function _contextualDocumentPath(value, context) {
    const raw = String(value || '').trim();
    if (!raw || !context || !context.cwd) return raw;
    if (/^\/[^/]/.test(raw) && !_looksLikeAbsoluteUserPath(raw)) return raw.slice(1);
    return raw;
  }

  function _anchorReference(anchor, context) {
    const rawHref = _stripHrefDecoration(anchor.getAttribute('href') || '');
    const label = String(anchor.textContent || '').trim();
    const labelRef = _looksLikeDocumentReference(label) ? splitReference(label) : null;
    let candidate = rawHref;
    if (!candidate && labelRef) candidate = labelRef.label;
    if (candidate && /^\/[^/]/.test(candidate) && !_looksLikeAbsoluteUserPath(candidate) && labelRef && !pathIsRootLike(labelRef.path)) {
      candidate = labelRef.label;
    }
    candidate = _contextualDocumentPath(candidate, context);
    if (!_looksLikeDocumentReference(candidate)) return null;
    return splitReference(candidate);
  }

  function pathIsRootLike(value) {
    return /^\/|^~(?:\/|$)/.test(String(value || ''));
  }

  function _rawReference(ref) {
    return `${ref.path}:${Math.max(1, Number(ref.line) || 1)}`;
  }

  function _rewriteAnchor(anchor, context) {
    if (!anchor || anchor.closest(SKIP_SELECTOR.replace(/^a,?/, ''))) return false;
    const ref = _anchorReference(anchor, context);
    if (!ref) return false;
    anchor.classList.add('ctm-doc-review-link');
    anchor.href = hrefForReference(_rawReference(ref), context);
    anchor.title = 'Open document in Review';
    anchor.dataset.ctmDocPath = ref.path;
    anchor.dataset.ctmDocLine = String(ref.line);
    if (context && context.cwd) anchor.dataset.ctmDocCwd = context.cwd;
    if (context && context.sessionId) anchor.dataset.ctmDocSessionId = context.sessionId;
    anchor.removeAttribute('target');
    if (!anchor.dataset.ctmDocBound) {
      anchor.addEventListener('click', (event) => {
        event.preventDefault();
        const clickContext = {
          cwd: anchor.dataset.ctmDocCwd || '',
          sessionId: anchor.dataset.ctmDocSessionId || '',
        };
        openReference(_rawReference({
          path: anchor.dataset.ctmDocPath || ref.path,
          line: Number(anchor.dataset.ctmDocLine || ref.line) || 1,
        }), clickContext);
      });
      anchor.dataset.ctmDocBound = '1';
    }
    return true;
  }

  function _rewriteExistingAnchors(root, context) {
    if (!root || !root.querySelectorAll) return;
    root.querySelectorAll('a[href]').forEach((anchor) => _rewriteAnchor(anchor, context));
  }

  function _replaceTextNode(node, context) {
    const text = node.nodeValue || '';
    const refs = findReferences(text);
    if (!refs.length) return;
    const frag = document.createDocumentFragment();
    let offset = 0;
    for (const ref of refs) {
      if (ref.start > offset) frag.appendChild(document.createTextNode(text.slice(offset, ref.start)));
      frag.appendChild(_makeAnchor(ref, context));
      offset = ref.end;
    }
    if (offset < text.length) frag.appendChild(document.createTextNode(text.slice(offset)));
    node.parentNode.replaceChild(frag, node);
  }

  function linkifyElement(root, opts) {
    if (!root || typeof document === 'undefined') return;
    const context = Object.assign({}, opts || defaultContext());
    _rewriteExistingAnchors(root, context);
    const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
      acceptNode(node) {
        const parent = node && node.parentElement;
        if (!parent || !String(node.nodeValue || '').trim()) return NodeFilter.FILTER_REJECT;
        if (parent.closest && parent.closest(SKIP_SELECTOR)) return NodeFilter.FILTER_REJECT;
        return findReferences(node.nodeValue || '').length
          ? NodeFilter.FILTER_ACCEPT
          : NodeFilter.FILTER_REJECT;
      },
    });
    const nodes = [];
    while (walker.nextNode()) nodes.push(walker.currentNode);
    for (const node of nodes) _replaceTextNode(node, context);
  }

  function _bufferLineText(term, y) {
    try {
      const line = term && term.buffer && term.buffer.active && term.buffer.active.getLine(y - 1);
      return line ? line.translateToString(true) : '';
    } catch {
      return '';
    }
  }

  function registerTerminalLinkProvider(term, sessionId) {
    if (!term || typeof term.registerLinkProvider !== 'function') return null;
    return term.registerLinkProvider({
      provideLinks(y, callback) {
        const text = _bufferLineText(term, y);
        const context = contextForSession(sessionId);
        const links = findReferences(text).map(ref => ({
          range: {
            start: { x: ref.start + 1, y },
            end: { x: ref.end, y },
          },
          text: ref.label,
          activate() { openReference(ref.label, context); },
          hover() {},
          leave() {},
        }));
        callback(links.length ? links : undefined);
      },
    });
  }

  const api = {
    findReferences,
    splitReference,
    contextForSession,
    defaultContext,
    hashForReference,
    hrefForReference,
    openReference,
    linkifyElement,
    registerTerminalLinkProvider,
  };

  if (typeof window !== 'undefined') window.CTMDocLinks = api;
  if (typeof module !== 'undefined' && module.exports) module.exports = api;
})();
