// ==UserScript==
// @name         DetectiveChiptunizer
// @version      9001
// @description  Shows pussy
// @match        *://yuntian-deng-chatgpt.hf.space/*
// @grant        none
// ==/UserScript==

(function() {
  'use strict';

  // == Config ==
  const DEBOUNCE_MS = 700;  // ms to wait after last token before stopping
  const PROGRESS_STEP = 5;  // progress bar % increment per token batch
  const MAX_PROGRESS = 95;  // max % progress before finishing

  // Create progress bar UI
  const barContainer = document.createElement('div');
  Object.assign(barContainer.style, {
    position: 'fixed',
    top: '0',
    left: '0',
    width: '100%',
    height: '4px',
    backgroundColor: 'rgba(0,0,0,0.1)',
    zIndex: 9999,
    opacity: '0',
    transition: 'opacity 0.3s ease'
  });

  const bar = document.createElement('div');
  Object.assign(bar.style, {
    height: '100%',
    width: '0%',
    backgroundColor: '#39ff14',
    transition: 'width 0.3s ease'
  });

  barContainer.appendChild(bar);
  document.body.appendChild(barContainer);

  // AudioContext chiptune synth setup
  const AudioContext = window.AudioContext || window.webkitAudioContext;
  const audioCtx = new AudioContext();

  let oscillator = null;
  let gainNode = null;
  let beepIntervalId = null;

  function playChiptune() {
    if (audioCtx.state === 'suspended') {
      audioCtx.resume();
    }
    if (beepIntervalId) return; // already playing

    beepIntervalId = setInterval(() => {
      oscillator = audioCtx.createOscillator();
      gainNode = audioCtx.createGain();

      oscillator.type = 'square';
      oscillator.frequency.setValueAtTime(880, audioCtx.currentTime); // A5 note

      gainNode.gain.setValueAtTime(0.15, audioCtx.currentTime);

      oscillator.connect(gainNode);
      gainNode.connect(audioCtx.destination);

      oscillator.start();
      oscillator.stop(audioCtx.currentTime + 0.1);

      oscillator.onended = () => {
        if (oscillator) {
          oscillator.disconnect();
          oscillator = null;
        }
        if (gainNode) {
          gainNode.disconnect();
          gainNode = null;
        }
      };
    }, 350);
  }

  function stopChiptune() {
    if (beepIntervalId) {
      clearInterval(beepIntervalId);
      beepIntervalId = null;
    }
    if (oscillator) {
      oscillator.disconnect();
      oscillator = null;
    }
    if (gainNode) {
      gainNode.disconnect();
      gainNode = null;
    }
  }

  let progress = 0;
  let active = false;
  let stopTimeout = null;

  function startProgress() {
    if (!active) {
      active = true;
      progress = 0;
      bar.style.width = '0%';
      barContainer.style.opacity = '1';
      playChiptune();
    }
    incrementProgress();
    deferStop();
  }

  function incrementProgress() {
    if (progress < MAX_PROGRESS) {
      progress = Math.min(progress + PROGRESS_STEP, MAX_PROGRESS);
      bar.style.width = progress + '%';
    }
  }

  function finishProgress() {
    progress = 100;
    bar.style.width = '100%';
    setTimeout(() => {
      barContainer.style.opacity = '0';
      bar.style.width = '0%';
      active = false;
      stopChiptune();
    }, 500);
  }

  function deferStop() {
    if (stopTimeout) {
      clearTimeout(stopTimeout);
    }
    stopTimeout = setTimeout(() => {
      finishProgress();
      stopTimeout = null;
    }, DEBOUNCE_MS);
  }

  // Observe chat area for new tokens streamed in
  const chatSelector = '.bubble-wrap.svelte-gjtrl6';

  function init() {
    const chatContainer = document.querySelector(chatSelector);
    if (!chatContainer) return false;

    const observer = new MutationObserver(mutations => {
      let tokensDetected = false;
      for (const m of mutations) {
        for (const node of m.addedNodes) {
          if (!(node instanceof HTMLElement)) continue;
          if (node.textContent.trim().length > 0) {
            tokensDetected = true;
            break;
          }
        }
        if (tokensDetected) break;
      }
      if (tokensDetected) startProgress();
    });
    observer.observe(chatContainer, { childList: true, subtree: true });

    return true;
  }

  // Wait for chat container then initialize
  const intervalId = setInterval(() => {
    if (init()) {
      clearInterval(intervalId);
    }
  }, 300);

})();