<script src="https://cdn.jsdelivr.net/pyodide/v0.17.0/full/pyodide.js"></script>

<script type="text/javascript">
(() => {
  let busy = false;
  let requests = 0;
  let lastText = '';
  const reIdNum = /.*?_(\d+)$/;
  let initialized = false;
  const pycode = `
--8<-- "playground.py.txt"
`

  async function pyexecute(current_id) {
    // Execute Python code

    const current_inputs = document.getElementById(`__playground-inputs_${current_id}`);
    current_inputs.setAttribute('readonly', '');
    pyodide.globals.set('id_num', current_id);
    await pyodide.runPythonAsync(pycode);
    current_inputs.removeAttribute('readonly');
  }

  async function load_pyodide() {
    if (!initialized) {
      const buttons = document.querySelectorAll('.playground button');
      if (buttons) {
        buttons.forEach((b) => {
          b.setAttribute('disabled', '');
          if (b.classList.contains('playground-edit')) {
            b.innerHTML = 'Loading...';
          }
        });
      }
      initialized = true;
      await loadPyodide({indexURL : "https://cdn.jsdelivr.net/pyodide/v0.17.0/full/" });
      await pyodide.loadPackage(['micropip', 'Pygments']);
      if (buttons) {
        buttons.forEach((b) => {
          b.removeAttribute('disabled');
          if (b.classList.contains('playground-edit')) {
            b.innerHTML = 'Edit';
          }
        });
      }
    }
  }

  function highlight (inputs, current_id) {
    // Manage incoming highlight requests.

    // Throttle requests
    if (busy) {
      return;
    }

    // Execute
    if (requests) {
      requests = 0;
      busy = true;
      pyhighlight(inputs.value, current_id);
    }
  };

  async function pyhighlight(inputs, current_id) {
    // Highlight current input code

    let pycode = `
from js import document
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import find_formatter_class

HtmlFormatter = find_formatter_class('html')

def colorize(src, lang, **options):
    """Colorize."""

    lexer = get_lexer_by_name(lang, **options)
    formatter = HtmlFormatter(cssclass="highlight", wrapcode=True)
    return highlight(src, lexer, formatter).strip()

inputs = document.getElementById("__playground-inputs_${current_id}")
pgcode = document.getElementById("__playground-code_${current_id}")

div = pgcode.querySelector(".highlight")
template = document.createElement('template')
html = colorize(inputs.value + '\\n', 'py3', **{'python3': True, 'stripnl': False})
template.innerHTML = html;
pgcode.replaceChild(template.content.firstChild, div)
code = pgcode.querySelector('code')
code.scrollLeft = inputs.scrollLeft
code.scrollTop = inputs.scrollTop
`
    await pyodide.runPythonAsync(pycode);
    busy = false;
    if (requests) {
      setTimeout(() => {highlight(inputs, current_id);}, 100);
    }
  }

  async function loadExternalSrc() {
    if (window.location.pathname.endsWith("/playground/")) {
      const pginputs = document.querySelector('.playground-inputs');
      const pg_id = pginputs.id.replace(reIdNum, '$1');
      let params = new URLSearchParams(window.location.search);
      if (params.has('source')) {
        // A source was specified, so load it.
        await load_pyodide();
        try {
          const uri = params.get('source');
          let xhr = new XMLHttpRequest();
          xhr.open("GET", uri, true);
          xhr.onload = function (e) {
            // Try and load the requested content
            if (xhr.readyState === 4) {
              if (xhr.status === 200) {
                pginputs.value = xhr.responseText;
              }
            }
            requests = 1;
            highlight(pginputs, pg_id);
            pyexecute(pg_id);
          };
          xhr.send();
        } catch (err) {}
      } else if (params.has('code')) {
        // Raw code provided.
        await load_pyodide();
        requests = 1;
        pginputs.value = params.get('code');
        highlight(pginputs, pg_id);
        pyexecute(pg_id);
      }
    }
  }

  async function main() {
    // Setup input highlighting and events to run Python code blocks.

    const playgrounds = document.querySelectorAll('.playground');
    playgrounds.forEach(async function(pg) {

      const current_id = pg.id.replace(reIdNum, '$1');
      const inputs = document.getElementById(`__playground-inputs_${current_id}`);
      const results = document.getElementById(`__playground-results_${current_id}`);
      const pgcode = document.getElementById(`__playground-code_${current_id}`);
      const button_edit = document.querySelector(`button#__playground-edit_${current_id}`);
      const button_share = document.querySelector(`button#__playground-share_${current_id}`);
      const button_run = document.querySelector(`button#__playground-run_${current_id}`);
      const button_cancel = document.querySelector(`button#__playground-cancel_${current_id}`);
      const default_text = "# Insert your code here!\ncoloraide.__version__\nColor('red')";

      inputs.addEventListener("input", (e) => {
        // Handle highlighting as input changes.

        requests++;
        highlight(inputs, current_id);
      });

      inputs.addEventListener("scroll", (e) => {
        // Sync overlay textarea scrolling with highlighted code block underneath.

        const code = pgcode.querySelector("code");
        code.scrollLeft = e.target.scrollLeft;
        code.scrollTop = e.target.scrollTop;
      });

      document.addEventListener("keydown", (e) => {
        // Ctrl + Enter in textarea will execute the code.

        if (event.defaultPrevented) {
          return; // Do nothing if the event was already processed
        }

        if (
          e.ctrlKey &&
          e.key === "Enter" &&
          button_edit.classList.contains('hidden') &&
          inputs === document.activeElement
        ) {
          button_run.click();
        } else if (
          e.key === "Escape" &&
          button_edit.classList.contains('hidden') &&
          inputs === document.activeElement
        ) {
          button_cancel.click();
        }
      });

      inputs.addEventListener('touchmove', (e) => {
        // Stop propogation on "touchmove".

        e.stopPropagation();
      });

      button_edit.addEventListener("click", async function(e) {
        // Handle the button click: show source or execute source.

        if (busy) {
          return;
        }

        // Load Piodide and related packages.
        await load_pyodide();

        busy = true;
        lastText = inputs.value;
        pgcode.classList.toggle('hidden');
        results.classList.toggle('hidden');
        button_run.classList.toggle('hidden');
        button_cancel.classList.toggle('hidden');
        button_edit.classList.toggle('hidden');
        button_share.classList.toggle('hidden');
        inputs.focus();
        busy = false;
      });

      button_share.addEventListener("click", (e) => {
        // Handle the share click: copy URL with code as parameter.

        if (busy) {
          return;
        }

        busy = true;
        const uri = encodeURIComponent(inputs.value).replace(/[.!'()*]/g, (c) => {
          return '%' + c.charCodeAt(0).toString(16);
        });
        const loc = window.location;
        let pathname = '/playground/';
        if (loc.pathname.startsWith('/coloraide/')) {
          pathname = '/coloraide/playground/'
        }
        const path = loc.protocol + '//' + loc.host + pathname + '?code=' + uri;
        if (uri.length > 1000) {
          alert('Code must be under a 1000 characters to generate a URL!');
        } else {
          navigator.clipboard.writeText(path).then(function() {
            alert('Link copied to clipboard :)');
          }, function() {
            alert('Failed to copy link clipboard!');
          });
        }
        busy = false;
      });

      button_run.addEventListener("click", async function(e) {
        // Handle the button click: show source or execute source.

        if (busy) {
          return;
        }

        busy = true;
        results.querySelector('code').innerHTML = '';
        pyexecute(current_id);
        pgcode.classList.toggle('hidden');
        results.classList.toggle('hidden');
        button_edit.classList.toggle('hidden');
        button_share.classList.toggle('hidden');
        button_run.classList.toggle('hidden');
        button_cancel.classList.toggle('hidden');
        busy = false;
      });

      button_cancel.addEventListener("click", (e) => {
        // Cancel edit.

        if (busy) {
          return;
        }

        busy = true;
        inputs.value = lastText;
        pgcode.classList.toggle('hidden');
        results.classList.toggle('hidden');
        button_edit.classList.toggle('hidden');
        button_share.classList.toggle('hidden');
        button_run.classList.toggle('hidden');
        button_cancel.classList.toggle('hidden');
        busy = false;
        requests++;
        highlight(inputs, current_id);
      });
    });

    await loadExternalSrc();
  }

  // Run main
  main();
})()
</script>
