// Helpers for reading/writing Shoptet /admin/html-code CodeMirror editors
// and for building/removing Biatec widget + verification snippets.
//
// Scripts that must run in MAIN world (where CodeMirror instances live)
// are exposed as source strings — callers inject them via
// chrome.scripting.executeScript({func}) or webContents.executeJavaScript(src).
(function (root) {
  // ----- MAIN-world scripts -----

  // Build a script that reads the CodeMirror value for the given textarea id.
  function buildGetCmScript(textareaId) {
    return '(function () {'
      + '  var ta = document.getElementById(' + JSON.stringify(textareaId) + ');'
      + '  if (!ta) return "";'
      + '  var cm = ta.nextElementSibling;'
      + '  if (cm && cm.CodeMirror) return cm.CodeMirror.getValue();'
      + '  return ta.value;'
      + '})()';
  }

  // Read the RAW <textarea> value (what Shoptet actually submits on Save) —
  // bypasses CodeMirror. Used to confirm cm.save() flushed the editor into the
  // textarea; a mismatch vs the CodeMirror value means the save would persist
  // stale content.
  function buildGetTextareaRawScript(textareaId) {
    return '(function () {'
      + '  var ta = document.getElementById(' + JSON.stringify(textareaId) + ');'
      + '  return ta ? ta.value : "";'
      + '})()';
  }

  // Build a script that sets the CodeMirror value for the given textarea id.
  // Value is inlined via JSON.stringify so it is safely escaped.
  //
  // CRITICAL: cm.setValue() only updates the CodeMirror editor — NOT the
  // underlying <textarea>. Shoptet's Save submits the textarea, so without
  // cm.save() (which flushes the editor content back into the textarea) the
  // server keeps the OLD content while the editor (and any getValue() check)
  // shows the new one — a silent no-op save + false-positive "persisted".
  // We also fire input/change so Shoptet's dirty-tracking registers the edit.
  function buildSetCmScript(textareaId, value) {
    return '(function () {'
      + '  var val = ' + JSON.stringify(value) + ';'
      + '  var ta = document.getElementById(' + JSON.stringify(textareaId) + ');'
      + '  if (!ta) return false;'
      + '  var cm = ta.nextElementSibling;'
      + '  if (cm && cm.CodeMirror) {'
      + '    cm.CodeMirror.setValue(val);'
      + '    if (typeof cm.CodeMirror.save === "function") cm.CodeMirror.save();'
      + '  } else {'
      + '    ta.value = val;'
      + '  }'
      + '  try { ta.dispatchEvent(new Event("input", { bubbles: true })); } catch (e) {}'
      + '  try { ta.dispatchEvent(new Event("change", { bubbles: true })); } catch (e) {}'
      + '  return true;'
      + '})()';
  }

  // Source for clicking the "Save" / "Uložit" / "Uložiť" button on html-code page.
  function clickSaveButton() {
    var link = document.querySelector('a.submit-js[rel="saveAndStay"]');
    if (link) { link.click(); return true; }
    var els = document.querySelectorAll('button, input[type=submit], a.submit-js');
    for (var i = 0; i < els.length; i++) {
      var text = (els[i].textContent || els[i].value || '').trim().toLowerCase();
      if (text === 'save' || text === 'uložit' || text === 'uložiť') {
        els[i].click();
        return true;
      }
    }
    return false;
  }
  var CLICK_SAVE_SRC = '(' + clickSaveButton.toString() + ')()';

  // ----- Snippet builders / transformers (pure string ops) -----

  function buildVerifyComment(token) {
    return '<!-- ' + token + ' -->';
  }

  // Self-contained <script> block. MUST be its own block (not merged into a
  // user's existing script) because if any other code in the same block
  // throws — e.g. a language-switcher that assumes elements which only
  // exist on the homepage — the exception aborts the block before our
  // injection runs and the widget never loads. Real-world hit on
  // sametic.cz: a `ul[0].getElementsByTagName(...)` line above ours threw
  // on the checkout page where the language menu doesn't exist.
  function buildWidgetLoaderSnippet(widgetUrl) {
    return '<script>var s=document.createElement("script");s.src="'
      + widgetUrl + '?v="+Date.now();document.body.appendChild(s);</script>';
  }

  // Pattern matches:
  //   <!-- biatec-verify-ABC123 -->        (normal verify token from BE)
  //   <!-- biatec-spike-verify: 12345 -->  (legacy marker from tmp/electron-spike)
  // Kept narrow to avoid touching comments users wrote themselves.
  var BIATEC_MARKER_RE = /<!--\s*biatec-(?:spike-)?verify[^>]*-->\n?/g;

  // Add verify token to header (idempotent: strip old tokens first).
  function injectVerifyIntoHeader(headerValue, token) {
    var cleaned = (headerValue || '').replace(BIATEC_MARKER_RE, '');
    return (cleaned.trim() + '\n' + buildVerifyComment(token)).trim();
  }

  // Add widget loader to footer (idempotent: skip if widgetDomain already present).
  // Always prepended as its own <script> block so a throwing user script later
  // in the footer can't prevent our loader from injecting widget.js.
  function injectWidgetIntoFooter(footerValue, widgetLoaderSnippet, widgetDomain) {
    var val = footerValue || '';
    if (val.indexOf(widgetDomain) !== -1) return val;
    return (widgetLoaderSnippet + '\n' + val.trim()).trim();
  }

  function removeVerify(headerValue) {
    return (headerValue || '').replace(BIATEC_MARKER_RE, '').trim();
  }

  // Remove our loader. Handles two shapes:
  //   1. New self-contained block produced by buildWidgetLoaderSnippet.
  //      Recognised by: a <script>…</script> whose entire body is the
  //      loader (createElement + our domain + appendChild and nothing else).
  //      Removed whole.
  //   2. Legacy bare-line form, where the loader was spliced inside a user's
  //      existing <script> block. Only the offending line is removed; the
  //      surrounding user code is preserved.
  function removeWidget(footerValue, widgetDomain) {
    var val = footerValue || '';
    var domainEsc = widgetDomain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    // 1. Self-contained loader-only block. Body must contain createElement
    //    + appendChild + our domain — and nothing else (apart from
    //    whitespace, semicolons, quotes etc.). Avoids nuking a user's
    //    custom script that happens to mention our domain in a comment.
    // The src assignment is `s.src="https://<domain>…?v="+Date.now();` — note
    // the `+Date.now()` AFTER the closing quote. Match everything from `s.src=`
    // up to the terminating `;` (must contain our domain), so the whole loader
    // block is removed regardless of any `+expr` suffix. Still requires the
    // createElement + appendChild structure so a user script that merely
    // mentions our domain isn't nuked.
    var loaderBlockRe = new RegExp(
      '<script\\b[^>]*>\\s*' +
      'var\\s+s\\s*=\\s*document\\.createElement\\([\'"]script[\'"]\\)\\s*;' +
      '\\s*s\\.src\\s*=\\s*[^;]*' + domainEsc + '[^;]*;' +
      '\\s*document\\.body\\.appendChild\\(s\\)\\s*;?\\s*' +
      '</script>\\s*',
      'gi'
    );
    val = val.replace(loaderBlockRe, '');
    // 2. Legacy: bare lines mentioning the domain inside a user's script block.
    val = val.split('\n').filter(function (line) {
      return line.indexOf(widgetDomain) === -1;
    }).join('\n');
    // 3. Clean empty <script></script> left behind by step 2.
    return val.replace(/<script>\s*<\/script>/g, '').trim();
  }

  var api = {
    buildGetCmScript: buildGetCmScript,
    buildGetTextareaRawScript: buildGetTextareaRawScript,
    buildSetCmScript: buildSetCmScript,
    CLICK_SAVE_SRC: CLICK_SAVE_SRC,
    buildVerifyComment: buildVerifyComment,
    buildWidgetLoaderSnippet: buildWidgetLoaderSnippet,
    injectVerifyIntoHeader: injectVerifyIntoHeader,
    injectWidgetIntoFooter: injectWidgetIntoFooter,
    removeVerify: removeVerify,
    removeWidget: removeWidget
  };
  if (typeof module !== 'undefined' && module.exports) module.exports = api;
  else { root.NP = root.NP || {}; root.NP.shoptetHtmlCode = api; }
})(typeof self !== 'undefined' ? self : this);
