// ==UserScript==
// @name         codecurber 2.3 - fix overflow in unfolded state with stable folding and copy button
// @version      2.3
// @description  Fold large pre>code blocks with stable state during streaming, limit width to 97vw, fix overflow in unfolded state, debounce updates, custom copy button
// @match        *://yuntian-deng-chatgpt.hf.space/*
// @match        *://yuntian-deng-o1mini.hf.space/*
// @match        *://yuntian-deng-chatgpt4.hf.space/*
// @match        *://yuntian-deng-o1.hf.space/*
// @grant        none
// ==/UserScript==

(function() {
  'use strict';

  const MIN_LINES = 6;
  const FOLD_MAX_HEIGHT = '8em';
  const MAX_WIDTH = '97vw';
  const DEBOUNCE_MS = 150;

  const style = document.createElement('style');
  style.textContent = `
    pre.codecurber-folded {
      max-height: ${FOLD_MAX_HEIGHT} !important;
      overflow-y: auto !important;
      position: relative !important;
      transition: max-height 0.3s ease !important;
      white-space: pre-wrap !important;
      word-break: break-word !important;
      max-width: ${MAX_WIDTH} !important;
      box-sizing: border-box !important;
      width: 100% !important;
    }
    pre:not(.codecurber-folded) {
      max-width: ${MAX_WIDTH} !important;
      box-sizing: border-box !important;
      /* Prevent overflow in unfolded state */
      overflow-wrap: break-word !important;
      word-wrap: break-word !important;
      word-break: break-word !important;
      white-space: pre-wrap !important;
      width: 100% !important;
    }
    .codecurber-container {
      width: ${MAX_WIDTH};
      display: contents;
      flex-direction: column-reverse;
      margin-top: 0.25em;
      margin-bottom: 1em;
      box-sizing: border-box;
    }
    .codecurber-toggle-btn,
    .codecurber-copy-btn {
      margin-top: 0.5em !important;
      cursor: pointer !important;
      color: #39ff14 !important;
      background: transparent !important;
      user-select: none !important;
      padding: 6px 14px !important;
      display: inline-block !important;
      transition: background-color 0.25s ease, color 0.25s ease !important;
      max-width: ${MAX_WIDTH} !important;
      box-sizing: border-box;
      border-radius: 8px !important;
      border: 1.5px solid transparent !important;
    }
    .codecurber-toggle-btn:hover,
    .codecurber-copy-btn:hover {
      background-color: #39ff14 !important;
      color: black !important;
      border-color: #39ff14 !important;
    }
    .codecurber-btns {
      display: flex;
      gap: 10px;
      flex-wrap: wrap;
      justify-content: flex-start;
      max-width: ${MAX_WIDTH};
      box-sizing: border-box;
    }
    .copy_code_button {
      display: none !important;
    }
  `;
  document.head.appendChild(style);

  const foldedBlocks = new WeakMap();
  let pendingBlocks = new Set();
  let foldTimer = null;

  function debounceProcess() {
    if (foldTimer) clearTimeout(foldTimer);
    foldTimer = setTimeout(() => {
      const blocks = Array.from(pendingBlocks);
      pendingBlocks.clear();
      blocks.forEach(pre => processCodeBlock(pre));
    }, DEBOUNCE_MS);
  }

  function isFoldable(preEl) {
    if (!preEl) return false;
    const codeEl = preEl.querySelector('code');
    if (!codeEl) return false;
    const lines = (codeEl.textContent || '').split('\n').length;
    return lines >= MIN_LINES;
  }

  function processCodeBlock(preEl) {
    if (!isFoldable(preEl)) return;
    const codeEl = preEl.querySelector('code');
    if (!codeEl) return;

    const currLen = codeEl.textContent.length;
    const prevLen = foldedBlocks.get(preEl);

    if (prevLen !== undefined && prevLen === currLen && preEl.dataset.codecurberFolded === 'true') {
      return;
    }

    foldedBlocks.set(preEl, currLen);

    if (preEl.dataset.codecurberFolded !== 'true') {
      foldCodeBlock(preEl);
    }
  }

  function foldCodeBlock(preEl) {
    if (preEl.dataset.codecurberFolded === 'true') return;
    preEl.dataset.codecurberFolded = 'true';

    preEl.classList.add('codecurber-folded');

    const container = document.createElement('div');
    container.className = 'codecurber-container';

    preEl.parentNode.insertBefore(container, preEl);
    container.appendChild(preEl);

    const btns = document.createElement('div');
    btns.className = 'codecurber-btns';

    const toggleBtn = document.createElement('button');
    toggleBtn.type = 'button';
    toggleBtn.className = 'codecurber-toggle-btn';
    toggleBtn.textContent = 'Show full code';
    toggleBtn.setAttribute('aria-expanded', 'false');
    toggleBtn.setAttribute('aria-label', 'Show full code block');

    const copyBtn = document.createElement('button');
    copyBtn.type = 'button';
    copyBtn.className = 'codecurber-copy-btn';
    copyBtn.textContent = 'Copy code';
    copyBtn.setAttribute('aria-label', 'Copy code to clipboard');

    btns.appendChild(toggleBtn);
    btns.appendChild(copyBtn);
    container.appendChild(btns);

    toggleBtn.addEventListener('click', () => {
      const folded = preEl.classList.contains('codecurber-folded');
      if (folded) {
        preEl.style.maxHeight = 'none';
        preEl.style.overflow = 'visible';
        preEl.classList.remove('codecurber-folded');
        toggleBtn.textContent = 'Fold code';
        toggleBtn.setAttribute('aria-expanded', 'true');
      } else {
        preEl.style.maxHeight = FOLD_MAX_HEIGHT;
        preEl.style.overflow = 'auto';
        preEl.classList.add('codecurber-folded');
        toggleBtn.textContent = 'Show full code';
        toggleBtn.setAttribute('aria-expanded', 'false');
      }
    });

    copyBtn.addEventListener('click', async () => {
      try {
        const codeEl = preEl.querySelector('code');
        if (!codeEl) return;
        await navigator.clipboard.writeText(codeEl.textContent);
        const originalText = copyBtn.textContent;
        copyBtn.textContent = 'Copied!';
        setTimeout(() => {
          copyBtn.textContent = originalText;
        }, 2000);
      } catch {
        alert('Failed to copy code.');
      }
    });
  }

  function scanAndFoldAll() {
    const chat = document.querySelector('.bubble-wrap.svelte-gjtrl6');
    if (!chat) return;
    chat.querySelectorAll('pre').forEach(preEl => {
      if (!isFoldable(preEl)) return;
      pendingBlocks.add(preEl);
    });
    debounceProcess();
  }

  function observeChatMutations() {
    const chat = document.querySelector('.bubble-wrap.svelte-gjtrl6');
    if (!chat) return;

    const observer = new MutationObserver(mutations => {
      for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
          if (!(node instanceof HTMLElement)) continue;

          if (node.tagName === 'PRE') {
            if (isFoldable(node)) pendingBlocks.add(node);
          } else {
            node.querySelectorAll && node.querySelectorAll('pre').forEach(preEl => {
              if (isFoldable(preEl)) pendingBlocks.add(preEl);
            });
          }
        }
      }
      if (pendingBlocks.size > 0) debounceProcess();
    });

    observer.observe(chat, { childList: true, subtree: true });
  }

  const initTimer = setInterval(() => {
    if (document.querySelector('.bubble-wrap.svelte-gjtrl6')) {
      clearInterval(initTimer);
      scanAndFoldAll();
      observeChatMutations();
    }
  }, 300);
})();