GGn Upload Templator

Auto-fill upload forms using torrent file data with configurable templates

// ==UserScript==
// @name        GGn Upload Templator
// @namespace   https://greatest.deepsurf.us/
// @version     0.12
// @description Auto-fill upload forms using torrent file data with configurable templates
// @author      leveldesigner
// @license     Unlicense
// @source      https://github.com/lvldesigner/userscripts/tree/main/ggn-upload-templator
// @supportURL  https://github.com/lvldesigner/userscripts/tree/main/ggn-upload-templator
// @icon        https://gazellegames.net/favicon.ico
// @match       https://*.gazellegames.net/upload.php*
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_addStyle
// @grant       unsafeWindow
// ==/UserScript==

(function() {
  "use strict";
  const DEFAULT_CONFIG = {
    TARGET_FORM_SELECTOR: "#upload_table",
    SUBMIT_KEYBINDING: true,
    CUSTOM_SUBMIT_KEYBINDING: "Ctrl+Enter",
    APPLY_KEYBINDING: true,
    CUSTOM_APPLY_KEYBINDING: "Ctrl+Shift+A",
    CUSTOM_FIELD_SELECTORS: [],
    IGNORED_FIELDS_BY_DEFAULT: [
      "linkgroup",
      "groupid",
      "apikey",
      "type",
      "amazonuri",
      "googleplaybooksuri",
      "goodreadsuri",
      "isbn",
      "scan_dpi",
      "other_dpi",
      "release_desc",
      "anonymous",
      "dont_check_rules",
      "title",
      "tags",
      "image",
      "gameswebsiteuri",
      "wikipediauri",
      "album_desc",
      "submit_upload"
    ]
  };
  const logDebug = (...messages) => {
    const css = "color: #4dd0e1; font-weight: 900;";
    console.debug("%c[GGn Upload Templator]", css, ...messages);
  };
  function getCurrentFormData(config) {
    const formData = {};
    const formSelector = config.TARGET_FORM_SELECTOR || "form";
    const targetForm = document.querySelector(formSelector);
    const defaultSelector = "input[name], select[name], textarea[name]";
    const customSelectors = config.CUSTOM_FIELD_SELECTORS || [];
    const fieldSelector = customSelectors.length > 0 ? `${defaultSelector}, ${customSelectors.join(", ")}` : defaultSelector;
    const inputs = targetForm ? targetForm.querySelectorAll(fieldSelector) : document.querySelectorAll(fieldSelector);
    inputs.forEach((input) => {
      const isCustomField = isElementMatchedByCustomSelector(input, config);
      const hasValidIdentifier = isCustomField ? input.name || input.id || input.getAttribute("data-field") || input.getAttribute("data-name") : input.name;
      if (!hasValidIdentifier) return;
      if (!isCustomField && (input.type === "file" || input.type === "button" || input.type === "submit")) {
        return;
      }
      const fieldName = input.name || input.id || input.getAttribute("data-field") || input.getAttribute("data-name");
      if (fieldName) {
        if (input.type === "radio" && formData[fieldName]) {
          return;
        }
        const fieldInfo = {
          value: isCustomField ? input.value || input.textContent || input.getAttribute("data-value") || "" : input.type === "checkbox" || input.type === "radio" ? input.checked : input.value || "",
          label: getFieldLabel(input, config),
          type: input.tagName.toLowerCase(),
          inputType: input.type || "custom"
        };
        if (input.type === "radio") {
          const radioGroup = document.querySelectorAll(
            `input[name="${fieldName}"][type="radio"]`
          );
          fieldInfo.radioOptions = Array.from(radioGroup).map((radio) => ({
            value: radio.value,
            checked: radio.checked,
            label: getFieldLabel(radio, config) || radio.value
          }));
          const selectedRadio = Array.from(radioGroup).find(
            (radio) => radio.checked
          );
          fieldInfo.selectedValue = selectedRadio ? selectedRadio.value : "";
          fieldInfo.value = fieldInfo.selectedValue;
        }
        if (input.tagName.toLowerCase() === "select") {
          fieldInfo.options = Array.from(input.options).map((option) => ({
            value: option.value,
            text: option.textContent.trim(),
            selected: option.selected
          }));
          fieldInfo.selectedValue = input.value;
        }
        formData[fieldName] = fieldInfo;
      }
    });
    return formData;
  }
  function isElementMatchedByCustomSelector(element, config) {
    const customSelectors = config.CUSTOM_FIELD_SELECTORS || [];
    if (customSelectors.length === 0) return false;
    return customSelectors.some((selector) => {
      try {
        return element.matches(selector);
      } catch (e) {
        console.warn(`Invalid custom selector: ${selector}`, e);
        return false;
      }
    });
  }
  function cleanLabelText(text) {
    if (!text) return text;
    const tempElement = document.createElement("div");
    tempElement.innerHTML = text;
    const linkElements = tempElement.querySelectorAll("a");
    linkElements.forEach((link) => {
      link.remove();
    });
    let cleanedText = tempElement.textContent || tempElement.innerText || "";
    cleanedText = cleanedText.trim();
    if (cleanedText.endsWith(":")) {
      cleanedText = cleanedText.slice(0, -1).trim();
    }
    return cleanedText;
  }
  function getFieldLabel(input, config) {
    const isCustomField = isElementMatchedByCustomSelector(input, config);
    if (isCustomField) {
      const parent = input.parentElement;
      if (parent) {
        const labelElement = parent.querySelector("label");
        if (labelElement) {
          const rawText = labelElement.innerHTML || labelElement.textContent || "";
          const cleanedText = cleanLabelText(rawText);
          return cleanedText || input.id || input.name || "Custom Field";
        }
        const labelClassElement = parent.querySelector('*[class*="label"]');
        if (labelClassElement) {
          const rawText = labelClassElement.innerHTML || labelClassElement.textContent || "";
          const cleanedText = cleanLabelText(rawText);
          return cleanedText || input.id || input.name || "Custom Field";
        }
      }
      return input.id || input.name || "Custom Field";
    }
    if (input.type === "radio" && input.id) {
      const parentTd = input.closest("td");
      if (parentTd) {
        const associatedLabel = parentTd.querySelector(
          `label[for="${input.id}"]`
        );
        if (associatedLabel) {
          const rawText = associatedLabel.innerHTML || associatedLabel.textContent || "";
          const cleanedText = cleanLabelText(rawText);
          return cleanedText || input.value;
        }
      }
    }
    const parentRow = input.closest("tr");
    if (parentRow) {
      const labelCell = parentRow.querySelector("td.label");
      if (labelCell) {
        const rawText = labelCell.innerHTML || labelCell.textContent || "";
        const cleanedText = cleanLabelText(rawText);
        return cleanedText ? `${cleanedText} (${input.name})` : input.name;
      }
    }
    return input.name;
  }
  function findElementByFieldName(fieldName, config) {
    config.TARGET_FORM_SELECTOR ? `${config.TARGET_FORM_SELECTOR} ` : "";
    const defaultSelector = "input[name], select[name], textarea[name]";
    const customSelectors = config.CUSTOM_FIELD_SELECTORS || [];
    const fieldSelector = customSelectors.length > 0 ? `${defaultSelector}, ${customSelectors.join(", ")}` : defaultSelector;
    const targetForm = config.TARGET_FORM_SELECTOR ? document.querySelector(config.TARGET_FORM_SELECTOR) : null;
    const inputs = targetForm ? targetForm.querySelectorAll(fieldSelector) : document.querySelectorAll(fieldSelector);
    for (const input of inputs) {
      const isCustomField = isElementMatchedByCustomSelector(input, config);
      const hasValidIdentifier = isCustomField ? input.name || input.id || input.getAttribute("data-field") || input.getAttribute("data-name") : input.name;
      if (!hasValidIdentifier) continue;
      if (!isCustomField && (input.type === "file" || input.type === "button" || input.type === "submit")) {
        continue;
      }
      const elementFieldName = input.name || input.id || input.getAttribute("data-field") || input.getAttribute("data-name");
      if (elementFieldName === fieldName) {
        return input;
      }
    }
    return null;
  }
  class TorrentUtils {
    // Parse torrent file for metadata
    static async parseTorrentFile(file) {
      const arrayBuffer = await file.arrayBuffer();
      const data = new Uint8Array(arrayBuffer);
      try {
        const [torrent2] = TorrentUtils.decodeBencode(data);
        return {
          name: torrent2.info?.name || file.name,
          comment: torrent2.comment || "",
          files: torrent2.info?.files?.map((f) => ({
            path: f.path.join("/"),
            length: f.length
          })) || [
            {
              path: torrent2.info?.name || file.name,
              length: torrent2.info?.length
            }
          ]
        };
      } catch (e) {
        console.warn("Could not parse torrent file:", e);
        return { name: file.name, comment: "", files: [] };
      }
    }
    static parseCommentVariables(comment) {
      if (!comment || typeof comment !== "string") return {};
      const variables = {};
      const pairs = comment.split(";");
      for (const pair of pairs) {
        const trimmedPair = pair.trim();
        if (!trimmedPair) continue;
        const eqIndex = trimmedPair.indexOf("=");
        if (eqIndex === -1) continue;
        const key = trimmedPair.substring(0, eqIndex).trim();
        const value = trimmedPair.substring(eqIndex + 1).trim();
        if (key) {
          variables[`_${key}`] = value;
        }
      }
      return variables;
    }
    // Simple bencode decoder
    static decodeBencode(data, offset = 0) {
      const char = String.fromCharCode(data[offset]);
      if (char === "d") {
        const dict = {};
        offset++;
        while (data[offset] !== 101) {
          const [key, newOffset1] = TorrentUtils.decodeBencode(data, offset);
          const [value, newOffset2] = TorrentUtils.decodeBencode(
            data,
            newOffset1
          );
          dict[key] = value;
          offset = newOffset2;
        }
        return [dict, offset + 1];
      }
      if (char === "l") {
        const list = [];
        offset++;
        while (data[offset] !== 101) {
          const [value, newOffset] = TorrentUtils.decodeBencode(data, offset);
          list.push(value);
          offset = newOffset;
        }
        return [list, offset + 1];
      }
      if (char === "i") {
        offset++;
        let num = "";
        while (data[offset] !== 101) {
          num += String.fromCharCode(data[offset]);
          offset++;
        }
        return [parseInt(num), offset + 1];
      }
      if (char >= "0" && char <= "9") {
        let lengthStr = "";
        while (data[offset] !== 58) {
          lengthStr += String.fromCharCode(data[offset]);
          offset++;
        }
        const length = parseInt(lengthStr);
        offset++;
        const str = new TextDecoder("utf-8", { fatal: false }).decode(
          data.slice(offset, offset + length)
        );
        return [str, offset + length];
      }
      throw new Error("Invalid bencode data");
    }
  }
  const torrent = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
    __proto__: null,
    TorrentUtils
  }, Symbol.toStringTag, { value: "Module" }));
  const MAX_VARIABLE_NAME_LENGTH = 50;
  function parseVariableWithHint(varString) {
    const colonIndex = varString.indexOf(":");
    if (colonIndex === -1) {
      return { varName: varString, hint: null };
    }
    return {
      varName: varString.substring(0, colonIndex),
      hint: varString.substring(colonIndex + 1)
    };
  }
  function parseHint(hintString, availableHints = {}) {
    if (!hintString) {
      return { type: "none", data: null };
    }
    if (hintString.startsWith("/")) {
      const regexPattern = hintString.slice(1).replace(/\/$/, "");
      return { type: "regex", data: regexPattern };
    }
    if (/[*#@?]/.test(hintString)) {
      return { type: "pattern", data: hintString };
    }
    const namedHint = availableHints[hintString];
    if (namedHint) {
      return { type: namedHint.type, data: namedHint };
    }
    return { type: "unknown", data: hintString };
  }
  function compileHintToRegex(hint, availableHints = {}) {
    const parsed = parseHint(hint, availableHints);
    switch (parsed.type) {
      case "regex":
        return typeof parsed.data === "string" ? parsed.data : parsed.data.pattern;
      case "pattern":
        return compileSimplePattern(parsed.data);
      case "map":
        const mappings = typeof parsed.data === "object" && parsed.data.mappings ? parsed.data.mappings : parsed.data;
        const keys = Object.keys(mappings || {});
        if (keys.length === 0) return ".+";
        const escapedKeys = keys.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
        return `(?:${escapedKeys.join("|")})`;
      case "unknown":
      case "none":
      default:
        return null;
    }
  }
  function escapeSpecialChars(text) {
    return text.replace(/\\\$/g, "___ESCAPED_DOLLAR___").replace(/\\\{/g, "___ESCAPED_LBRACE___").replace(/\\\}/g, "___ESCAPED_RBRACE___").replace(/\\\\/g, "___ESCAPED_BACKSLASH___");
  }
  function unescapeSpecialChars(text) {
    return text.replace(/___ESCAPED_DOLLAR___/g, "\\$").replace(/___ESCAPED_LBRACE___/g, "\\{").replace(/___ESCAPED_RBRACE___/g, "\\}").replace(/___ESCAPED_BACKSLASH___/g, "\\\\");
  }
  function extractVariablePlaceholders(text, startIndex = 0) {
    const variablePlaceholders = [];
    let placeholderIndex = startIndex;
    const result = text.replace(/\$\{([^}]+)\}/g, (match, varString) => {
      const placeholder = `___VAR_PLACEHOLDER_${placeholderIndex}___`;
      variablePlaceholders.push({ placeholder, varString, match });
      placeholderIndex++;
      return placeholder;
    });
    return { result, variablePlaceholders };
  }
  function compileSimplePattern(pattern) {
    let regex = "";
    let i = 0;
    while (i < pattern.length) {
      const char = pattern[i];
      const nextChar = pattern[i + 1];
      if (char === "*") {
        regex += ".*?";
        i++;
      } else if (char === "#") {
        if (nextChar === "+") {
          regex += "\\d+";
          i += 2;
        } else {
          let count = 1;
          while (pattern[i + count] === "#") {
            count++;
          }
          if (count > 1) {
            regex += `\\d{${count}}`;
            i += count;
          } else {
            regex += "\\d";
            i++;
          }
        }
      } else if (char === "@") {
        if (nextChar === "+") {
          regex += "[a-zA-Z]+";
          i += 2;
        } else {
          let count = 1;
          while (pattern[i + count] === "@") {
            count++;
          }
          if (count > 1) {
            regex += `[a-zA-Z]{${count}}`;
            i += count;
          } else {
            regex += "[a-zA-Z]";
            i++;
          }
        }
      } else if (char === "?") {
        regex += ".";
        i++;
      } else {
        regex += char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
        i++;
      }
    }
    return regex;
  }
  function determineCaptureGroup(varName, hint, isLastVariable, afterPlaceholder, availableHints = {}) {
    const hintPattern = hint ? compileHintToRegex(hint, availableHints) : null;
    if (hintPattern) {
      return `(?<${varName}>${hintPattern})`;
    }
    if (isLastVariable || !afterPlaceholder) {
      return `(?<${varName}>.+)`;
    }
    const nextTwoChars = afterPlaceholder.substring(0, 2);
    const nextChar = nextTwoChars[0];
    if (nextChar === " ") {
      const afterSpace = afterPlaceholder.substring(1);
      const boundaryMatch = afterSpace.match(/^(\\?.)/);
      const boundaryChar = boundaryMatch ? boundaryMatch[1] : null;
      if (boundaryChar && boundaryChar.startsWith("\\") && boundaryChar.length === 2) {
        const actualChar = boundaryChar[1];
        if (actualChar === "]") {
          return `(?<${varName}>[^\\]]+)`;
        }
        return `(?<${varName}>[^\\${actualChar}]+)`;
      }
      if (boundaryChar) {
        return `(?<${varName}>[^${boundaryChar}]+)`;
      }
      return `(?<${varName}>[^ ]+)`;
    }
    if (nextTwoChars.startsWith("\\") && nextTwoChars.length >= 2) {
      const escapedChar = nextTwoChars[1];
      if (escapedChar === "]") {
        return `(?<${varName}>[^\\]]+)`;
      }
      if (escapedChar === "[" || escapedChar === "(" || escapedChar === ")" || escapedChar === "." || escapedChar === "-" || escapedChar === "_") {
        return `(?<${varName}>[^\\${escapedChar}]+)`;
      }
    }
    return `(?<${varName}>.+?)`;
  }
  function determineCaptureGroupWithOptionals(varName, hint, isLastVariable, afterPlaceholder, availableHints = {}) {
    const hintPattern = hint ? compileHintToRegex(hint, availableHints) : null;
    if (hintPattern) {
      return `(?<${varName}>${hintPattern})`;
    }
    if (isLastVariable || !afterPlaceholder) {
      return `(?<${varName}>.+)`;
    }
    const nextFourChars = afterPlaceholder.substring(0, 4);
    const nextTwoChars = afterPlaceholder.substring(0, 2);
    const atEndOfOptional = nextTwoChars === ")?";
    if (atEndOfOptional) {
      const afterOptional = afterPlaceholder.substring(2);
      if (afterOptional.startsWith("(?:")) {
        const nextOptionalMatch = afterOptional.match(/^\(\?:\(\?<_opt\d+>\)(.+?)\)\?/);
        if (nextOptionalMatch) {
          const nextOptionalContent = nextOptionalMatch[1];
          const literalMatch = nextOptionalContent.match(/^([^_]+?)___VAR/);
          const firstLiteral = literalMatch ? literalMatch[1] : nextOptionalContent;
          if (firstLiteral && firstLiteral.trim()) {
            const escapedLiteral = firstLiteral.replace(/\\/g, "\\");
            return `(?<${varName}>(?:(?!${escapedLiteral}).)+)`;
          }
        }
      }
      return `(?<${varName}>.+)`;
    }
    if (nextFourChars.startsWith("(?:")) {
      const boundaries = [];
      let remaining = afterPlaceholder;
      while (remaining.startsWith("(?:")) {
        const optionalMatch = remaining.match(/^\(\?:\(\?<_opt\d+>\)(.+?)\)\?/);
        if (optionalMatch) {
          const optionalContent = optionalMatch[1];
          const literalMatch = optionalContent.match(/^([^_]+?)___VAR/);
          const firstLiteral = literalMatch ? literalMatch[1] : optionalContent.substring(0, 10);
          if (firstLiteral && firstLiteral.trim()) {
            boundaries.push(firstLiteral.replace(/\\/g, "\\"));
          }
          remaining = remaining.substring(optionalMatch[0].length);
        } else {
          break;
        }
      }
      if (boundaries.length > 0) {
        const lookaheads = boundaries.map((b) => `(?!${b})`).join("");
        return `(?<${varName}>(?:${lookaheads}.)+)`;
      }
      return `(?<${varName}>.+?)`;
    }
    return determineCaptureGroup(varName, hint, false, afterPlaceholder, availableHints);
  }
  function applyValueMap(variables, mask, availableHints = {}) {
    const mapped = {};
    const varPattern = /\$\{([^}]+)\}/g;
    let match;
    while ((match = varPattern.exec(mask)) !== null) {
      const { varName, hint } = parseVariableWithHint(match[1]);
      if (hint && variables[varName] !== void 0) {
        const parsed = parseHint(hint, availableHints);
        if (parsed.type === "map" && parsed.data.mappings) {
          const mappedValue = parsed.data.mappings[variables[varName]];
          if (mappedValue !== void 0) {
            mapped[varName] = mappedValue;
          } else if (parsed.data.strict === false) {
            mapped[varName] = variables[varName];
          }
        } else {
          mapped[varName] = variables[varName];
        }
      } else if (variables[varName] !== void 0) {
        mapped[varName] = variables[varName];
      }
    }
    return mapped;
  }
  function compileMaskToRegexPattern(mask, useNonGreedy = true, availableHints = {}) {
    let regexPattern = escapeSpecialChars(mask);
    const { result, variablePlaceholders } = extractVariablePlaceholders(regexPattern);
    regexPattern = result;
    regexPattern = regexPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    for (let i = 0; i < variablePlaceholders.length; i++) {
      const { placeholder, varString } = variablePlaceholders[i];
      const { varName, hint } = parseVariableWithHint(varString);
      const isLastVariable = i === variablePlaceholders.length - 1;
      const escapedPlaceholder = placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
      const placeholderPos = regexPattern.indexOf(escapedPlaceholder);
      const afterPlaceholder = regexPattern.substring(placeholderPos + escapedPlaceholder.length);
      const captureGroup = determineCaptureGroup(varName, hint, isLastVariable, afterPlaceholder, availableHints);
      regexPattern = regexPattern.replace(escapedPlaceholder, captureGroup);
    }
    regexPattern = unescapeSpecialChars(regexPattern);
    return regexPattern;
  }
  function validateMaskWithDetails(mask, availableHints = {}) {
    if (!mask) {
      return {
        valid: true,
        errors: [],
        warnings: [],
        info: [],
        variables: { valid: [], invalid: [], reserved: [] }
      };
    }
    const errors = [];
    const warnings = [];
    const info = [];
    const validVars = [];
    const invalidVars = [];
    const reservedVars = [];
    const seenVars = /* @__PURE__ */ new Set();
    const duplicates = /* @__PURE__ */ new Set();
    try {
      const parsed = parseMaskStructure(mask);
      if (parsed.optionalCount > 0) {
        info.push({ type: "info", message: `${parsed.optionalCount} optional block${parsed.optionalCount === 1 ? "" : "s"} defined` });
      }
    } catch (e) {
      const posMatch = e.message.match(/position (\d+)/);
      const position = posMatch ? parseInt(posMatch[1], 10) : 0;
      const rangeEnd = e.rangeEnd !== void 0 ? e.rangeEnd : position + 2;
      errors.push({ type: "error", message: e.message, position, rangeEnd });
    }
    const unclosedPattern = /\$\{[^}]*$/;
    if (unclosedPattern.test(mask)) {
      const position = mask.lastIndexOf("${");
      const rangeEnd = mask.length;
      errors.push({ type: "error", message: 'Unclosed variable: missing closing brace "}"', position, rangeEnd });
    }
    const emptyVarPattern = /\$\{\s*\}/g;
    let emptyMatch;
    while ((emptyMatch = emptyVarPattern.exec(mask)) !== null) {
      const position = emptyMatch.index;
      const rangeEnd = position + emptyMatch[0].length;
      errors.push({ type: "error", message: "Empty variable: ${}", position, rangeEnd });
    }
    const nestedPattern = /\$\{[^}]*\$\{/g;
    let nestedMatch;
    while ((nestedMatch = nestedPattern.exec(mask)) !== null) {
      const position = nestedMatch.index;
      const rangeEnd = nestedMatch.index + nestedMatch[0].length;
      errors.push({ type: "error", message: "Nested braces are not allowed", position, rangeEnd });
    }
    const varPattern = /\$\{([^}]+)\}/g;
    let match;
    const varPositions = /* @__PURE__ */ new Map();
    while ((match = varPattern.exec(mask)) !== null) {
      const fullVarString = match[1];
      const { varName, hint } = parseVariableWithHint(fullVarString);
      const position = match.index;
      if (fullVarString !== fullVarString.trim()) {
        warnings.push({ type: "warning", message: `Variable "\${${fullVarString}}" has leading or trailing whitespace`, position });
      }
      if (!/^[a-zA-Z0-9_]+$/.test(varName)) {
        invalidVars.push(varName);
        const rangeEnd = position + match[0].length;
        errors.push({ type: "error", message: `Invalid variable name "\${${varName}}": only letters, numbers, and underscores allowed`, position, rangeEnd });
        continue;
      }
      if (varName.startsWith("_")) {
        reservedVars.push(varName);
        warnings.push({ type: "warning", message: `Variable "\${${varName}}" uses reserved prefix "_" (reserved for comment variables)`, position });
        continue;
      }
      if (hint) {
        const parsed = parseHint(hint, availableHints);
        if (parsed.type === "unknown") {
          warnings.push({ type: "warning", message: `Unknown hint "${hint}" for variable "\${${varName}}" - will be treated as literal pattern`, position });
        } else if (parsed.type === "regex") {
          try {
            new RegExp(parsed.data);
          } catch (e) {
            errors.push({ type: "error", message: `Invalid regex pattern in hint for "\${${varName}}": ${e.message}`, position, rangeEnd: position + match[0].length });
          }
        }
      }
      if (/^\d/.test(varName)) {
        warnings.push({ type: "warning", message: `Variable "\${${varName}}" starts with a number (potentially confusing)`, position });
      }
      if (varName.length > MAX_VARIABLE_NAME_LENGTH) {
        warnings.push({ type: "warning", message: `Variable "\${${varName}}" is very long (${varName.length} characters)`, position });
      }
      if (seenVars.has(varName)) {
        duplicates.add(varName);
        if (!varPositions.has(varName)) {
          varPositions.set(varName, position);
        }
      } else {
        seenVars.add(varName);
        varPositions.set(varName, position);
      }
      validVars.push(varName);
    }
    if (duplicates.size > 0) {
      const firstDuplicatePos = Math.min(...Array.from(duplicates).map((v) => varPositions.get(v)));
      warnings.push({ type: "warning", message: `Duplicate variables: ${Array.from(duplicates).map((v) => `\${${v}}`).join(", ")}`, position: firstDuplicatePos });
    }
    const totalVars = validVars.length + reservedVars.length;
    if (totalVars > 0) {
      info.push({ type: "info", message: `${totalVars} variable${totalVars === 1 ? "" : "s"} defined` });
    }
    if (totalVars === 0 && mask.length > 0) {
      info.push({ type: "info", message: "No variables defined. Add variables like ${name} to extract data." });
    }
    return {
      valid: errors.length === 0,
      errors,
      warnings,
      info,
      variables: { valid: validVars, invalid: invalidVars, reserved: reservedVars }
    };
  }
  function interpolate(template, data, commentVariables = {}) {
    if (!template) return template;
    const allData = { ...data, ...commentVariables };
    return template.replace(/\$\{([^}]+)\}/g, (match, key) => {
      const value = allData[key];
      return value !== void 0 && value !== null && value !== "" ? value : "";
    });
  }
  function findMatchingOption(options, variableValue, matchType) {
    if (!options || !variableValue) return null;
    const normalizedValue = variableValue.toLowerCase();
    for (const option of options) {
      const optionText = option.textContent ? option.textContent.toLowerCase() : option.text.toLowerCase();
      const optionValue = option.value.toLowerCase();
      let matches = false;
      switch (matchType) {
        case "exact":
          matches = optionText === normalizedValue || optionValue === normalizedValue;
          break;
        case "contains":
          matches = optionText.includes(normalizedValue) || optionValue.includes(normalizedValue);
          break;
        case "starts":
          matches = optionText.startsWith(normalizedValue) || optionValue.startsWith(normalizedValue);
          break;
        case "ends":
          matches = optionText.endsWith(normalizedValue) || optionValue.endsWith(normalizedValue);
          break;
      }
      if (matches) {
        return {
          value: option.value,
          text: option.textContent || option.text
        };
      }
    }
    return null;
  }
  function parseMaskStructure(mask) {
    if (!mask) {
      return { parts: [], optionalCount: 0 };
    }
    const parts = [];
    let current = "";
    let i = 0;
    let optionalCount = 0;
    let inOptional = false;
    let optionalStart = -1;
    while (i < mask.length) {
      if (mask[i] === "\\" && i + 1 < mask.length) {
        current += mask.slice(i, i + 2);
        i += 2;
        continue;
      }
      if (mask[i] === "{" && mask[i + 1] === "?") {
        if (inOptional) {
          let nestedEnd = i + 2;
          while (nestedEnd < mask.length) {
            if (mask[nestedEnd] === "\\" && nestedEnd + 1 < mask.length) {
              nestedEnd += 2;
              continue;
            }
            if (mask[nestedEnd] === "?" && mask[nestedEnd + 1] === "}") {
              nestedEnd += 2;
              break;
            }
            nestedEnd++;
          }
          const error = new Error(`Nested optional blocks not allowed at position ${i}`);
          error.rangeEnd = nestedEnd;
          throw error;
        }
        if (current) {
          parts.push({ type: "required", content: current });
          current = "";
        }
        inOptional = true;
        optionalStart = i;
        i += 2;
        continue;
      }
      if (mask[i] === "?" && mask[i + 1] === "}" && inOptional) {
        if (current.trim() === "" && current === "") {
          throw new Error(`Empty optional block at position ${optionalStart}`);
        }
        parts.push({ type: "optional", content: current });
        current = "";
        inOptional = false;
        optionalCount++;
        i += 2;
        continue;
      }
      current += mask[i];
      i++;
    }
    if (inOptional) {
      throw new Error(`Unclosed optional block starting at position ${optionalStart}`);
    }
    if (current) {
      parts.push({ type: "required", content: current });
    }
    return { parts, optionalCount };
  }
  function parseTemplateWithOptionals(mask, torrentName, availableHints = {}) {
    if (!mask || !torrentName) return {};
    try {
      const parsed = parseMaskStructure(mask);
      const regexPattern = compileUserMaskToRegex(mask, availableHints);
      const regex = new RegExp(regexPattern, "i");
      const match = torrentName.match(regex);
      if (!match) return {};
      const extracted = match.groups || {};
      const matchedOptionals = [];
      if (parsed.optionalCount > 0) {
        for (let i = 0; i < parsed.optionalCount; i++) {
          const markerKey = `_opt${i}`;
          matchedOptionals.push(extracted[markerKey] !== void 0);
          delete extracted[markerKey];
        }
      }
      const result = applyValueMap(extracted, mask, availableHints);
      if (parsed.optionalCount > 0) {
        result._matchedOptionals = matchedOptionals;
        result._optionalCount = parsed.optionalCount;
      }
      return result;
    } catch (e) {
      console.warn("Invalid template with optionals:", e);
      return {};
    }
  }
  function compileUserMaskToRegex(mask, availableHints = {}) {
    if (!mask) return "";
    try {
      const parsed = parseMaskStructure(mask);
      if (parsed.optionalCount === 0) {
        return compileMaskToRegexPattern(mask, true, availableHints);
      }
      const regexPattern = compileMaskToRegexPatternWithOptionals(parsed, availableHints);
      return regexPattern;
    } catch (e) {
      return compileMaskToRegexPattern(mask, true, availableHints);
    }
  }
  function compileMaskToRegexPatternWithOptionals(parsed, availableHints = {}) {
    const parts = parsed.parts;
    const processedParts = [];
    let placeholderIndex = 0;
    for (const part of parts) {
      const escapedContent = escapeSpecialChars(part.content);
      const { result, variablePlaceholders } = extractVariablePlaceholders(escapedContent, placeholderIndex);
      placeholderIndex += variablePlaceholders.length;
      const finalContent = result.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
      processedParts.push({
        type: part.type,
        content: finalContent,
        variablePlaceholders
      });
    }
    let regexPattern = "";
    let optionalIndex = 0;
    for (const part of processedParts) {
      if (part.type === "optional") {
        regexPattern += `(?:(?<_opt${optionalIndex}>)${part.content})?`;
        optionalIndex++;
      } else {
        regexPattern += part.content;
      }
    }
    const allVariablePlaceholders = processedParts.flatMap((p) => p.variablePlaceholders);
    for (let i = 0; i < allVariablePlaceholders.length; i++) {
      const { placeholder, varString } = allVariablePlaceholders[i];
      const { varName, hint } = parseVariableWithHint(varString);
      const isLastVariable = i === allVariablePlaceholders.length - 1;
      const escapedPlaceholder = placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
      const placeholderPos = regexPattern.indexOf(escapedPlaceholder);
      const afterPlaceholder = regexPattern.substring(placeholderPos + escapedPlaceholder.length);
      const captureGroup = determineCaptureGroupWithOptionals(varName, hint, isLastVariable, afterPlaceholder, availableHints);
      regexPattern = regexPattern.replace(escapedPlaceholder, captureGroup);
    }
    regexPattern = unescapeSpecialChars(regexPattern);
    return regexPattern;
  }
  function testMaskAgainstSamples(mask, sampleNames, availableHints = {}) {
    const validation = validateMaskWithDetails(mask, availableHints);
    const sampleArray = Array.isArray(sampleNames) ? sampleNames : sampleNames.split("\n").map((s) => s.trim()).filter((s) => s);
    return {
      validation,
      results: sampleArray.map((name) => {
        try {
          const parsed = parseTemplateWithOptionals(mask, name, availableHints);
          const variables = parsed;
          const matched = Object.keys(variables).length > 0;
          const positions = {};
          if (matched) {
            for (const [varName, value] of Object.entries(variables)) {
              const index = name.indexOf(value);
              if (index !== -1) {
                positions[varName] = { start: index, end: index + value.length };
              }
            }
          }
          return {
            name,
            matched,
            variables,
            positions
          };
        } catch (e) {
          return {
            name,
            matched: false,
            variables: {},
            positions: {},
            error: e.message
          };
        }
      })
    };
  }
  function updateMaskHighlighting(maskInput, overlayDiv, availableHints = {}) {
    if (!maskInput || !overlayDiv) return;
    const text = maskInput.value;
    const varPattern = /\$\{([^}]*)\}?/g;
    const optionalBlocks = findOptionalBlocks(text);
    const nestedOptionalErrors = findNestedOptionalErrors(text);
    const varMatches = [];
    let match;
    while ((match = varPattern.exec(text)) !== null) {
      varMatches.push({ match, index: match.index });
    }
    let highlightedHTML = buildLayeredHighlighting(text, optionalBlocks, varMatches, nestedOptionalErrors, availableHints);
    overlayDiv.innerHTML = highlightedHTML;
    overlayDiv.scrollTop = maskInput.scrollTop;
    overlayDiv.scrollLeft = maskInput.scrollLeft;
  }
  function buildLayeredHighlighting(text, optionalBlocks, varMatches, nestedOptionalErrors, availableHints = {}) {
    let result = "";
    const segments = [];
    for (let i = 0; i < text.length; i++) {
      const inOptional = optionalBlocks.find((block) => i >= block.start && i < block.end);
      const varMatch = varMatches.find((v) => i >= v.index && i < v.index + v.match[0].length);
      const inNestedError = nestedOptionalErrors.find((err) => i >= err.start && i < err.end);
      const currentSegment = segments[segments.length - 1];
      if (currentSegment && currentSegment.inOptional === !!inOptional && currentSegment.varMatch === varMatch && currentSegment.inNestedError === !!inNestedError) {
        currentSegment.end = i + 1;
      } else {
        segments.push({
          start: i,
          end: i + 1,
          inOptional: !!inOptional,
          varMatch,
          inNestedError: !!inNestedError
        });
      }
    }
    for (const segment of segments) {
      const content = text.slice(segment.start, segment.end);
      let html = escapeHtml$1(content);
      if (segment.inNestedError) {
        if (segment.inOptional) {
          html = `<span class="gut-highlight-optional"><span class="gut-highlight-error">${html}</span></span>`;
        } else {
          html = `<span class="gut-highlight-error">${html}</span>`;
        }
      } else if (segment.varMatch) {
        const varName = segment.varMatch.match[1];
        const fullMatch = segment.varMatch.match[0];
        const isUnclosed = !fullMatch.endsWith("}");
        const isEmpty = varName.trim() === "";
        const isInvalid = varName && !/^[a-zA-Z0-9_]+(?::[^}]+)?$/.test(varName.trim());
        const isReserved = varName.trim().startsWith("_");
        let varClass = "gut-highlight-variable";
        if (isUnclosed || isEmpty) {
          varClass = "gut-highlight-error";
        } else if (isInvalid) {
          varClass = "gut-highlight-error";
        } else if (isReserved) {
          varClass = "gut-highlight-warning";
        }
        const hintData = getHintDataAttributes(varName, availableHints);
        if (segment.inOptional) {
          html = `<span class="gut-highlight-optional"><span class="${varClass}"${hintData}>${html}</span></span>`;
        } else {
          html = `<span class="${varClass}"${hintData}>${html}</span>`;
        }
      } else if (segment.inOptional) {
        html = `<span class="gut-highlight-optional">${html}</span>`;
      }
      result += html;
    }
    return result;
  }
  function findOptionalBlocks(text) {
    const blocks = [];
    let i = 0;
    while (i < text.length) {
      if (text[i] === "\\" && i + 1 < text.length) {
        i += 2;
        continue;
      }
      if (text[i] === "{" && text[i + 1] === "?") {
        const start = i;
        i += 2;
        let depth = 1;
        while (i < text.length && depth > 0) {
          if (text[i] === "\\" && i + 1 < text.length) {
            i += 2;
            continue;
          }
          if (text[i] === "{" && text[i + 1] === "?") {
            depth++;
            i += 2;
          } else if (text[i] === "?" && text[i + 1] === "}") {
            depth--;
            if (depth === 0) {
              i += 2;
              blocks.push({ start, end: i });
              break;
            }
            i += 2;
          } else {
            i++;
          }
        }
        if (depth > 0) {
          blocks.push({ start, end: text.length });
        }
      } else {
        i++;
      }
    }
    return blocks;
  }
  function findNestedOptionalErrors(text) {
    const errors = [];
    let i = 0;
    let inOptional = false;
    while (i < text.length) {
      if (text[i] === "\\" && i + 1 < text.length) {
        i += 2;
        continue;
      }
      if (text[i] === "{" && text[i + 1] === "?") {
        if (inOptional) {
          const nestedStart = i;
          i += 2;
          let nestedEnd = i;
          while (nestedEnd < text.length) {
            if (text[nestedEnd] === "\\" && nestedEnd + 1 < text.length) {
              nestedEnd += 2;
              continue;
            }
            if (text[nestedEnd] === "?" && text[nestedEnd + 1] === "}") {
              nestedEnd += 2;
              break;
            }
            nestedEnd++;
          }
          errors.push({ start: nestedStart, end: nestedEnd });
          continue;
        }
        inOptional = true;
        i += 2;
        continue;
      }
      if (text[i] === "?" && text[i + 1] === "}") {
        inOptional = false;
        i += 2;
        continue;
      }
      i++;
    }
    return errors;
  }
  const ICON_ERROR = '<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="7" cy="7" r="6" stroke="currentColor" stroke-width="1.5"/><path d="M4.5 4.5L9.5 9.5M9.5 4.5L4.5 9.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>';
  const ICON_WARNING = '<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7 1L13 12H1L7 1Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M7 5.5V8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="10" r="0.5" fill="currentColor"/></svg>';
  const ICON_INFO = '<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="7" cy="7" r="6" stroke="currentColor" stroke-width="1.5"/><path d="M7 6.5V10.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="7" cy="4.5" r="0.5" fill="currentColor"/></svg>';
  function renderStatusMessages(container, validation) {
    if (!container || !validation) return;
    const { errors, warnings, info, valid } = validation;
    const messages = [...errors, ...warnings, ...info];
    if (messages.length === 0 && valid) {
      container.innerHTML = `<div class="gut-status-message gut-status-info">${ICON_INFO} Add variables like \${name} to extract data.</div>`;
      container.classList.add("visible");
      return;
    }
    if (messages.length === 0) {
      container.innerHTML = "";
      container.classList.remove("visible");
      return;
    }
    const sortedMessages = messages.sort((a, b) => {
      if (a.position !== void 0 && b.position !== void 0) {
        return a.position - b.position;
      }
      if (a.position !== void 0) return -1;
      if (b.position !== void 0) return 1;
      const priority = { error: 0, warning: 1, info: 2 };
      return priority[a.type] - priority[b.type];
    });
    const priorityMessage = sortedMessages.slice(0, 3);
    const html = priorityMessage.map((msg) => {
      let className = "gut-status-message";
      let icon = "";
      switch (msg.type) {
        case "error":
          className += " gut-status-error";
          icon = ICON_ERROR;
          break;
        case "warning":
          className += " gut-status-warning";
          icon = ICON_WARNING;
          break;
        case "info":
          className += " gut-status-info";
          icon = ICON_INFO;
          break;
      }
      return `<div class="${className}">${icon} ${escapeHtml$1(msg.message)}</div>`;
    }).join("");
    if (sortedMessages.length > 3) {
      const remaining = sortedMessages.length - 3;
      const remainingHtml = `<div class="gut-status-message gut-status-info">+ ${remaining} more message${remaining === 1 ? "" : "s"}</div>`;
      container.innerHTML = html + remainingHtml;
    } else {
      container.innerHTML = html;
    }
    container.classList.add("visible");
  }
  function getHintDataAttributes(varString, availableHints = {}) {
    if (!varString || !varString.includes(":")) {
      return "";
    }
    const colonIndex = varString.indexOf(":");
    const hint = varString.substring(colonIndex + 1);
    if (!hint) return "";
    let hintType = "";
    let hintPattern = "";
    if (hint.startsWith("/")) {
      hintType = "regex";
      hintPattern = hint.slice(1).replace(/\/$/, "");
    } else if (/[*#@?]/.test(hint)) {
      hintType = "pattern";
      hintPattern = hint;
    } else if (availableHints[hint]) {
      const namedHint = availableHints[hint];
      hintType = namedHint.type;
      if (namedHint.type === "pattern") {
        hintPattern = namedHint.pattern;
      } else if (namedHint.type === "regex") {
        hintPattern = namedHint.pattern;
      } else if (namedHint.type === "map" && namedHint.mappings) {
        hintPattern = `${Object.keys(namedHint.mappings).length} values`;
      }
    }
    if (hintType && hintPattern) {
      const escapedType = escapeHtml$1(hintType);
      const escapedPattern = escapeHtml$1(hintPattern);
      return ` data-hint-type="${escapedType}" data-hint-pattern="${escapedPattern}"`;
    }
    return "";
  }
  function escapeHtml$1(text) {
    const div = document.createElement("div");
    div.textContent = text;
    return div.innerHTML;
  }
  const DEFAULT_HINTS = {
    number: {
      type: "pattern",
      pattern: "#+",
      description: "Digits only"
    },
    alpha: {
      type: "pattern",
      pattern: "@+",
      description: "Letters only"
    },
    beta: {
      type: "pattern",
      pattern: "@+",
      description: "Letters only"
    },
    alnum: {
      type: "pattern",
      pattern: "*",
      description: "Alphanumeric characters"
    },
    version: {
      type: "regex",
      pattern: "v\\d+(?:\\.\\d+)*",
      description: 'Version numbers starting with "v" (e.g., v1, v2.0)'
    },
    date_ymd_dots: {
      type: "pattern",
      pattern: "####.##.##",
      description: "Date in YYYY.MM.DD format"
    },
    date_ymd_dashes: {
      type: "pattern",
      pattern: "####-##-##",
      description: "Date in YYYY-MM-DD format"
    },
    date_dmy_dots: {
      type: "pattern",
      pattern: "##.##.####",
      description: "Date in DD.MM.YYYY format"
    },
    date_dmy_dashes: {
      type: "pattern",
      pattern: "##-##-####",
      description: "Date in DD-MM-YYYY format"
    },
    date_mdy_dots: {
      type: "pattern",
      pattern: "##.##.####",
      description: "Date in MM.DD.YYYY format"
    },
    date_mdy_dashes: {
      type: "pattern",
      pattern: "##-##-####",
      description: "Date in MM-DD-YYYY format"
    },
    lang_codes: {
      type: "map",
      description: "Common language codes to full names",
      strict: false,
      mappings: {
        "en-US": "English",
        "en-GB": "English",
        en: "English",
        "fr-FR": "French",
        fr: "French",
        "de-DE": "German",
        de: "German",
        "es-ES": "Spanish",
        es: "Spanish",
        "it-IT": "Italian",
        it: "Italian",
        "ja-JP": "Japanese",
        ja: "Japanese",
        "zh-CN": "Chinese",
        zh: "Chinese",
        "ko-KR": "Korean",
        ko: "Korean",
        "pt-BR": "Portuguese",
        pt: "Portuguese",
        "ru-RU": "Russian",
        ru: "Russian",
        ar: "Arabic",
        nl: "Dutch",
        pl: "Polish",
        sv: "Swedish",
        no: "Norwegian",
        da: "Danish",
        fi: "Finnish",
        tr: "Turkish",
        el: "Greek",
        he: "Hebrew",
        th: "Thai",
        vi: "Vietnamese",
        id: "Indonesian",
        ms: "Malay",
        hi: "Hindi"
      }
    }
  };
  function loadHints() {
    try {
      const stored = GM_getValue("hints", null);
      return stored ? JSON.parse(stored) : { ...DEFAULT_HINTS };
    } catch (e) {
      console.error("Failed to load hints:", e);
      return { ...DEFAULT_HINTS };
    }
  }
  function saveHints(hints) {
    try {
      GM_setValue("hints", JSON.stringify(hints));
      return true;
    } catch (e) {
      console.error("Failed to save hints:", e);
      return false;
    }
  }
  function resetAllHints() {
    try {
      GM_setValue("hints", JSON.stringify({ ...DEFAULT_HINTS }));
      return true;
    } catch (e) {
      console.error("Failed to reset hints:", e);
      return false;
    }
  }
  function isDefaultHint(name) {
    return !!DEFAULT_HINTS[name];
  }
  function loadIgnoredHints() {
    try {
      const stored = GM_getValue("ignoredHints", null);
      return stored ? JSON.parse(stored) : [];
    } catch (e) {
      console.error("Failed to load ignored hints:", e);
      return [];
    }
  }
  function saveIgnoredHints(ignoredHints) {
    try {
      GM_setValue("ignoredHints", JSON.stringify(ignoredHints));
      return true;
    } catch (e) {
      console.error("Failed to save ignored hints:", e);
      return false;
    }
  }
  function addToIgnoredHints(hintName) {
    const ignoredHints = loadIgnoredHints();
    if (!ignoredHints.includes(hintName)) {
      ignoredHints.push(hintName);
      return saveIgnoredHints(ignoredHints);
    }
    return true;
  }
  function removeFromIgnoredHints(hintName) {
    const ignoredHints = loadIgnoredHints();
    const filtered = ignoredHints.filter((name) => name !== hintName);
    return saveIgnoredHints(filtered);
  }
  function isHintIgnored(hintName) {
    const ignoredHints = loadIgnoredHints();
    return ignoredHints.includes(hintName);
  }
  function loadDeletedDefaultHints() {
    try {
      const stored = GM_getValue("deletedDefaultHints", null);
      return stored ? JSON.parse(stored) : [];
    } catch (e) {
      console.error("Failed to load deleted default hints:", e);
      return [];
    }
  }
  function saveDeletedDefaultHints(deletedHints) {
    try {
      GM_setValue("deletedDefaultHints", JSON.stringify(deletedHints));
      return true;
    } catch (e) {
      console.error("Failed to save deleted default hints:", e);
      return false;
    }
  }
  function addToDeletedDefaultHints(hintName) {
    const deletedHints = loadDeletedDefaultHints();
    if (!deletedHints.includes(hintName)) {
      deletedHints.push(hintName);
      return saveDeletedDefaultHints(deletedHints);
    }
    return true;
  }
  function removeFromDeletedDefaultHints(hintName) {
    const deletedHints = loadDeletedDefaultHints();
    const filtered = deletedHints.filter((name) => name !== hintName);
    return saveDeletedDefaultHints(filtered);
  }
  function getNewDefaultHints(userHints) {
    const newHints = {};
    const ignoredHints = loadIgnoredHints();
    const deletedHints = loadDeletedDefaultHints();
    for (const [name, def] of Object.entries(DEFAULT_HINTS)) {
      if (!userHints[name] && !ignoredHints.includes(name) && !deletedHints.includes(name)) {
        newHints[name] = def;
      }
    }
    return newHints;
  }
  const MODAL_HTML = (instance) => `
  <div class="gut-modal-content">
    <div class="gut-modal-header">
      <button class="gut-modal-close-btn" id="modal-close-x" title="Close">&times;</button>
      <div class="gut-modal-tabs">
        <button class="gut-tab-btn active" data-tab="templates">Templates</button>
        <button class="gut-tab-btn" data-tab="hints">Variable Hints</button>
        <button class="gut-tab-btn" data-tab="sandbox">Mask Sandbox</button>
        <button class="gut-tab-btn" data-tab="settings">Settings</button>
      </div>
    </div>

    <div class="gut-modal-body">
      <div class="gut-tab-content active" id="templates-tab">
      ${Object.keys(instance.templates).length === 0 ? '<div style="padding: 20px; text-align: center; color: #888;">No templates found. Create a template first.</div>' : `<div class="gut-template-list">
            ${Object.keys(instance.templates).map(
    (name) => `
                <div class="gut-template-item">
                  <span class="gut-template-name">${instance.escapeHtml(name)}</span>
                  <div class="gut-template-actions">
                    <button class="gut-btn gut-btn-secondary gut-btn-small" data-action="edit" data-template="${instance.escapeHtml(name)}">Edit</button>
                    <button class="gut-btn gut-btn-secondary gut-btn-small" data-action="clone" data-template="${instance.escapeHtml(name)}">Clone</button>
                    <button class="gut-btn gut-btn-danger gut-btn-small" data-action="delete" data-template="${instance.escapeHtml(name)}">Delete</button>
                  </div>
                </div>
              `
  ).join("")}
          </div>`}
    </div>

    <div class="gut-tab-content" id="settings-tab">
      <div class="gut-form-group">
        <label for="setting-form-selector">Target Form Selector:</label>
        <input type="text" id="setting-form-selector" value="${instance.escapeHtml(instance.config.TARGET_FORM_SELECTOR)}" placeholder="#upload_table">
      </div>

       <div class="gut-form-group">
         <div class="gut-keybinding-controls">
           <label class="gut-checkbox-label">
             <input type="checkbox" id="setting-submit-keybinding" ${instance.config.SUBMIT_KEYBINDING ? "checked" : ""}>
             <span class="gut-checkbox-text">\u26A1 Enable form submission keybinding: <span class="gut-keybinding-text">${instance.config.CUSTOM_SUBMIT_KEYBINDING || "Ctrl+Enter"}</span></span>
           </label>
           <button type="button" id="record-submit-keybinding-btn" class="gut-btn gut-btn-secondary gut-btn-small">Record</button>
         </div>
         <input type="hidden" id="custom-submit-keybinding-input" value="${instance.config.CUSTOM_SUBMIT_KEYBINDING || "Ctrl+Enter"}">
       </div>

       <div class="gut-form-group">
         <div class="gut-keybinding-controls">
           <label class="gut-checkbox-label">
             <input type="checkbox" id="setting-apply-keybinding" ${instance.config.APPLY_KEYBINDING ? "checked" : ""}>
             <span class="gut-checkbox-text">\u26A1 Enable apply template keybinding: <span class="gut-keybinding-text">${instance.config.CUSTOM_APPLY_KEYBINDING || "Ctrl+Shift+A"}</span></span>
           </label>
           <button type="button" id="record-apply-keybinding-btn" class="gut-btn gut-btn-secondary gut-btn-small">Record</button>
         </div>
         <input type="hidden" id="custom-apply-keybinding-input" value="${instance.config.CUSTOM_APPLY_KEYBINDING || "Ctrl+Shift+A"}">
       </div>

      <div class="gut-form-group">
        <label for="setting-custom-selectors">Custom Field Selectors (one per line):</label>
        <textarea id="setting-custom-selectors" rows="4" placeholder="div[data-field]
.custom-input[name]
button[data-value]">${(instance.config.CUSTOM_FIELD_SELECTORS || []).join("\n")}</textarea>
        <div style="font-size: 12px; color: #888; margin-top: 5px;">
          Additional CSS selectors to find form fields. e.g: <a href="#" id="ggn-infobox-link" class="gut-link">GGn Infobox</a>
        </div>
      </div>

      <div class="gut-form-group" id="custom-selectors-preview-group" style="display: none;">
        <label id="matched-elements-label">Matched Elements:</label>
        <div id="custom-selectors-matched" class="gut-extracted-vars">
          <div class="gut-no-variables">No elements matched by custom selectors.</div>
        </div>
      </div>

      <div class="gut-form-group">
        <label for="setting-ignored-fields">Ignored Fields (one per line):</label>
        <textarea id="setting-ignored-fields" rows="6" placeholder="linkgroup
groupid
apikey">${instance.config.IGNORED_FIELDS_BY_DEFAULT.join("\n")}</textarea>
      </div>

      <div class="gut-form-group">
        <div style="display: flex; justify-content: space-between; align-items: center;">
          <div style="display: flex; gap: 10px;">
            <button class="gut-btn gut-btn-primary" id="save-settings">Save Settings</button>
            <button class="gut-btn gut-btn-secondary" id="reset-settings">Reset to Defaults</button>
          </div>
          <button class="gut-btn gut-btn-danger" id="delete-all-config">Delete All Local Config</button>
        </div>
      </div>
    </div>

    ${HINTS_TAB_HTML(instance)}

    ${SANDBOX_TAB_HTML(instance)}
    </div>

    <div class="gut-modal-footer">
      <button class="gut-btn" id="close-manager">Close</button>
    </div>
  </div>
`;
  const VARIABLES_MODAL_HTML = (instance) => `
  <div class="gut-modal-content">
    <div class="gut-modal-header">
      <button class="gut-modal-close-btn" id="modal-close-x" title="Close">&times;</button>
      <h2>Available Variables</h2>
    </div>

    <div class="gut-modal-body">
      <div class="gut-form-group">
        <div id="variables-results-container" class="gut-extracted-vars">
          <div class="gut-no-variables">No variables available. Select a template with a torrent name mask to see extracted variables.</div>
        </div>
      </div>
    </div>

    <div class="gut-modal-footer">
      <button class="gut-btn" id="close-variables-modal">Close</button>
    </div>
  </div>
`;
  const TEMPLATE_SELECTOR_HTML = (instance) => `
  <option value="">Select Template</option>
  ${Object.keys(instance.templates).map(
    (name) => `<option value="${name}" ${name === instance.selectedTemplate ? "selected" : ""}>${name}</option>`
  ).join("")}
`;
  const TEMPLATE_LIST_HTML = (instance) => Object.keys(instance.templates).length === 0 ? '<div style="padding: 20px; text-align: center; color: #888;">No templates found. Close this dialog and create a template first.</div>' : `<div class="gut-template-list">
         ${Object.keys(instance.templates).map(
    (name) => `
             <div class="gut-template-item">
               <span class="gut-template-name">${instance.escapeHtml(name)}</span>
               <div class="gut-template-actions">
                 <button class="gut-btn gut-btn-secondary gut-btn-small" data-action="edit" data-template="${instance.escapeHtml(name)}">Edit</button>
                 <button class="gut-btn gut-btn-secondary gut-btn-small" data-action="clone" data-template="${instance.escapeHtml(name)}">Clone</button>
                 <button class="gut-btn gut-btn-danger gut-btn-small" data-action="delete" data-template="${instance.escapeHtml(name)}">Delete</button>
               </div>
             </div>
           `
  ).join("")}
       </div>`;
  const TEMPLATE_CREATOR_HTML = (formData, instance, editTemplateName, editTemplate2, selectedTorrentName) => `
  <div class="gut-modal-content">
    <div class="gut-modal-header">
      <button class="gut-modal-close-btn" id="modal-close-x" title="Close">&times;</button>
      <h2>
        ${editTemplateName ? '<button class="gut-modal-back-btn" id="back-to-manager" title="Back to Template Manager">&lt;</button>' : ""}
        ${editTemplateName ? "Edit Template" : "Create Template"}
      </h2>
    </div>

    <div class="gut-modal-body">

    <div class="gut-form-group">
      <label for="template-name">Template Name:</label>
      <input type="text" id="template-name" placeholder="e.g., Magazine Template" value="${editTemplateName ? instance.escapeHtml(editTemplateName) : ""}">
    </div>

    <div class="gut-form-group">
      <label for="sample-torrent">Sample Torrent Name (for preview):</label>
      <input type="text" id="sample-torrent" value="${instance.escapeHtml(selectedTorrentName)}" placeholder="e.g., PCWorld - Issue 05 - 01-2024.zip">
    </div>

    <div class="gut-form-group" style="margin-bottom: 8px;">
      <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;">
        <label for="torrent-mask" style="margin-bottom: 0;">Torrent Name Mask:</label>
        <a href="#" id="test-mask-sandbox-link" class="gut-link" style="font-size: 11px;">Test mask in sandbox \u2192</a>
      </div>
      <div class="gut-mask-input-container">
        <div class="gut-mask-highlight-overlay" id="mask-highlight-overlay"></div>
        <input type="text" id="torrent-mask" autocomplete="off" class="gut-mask-input" placeholder="e.g., \${magazine} - Issue \${issue} - \${month}-\${year}.\${ext}" value="${editTemplate2 ? instance.escapeHtml(editTemplate2.mask) : ""}">
      </div>
      <div class="gut-mask-cursor-info" id="mask-cursor-info"></div>
      <div class="gut-mask-status-container" id="mask-status-container"></div>
    </div>

    <div class="gut-form-group">
      <label>Extracted Variables:</label>
      <div id="extracted-variables" class="gut-extracted-vars">
        <div class="gut-no-variables">No variables defined yet. Add variables like \${name} to your mask.</div>
      </div>
    </div>

    <div class="gut-form-group">
      <div style="display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 10px;">
        <label style="margin: 0;">Form Fields:</label>
        <div style="display: flex; align-items: center; gap: 10px;">
          <input type="text" id="field-filter" placeholder="Filter fields..." autocomplete="off" style="padding: 6px 8px; border: 1px solid #404040; border-radius: 3px; background: #2a2a2a; color: #e0e0e0; font-size: 12px; min-width: 150px;">
          <button type="button" class="gut-btn gut-btn-secondary" id="toggle-unselected" style="padding: 6px 12px; font-size: 12px; white-space: nowrap;">Show Unselected</button>
        </div>
      </div>
      <div class="gut-field-list">
        ${Object.entries(formData).map(([name, fieldData]) => {
    const isIgnoredByDefault = instance.config.IGNORED_FIELDS_BY_DEFAULT.includes(
      name.toLowerCase()
    );
    const isInTemplate = editTemplate2 && editTemplate2.fieldMappings.hasOwnProperty(name);
    const templateValue = isInTemplate ? editTemplate2.fieldMappings[name] : null;
    let shouldBeChecked = isInTemplate || !isIgnoredByDefault;
    if (editTemplate2 && editTemplate2.customUnselectedFields) {
      const customField = editTemplate2.customUnselectedFields.find(
        (f) => f.field === name
      );
      if (customField) {
        shouldBeChecked = customField.selected;
      }
    }
    return `
               <div class="gut-field-row ${isIgnoredByDefault && !isInTemplate && !shouldBeChecked ? "gut-hidden" : ""}">
                 ${fieldData.type === "select" ? (() => {
      const hasVariableMatching = editTemplate2 && editTemplate2.variableMatching && editTemplate2.variableMatching[name];
      hasVariableMatching ? editTemplate2.variableMatching[name] : null;
      const isVariableMode = hasVariableMatching;
      return `<div style="display: flex; align-items: flex-start; width: 100%;">
                           <a href="#" class="gut-link gut-variable-toggle" data-field="${name}" data-state="${isVariableMode ? "on" : "off"}">Match from variable: ${isVariableMode ? "ON" : "OFF"}</a>
                         </div>`;
    })() : ""}
                 <input type="checkbox" ${shouldBeChecked ? "checked" : ""} data-field="${name}">
                 <label title="${name}">${fieldData.label}:</label>
                 ${fieldData.type === "select" ? (() => {
      const hasVariableMatching = editTemplate2 && editTemplate2.variableMatching && editTemplate2.variableMatching[name];
      const variableConfig = hasVariableMatching ? editTemplate2.variableMatching[name] : null;
      const isVariableMode = hasVariableMatching;
      return `<div class="gut-select-container" style="display: flex; flex-direction: column; gap: 4px; flex: 1;">
                             <div style="display: flex; flex-direction: column; align-items: flex-end;">
                               <select data-template="${name}" class="template-input gut-select select-static-mode" style="width: 100%; ${isVariableMode ? "display: none;" : ""}">
                                 ${fieldData.options.map((option) => {
        let selected = option.selected;
        if (templateValue && templateValue === option.value) {
          selected = true;
        }
        return `<option value="${instance.escapeHtml(option.value)}" ${selected ? "selected" : ""}>${instance.escapeHtml(option.text)}</option>`;
      }).join("")}
                               </select>
                             </div>
                            <div class="gut-variable-controls" data-field="${name}" style="display: ${isVariableMode ? "flex" : "none"}; gap: 8px;">
                              <select class="gut-match-type" data-field="${name}" style="padding: 6px 8px; border: 1px solid #404040; border-radius: 3px; background: #1a1a1a; color: #e0e0e0; font-size: 12px;">
                              <option value="exact" ${variableConfig && variableConfig.matchType === "exact" ? "selected" : ""}>Is exactly</option>
                              <option value="contains" ${variableConfig && variableConfig.matchType === "contains" ? "selected" : ""}>Contains</option>
                              <option value="starts" ${variableConfig && variableConfig.matchType === "starts" ? "selected" : ""}>Starts with</option>
                              <option value="ends" ${variableConfig && variableConfig.matchType === "ends" ? "selected" : ""}>Ends with</option>
                            </select>
                            <input type="text" class="gut-variable-input" data-field="${name}" placeholder="\${variable_name}" value="${variableConfig ? instance.escapeHtml(variableConfig.variableName) : ""}" style="flex: 1; padding: 6px 8px; border: 1px solid #404040; border-radius: 3px; background: #1a1a1a; color: #e0e0e0; font-size: 12px;">
                            </div>
                          </div>`;
    })() : fieldData.inputType === "checkbox" ? `<input type="checkbox" ${templateValue !== null ? templateValue ? "checked" : "" : fieldData.value ? "checked" : ""} data-template="${name}" class="template-input">` : fieldData.inputType === "radio" ? `<select data-template="${name}" class="template-input gut-select">
                              ${fieldData.radioOptions.map((option) => {
      let selected = option.checked;
      if (templateValue && templateValue === option.value) {
        selected = true;
      }
      return `<option value="${instance.escapeHtml(option.value)}" ${selected ? "selected" : ""}>${instance.escapeHtml(option.label)}</option>`;
    }).join("")}
                            </select>` : fieldData.type === "textarea" ? `<textarea data-template="${name}" class="template-input" rows="4" style="resize: vertical; width: 100%;">${templateValue !== null ? instance.escapeHtml(String(templateValue)) : instance.escapeHtml(String(fieldData.value))}</textarea>` : `<input type="text" value="${templateValue !== null ? instance.escapeHtml(String(templateValue)) : instance.escapeHtml(String(fieldData.value))}" data-template="${name}" class="template-input">`}
                 <span class="gut-preview" data-preview="${name}"></span>
               </div>
             `;
  }).join("")}
      </div>
    </div>
    </div>

    <div class="gut-modal-footer">
      <button class="gut-btn" id="cancel-template">Cancel</button>
      <button class="gut-btn gut-btn-primary" id="save-template">${editTemplateName ? "Update Template" : "Save Template"}</button>
    </div>
  </div>
`;
  const HINTS_TAB_HTML = (instance) => {
    const hints = instance.hints || {};
    const renderHintRow = (name, hint) => {
      const mappingsHtml = hint.type === "map" && hint.mappings ? `
      <div class="gut-hint-mappings-inline">
        <div class="gut-hint-mappings-header">
          <div style="display: flex; align-items: center; gap: 6px; cursor: pointer;" class="gut-hint-mappings-toggle" data-hint="${instance.escapeHtml(name)}">
            <svg class="gut-hint-caret" width="12" height="12" viewBox="0 0 12 12" style="transition: transform 0.2s ease;">
              <path d="M4 3 L8 6 L4 9" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
            </svg>
            <span>${Object.keys(hint.mappings).length} mappings${hint.strict === false ? " (non-strict)" : ""}</span>
          </div>
          <div style="display: flex; gap: 8px; align-items: center;">
            <a href="#" class="gut-link" data-action="mass-edit-mappings" data-hint="${instance.escapeHtml(name)}">Mass Edit</a>
          </div>
        </div>
        <div class="gut-hint-mappings-content" style="display: none; max-height: 0; overflow: hidden; transition: max-height 0.2s ease;">
          <div style="max-height: 200px; overflow-y: auto;">
            ${Object.entries(hint.mappings).map(
        ([key, value]) => `
                <div class="gut-variable-item">
                  <span class="gut-variable-name">${instance.escapeHtml(key)}</span>
                  <span class="gut-variable-value">${instance.escapeHtml(value)}</span>
                </div>
              `
      ).join("")}
          </div>
        </div>
      </div>
    ` : "";
      return `
      <div class="gut-hint-item" data-hint="${instance.escapeHtml(name)}">
        <div class="gut-hint-header">
          <div class="gut-hint-name-group">
            <span class="gut-hint-name">${instance.escapeHtml(name)}</span>
            <span class="gut-hint-type-badge">${hint.type}</span>
          </div>
          <div class="gut-hint-actions">
            <a href="#" class="gut-link" data-action="edit-hint">Edit</a>
            <span class="gut-hint-actions-separator">\u2022</span>
            <a href="#" class="gut-link gut-link-danger" data-action="delete-hint">Delete</a>
          </div>
        </div>
        ${hint.description ? `<div class="gut-hint-description">${instance.escapeHtml(hint.description)}</div>` : ""}
        ${hint.type === "pattern" ? `<div class="gut-hint-pattern"><code>${instance.escapeHtml(hint.pattern)}</code></div>` : ""}
        ${hint.type === "regex" ? `<div class="gut-hint-pattern"><code>/${instance.escapeHtml(hint.pattern)}/</code></div>` : ""}
        ${mappingsHtml}
      </div>
    `;
    };
    const hintRows = Object.entries(hints).map(([name, hint]) => renderHintRow(name, hint)).join("");
    return `
    <div class="gut-tab-content" id="hints-tab">
      <div class="gut-form-group">
        <div style="display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 10px;">
          <input type="text" id="hint-filter-input" class="gut-input" placeholder="Filter hints by name, description, pattern..." style="flex: 1;">
          <button class="gut-btn gut-btn-primary gut-btn-small" id="add-hint-btn">+ Add Hint</button>
        </div>
        <div id="hint-filter-count" style="font-size: 11px; color: #888; margin-top: 5px;"></div>
      </div>

      <div class="gut-form-group">
        <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
          <label>Hints</label>
          <div style="display: flex; gap: 8px; align-items: center;">
            ${(() => {
      const newHints = getNewDefaultHints(instance.hints);
      const newHintCount = Object.keys(newHints).length;
      return newHintCount > 0 ? `<a href="#" class="gut-link" id="import-new-hints-btn">Import New Hints (${newHintCount})</a>` : "";
    })()}
            <a href="#" class="gut-link" id="reset-defaults-btn">Reset Defaults</a>
            <a href="#" class="gut-link" id="delete-all-hints-btn" style="color: #f44336;">Delete All</a>
          </div>
        </div>
        <div class="gut-hints-list" id="hints-list">
          ${hintRows}
        </div>
      </div>
    </div>
  `;
  };
  const SANDBOX_TAB_HTML = (instance) => {
    const savedSets = instance.sandboxSets || {};
    const currentSet = instance.currentSandboxSet || "";
    return `
    <div class="gut-tab-content" id="sandbox-tab">
      <div style="display: flex; flex-direction: column; gap: 8px; margin-bottom: 15px;">
        <div style="display: flex; align-items: center; gap: 8px;">
          <select id="sandbox-set-select" class="gut-select" style="flex: 1;">
            <option value="">New test set</option>
            ${Object.keys(savedSets).map(
      (name) => `<option value="${instance.escapeHtml(name)}" ${name === currentSet ? "selected" : ""}>${instance.escapeHtml(name)}</option>`
    ).join("")}
          </select>
          <button class="gut-btn gut-btn-secondary gut-btn-small" id="save-sandbox-set" title="Save or update test set">Save</button>
          <button class="gut-btn gut-btn-secondary gut-btn-small" id="rename-sandbox-set" style="display: none;" title="Rename current test set">Rename</button>
          <button class="gut-btn gut-btn-danger gut-btn-small" id="delete-sandbox-set" style="display: none;" title="Delete current test set">Delete</button>
        </div>
        <div style="display: flex; justify-content: flex-start;">
          <a href="#" id="reset-sandbox-fields" class="gut-link" style="font-size: 11px;">Reset fields</a>
        </div>
      </div>

      <div class="gut-form-group">
        <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;">
          <label for="sandbox-mask-input" style="margin-bottom: 0;">Mask:</label>
          <a href="#" id="toggle-compiled-regex" class="gut-link" style="font-size: 11px;">Show compiled regex</a>
        </div>
        <div class="gut-mask-input-container">
          <div class="gut-mask-highlight-overlay" id="sandbox-mask-display"></div>
          <input type="text" id="sandbox-mask-input" autocomplete="off" class="gut-mask-input" placeholder="\${artist} - \${album} {?[\${year}]?}">
        </div>
        <div class="gut-mask-cursor-info" id="sandbox-mask-cursor-info"></div>
        <div class="gut-compiled-regex-display" id="sandbox-compiled-regex"></div>
        <div class="gut-mask-status-container" id="sandbox-mask-status"></div>
      </div>

      <div class="gut-form-group">
        <label for="sandbox-sample-input">Sample Torrent Names (one per line):</label>
        <textarea id="sandbox-sample-input" style="font-family: 'Fira Code', monospace; font-size: 13px; resize: vertical; width: 100%; line-height: 1.4; overflow-y: auto; box-sizing: border-box;" placeholder="Artist Name - Album Title [2024]
Another Artist - Some Album
Third Example - Test [2023]"></textarea>
      </div>

      <div class="gut-form-group">
        <label id="sandbox-results-label">Match Results:</label>
        <div id="sandbox-results" class="gut-sandbox-results">
          <div class="gut-no-variables">Enter a mask and sample names to see match results.</div>
        </div>
      </div>
    </div>
  `;
  };
  const HINT_EDITOR_MODAL_HTML = (instance, hintName = null, hintData = null) => {
    const isEdit = !!hintName;
    const hint = hintData || {
      type: "pattern",
      pattern: "",
      description: "",
      mappings: {},
      strict: true
    };
    const mappingsArray = hint.type === "map" && hint.mappings ? Object.entries(hint.mappings) : [["", ""]];
    return `
    <div class="gut-modal">
      <div class="gut-modal-content gut-hint-editor-modal">
        <div class="gut-modal-header">
          <button class="gut-modal-close-btn" id="modal-close-x" title="Close">&times;</button>
          <h2>${isEdit ? "Edit Hint" : "Create New Hint"}</h2>
        </div>

      <div class="gut-modal-body">
        <div class="gut-form-group">
          <label for="hint-editor-name">Hint Name *</label>
          <input
            type="text"
            id="hint-editor-name"
            class="gut-input"
            placeholder="e.g., my_hint"
            value="${isEdit ? instance.escapeHtml(hintName) : ""}"
            ${isEdit ? "readonly" : ""}
            pattern="[a-zA-Z0-9_]+"
          >
          <div style="font-size: 11px; color: #888; margin-top: 4px;">
            Letters, numbers, and underscores only
          </div>
        </div>

        <div class="gut-form-group">
          <label for="hint-editor-description">Description</label>
          <textarea
            id="hint-editor-description"
            class="gut-input"
            rows="1"
            placeholder="Describe what this hint matches"
          >${instance.escapeHtml(hint.description || "")}</textarea>
        </div>

        <div class="gut-form-group">
          <label>Hint Type *</label>
          <div class="gut-hint-type-selector">
            <label class="gut-radio-label" title="Use # for digits, @ for letters, * for alphanumeric">
              <input type="radio" name="hint-type" value="pattern" ${hint.type === "pattern" ? "checked" : ""}>
              <span>Pattern</span>
            </label>
            <label class="gut-radio-label" title="Regular expression pattern">
              <input type="radio" name="hint-type" value="regex" ${hint.type === "regex" ? "checked" : ""}>
              <span>Regex</span>
            </label>
            <label class="gut-radio-label" title="Map input values to output values">
              <input type="radio" name="hint-type" value="map" ${hint.type === "map" ? "checked" : ""}>
              <span>Value Map</span>
            </label>
          </div>
        </div>

        <div class="gut-form-group" id="hint-pattern-group" style="display: ${hint.type === "pattern" || hint.type === "regex" ? "block" : "none"};">
          <label for="hint-editor-pattern">
            <span id="hint-pattern-label">${hint.type === "regex" ? "Regex Pattern" : "Pattern"} *</span>
          </label>
          <input
            type="text"
            id="hint-editor-pattern"
            class="gut-input"
            placeholder="${hint.type === "regex" ? "e.g., v\\d+(?:\\.\\d+)*" : "e.g., ##.##.####"}"
            value="${hint.type !== "map" ? instance.escapeHtml(hint.pattern || "") : ""}"
          >
        </div>

        <div class="gut-form-group" id="hint-mappings-group" style="display: ${hint.type === "map" ? "block" : "none"};">
          <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
            <label style="margin: 0;">Value Mappings *</label>
            <div style="display: flex; gap: 8px; align-items: center;">
              <a href="#" class="gut-link" id="hint-editor-import-btn">Import</a>
              <a href="#" class="gut-link" id="hint-editor-mass-edit-btn">Mass Edit</a>
            </div>
          </div>
          <label class="gut-checkbox-label" style="margin-top: 10px;">
            <input type="checkbox" id="hint-editor-strict" ${hint.strict === false ? "" : "checked"}>
            <span class="gut-checkbox-text">Strict mode (reject values not in map)</span>
          </label>
          <div id="hint-mappings-table">
            <div class="gut-mappings-table-header">
              <span style="flex: 1;">Input Value</span>
              <span style="flex: 1;">Output Value</span>
              <span style="width: 40px;"></span>
            </div>
            <div id="hint-mappings-rows">
              ${mappingsArray.map(
      ([key, value], idx) => `
                <div class="gut-mappings-row" data-row-index="${idx}">
                  <input type="text" class="gut-input gut-mapping-key" placeholder="e.g., en" value="${instance.escapeHtml(key)}">
                  <input type="text" class="gut-input gut-mapping-value" placeholder="e.g., English" value="${instance.escapeHtml(value)}">
                  <button class="gut-btn gut-btn-danger gut-btn-small gut-remove-mapping" title="Remove">\u2212</button>
                </div>
              `
    ).join("")}
            </div>
            <button class="gut-btn gut-btn-secondary gut-btn-small" id="hint-add-mapping">+ Add Mapping</button>
          </div>
        </div>
      </div>

      <div class="gut-modal-footer">
        <button class="gut-btn" id="hint-editor-cancel">Cancel</button>
        <button class="gut-btn gut-btn-primary" id="hint-editor-save">${isEdit ? "Save Changes" : "Create Hint"}</button>
      </div>
      </div>
    </div>
  `;
  };
  const MAP_IMPORT_MODAL_HTML = (instance, hintName, existingMappings = {}, mode = "import") => {
    const isMassEdit = mode === "mass-edit";
    const prefilledText = isMassEdit ? Object.entries(existingMappings).map(([k, v]) => `${k},${v}`).join("\n") : "";
    return `
    <div class="gut-modal">
      <div class="gut-modal-content">
        <div class="gut-modal-header">
          <button class="gut-modal-close-btn" id="modal-close-x" title="Close">&times;</button>
          <h2>${isMassEdit ? "Mass Edit" : "Import"} Mappings for "${instance.escapeHtml(hintName)}"</h2>
        </div>

        <div class="gut-modal-body">
          <div class="gut-form-group">
            <label for="import-separator-select">Separator:</label>
            <div style="display: flex; gap: 8px; align-items: center;">
              <select id="import-separator-select" class="gut-select" style="flex: 1;">
                <option value="," selected>Comma (,)</option>
                <option value="	">Tab</option>
                <option value=";">Semicolon (;)</option>
                <option value="|">Pipe (|)</option>
                <option value=":">Colon (:)</option>
                <option value="=">Equals (=)</option>
                <option value="custom">Custom...</option>
              </select>
              <input
                type="text"
                id="import-custom-separator"
                class="gut-input"
                placeholder="Enter separator"
                maxlength="3"
                style="display: none; width: 100px;"
              >
            </div>
          </div>

          <div class="gut-form-group">
            <label for="import-mappings-textarea">Mappings (one per line):</label>
            <textarea
              id="import-mappings-textarea"
              class="gut-input"
              placeholder="en,English
fr,French
de,German"
              style="font-family: 'Fira Code', monospace; font-size: 13px; resize: vertical; width: 100%; line-height: 1.4;"
            >${prefilledText}</textarea>
            <div style="font-size: 11px; color: #888; margin-top: 4px;">
              Format: key${isMassEdit ? "" : "<separator>"}value (one mapping per line)
            </div>
          </div>

          ${!isMassEdit ? `
          <div class="gut-form-group">
            <label class="gut-checkbox-label">
              <input type="checkbox" id="import-overwrite-checkbox">
              <span class="gut-checkbox-text">Overwrite existing mappings</span>
            </label>
            <div style="font-size: 11px; color: #888; margin-top: 4px;">
              If unchecked, only new keys will be added (existing keys will be kept)
            </div>
          </div>
          ` : ""}

          <div class="gut-form-group" id="import-preview-group" style="display: none;">
            <label>Preview:</label>
            <div id="import-preview-content" class="gut-extracted-vars" style="max-height: 200px; overflow-y: auto;">
            </div>
            <div id="import-preview-summary" style="font-size: 11px; color: #888; margin-top: 4px;"></div>
          </div>
        </div>

        <div class="gut-modal-footer">
          <button class="gut-btn" id="import-cancel-btn">Cancel</button>
          <button class="gut-btn gut-btn-primary" id="import-confirm-btn">${isMassEdit ? "Apply Changes" : "Import"}</button>
        </div>
      </div>
    </div>
  `;
  };
  const MAIN_UI_HTML = (instance) => `
  <div id="ggn-upload-templator-controls" class="ggn-upload-templator-controls" style="align-items: flex-end;">
    <div style="display: flex; flex-direction: column; gap: 5px;">
      <div style="display: flex; justify-content: space-between; align-items: center;">
        <label for="template-selector" style="font-size: 12px; color: #b0b0b0; margin: 0;">Select template</label>
        <a href="#" id="edit-selected-template-btn" class="gut-link" style="${instance.selectedTemplate && instance.selectedTemplate !== "none" && instance.templates[instance.selectedTemplate] ? "" : "display: none;"}">Edit</a>
      </div>
       <div style="display: flex; gap: 10px; align-items: center;">
         <select id="template-selector" class="gut-select">
           <option value="">Select Template</option>
           ${Object.keys(instance.templates).map(
    (name) => `<option value="${name}" ${name === instance.selectedTemplate ? "selected" : ""}>${name}</option>`
  ).join("")}
         </select>
       </div>
    </div>
    <button type="button" id="apply-template-btn" class="gut-btn gut-btn-primary">Apply Template</button>
    <button type="button" id="create-template-btn" class="gut-btn gut-btn-primary">+ Create Template</button>
    <button id="manage-templates-btn" type="button" class="gut-btn gut-btn-secondary" title="Manage Templates & Settings">
      Manage
    </button>
  </div>
  <div id="variables-row" style="display: none; padding: 10px 0; font-size: 12px; cursor: pointer; user-select: none;"></div>
`;
  const IMPORT_NEW_HINTS_MODAL_HTML = (newHints, ignoredHints, instance) => {
    const hintEntries = Object.entries(newHints);
    const selectedCount = hintEntries.filter(([name]) => !ignoredHints.includes(name)).length;
    const renderHintRow = (name, hint, isIgnored) => {
      const mappingsHtml = hint.type === "map" && hint.mappings ? `
      <div class="gut-hint-mappings-inline">
        <div class="gut-hint-mappings-header">
          <div style="display: flex; align-items: center; gap: 6px; cursor: pointer;" class="gut-hint-mappings-toggle" data-hint="${instance.escapeHtml(name)}">
            <svg class="gut-hint-caret" width="12" height="12" viewBox="0 0 12 12" style="transition: transform 0.2s ease;">
              <path d="M4 3 L8 6 L4 9" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
            </svg>
            <span>${Object.keys(hint.mappings).length} mappings${hint.strict === false ? " (non-strict)" : ""}</span>
          </div>
        </div>
        <div class="gut-hint-mappings-content" style="display: none; max-height: 0; overflow: hidden; transition: max-height 0.2s ease;">
          <div style="max-height: 200px; overflow-y: auto;">
            ${Object.entries(hint.mappings).map(
        ([key, value]) => `
                <div class="gut-variable-item">
                  <span class="gut-variable-name">${instance.escapeHtml(key)}</span>
                  <span class="gut-variable-value">${instance.escapeHtml(value)}</span>
                </div>
              `
      ).join("")}
          </div>
        </div>
      </div>
    ` : "";
      return `
      <div class="gut-hint-item gut-hint-import-item" data-hint-name="${instance.escapeHtml(name)}">
        <div class="gut-hint-header">
          <div style="display: flex; align-items: center; gap: 12px; flex: 1;">
            <input 
              type="checkbox" 
              class="hint-select-checkbox" 
              data-hint-name="${instance.escapeHtml(name)}"
              ${isIgnored ? "" : "checked"}
            >
            <div class="gut-hint-name-group">
              <span class="gut-hint-name">${instance.escapeHtml(name)}</span>
              <span class="gut-hint-type-badge">${hint.type}</span>
            </div>
          </div>
          <div class="gut-hint-actions">
            <a 
              href="#" 
              class="gut-link hint-ignore-btn" 
              data-hint-name="${instance.escapeHtml(name)}"
            >
              ${isIgnored ? "Unignore" : "Ignore"}
            </a>
          </div>
        </div>
        ${hint.description ? `<div class="gut-hint-description">${instance.escapeHtml(hint.description)}</div>` : ""}
        ${hint.type === "pattern" ? `<div class="gut-hint-pattern"><code>${instance.escapeHtml(hint.pattern)}</code></div>` : ""}
        ${hint.type === "regex" ? `<div class="gut-hint-pattern"><code>/${instance.escapeHtml(hint.pattern)}/</code></div>` : ""}
        ${mappingsHtml}
      </div>
    `;
    };
    const buttonText = selectedCount === 0 ? "Import Selected" : selectedCount === hintEntries.length ? "Import All" : `Import ${selectedCount}/${hintEntries.length} Selected`;
    return `
    <div class="gut-modal">
      <div class="gut-modal-content" style="max-width: 700px;">
        <div class="gut-modal-header">
          <button class="gut-modal-close-btn" id="modal-close-x" title="Close">&times;</button>
          <h2>Import New Default Hints</h2>
        </div>

        <div class="gut-modal-body">
          <div style="padding: 12px; background: #2a3a4a; border-left: 3px solid #4caf50; margin-bottom: 16px; border-radius: 4px;">
            <strong style="color: #4caf50;">New default hints are available!</strong>
            <p style="margin: 8px 0 0 0; color: #b0b0b0; font-size: 13px;">
              Select which hints you'd like to import. You can ignore hints you don't need.
            </p>
          </div>

          <div style="display: flex; gap: 8px; margin-bottom: 12px; font-size: 12px;">
            <a href="#" class="gut-link" id="import-select-all-btn">Select All</a>
            <span style="color: #666;">\u2022</span>
            <a href="#" class="gut-link" id="import-select-none-btn">Select None</a>
          </div>

          <div class="gut-hints-list">
            ${hintEntries.map(([name, hint]) => {
      const isIgnored = ignoredHints.includes(name);
      return renderHintRow(name, hint, isIgnored);
    }).join("")}
          </div>
        </div>

        <div class="gut-modal-footer">
          <button class="gut-btn" id="import-hints-cancel-btn">Cancel</button>
          <button class="gut-btn gut-btn-primary" id="import-hints-confirm-btn" ${selectedCount === 0 ? "disabled" : ""}>${buttonText}</button>
        </div>
      </div>
    </div>
  `;
  };
  const RESET_DEFAULTS_MODAL_HTML = (userHints, ignoredHints, deletedHints, instance) => {
    const defaultEntries = Object.entries(DEFAULT_HINTS);
    const hintsWithStatus = defaultEntries.map(([name, def]) => {
      const userHint = userHints[name];
      const isDeleted = deletedHints.includes(name);
      const isEdited = userHint && !isDeleted && JSON.stringify(userHint) !== JSON.stringify(def);
      const isMissing = !userHint && !isDeleted;
      const isIgnored = ignoredHints.includes(name);
      return { name, def, isEdited, isMissing, isDeleted, isIgnored };
    });
    const selectedCount = hintsWithStatus.filter((h) => !h.isIgnored).length;
    const buttonText = selectedCount === 0 ? "Reset Selected" : selectedCount === defaultEntries.length ? "Reset All" : `Reset ${selectedCount}/${defaultEntries.length} Selected`;
    const renderHintRow = (name, hint, isIgnored, statusBadge) => {
      const mappingsHtml = hint.type === "map" && hint.mappings ? `
      <div class="gut-hint-mappings-inline">
        <div class="gut-hint-mappings-header">
          <div style="display: flex; align-items: center; gap: 6px; cursor: pointer;" class="gut-hint-mappings-toggle" data-hint="${instance.escapeHtml(name)}">
            <svg class="gut-hint-caret" width="12" height="12" viewBox="0 0 12 12" style="transition: transform 0.2s ease;">
              <path d="M4 3 L8 6 L4 9" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
            </svg>
            <span>${Object.keys(hint.mappings).length} mappings${hint.strict === false ? " (non-strict)" : ""}</span>
          </div>
        </div>
        <div class="gut-hint-mappings-content" style="display: none; max-height: 0; overflow: hidden; transition: max-height 0.2s ease;">
          <div style="max-height: 200px; overflow-y: auto;">
            ${Object.entries(hint.mappings).map(
        ([key, value]) => `
                <div class="gut-variable-item">
                  <span class="gut-variable-name">${instance.escapeHtml(key)}</span>
                  <span class="gut-variable-value">${instance.escapeHtml(value)}</span>
                </div>
              `
      ).join("")}
          </div>
        </div>
      </div>
    ` : "";
      return `
      <div class="gut-hint-item gut-hint-import-item" data-hint-name="${instance.escapeHtml(name)}">
        <div class="gut-hint-header">
          <div style="display: flex; align-items: center; gap: 12px; flex: 1;">
            <input 
              type="checkbox" 
              class="hint-select-checkbox" 
              data-hint-name="${instance.escapeHtml(name)}"
              ${isIgnored ? "" : "checked"}
            >
            <div class="gut-hint-name-group">
              <span class="gut-hint-name">${instance.escapeHtml(name)}</span>
              <span class="gut-hint-type-badge">${hint.type}</span>
              ${statusBadge}
            </div>
          </div>
          <div class="gut-hint-actions">
            <a 
              href="#" 
              class="gut-link hint-ignore-btn" 
              data-hint-name="${instance.escapeHtml(name)}"
            >
              ${isIgnored ? "Unignore" : "Ignore"}
            </a>
          </div>
        </div>
        ${hint.description ? `<div class="gut-hint-description">${instance.escapeHtml(hint.description)}</div>` : ""}
        ${hint.type === "pattern" ? `<div class="gut-hint-pattern"><code>${instance.escapeHtml(hint.pattern)}</code></div>` : ""}
        ${hint.type === "regex" ? `<div class="gut-hint-pattern"><code>/${instance.escapeHtml(hint.pattern)}/</code></div>` : ""}
        ${mappingsHtml}
      </div>
    `;
    };
    return `
    <div class="gut-modal">
      <div class="gut-modal-content" style="max-width: 700px;">
        <div class="gut-modal-header">
          <button class="gut-modal-close-btn" id="modal-close-x" title="Close">&times;</button>
          <h2>Reset Default Hints</h2>
        </div>

        <div class="gut-modal-body">
          <div style="padding: 12px; background: #4a3a2a; border-left: 3px solid #ff9800; margin-bottom: 16px; border-radius: 4px;">
            <strong style="color: #ff9800;">\u26A0 Warning</strong>
            <p style="margin: 8px 0 0 0; color: #b0b0b0; font-size: 13px;">
              Selected hints will be reset to their default values. This will overwrite any customizations you've made to these hints.
            </p>
          </div>

          <div style="display: flex; gap: 8px; margin-bottom: 12px; font-size: 12px;">
            <a href="#" class="gut-link" id="reset-select-all-btn">Select All</a>
            <span style="color: #666;">\u2022</span>
            <a href="#" class="gut-link" id="reset-select-none-btn">Select None</a>
          </div>

          <div class="gut-hints-list">
            ${hintsWithStatus.map(({ name, def, isEdited, isMissing, isDeleted, isIgnored }) => {
      let statusBadge = "";
      if (isDeleted) {
        statusBadge = '<span style="padding: 2px 6px; background: #4a2a2a; color: #f44336; border-radius: 3px; font-size: 11px; font-weight: 500;">Deleted</span>';
      } else if (isMissing) {
        statusBadge = '<span style="padding: 2px 6px; background: #3a2a4a; color: #9c27b0; border-radius: 3px; font-size: 11px; font-weight: 500;">Missing</span>';
      } else if (isEdited) {
        statusBadge = '<span style="padding: 2px 6px; background: #4a3a2a; color: #ff9800; border-radius: 3px; font-size: 11px; font-weight: 500;">Edited</span>';
      }
      return renderHintRow(name, def, isIgnored, statusBadge);
    }).join("")}
          </div>
        </div>

        <div class="gut-modal-footer">
          <button class="gut-btn" id="reset-hints-cancel-btn">Cancel</button>
          <button class="gut-btn gut-btn-primary" id="reset-hints-confirm-btn" ${selectedCount === 0 ? "disabled" : ""}>${buttonText}</button>
        </div>
      </div>
    </div>
  `;
  };
  const DELETE_ALL_HINTS_MODAL_HTML = (instance) => {
    return `
    <div class="gut-modal">
      <div class="gut-modal-content" style="max-width: 500px;">
        <div class="gut-modal-header">
          <button class="gut-modal-close-btn" id="modal-close-x" title="Close">&times;</button>
          <h2>Delete All Hints</h2>
        </div>

        <div class="gut-modal-body">
          <div style="padding: 12px; background: #4a2a2a; border-left: 3px solid #f44336; margin-bottom: 16px; border-radius: 4px;">
            <strong style="color: #f44336;">\u26A0 Critical Warning</strong>
            <p style="margin: 8px 0 0 0; color: #b0b0b0; font-size: 13px;">
              This will permanently delete <strong>ALL</strong> variable hints, including:
            </p>
            <ul style="margin: 8px 0 0 20px; color: #b0b0b0; font-size: 13px;">
              <li>All default hints</li>
              <li>All custom hints you've created</li>
              <li>All edited hints</li>
            </ul>
            <p style="margin: 8px 0 0 0; color: #b0b0b0; font-size: 13px;">
              <strong>This action cannot be undone.</strong> You can restore default hints later, but custom hints will be lost forever.
            </p>
          </div>

          <div style="padding: 12px; background: #1a1a1a; border-radius: 4px;">
            <p style="margin: 0; color: #b0b0b0; font-size: 13px;">
              Are you absolutely sure you want to delete all hints?
            </p>
          </div>
        </div>

        <div class="gut-modal-footer">
          <button class="gut-btn" id="delete-all-hints-cancel-btn">Cancel</button>
          <button class="gut-btn gut-btn-danger" id="delete-all-hints-confirm-btn">Delete All Hints</button>
        </div>
      </div>
    </div>
  `;
  };
  const APPLY_CONFIRMATION_MODAL_HTML = (changes, instance) => {
    const changesCount = changes.length;
    const fieldWord = changesCount === 1 ? "field" : "fields";
    return `
    <div class="gut-modal">
      <div class="gut-modal-content gut-confirmation-modal" style="max-width: 800px;">
        <div class="gut-modal-header">
          <button class="gut-modal-close-btn" id="modal-close-x" title="Close">&times;</button>
          <h2>\u26A0\uFE0F Confirm Template Application</h2>
        </div>

        <div class="gut-modal-body">
          <div style="padding: 10px 12px; background: #4a3a2a; border-left: 3px solid #ff9800; margin-bottom: 12px; border-radius: 4px;">
            <p style="margin: 0; color: #e0e0e0; font-size: 13px;">
              <strong>Warning:</strong> ${changesCount} ${fieldWord} will be overwritten
            </p>
          </div>

          <div class="gut-field-changes-list">
            ${changes.map((change) => `
              <div class="gut-field-change-item">
                <div class="gut-field-change-row">
                  <div class="gut-field-name">
                    <strong>${instance.escapeHtml(change.label || change.fieldName)}</strong>
                    <span class="gut-field-type-badge">${change.fieldType || "text"}</span>
                  </div>
                  <div class="gut-field-values">
                    <span class="gut-value gut-value-old">${instance.escapeHtml(String(change.currentValue))}</span>
                    <span class="gut-value-arrow">\u2192</span>
                    <span class="gut-value gut-value-new">${instance.escapeHtml(String(change.newValue))}</span>
                  </div>
                </div>
              </div>
            `).join("")}
          </div>
        </div>

        <div class="gut-modal-footer">
          <button class="gut-btn" id="apply-confirm-cancel-btn">Cancel</button>
          <button class="gut-btn gut-btn-primary" id="apply-confirm-apply-btn">Apply Template</button>
        </div>
      </div>
    </div>
  `;
  };
  function loadTemplates() {
    try {
      return JSON.parse(
        localStorage.getItem("ggn-upload-templator-templates") || "{}"
      );
    } catch (error) {
      console.error("Failed to load templates:", error);
      return {};
    }
  }
  function saveTemplates(templates) {
    try {
      localStorage.setItem(
        "ggn-upload-templator-templates",
        JSON.stringify(templates)
      );
    } catch (error) {
      console.error("Failed to save templates:", error);
    }
  }
  function loadSelectedTemplate() {
    try {
      return localStorage.getItem("ggn-upload-templator-selected") || null;
    } catch (error) {
      console.error("Failed to load selected template:", error);
      return null;
    }
  }
  function saveSelectedTemplate(name) {
    try {
      localStorage.setItem("ggn-upload-templator-selected", name);
    } catch (error) {
      console.error("Failed to save selected template:", error);
    }
  }
  function removeSelectedTemplate() {
    try {
      localStorage.removeItem("ggn-upload-templator-selected");
    } catch (error) {
      console.error("Failed to remove selected template:", error);
    }
  }
  function loadHideUnselected() {
    try {
      return JSON.parse(
        localStorage.getItem("ggn-upload-templator-hide-unselected") || "true"
      );
    } catch (error) {
      console.error("Failed to load hide unselected setting:", error);
      return true;
    }
  }
  function loadSettings() {
    try {
      return JSON.parse(
        localStorage.getItem("ggn-upload-templator-settings") || "{}"
      );
    } catch (error) {
      console.error("Failed to load settings:", error);
      return {};
    }
  }
  function saveSettings(settings) {
    try {
      localStorage.setItem(
        "ggn-upload-templator-settings",
        JSON.stringify(settings)
      );
    } catch (error) {
      console.error("Failed to save settings:", error);
    }
  }
  function removeSettings() {
    try {
      localStorage.removeItem("ggn-upload-templator-settings");
    } catch (error) {
      console.error("Failed to remove settings:", error);
    }
  }
  function loadSandboxSets() {
    try {
      return JSON.parse(
        localStorage.getItem("ggn-upload-templator-sandbox-sets") || "{}"
      );
    } catch (error) {
      console.error("Failed to load sandbox sets:", error);
      return {};
    }
  }
  function saveSandboxSets(sets) {
    try {
      localStorage.setItem(
        "ggn-upload-templator-sandbox-sets",
        JSON.stringify(sets)
      );
    } catch (error) {
      console.error("Failed to save sandbox sets:", error);
    }
  }
  function loadCurrentSandboxSet() {
    try {
      return localStorage.getItem("ggn-upload-templator-sandbox-current") || "";
    } catch (error) {
      console.error("Failed to load current sandbox set:", error);
      return "";
    }
  }
  function saveCurrentSandboxSet(name) {
    try {
      localStorage.setItem("ggn-upload-templator-sandbox-current", name);
    } catch (error) {
      console.error("Failed to save current sandbox set:", error);
    }
  }
  function deleteAllConfig$1() {
    try {
      localStorage.removeItem("ggn-upload-templator-templates");
      localStorage.removeItem("ggn-upload-templator-selected");
      localStorage.removeItem("ggn-upload-templator-hide-unselected");
      localStorage.removeItem("ggn-upload-templator-settings");
    } catch (error) {
      console.error("Failed to delete config:", error);
    }
  }
  function loadModalWidth() {
    try {
      const width = localStorage.getItem("ggn-upload-templator-modal-width");
      return width ? parseInt(width, 10) : null;
    } catch (error) {
      console.error("Failed to load modal width:", error);
      return null;
    }
  }
  function saveModalWidth(width) {
    try {
      localStorage.setItem("ggn-upload-templator-modal-width", width.toString());
    } catch (error) {
      console.error("Failed to save modal width:", error);
    }
  }
  class ModalStackManager {
    constructor() {
      this.stack = [];
      this.baseZIndex = 1e4;
      this.keybindingRecorderActive = false;
      this.escapeHandlers = [];
      this.resizeHandleWidth = 10;
      this.isResizing = false;
      this.currentResizeModal = null;
      this.resizeStartX = 0;
      this.resizeStartWidth = 0;
      this.resizeSide = null;
      this.setupGlobalHandlers();
    }
    push(element, options = {}) {
      if (!element) {
        console.error("ModalStack.push: element is required");
        return;
      }
      const type = options.type || "stack";
      if (type !== "stack" && type !== "replace") {
        console.error('ModalStack.push: type must be "stack" or "replace"');
        return;
      }
      if (type === "replace" && this.stack.length > 0) {
        const current = this.stack[this.stack.length - 1];
        if (current.element && document.body.contains(current.element)) {
          this.removeResizeHandles(current.element);
          document.body.removeChild(current.element);
        }
      }
      const entry = {
        element,
        type,
        onClose: options.onClose || null,
        canGoBack: options.canGoBack || false,
        backFactory: options.backFactory || null,
        metadata: options.metadata || {},
        originalDimensions: null
      };
      this.stack.push(entry);
      if (!document.body.contains(element)) {
        document.body.appendChild(element);
      }
      if (this.stack.length === 1) {
        document.body.style.overflow = "hidden";
      }
      this.updateZIndices();
      this.updateResizeHandles();
    }
    replace(element, options = {}) {
      this.push(element, { ...options, type: "replace" });
    }
    pop() {
      if (this.stack.length === 0) {
        return null;
      }
      const entry = this.stack.pop();
      if (entry.onClose) {
        entry.onClose();
      }
      if (entry.element && document.body.contains(entry.element)) {
        this.removeResizeHandles(entry.element);
        document.body.removeChild(entry.element);
      }
      if (this.stack.length === 0) {
        this.clearEscapeHandlers();
        document.body.style.overflow = "";
      }
      this.updateZIndices();
      this.updateResizeHandles();
      return entry;
    }
    back() {
      if (this.stack.length === 0) {
        return;
      }
      const current = this.stack[this.stack.length - 1];
      if (current.type !== "replace" || !current.canGoBack || !current.backFactory) {
        console.warn(
          "ModalStack.back: current modal does not support back navigation"
        );
        return;
      }
      this.pop();
      current.backFactory();
    }
    clear() {
      while (this.stack.length > 0) {
        this.pop();
      }
      this.clearEscapeHandlers();
    }
    getCurrentModal() {
      return this.stack.length > 0 ? this.stack[this.stack.length - 1] : null;
    }
    getStackDepth() {
      return this.stack.length;
    }
    setKeybindingRecorderActive(active) {
      this.keybindingRecorderActive = active;
    }
    isKeybindingRecorderActive() {
      return this.keybindingRecorderActive;
    }
    pushEscapeHandler(handler) {
      if (typeof handler !== "function") {
        console.error("ModalStack.pushEscapeHandler: handler must be a function");
        return;
      }
      this.escapeHandlers.push(handler);
    }
    popEscapeHandler() {
      return this.escapeHandlers.pop();
    }
    clearEscapeHandlers() {
      this.escapeHandlers = [];
    }
    hasEscapeHandlers() {
      return this.escapeHandlers.length > 0;
    }
    isResizingModal() {
      return this.isResizing;
    }
    updateZIndices() {
      let previousWidth = null;
      let previousMaxWidth = null;
      let previousHeight = null;
      let previousMaxHeight = null;
      let previousAlpha = 0.4;
      let stackDepth = 0;
      this.stack.forEach((entry, index) => {
        if (entry.element) {
          entry.element.style.zIndex = this.baseZIndex + index * 10;
          if (entry.type === "stack") {
            stackDepth++;
          } else {
            stackDepth = 0;
          }
          const isStacked = stackDepth > 0;
          if (isStacked) {
            const alpha = Math.max(0.05, previousAlpha * 0.5);
            entry.element.style.background = `rgba(0, 0, 0, ${alpha})`;
            previousAlpha = alpha;
          } else {
            entry.element.style.background = "rgba(0, 0, 0, 0.4)";
            previousAlpha = 0.4;
          }
          const modalContent = entry.element.querySelector(".gut-modal-content");
          if (isStacked && modalContent) {
            entry.element.classList.add("gut-modal-stacked");
            if (!entry.originalDimensions) {
              const computedStyle = window.getComputedStyle(modalContent);
              entry.originalDimensions = {
                width: computedStyle.width,
                maxWidth: computedStyle.maxWidth,
                height: computedStyle.height,
                maxHeight: computedStyle.maxHeight
              };
            }
            const offsetAmount = stackDepth * 20;
            let targetWidth = previousWidth;
            let targetMaxWidth = previousMaxWidth;
            let targetHeight = previousHeight;
            let targetMaxHeight = previousMaxHeight;
            if (targetWidth === null) {
              targetWidth = parseFloat(entry.originalDimensions.width);
            }
            if (targetMaxWidth === null) {
              targetMaxWidth = parseFloat(entry.originalDimensions.maxWidth);
            }
            if (targetHeight === null) {
              targetHeight = parseFloat(entry.originalDimensions.height);
            }
            if (targetMaxHeight === null) {
              targetMaxHeight = parseFloat(entry.originalDimensions.maxHeight);
            }
            const scaledWidth = targetWidth * 0.95;
            const scaledMaxWidth = targetMaxWidth * 0.95;
            const scaledHeight = targetHeight * 0.9;
            const scaledMaxHeight = targetMaxHeight * 0.9;
            if (entry.originalDimensions.width && entry.originalDimensions.width !== "auto") {
              modalContent.style.width = `${scaledWidth}px`;
              previousWidth = scaledWidth;
            }
            if (entry.originalDimensions.maxWidth && entry.originalDimensions.maxWidth !== "none") {
              modalContent.style.maxWidth = `${scaledMaxWidth}px`;
              previousMaxWidth = scaledMaxWidth;
            }
            if (entry.originalDimensions.height && entry.originalDimensions.height !== "auto") {
              modalContent.style.height = `${scaledHeight}px`;
              previousHeight = scaledHeight;
            }
            if (entry.originalDimensions.maxHeight && entry.originalDimensions.maxHeight !== "none") {
              modalContent.style.maxHeight = `${scaledMaxHeight}px`;
              previousMaxHeight = scaledMaxHeight;
            }
            modalContent.style.marginTop = `${offsetAmount}px`;
          } else {
            entry.element.classList.remove("gut-modal-stacked");
            if (modalContent) {
              const savedWidth = loadModalWidth();
              modalContent.style.width = "";
              modalContent.style.height = "";
              modalContent.style.marginTop = "";
              if (savedWidth) {
                modalContent.style.maxWidth = `${savedWidth}px`;
              } else {
                modalContent.style.maxWidth = "";
              }
              const computedStyle = window.getComputedStyle(modalContent);
              previousWidth = parseFloat(computedStyle.width);
              previousMaxWidth = savedWidth || parseFloat(computedStyle.maxWidth);
              previousHeight = parseFloat(computedStyle.height);
              previousMaxHeight = parseFloat(computedStyle.maxHeight);
            }
          }
        }
      });
    }
    updateResizeHandles() {
      this.stack.forEach((entry, index) => {
        const isTopModal = index === this.stack.length - 1;
        if (isTopModal) {
          this.addResizeHandles(entry.element);
        } else {
          this.removeResizeHandles(entry.element);
        }
      });
    }
    addResizeHandles(modalElement) {
      if (!modalElement) return;
      const modalContent = modalElement.querySelector(".gut-modal-content");
      if (!modalContent) return;
      if (modalContent.querySelector(".gut-resize-handle")) {
        return;
      }
      const leftHandle = document.createElement("div");
      leftHandle.className = "gut-resize-handle gut-resize-handle-left";
      leftHandle.dataset.side = "left";
      const rightHandle = document.createElement("div");
      rightHandle.className = "gut-resize-handle gut-resize-handle-right";
      rightHandle.dataset.side = "right";
      modalContent.appendChild(leftHandle);
      modalContent.appendChild(rightHandle);
      [leftHandle, rightHandle].forEach((handle) => {
        handle.addEventListener("mouseenter", () => {
          if (!this.isResizing) {
            handle.classList.add("gut-resize-handle-hover");
          }
        });
        handle.addEventListener("mouseleave", () => {
          if (!this.isResizing) {
            handle.classList.remove("gut-resize-handle-hover");
          }
        });
        handle.addEventListener("mousedown", (e) => {
          e.preventDefault();
          e.stopPropagation();
          this.startResize(e, modalContent, handle.dataset.side);
        });
      });
    }
    removeResizeHandles(modalElement) {
      if (!modalElement) return;
      const modalContent = modalElement.querySelector(".gut-modal-content");
      if (!modalContent) return;
      const handles = modalContent.querySelectorAll(".gut-resize-handle");
      handles.forEach((handle) => handle.remove());
    }
    startResize(e, modalContent, side) {
      this.isResizing = true;
      this.currentResizeModal = modalContent;
      this.resizeStartX = e.clientX;
      this.resizeSide = side;
      const computedStyle = window.getComputedStyle(modalContent);
      this.resizeStartWidth = parseFloat(computedStyle.width);
      document.body.style.cursor = "ew-resize";
      document.body.style.userSelect = "none";
      const handles = modalContent.querySelectorAll(".gut-resize-handle");
      handles.forEach(
        (handle) => handle.classList.add("gut-resize-handle-active")
      );
    }
    handleResize(e) {
      if (!this.isResizing || !this.currentResizeModal) return;
      const deltaX = e.clientX - this.resizeStartX;
      const adjustedDelta = this.resizeSide === "left" ? -deltaX : deltaX;
      const newWidth = Math.max(
        400,
        Math.min(2e3, this.resizeStartWidth + adjustedDelta)
      );
      this.currentResizeModal.style.maxWidth = `${newWidth}px`;
    }
    endResize() {
      if (!this.isResizing || !this.currentResizeModal) return;
      const computedStyle = window.getComputedStyle(this.currentResizeModal);
      const finalWidth = parseFloat(computedStyle.maxWidth);
      saveModalWidth(Math.round(finalWidth));
      document.body.style.cursor = "";
      document.body.style.userSelect = "";
      const handles = this.currentResizeModal.querySelectorAll(".gut-resize-handle");
      handles.forEach((handle) => {
        handle.classList.remove("gut-resize-handle-active");
        handle.classList.remove("gut-resize-handle-hover");
      });
      setTimeout(() => {
        this.isResizing = false;
        this.currentResizeModal = null;
        this.resizeSide = null;
      }, 50);
    }
    setupGlobalHandlers() {
      document.addEventListener("keydown", (e) => {
        if (e.key === "Escape" && this.stack.length > 0) {
          if (this.isKeybindingRecorderActive()) {
            return;
          }
          if (this.hasEscapeHandlers()) {
            const handler = this.escapeHandlers[this.escapeHandlers.length - 1];
            const result = handler(e);
            if (result === true) {
              return;
            }
          }
          const current = this.stack[this.stack.length - 1];
          if (current.type === "stack") {
            this.pop();
          } else if (current.type === "replace") {
            if (current.canGoBack) {
              this.back();
            } else {
              this.pop();
            }
          }
        }
      });
      document.addEventListener("mousemove", (e) => {
        this.handleResize(e);
      });
      document.addEventListener("mouseup", () => {
        this.endResize();
      });
    }
  }
  const ModalStack = new ModalStackManager();
  function setupMaskValidation(maskInput, cursorInfoElement, statusContainer, overlayElement, onValidationChange = null, availableHints = {}) {
    let autocompleteDropdown = null;
    let selectedIndex = -1;
    let filteredHints = [];
    const closeAutocomplete = () => {
      if (autocompleteDropdown) {
        autocompleteDropdown.remove();
        autocompleteDropdown = null;
        selectedIndex = -1;
        filteredHints = [];
        ModalStack.popEscapeHandler();
      }
    };
    const showAutocomplete = (hints, cursorPos) => {
      closeAutocomplete();
      if (hints.length === 0) return;
      filteredHints = hints;
      selectedIndex = 0;
      autocompleteDropdown = document.createElement("div");
      autocompleteDropdown.className = "gut-hint-autocomplete";
      const rect = maskInput.getBoundingClientRect();
      const inputContainer = maskInput.parentElement;
      const containerRect = inputContainer.getBoundingClientRect();
      autocompleteDropdown.style.position = "absolute";
      autocompleteDropdown.style.top = `${rect.bottom - containerRect.top + 2}px`;
      autocompleteDropdown.style.left = `${rect.left - containerRect.left}px`;
      autocompleteDropdown.style.minWidth = `${rect.width}px`;
      hints.forEach((hint, index) => {
        const item = document.createElement("div");
        item.className = "gut-hint-autocomplete-item";
        if (index === 0) item.classList.add("selected");
        item.innerHTML = `
        <div class="gut-hint-autocomplete-name">${escapeHtml(hint.name)}</div>
        <div class="gut-hint-autocomplete-type">${hint.type}</div>
        <div class="gut-hint-autocomplete-desc">${escapeHtml(hint.description || "")}</div>
      `;
        item.addEventListener("mouseenter", () => {
          autocompleteDropdown.querySelectorAll(".gut-hint-autocomplete-item").forEach((i) => i.classList.remove("selected"));
          item.classList.add("selected");
          selectedIndex = index;
        });
        item.addEventListener("click", () => {
          insertHint(hint.name);
          closeAutocomplete();
        });
        autocompleteDropdown.appendChild(item);
      });
      inputContainer.style.position = "relative";
      inputContainer.appendChild(autocompleteDropdown);
      ModalStack.pushEscapeHandler(() => {
        closeAutocomplete();
        return true;
      });
    };
    const insertHint = (hintName) => {
      const value = maskInput.value;
      const cursorPos = maskInput.selectionStart;
      const beforeCursor = value.substring(0, cursorPos);
      const afterCursor = value.substring(cursorPos);
      const match = beforeCursor.match(/\$\{([a-zA-Z0-9_]+):([a-zA-Z0-9_]*)$/);
      if (match) {
        const [fullMatch, varName, partialHint] = match;
        const newValue = beforeCursor.substring(0, beforeCursor.length - partialHint.length) + hintName + afterCursor;
        maskInput.value = newValue;
        maskInput.selectionStart = maskInput.selectionEnd = cursorPos - partialHint.length + hintName.length;
        maskInput.dispatchEvent(new Event("input"));
      }
    };
    const updateAutocomplete = () => {
      const cursorPos = maskInput.selectionStart;
      const value = maskInput.value;
      const beforeCursor = value.substring(0, cursorPos);
      const match = beforeCursor.match(/\$\{([a-zA-Z0-9_]+):([a-zA-Z0-9_]*)$/);
      if (match) {
        const [, varName, partialHint] = match;
        const hints = Object.entries(availableHints).filter(
          ([name]) => name.toLowerCase().startsWith(partialHint.toLowerCase())
        ).map(([name, hint]) => ({
          name,
          type: hint.type,
          description: hint.description || ""
        })).slice(0, 10);
        if (hints.length > 0) {
          showAutocomplete(hints);
        } else {
          closeAutocomplete();
        }
      } else {
        closeAutocomplete();
      }
    };
    const handleKeyDown = (e) => {
      if (!autocompleteDropdown) return;
      if (e.key === "ArrowDown") {
        e.preventDefault();
        selectedIndex = Math.min(selectedIndex + 1, filteredHints.length - 1);
        updateSelection();
      } else if (e.key === "ArrowUp") {
        e.preventDefault();
        selectedIndex = Math.max(selectedIndex - 1, 0);
        updateSelection();
      } else if (e.key === "Enter") {
        if (selectedIndex >= 0 && selectedIndex < filteredHints.length) {
          e.preventDefault();
          insertHint(filteredHints[selectedIndex].name);
          closeAutocomplete();
        }
      } else if (e.key === "Escape") {
        return;
      } else if (e.key === "Tab") {
        if (selectedIndex >= 0 && selectedIndex < filteredHints.length) {
          e.preventDefault();
          insertHint(filteredHints[selectedIndex].name);
          closeAutocomplete();
        }
      }
    };
    const updateSelection = () => {
      if (!autocompleteDropdown) return;
      const items = autocompleteDropdown.querySelectorAll(
        ".gut-hint-autocomplete-item"
      );
      items.forEach((item, index) => {
        if (index === selectedIndex) {
          item.classList.add("selected");
          item.scrollIntoView({ block: "nearest" });
        } else {
          item.classList.remove("selected");
        }
      });
    };
    const findVariableAtCursor = (mask, cursorPos) => {
      const varPattern = /\$\{([^}]+)\}/g;
      let match;
      while ((match = varPattern.exec(mask)) !== null) {
        const varStart = match.index;
        const varEnd = varStart + match[0].length;
        if (cursorPos >= varStart && cursorPos <= varEnd) {
          const content = match[1];
          const colonIndex = content.indexOf(":");
          if (colonIndex === -1) {
            return null;
          }
          const hintName = content.substring(colonIndex + 1).trim();
          const hintStartInVar = colonIndex + 1;
          const hintStart = varStart + 2 + hintStartInVar;
          const hintEnd = varEnd - 1;
          if (cursorPos >= hintStart && cursorPos <= hintEnd) {
            return {
              hintName,
              varContent: content,
              hintStart,
              hintEnd
            };
          }
        }
      }
      return null;
    };
    const formatHintInfo = (varName, hint) => {
      if (!hint) {
        return `<span style="color: #888;">No hint defined</span>`;
      }
      const parts = [];
      if (hint.type === "pattern") {
        parts.push(`<span style="color: #4dd0e1;">pattern:</span> <span style="color: #a5d6a7;">${escapeHtml(hint.pattern)}</span>`);
      } else if (hint.type === "regex") {
        parts.push(`<span style="color: #4dd0e1;">regex:</span> <span style="color: #a5d6a7;">${escapeHtml(hint.pattern)}</span>`);
      } else if (hint.type === "map") {
        const count = Object.keys(hint.mappings || {}).length;
        const preview = Object.keys(hint.mappings || {}).slice(0, 3).join(", ");
        parts.push(`<span style="color: #4dd0e1;">map:</span> <span style="color: #b39ddb;">${count} value${count !== 1 ? "s" : ""}</span> <span style="color: #888;">(${escapeHtml(preview)}${count > 3 ? "..." : ""})</span>`);
      }
      if (hint.description) {
        parts.push(`<span style="color: #999;">${escapeHtml(hint.description)}</span>`);
      }
      return parts.join(" \xB7 ");
    };
    const updateCursorInfo = (validation) => {
      const pos = maskInput.selectionStart;
      const maskValue = maskInput.value;
      const variable = findVariableAtCursor(maskValue, pos);
      if (variable) {
        const hint = availableHints[variable.hintName];
        cursorInfoElement.style.display = "block";
        cursorInfoElement.innerHTML = `<span style="color: #64b5f6; font-weight: 500;">\${${escapeHtml(variable.varContent)}}</span> \xB7 ${formatHintInfo(variable.hintName, hint)}`;
        return;
      }
      if (!validation || validation.errors.length === 0) {
        cursorInfoElement.textContent = "";
        cursorInfoElement.style.display = "none";
        return;
      }
      const firstError = validation.errors[0];
      const errorPos = firstError.position !== void 0 ? firstError.position : null;
      if (errorPos === null) {
        cursorInfoElement.textContent = "";
        cursorInfoElement.style.display = "none";
        return;
      }
      cursorInfoElement.style.display = "block";
      const errorRangeEnd = firstError.rangeEnd !== void 0 ? firstError.rangeEnd : errorPos + 1;
      if (pos >= errorPos && pos < errorRangeEnd) {
        const charAtError = errorPos < maskValue.length ? maskValue[errorPos] : "";
        cursorInfoElement.innerHTML = `<span style="color: #f44336;">\u26A0 Error at position ${errorPos}${charAtError ? ` ('${escapeHtml(charAtError)}')` : " (end)"}</span>`;
      } else {
        const charAtPos = pos !== null && pos < maskValue.length ? maskValue[pos] : "";
        const charAtError = errorPos < maskValue.length ? maskValue[errorPos] : "";
        cursorInfoElement.innerHTML = `Cursor: ${pos}${charAtPos ? ` ('${escapeHtml(charAtPos)}')` : " (end)"} | <span style="color: #f44336;">Error: ${errorPos}${charAtError ? ` ('${escapeHtml(charAtError)}')` : " (end)"}</span>`;
      }
    };
    const performValidation = () => {
      const validation = validateMaskWithDetails(maskInput.value, availableHints);
      updateMaskHighlighting(maskInput, overlayElement, availableHints);
      renderStatusMessages(statusContainer, validation);
      updateCursorInfo(validation);
      updateAutocomplete();
      if (onValidationChange) {
        onValidationChange(validation);
      }
      return validation;
    };
    maskInput.addEventListener("input", performValidation);
    maskInput.addEventListener("click", () => {
      const validation = validateMaskWithDetails(maskInput.value, availableHints);
      updateCursorInfo(validation);
    });
    maskInput.addEventListener("keyup", (e) => {
      if (!["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Enter", "Escape", "Tab"].includes(e.key)) {
        const validation = validateMaskWithDetails(
          maskInput.value,
          availableHints
        );
        updateCursorInfo(validation);
        updateAutocomplete();
      } else if (["ArrowLeft", "ArrowRight"].includes(e.key)) {
        const validation = validateMaskWithDetails(
          maskInput.value,
          availableHints
        );
        updateCursorInfo(validation);
      }
    });
    maskInput.addEventListener("keydown", handleKeyDown);
    maskInput.addEventListener("focus", () => {
      const validation = validateMaskWithDetails(maskInput.value, availableHints);
      updateCursorInfo(validation);
      updateAutocomplete();
    });
    maskInput.addEventListener("blur", () => {
      setTimeout(closeAutocomplete, 200);
    });
    return performValidation;
  }
  function injectUI(instance) {
    const fileInput = document.querySelector('input[type="file"]');
    if (!fileInput) {
      console.warn("No file input found on page, UI injection aborted");
      return;
    }
    const existingUI = document.getElementById("ggn-upload-templator-ui");
    if (existingUI) {
      existingUI.remove();
    }
    const uiContainer = document.createElement("div");
    uiContainer.id = "ggn-upload-templator-ui";
    uiContainer.innerHTML = MAIN_UI_HTML(instance);
    try {
      fileInput.parentNode.insertBefore(uiContainer, fileInput);
    } catch (error) {
      console.error("Failed to insert UI container:", error);
      return;
    }
    try {
      const createBtn = document.getElementById("create-template-btn");
      const templateSelector = document.getElementById("template-selector");
      const manageBtn = document.getElementById("manage-templates-btn");
      const editBtn = document.getElementById("edit-selected-template-btn");
      const applyBtn = document.getElementById("apply-template-btn");
      if (createBtn) {
        createBtn.addEventListener(
          "click",
          async () => await instance.showTemplateCreator()
        );
      }
      if (templateSelector) {
        templateSelector.addEventListener(
          "change",
          (e) => instance.selectTemplate(e.target.value)
        );
      }
      if (manageBtn) {
        manageBtn.addEventListener(
          "click",
          () => instance.showTemplateAndSettingsManager()
        );
      }
      if (editBtn) {
        editBtn.addEventListener("click", (e) => {
          e.preventDefault();
          instance.editTemplate(instance.selectedTemplate);
        });
      }
      if (applyBtn) {
        applyBtn.addEventListener(
          "click",
          () => instance.applyTemplateToCurrentTorrent()
        );
      }
      const variablesRow = document.getElementById("variables-row");
      if (variablesRow) {
        variablesRow.addEventListener("click", async () => {
          const variables = await instance.getCurrentVariables();
          const totalCount = Object.keys(variables.all).length;
          if (totalCount > 0) {
            instance.showVariablesModal();
          }
        });
      }
    } catch (error) {
      console.error("Failed to bind UI events:", error);
    }
  }
  async function showTemplateCreator(instance, editTemplateName = null, editTemplate2 = null) {
    const formData = getCurrentFormData(instance.config);
    if (Object.keys(formData).length === 0) {
      alert("No form fields found on this page.");
      return;
    }
    let selectedTorrentName = "";
    let commentVariables = {};
    const fileInputs = instance.config.TARGET_FORM_SELECTOR ? document.querySelectorAll(
      `${instance.config.TARGET_FORM_SELECTOR} input[type="file"]`
    ) : document.querySelectorAll('input[type="file"]');
    for (const input of fileInputs) {
      if (input.files && input.files[0] && input.files[0].name.toLowerCase().endsWith(".torrent")) {
        try {
          const torrentData = await TorrentUtils.parseTorrentFile(input.files[0]);
          selectedTorrentName = torrentData.name || "";
          commentVariables = TorrentUtils.parseCommentVariables(
            torrentData.comment
          );
          break;
        } catch (error) {
          console.warn("Could not parse selected torrent file:", error);
        }
      }
    }
    const modal = document.createElement("div");
    modal.className = "gut-modal";
    modal.innerHTML = TEMPLATE_CREATOR_HTML(
      formData,
      instance,
      editTemplateName,
      editTemplate2,
      selectedTorrentName
    );
    const canGoBack = editTemplateName !== null;
    ModalStack.replace(modal, {
      type: "replace",
      canGoBack,
      backFactory: canGoBack ? () => instance.showTemplateAndSettingsManager() : null,
      metadata: { instance, editTemplateName, editTemplate: editTemplate2 }
    });
    const maskInput = modal.querySelector("#torrent-mask");
    const sampleInput = modal.querySelector("#sample-torrent");
    const templateInputs = modal.querySelectorAll(".template-input");
    const cursorInfo = modal.querySelector("#mask-cursor-info");
    const toggleBtn = modal.querySelector("#toggle-unselected");
    const filterInput = modal.querySelector("#field-filter");
    const filterFields = () => {
      const filterValue = filterInput.value.toLowerCase();
      const fieldRows = modal.querySelectorAll(".gut-field-row");
      const fieldList = modal.querySelector(".gut-field-list");
      let visibleCount = 0;
      const existingMessage = fieldList.querySelector(".gut-no-results");
      if (existingMessage) {
        existingMessage.remove();
      }
      fieldRows.forEach((row) => {
        const checkbox = row.querySelector('input[type="checkbox"]');
        const label = row.querySelector("label");
        const fieldName = checkbox.dataset.field.toLowerCase();
        const labelText = label.textContent.toLowerCase();
        const matchesFilter = !filterValue || fieldName.includes(filterValue) || labelText.includes(filterValue);
        const shouldShowBasedOnSelection = checkbox.checked || !instance.hideUnselectedFields;
        const shouldShow = matchesFilter && shouldShowBasedOnSelection;
        if (shouldShow) {
          row.classList.remove("gut-hidden");
          visibleCount++;
        } else {
          row.classList.add("gut-hidden");
        }
      });
      if (filterValue && visibleCount === 0) {
        const noResultsMessage = document.createElement("div");
        noResultsMessage.className = "gut-no-results";
        noResultsMessage.style.cssText = "padding: 20px; text-align: center; color: #888; font-style: italic;";
        noResultsMessage.textContent = `No fields found matching "${filterValue}"`;
        fieldList.appendChild(noResultsMessage);
      }
    };
    const toggleUnselectedFields = () => {
      instance.hideUnselectedFields = !instance.hideUnselectedFields;
      localStorage.setItem(
        "ggn-upload-templator-hide-unselected",
        JSON.stringify(instance.hideUnselectedFields)
      );
      toggleBtn.textContent = instance.hideUnselectedFields ? "Show Unselected" : "Hide Unselected";
      filterFields();
    };
    toggleBtn.textContent = instance.hideUnselectedFields ? "Show Unselected" : "Hide Unselected";
    filterFields();
    toggleBtn.addEventListener("click", toggleUnselectedFields);
    filterInput.addEventListener("input", filterFields);
    const overlayDiv = modal.querySelector("#mask-highlight-overlay");
    const statusContainer = modal.querySelector("#mask-status-container");
    const saveButton = modal.querySelector("#save-template");
    const performValidation = setupMaskValidation(
      maskInput,
      cursorInfo,
      statusContainer,
      overlayDiv,
      (validation) => {
        saveButton.disabled = !validation.valid;
        updatePreviews();
      },
      instance.hints
    );
    const updatePreviews = () => {
      const mask = maskInput.value;
      const sample = sampleInput.value;
      const validation = validateMaskWithDetails(mask, instance.hints);
      const parseResult = parseTemplateWithOptionals(
        mask,
        sample,
        instance.hints
      );
      const maskExtracted = { ...parseResult };
      delete maskExtracted._matchedOptionals;
      delete maskExtracted._optionalCount;
      const allVariables = { ...commentVariables, ...maskExtracted };
      const extractedVarsContainer = modal.querySelector("#extracted-variables");
      if (Object.keys(allVariables).length === 0) {
        const hasMaskVariables = validation.variables.valid.length > 0 || validation.variables.reserved.length > 0;
        if (hasMaskVariables) {
          extractedVarsContainer.innerHTML = '<div class="gut-no-variables">Select a torrent file or provide a sample torrent name to extract variables.</div>';
        } else {
          extractedVarsContainer.innerHTML = '<div class="gut-no-variables">No variables defined yet. Add variables like ${name} to your mask.</div>';
        }
      } else {
        extractedVarsContainer.innerHTML = Object.entries(allVariables).map(
          ([varName, varValue]) => `
            <div class="gut-variable-item">
              <span class="gut-variable-name">\${${escapeHtml(varName)}}</span>
              <span class="gut-variable-value ${varValue ? "" : "empty"}">${varValue ? escapeHtml(varValue) : "(empty)"}</span>
            </div>
          `
        ).join("");
      }
      if (parseResult._matchedOptionals && parseResult._optionalCount) {
        const matchCount = parseResult._matchedOptionals.filter((x) => x).length;
        const optionalInfo = document.createElement("div");
        optionalInfo.className = "gut-variable-item";
        optionalInfo.style.cssText = "background: #2a4a3a; border-left: 3px solid #4caf50;";
        optionalInfo.innerHTML = `
        <span class="gut-variable-name" style="color: #4caf50;">Optional blocks</span>
        <span class="gut-variable-value">Matched ${matchCount}/${parseResult._optionalCount}</span>
      `;
        extractedVarsContainer.appendChild(optionalInfo);
      }
      templateInputs.forEach((input) => {
        const fieldName = input.dataset.template;
        const preview = modal.querySelector(`[data-preview="${fieldName}"]`);
        if (input.type === "checkbox") {
          preview.textContent = input.checked ? "\u2713 checked" : "\u2717 unchecked";
          preview.className = "gut-preview";
        } else if (input.tagName.toLowerCase() === "select") {
          const variableToggle = modal.querySelector(
            `.gut-variable-toggle[data-field="${fieldName}"]`
          );
          const isVariableMode = variableToggle && variableToggle.dataset.state === "on";
          if (isVariableMode) {
            const variableInput = modal.querySelector(
              `.gut-variable-input[data-field="${fieldName}"]`
            );
            const matchTypeSelect = modal.querySelector(
              `.gut-match-type[data-field="${fieldName}"]`
            );
            const variableName = variableInput ? variableInput.value.trim() : "";
            const matchType = matchTypeSelect ? matchTypeSelect.value : "exact";
            if (variableName && allVariables[variableName.replace(/^\$\{|\}$/g, "")]) {
              const variableValue = allVariables[variableName.replace(/^\$\{|\}$/g, "")];
              const matchedOption = findMatchingOption(
                input.options,
                variableValue,
                matchType
              );
              if (matchedOption) {
                preview.textContent = `\u2192 "${matchedOption.text}" (matched "${variableValue}" using ${matchType})`;
                preview.className = "gut-preview active visible";
              } else {
                preview.textContent = `\u2192 No match found for "${variableValue}" using ${matchType}`;
                preview.className = "gut-preview visible";
              }
            } else if (variableName) {
              preview.textContent = `\u2192 Variable ${variableName} not found in extracted data`;
              preview.className = "gut-preview visible";
            } else {
              preview.textContent = "";
              preview.className = "gut-preview";
            }
          } else {
            preview.textContent = "";
            preview.className = "gut-preview";
          }
        } else {
          const inputValue = input.value || "";
          const interpolated = interpolate(inputValue, allVariables);
          if (inputValue.includes("${") && Object.keys(allVariables).length > 0) {
            preview.textContent = `\u2192 ${interpolated}`;
            preview.className = "gut-preview active visible";
          } else {
            preview.textContent = "";
            preview.className = "gut-preview";
          }
        }
      });
    };
    [maskInput, sampleInput, ...templateInputs].forEach((input) => {
      input.addEventListener("input", updatePreviews);
      input.addEventListener("change", updatePreviews);
    });
    maskInput.addEventListener("scroll", () => {
      const overlayDiv2 = modal.querySelector("#mask-highlight-overlay");
      if (overlayDiv2) {
        overlayDiv2.scrollTop = maskInput.scrollTop;
        overlayDiv2.scrollLeft = maskInput.scrollLeft;
      }
    });
    performValidation();
    updatePreviews();
    modal.addEventListener("change", (e) => {
      if (e.target.type === "checkbox") {
        filterFields();
      }
    });
    modal.querySelector("#cancel-template").addEventListener("click", () => {
      if (canGoBack) {
        ModalStack.back();
      } else {
        ModalStack.pop();
      }
    });
    const closeX = modal.querySelector("#modal-close-x");
    if (closeX) {
      closeX.addEventListener("click", () => {
        if (canGoBack) {
          ModalStack.back();
        } else {
          ModalStack.pop();
        }
      });
    }
    modal.querySelector("#save-template").addEventListener("click", () => {
      instance.saveTemplate(modal, editTemplateName);
    });
    modal.addEventListener("click", (e) => {
      if (e.target === modal) {
        if (canGoBack) {
          ModalStack.back();
        } else {
          ModalStack.pop();
        }
      }
    });
    modal.addEventListener("click", (e) => {
      if (e.target.classList.contains("gut-variable-toggle")) {
        e.preventDefault();
        const fieldName = e.target.dataset.field;
        const currentState = e.target.dataset.state;
        const newState = currentState === "off" ? "on" : "off";
        e.target.dataset.state = newState;
        e.target.textContent = `Match from variable: ${newState.toUpperCase()}`;
        const staticSelect = modal.querySelector(
          `select.select-static-mode[data-template="${fieldName}"]`
        );
        const variableControls = modal.querySelector(
          `.gut-variable-controls[data-field="${fieldName}"]`
        );
        if (newState === "on") {
          staticSelect.classList.add("hidden");
          variableControls.classList.add("visible");
        } else {
          staticSelect.classList.remove("hidden");
          variableControls.classList.remove("visible");
        }
        updatePreviews();
      }
    });
    const variableInputs = modal.querySelectorAll(
      ".gut-variable-input, .gut-match-type"
    );
    variableInputs.forEach((input) => {
      input.addEventListener("input", updatePreviews);
      input.addEventListener("change", updatePreviews);
    });
    const backBtn = modal.querySelector("#back-to-manager");
    if (backBtn) {
      backBtn.addEventListener("click", () => {
        ModalStack.back();
      });
    }
    const sandboxLink = modal.querySelector("#test-mask-sandbox-link");
    if (sandboxLink) {
      sandboxLink.addEventListener("click", (e) => {
        e.preventDefault();
        const mask = maskInput.value;
        const sample = sampleInput.value;
        ModalStack.pop();
        instance.showSandboxWithMask(mask, sample);
      });
    }
  }
  function showVariablesModal(instance, variables, torrentName = "", mask = "") {
    const modal = document.createElement("div");
    modal.className = "gut-modal";
    modal.innerHTML = VARIABLES_MODAL_HTML();
    ModalStack.push(modal, {
      type: "stack",
      metadata: { instance, variables, torrentName, mask }
    });
    const resultsContainer = modal.querySelector("#variables-results-container");
    if (torrentName && mask) {
      const testResults = testMaskAgainstSamples(
        mask,
        [torrentName],
        instance.hints
      );
      renderMatchResults(resultsContainer, testResults, {
        showLabel: false
      });
    } else if (Object.keys(variables).length > 0) {
      const testResults = {
        results: [
          {
            name: torrentName || "Unknown",
            matched: true,
            variables,
            positions: {}
          }
        ]
      };
      renderMatchResults(resultsContainer, testResults, {
        showLabel: false
      });
    }
    modal.querySelector("#close-variables-modal").addEventListener("click", () => {
      ModalStack.pop();
    });
    const closeX = modal.querySelector("#modal-close-x");
    if (closeX) {
      closeX.addEventListener("click", () => {
        ModalStack.pop();
      });
    }
    modal.addEventListener("click", (e) => {
      if (e.target === modal) {
        ModalStack.pop();
      }
    });
  }
  function escapeHtml(text) {
    const div = document.createElement("div");
    div.textContent = text;
    return div.innerHTML;
  }
  function truncateValue(value, threshold = 20) {
    if (!value || value.length <= threshold) {
      return value;
    }
    const words = value.split(/\s+/);
    if (words.length <= 2) {
      return value;
    }
    let firstWord = words[0];
    let lastWord = words[words.length - 1];
    if (firstWord.length > 10) {
      firstWord = firstWord.substring(0, 10);
    }
    if (lastWord.length > 10) {
      lastWord = lastWord.substring(lastWord.length - 10);
    }
    return `${firstWord}<span class="gut-truncate-ellipsis">...</span>${lastWord}`;
  }
  function wrapWordsInSpans(text) {
    const chars = text.split("");
    return chars.map(
      (char, i) => `<span class="gut-char-span" data-char-index="${i}">${escapeHtml(char)}</span>`
    ).join("");
  }
  function renderMatchResults(container, testResults, options = {}) {
    const { showLabel = true, labelElement = null } = options;
    if (!container || !testResults || testResults.results.length === 0) {
      container.innerHTML = '<div class="gut-no-variables">Enter a mask and sample names to see match results.</div>';
      if (showLabel && labelElement) {
        labelElement.textContent = "Match Results:";
      }
      return;
    }
    const matchCount = testResults.results.filter((r) => r.matched).length;
    const totalCount = testResults.results.length;
    if (showLabel && labelElement) {
      labelElement.textContent = `Match Results (${matchCount}/${totalCount} matched):`;
    }
    const html = testResults.results.map((result, resultIndex) => {
      const isMatch = result.matched;
      const icon = isMatch ? "\u2713" : "\u2717";
      const className = isMatch ? "gut-sandbox-match" : "gut-sandbox-no-match";
      let variablesHtml = "";
      if (isMatch && Object.keys(result.variables).length > 0) {
        variablesHtml = '<div class="gut-match-variables-container">' + Object.entries(result.variables).filter(
          ([key]) => key !== "_matchedOptionals" && key !== "_optionalCount"
        ).map(([key, value]) => {
          const displayValue = value ? truncateValue(escapeHtml(value)) : "(empty)";
          return `<div class="gut-variable-item gut-match-variable-item" data-result-index="${resultIndex}" data-var-name="${escapeHtml(key)}">
            <span class="gut-variable-name">\${${escapeHtml(key)}}</span><span class="gut-match-separator"> = </span><span class="gut-variable-value">${displayValue}</span>
          </div>`;
        }).join("") + "</div>";
        if (result.optionalInfo) {
          variablesHtml += `<div class="gut-match-optional-info">
          Optional blocks: ${result.optionalInfo.matched}/${result.optionalInfo.total} matched
        </div>`;
        }
      }
      return `
      <div class="${className} gut-match-result-item" data-result-index="${resultIndex}">
        <div class="gut-match-result-header">
          <span class="gut-match-icon gut-match-icon-${isMatch ? "success" : "error"}">${icon}</span>
          <div class="gut-sandbox-sample-name" data-result-index="${resultIndex}">${wrapWordsInSpans(result.name)}</div>
        </div>
        ${variablesHtml}
      </div>
    `;
    }).join("");
    container.innerHTML = html;
    container._testResults = testResults;
    if (!container._hasEventListeners) {
      container.addEventListener(
        "mouseenter",
        (e) => {
          if (e.target.classList.contains("gut-variable-item")) {
            const resultIndex = parseInt(e.target.dataset.resultIndex);
            const varName = e.target.dataset.varName;
            const currentResults = container._testResults;
            if (!currentResults || !currentResults.results[resultIndex]) {
              return;
            }
            const result = currentResults.results[resultIndex];
            if (result.positions && result.positions[varName]) {
              const sampleNameEl = container.querySelector(
                `.gut-sandbox-sample-name[data-result-index="${resultIndex}"]`
              );
              const pos = result.positions[varName];
              const charSpans = sampleNameEl.querySelectorAll(".gut-char-span");
              charSpans.forEach((span) => {
                const charIndex = parseInt(span.dataset.charIndex);
                if (charIndex >= pos.start && charIndex < pos.end) {
                  span.classList.add("gut-match-highlight");
                }
              });
            }
          }
        },
        true
      );
      container.addEventListener(
        "mouseleave",
        (e) => {
          if (e.target.classList.contains("gut-variable-item")) {
            const resultIndex = parseInt(e.target.dataset.resultIndex);
            const currentResults = container._testResults;
            if (!currentResults || !currentResults.results[resultIndex]) {
              return;
            }
            currentResults.results[resultIndex];
            const sampleNameEl = container.querySelector(
              `.gut-sandbox-sample-name[data-result-index="${resultIndex}"]`
            );
            const charSpans = sampleNameEl.querySelectorAll(".gut-char-span");
            charSpans.forEach((span) => span.classList.remove("gut-match-highlight"));
          }
        },
        true
      );
      container._hasEventListeners = true;
    }
  }
  function renderSandboxResults(modal, testResults) {
    const resultsContainer = modal.querySelector("#sandbox-results");
    const resultsLabel = modal.querySelector("#sandbox-results-label");
    renderMatchResults(resultsContainer, testResults, {
      showLabel: true,
      labelElement: resultsLabel
    });
  }
  function saveTemplate(instance, modal, editingTemplateName = null) {
    const name = modal.querySelector("#template-name").value.trim();
    const mask = modal.querySelector("#torrent-mask").value.trim();
    if (!name || !mask) {
      alert("Please provide both template name and torrent mask.");
      return;
    }
    if (editingTemplateName && name !== editingTemplateName && instance.templates[name] || !editingTemplateName && instance.templates[name]) {
      if (!confirm(`Template "${name}" already exists. Overwrite?`)) {
        return;
      }
    }
    const fieldMappings = {};
    const variableMatchingConfig = {};
    const checkedFields = modal.querySelectorAll(
      '.gut-field-row input[type="checkbox"]:checked'
    );
    checkedFields.forEach((checkbox) => {
      const fieldName = checkbox.dataset.field;
      const templateInput = modal.querySelector(
        `[data-template="${fieldName}"]`
      );
      if (templateInput) {
        if (templateInput.type === "checkbox") {
          fieldMappings[fieldName] = templateInput.checked;
        } else if (templateInput.tagName.toLowerCase() === "select") {
          const variableToggle = modal.querySelector(
            `.gut-variable-toggle[data-field="${fieldName}"]`
          );
          const isVariableMode = variableToggle && variableToggle.dataset.state === "on";
          if (isVariableMode) {
            const variableInput = modal.querySelector(
              `.gut-variable-input[data-field="${fieldName}"]`
            );
            const matchTypeSelect = modal.querySelector(
              `.gut-match-type[data-field="${fieldName}"]`
            );
            variableMatchingConfig[fieldName] = {
              variableName: variableInput ? variableInput.value.trim() : "",
              matchType: matchTypeSelect ? matchTypeSelect.value : "exact"
            };
            fieldMappings[fieldName] = variableInput ? variableInput.value.trim() : "";
          } else {
            fieldMappings[fieldName] = templateInput.value;
          }
        } else {
          fieldMappings[fieldName] = templateInput.value;
        }
      }
    });
    const allFieldRows = modal.querySelectorAll(".gut-field-row");
    const customUnselectedFields = [];
    allFieldRows.forEach((row) => {
      const checkbox = row.querySelector('input[type="checkbox"]');
      if (checkbox) {
        const fieldName = checkbox.dataset.field;
        const isDefaultIgnored = instance.config.IGNORED_FIELDS_BY_DEFAULT.includes(
          fieldName.toLowerCase()
        );
        const isCurrentlyChecked = checkbox.checked;
        if (isDefaultIgnored && isCurrentlyChecked || !isDefaultIgnored && !isCurrentlyChecked) {
          customUnselectedFields.push({
            field: fieldName,
            selected: isCurrentlyChecked
          });
        }
      }
    });
    if (editingTemplateName && name !== editingTemplateName) {
      delete instance.templates[editingTemplateName];
      if (instance.selectedTemplate === editingTemplateName) {
        instance.selectedTemplate = name;
        saveSelectedTemplate(name);
      }
    }
    instance.templates[name] = {
      mask,
      fieldMappings,
      customUnselectedFields: customUnselectedFields.length > 0 ? customUnselectedFields : void 0,
      variableMatching: Object.keys(variableMatchingConfig).length > 0 ? variableMatchingConfig : void 0
    };
    saveTemplates(instance.templates);
    updateTemplateSelector(instance);
    instance.updateVariableCount();
    const action = editingTemplateName ? "updated" : "saved";
    instance.showStatus(`Template "${name}" ${action} successfully!`);
    ModalStack.pop();
  }
  function deleteTemplate(instance, templateName) {
    delete instance.templates[templateName];
    saveTemplates(instance.templates);
    if (instance.selectedTemplate === templateName) {
      instance.selectedTemplate = null;
      removeSelectedTemplate();
    }
    updateTemplateSelector(instance);
    instance.showStatus(`Template "${templateName}" deleted`);
  }
  function cloneTemplate(instance, templateName) {
    const originalTemplate = instance.templates[templateName];
    if (!originalTemplate) return;
    const cloneName = `${templateName} (Clone)`;
    instance.templates[cloneName] = {
      mask: originalTemplate.mask,
      fieldMappings: { ...originalTemplate.fieldMappings },
      customUnselectedFields: originalTemplate.customUnselectedFields ? [...originalTemplate.customUnselectedFields] : void 0,
      variableMatching: originalTemplate.variableMatching ? { ...originalTemplate.variableMatching } : void 0
    };
    saveTemplates(instance.templates);
    updateTemplateSelector(instance);
    instance.showStatus(`Template "${cloneName}" created`);
  }
  function editTemplate(instance, templateName) {
    const template = instance.templates[templateName];
    if (!template) return;
    instance.showTemplateCreator(templateName, template);
  }
  function selectTemplate(instance, templateName) {
    instance.selectedTemplate = templateName || null;
    if (templateName) {
      saveSelectedTemplate(templateName);
    } else {
      removeSelectedTemplate();
    }
    updateEditButtonVisibility(instance);
    instance.updateVariableCount();
    if (templateName === "none") {
      instance.showStatus("No template selected - auto-fill disabled");
    } else if (templateName) {
      instance.showStatus(`Template "${templateName}" selected`);
    }
  }
  function updateTemplateSelector(instance) {
    const selector = document.getElementById("template-selector");
    if (!selector) return;
    selector.innerHTML = TEMPLATE_SELECTOR_HTML(instance);
    updateEditButtonVisibility(instance);
  }
  function updateEditButtonVisibility(instance) {
    const editBtn = document.getElementById("edit-selected-template-btn");
    if (!editBtn) return;
    const shouldShow = instance.selectedTemplate && instance.selectedTemplate !== "none" && instance.templates[instance.selectedTemplate];
    editBtn.style.display = shouldShow ? "" : "none";
  }
  function refreshTemplateManager(instance, modal) {
    const templateList = modal.querySelector(".gut-template-list");
    if (!templateList) return;
    templateList.innerHTML = TEMPLATE_LIST_HTML(instance);
  }
  function setupAutoResize(textarea, options = {}) {
    if (!textarea || textarea.tagName !== "TEXTAREA") {
      console.warn("setupAutoResize: Invalid textarea element provided");
      return () => {
      };
    }
    const {
      minLines = 3,
      maxLines = 7,
      initialResize = true
    } = options;
    const autoResize = () => {
      textarea.style.height = "auto";
      const computedStyle = window.getComputedStyle(textarea);
      const lineHeight = parseFloat(computedStyle.lineHeight);
      const fontSize = parseFloat(computedStyle.fontSize);
      const actualLineHeight = lineHeight && lineHeight !== 0 ? lineHeight : fontSize * 1.4;
      const minHeight = actualLineHeight * minLines;
      const maxHeight = actualLineHeight * maxLines;
      const contentHeight = textarea.scrollHeight;
      const newHeight = Math.min(Math.max(contentHeight, minHeight), maxHeight);
      textarea.style.height = newHeight + "px";
    };
    textarea.addEventListener("input", autoResize);
    textarea.addEventListener("change", autoResize);
    if (initialResize) {
      setTimeout(autoResize, 0);
    }
    return autoResize;
  }
  function parseKeybinding(keybinding) {
    const parts = keybinding.split("+").map((k) => k.trim().toLowerCase());
    return {
      ctrl: parts.includes("ctrl"),
      meta: parts.includes("cmd") || parts.includes("meta"),
      shift: parts.includes("shift"),
      alt: parts.includes("alt"),
      key: parts.find((k) => !["ctrl", "cmd", "meta", "shift", "alt"].includes(k)) || "enter"
    };
  }
  function matchesKeybinding(event, keys) {
    return event.key.toLowerCase() === keys.key && !!event.ctrlKey === keys.ctrl && !!event.metaKey === keys.meta && !!event.shiftKey === keys.shift && !!event.altKey === keys.alt;
  }
  function buildKeybindingFromEvent(event) {
    const keys = [];
    if (event.ctrlKey) keys.push("Ctrl");
    if (event.metaKey) keys.push("Cmd");
    if (event.shiftKey) keys.push("Shift");
    if (event.altKey) keys.push("Alt");
    keys.push(event.key.charAt(0).toUpperCase() + event.key.slice(1));
    return keys.join("+");
  }
  function showTemplateAndSettingsManager(instance) {
    const modal = document.createElement("div");
    modal.className = "gut-modal";
    modal.innerHTML = MODAL_HTML(instance);
    ModalStack.push(modal, {
      type: "replace",
      canGoBack: false,
      metadata: { instance }
    });
    modal.querySelectorAll(".gut-tab-btn").forEach((btn) => {
      btn.addEventListener("click", (e) => {
        const tabName = e.target.dataset.tab;
        modal.querySelectorAll(".gut-tab-btn").forEach((b) => b.classList.remove("active"));
        e.target.classList.add("active");
        modal.querySelectorAll(".gut-tab-content").forEach((c) => c.classList.remove("active"));
        modal.querySelector(`#${tabName}-tab`).classList.add("active");
      });
    });
    const customSelectorsTextarea = modal.querySelector(
      "#setting-custom-selectors"
    );
    const previewGroup = modal.querySelector("#custom-selectors-preview-group");
    const matchedContainer = modal.querySelector("#custom-selectors-matched");
    const updateCustomSelectorsPreview = () => {
      const selectorsText = customSelectorsTextarea.value.trim();
      const selectors = selectorsText.split("\n").map((selector) => selector.trim()).filter((selector) => selector);
      const originalSelectors = instance.config.CUSTOM_FIELD_SELECTORS;
      instance.config.CUSTOM_FIELD_SELECTORS = selectors;
      if (selectors.length === 0) {
        previewGroup.style.display = "none";
        instance.config.CUSTOM_FIELD_SELECTORS = originalSelectors;
        return;
      }
      previewGroup.style.display = "block";
      let matchedElements = [];
      const formSelector = modal.querySelector("#setting-form-selector").value.trim() || instance.config.TARGET_FORM_SELECTOR;
      const targetForm = document.querySelector(formSelector);
      selectors.forEach((selector) => {
        try {
          const elements = targetForm ? targetForm.querySelectorAll(selector) : document.querySelectorAll(selector);
          Array.from(elements).forEach((element) => {
            const tagName = element.tagName.toLowerCase();
            const id = element.id;
            const name = element.name || element.getAttribute("name");
            const classes = element.className || "";
            const label = getFieldLabel(element, instance.config);
            const elementId = element.id || element.name || `${tagName}-${Array.from(element.parentNode.children).indexOf(element)}`;
            if (!matchedElements.find((e) => e.elementId === elementId)) {
              matchedElements.push({
                elementId,
                element,
                tagName,
                id,
                name,
                classes,
                label,
                selector
              });
            }
          });
        } catch (e) {
          console.warn(`Invalid custom selector: ${selector}`, e);
        }
      });
      const matchedElementsLabel = modal.querySelector("#matched-elements-label");
      if (matchedElements.length === 0) {
        matchedElementsLabel.textContent = "Matched Elements:";
        matchedContainer.innerHTML = '<div class="gut-no-variables">No elements matched by custom selectors.</div>';
      } else {
        matchedElementsLabel.textContent = `Matched Elements (${matchedElements.length}):`;
        matchedContainer.innerHTML = matchedElements.map((item) => {
          const displayName = item.label || item.name || item.id || `${item.tagName}`;
          const displayInfo = [
            item.tagName.toUpperCase(),
            item.id ? `#${item.id}` : "",
            item.name ? `name="${item.name}"` : "",
            item.classes ? `.${item.classes.split(" ").filter((c) => c).join(".")}` : ""
          ].filter((info) => info).join(" ");
          return `
            <div class="gut-variable-item">
              <span class="gut-variable-name">${instance.escapeHtml(displayName)}</span>
              <span class="gut-variable-value">${instance.escapeHtml(displayInfo)}</span>
            </div>
          `;
        }).join("");
      }
      instance.config.CUSTOM_FIELD_SELECTORS = originalSelectors;
    };
    updateCustomSelectorsPreview();
    customSelectorsTextarea.addEventListener(
      "input",
      updateCustomSelectorsPreview
    );
    modal.querySelector("#setting-form-selector").addEventListener("input", updateCustomSelectorsPreview);
    modal.querySelector("#ggn-infobox-link")?.addEventListener("click", (e) => {
      e.preventDefault();
      const currentValue = customSelectorsTextarea.value.trim();
      const ggnInfoboxSelector = ".infobox-input-holder input";
      if (!currentValue.includes(ggnInfoboxSelector)) {
        const newValue = currentValue ? `${currentValue}
${ggnInfoboxSelector}` : ggnInfoboxSelector;
        customSelectorsTextarea.value = newValue;
        updateCustomSelectorsPreview();
      }
    });
    modal.querySelector("#save-settings")?.addEventListener("click", () => {
      saveSettingsFromModal(instance, modal);
    });
    modal.querySelector("#reset-settings")?.addEventListener("click", () => {
      if (confirm(
        "Reset all settings to defaults? This will require a page reload."
      )) {
        resetSettings(instance, modal);
      }
    });
    modal.querySelector("#delete-all-config")?.addEventListener("click", () => {
      if (confirm(
        "\u26A0\uFE0F WARNING: This will permanently delete ALL GGn Upload Templator data including templates, settings, and selected template.\n\nThis action CANNOT be undone!\n\nAre you sure you want to continue?"
      )) {
        deleteAllConfig(instance);
      }
    });
    const sandboxMaskInput = modal.querySelector("#sandbox-mask-input");
    const sandboxMaskDisplay = modal.querySelector("#sandbox-mask-display");
    const sandboxSampleInput = modal.querySelector("#sandbox-sample-input");
    const sandboxResultsContainer = modal.querySelector("#sandbox-results");
    const sandboxSetSelect = modal.querySelector("#sandbox-set-select");
    const saveBtn = modal.querySelector("#save-sandbox-set");
    const renameBtn = modal.querySelector("#rename-sandbox-set");
    const deleteBtn = modal.querySelector("#delete-sandbox-set");
    const sandboxCursorInfo = modal.querySelector("#sandbox-mask-cursor-info");
    const sandboxStatusContainer = modal.querySelector("#sandbox-mask-status");
    const toggleCompiledRegexLink = modal.querySelector("#toggle-compiled-regex");
    const sandboxCompiledRegexDisplay = modal.querySelector(
      "#sandbox-compiled-regex"
    );
    let sandboxDebounceTimeout = null;
    let currentLoadedSet = instance.currentSandboxSet || "";
    let showingCompiledRegex = localStorage.getItem("ggn-upload-templator-show-compiled-regex") === "true";
    const updateButtonStates = () => {
      if (currentLoadedSet && currentLoadedSet !== "") {
        saveBtn.textContent = "Update";
        renameBtn.style.display = "";
        deleteBtn.style.display = "";
      } else {
        saveBtn.textContent = "Save";
        renameBtn.style.display = "none";
        deleteBtn.style.display = "none";
      }
    };
    updateButtonStates();
    const updateSandboxTest = () => {
      const mask = sandboxMaskInput.value;
      const sampleText = sandboxSampleInput.value.trim();
      const samples = sampleText.split("\n").map((s) => s.trim()).filter((s) => s);
      if (!mask || samples.length === 0) {
        sandboxResultsContainer.innerHTML = '<div class="gut-no-variables">Enter a mask and sample torrent names to test.</div>';
        return;
      }
      const result = testMaskAgainstSamples(mask, samples, instance.hints);
      renderSandboxResults(modal, result);
    };
    const updateCompiledRegex = () => {
      if (showingCompiledRegex) {
        const mask = sandboxMaskInput.value;
        if (mask) {
          try {
            const compiledRegex = compileUserMaskToRegex(mask, instance.hints);
            sandboxCompiledRegexDisplay.textContent = compiledRegex;
          } catch (error) {
            sandboxCompiledRegexDisplay.textContent = `Error: ${error.message}`;
          }
        } else {
          sandboxCompiledRegexDisplay.textContent = "";
        }
      }
    };
    const debouncedUpdateSandboxTest = () => {
      if (sandboxDebounceTimeout) {
        clearTimeout(sandboxDebounceTimeout);
      }
      sandboxDebounceTimeout = setTimeout(() => {
        updateSandboxTest();
        updateCompiledRegex();
      }, 300);
    };
    setupMaskValidation(
      sandboxMaskInput,
      sandboxCursorInfo,
      sandboxStatusContainer,
      sandboxMaskDisplay,
      () => {
        debouncedUpdateSandboxTest();
      },
      instance.hints
    );
    sandboxMaskInput?.addEventListener("scroll", () => {
      sandboxMaskDisplay.scrollTop = sandboxMaskInput.scrollTop;
      sandboxMaskDisplay.scrollLeft = sandboxMaskInput.scrollLeft;
    });
    sandboxSampleInput?.addEventListener("input", debouncedUpdateSandboxTest);
    sandboxSetSelect?.addEventListener("change", () => {
      const value = sandboxSetSelect.value;
      if (!value || value === "") {
        currentLoadedSet = "";
        instance.currentSandboxSet = "";
        updateButtonStates();
        return;
      }
      const sets = JSON.parse(
        localStorage.getItem("ggn-upload-templator-sandbox-sets") || "{}"
      );
      const data = sets[value];
      if (data) {
        sandboxMaskInput.value = data.mask || "";
        sandboxSampleInput.value = data.samples || "";
        sandboxMaskInput.dispatchEvent(new Event("change"));
        sandboxSampleInput.dispatchEvent(new Event("change"));
        updateMaskHighlighting(sandboxMaskInput, sandboxMaskDisplay, instance.hints);
        updateSandboxTest();
        currentLoadedSet = value;
        instance.currentSandboxSet = value;
        localStorage.setItem("ggn-upload-templator-sandbox-current", value);
        updateButtonStates();
        if (showingCompiledRegex) {
          updateCompiledRegex();
          sandboxCompiledRegexDisplay.classList.add("visible");
          toggleCompiledRegexLink.textContent = "Hide compiled regex";
        }
      }
    });
    if (sandboxSampleInput) {
      setupAutoResize(sandboxSampleInput, {
        minLines: 3,
        maxLines: 7,
        initialResize: true
      });
    }
    saveBtn?.addEventListener("click", () => {
      if (currentLoadedSet && currentLoadedSet !== "") {
        const data = {
          mask: sandboxMaskInput.value,
          samples: sandboxSampleInput.value
        };
        saveSandboxSet(instance, currentLoadedSet, data);
        instance.showStatus(
          `Test set "${currentLoadedSet}" updated successfully!`
        );
      } else {
        const name = prompt("Enter a name for this test set:");
        if (name && name.trim()) {
          const trimmedName = name.trim();
          const data = {
            mask: sandboxMaskInput.value,
            samples: sandboxSampleInput.value
          };
          saveSandboxSet(instance, trimmedName, data);
          instance.currentSandboxSet = trimmedName;
          currentLoadedSet = trimmedName;
          localStorage.setItem(
            "ggn-upload-templator-sandbox-current",
            trimmedName
          );
          const existingOption = sandboxSetSelect.querySelector(
            `option[value="${trimmedName}"]`
          );
          if (existingOption) {
            existingOption.selected = true;
          } else {
            const newOption = document.createElement("option");
            newOption.value = trimmedName;
            newOption.textContent = trimmedName;
            sandboxSetSelect.appendChild(newOption);
            newOption.selected = true;
          }
          updateButtonStates();
          instance.showStatus(`Test set "${trimmedName}" saved successfully!`);
        }
      }
    });
    deleteBtn?.addEventListener("click", () => {
      if (!currentLoadedSet || currentLoadedSet === "") {
        return;
      }
      if (confirm(`Delete test set "${currentLoadedSet}"?`)) {
        deleteSandboxSet(instance, currentLoadedSet);
        const option = sandboxSetSelect.querySelector(
          `option[value="${currentLoadedSet}"]`
        );
        if (option) {
          option.remove();
        }
        sandboxSetSelect.value = "";
        currentLoadedSet = "";
        instance.currentSandboxSet = "";
        localStorage.setItem("ggn-upload-templator-sandbox-current", "");
        sandboxMaskInput.value = "";
        sandboxSampleInput.value = "";
        sandboxResultsContainer.innerHTML = '<div class="gut-no-variables">Enter a mask and sample torrent names to test.</div>';
        updateButtonStates();
        instance.showStatus(`Test set deleted successfully!`);
      }
    });
    renameBtn?.addEventListener("click", () => {
      if (!currentLoadedSet || currentLoadedSet === "") {
        return;
      }
      const newName = prompt(
        `Rename test set "${currentLoadedSet}" to:`,
        currentLoadedSet
      );
      if (!newName || !newName.trim() || newName.trim() === currentLoadedSet) {
        return;
      }
      const trimmedName = newName.trim();
      if (instance.sandboxSets[trimmedName]) {
        alert(`A test set named "${trimmedName}" already exists.`);
        return;
      }
      const data = instance.sandboxSets[currentLoadedSet];
      instance.sandboxSets[trimmedName] = data;
      delete instance.sandboxSets[currentLoadedSet];
      localStorage.setItem(
        "ggn-upload-templator-sandbox-sets",
        JSON.stringify(instance.sandboxSets)
      );
      const option = sandboxSetSelect.querySelector(
        `option[value="${currentLoadedSet}"]`
      );
      if (option) {
        option.value = trimmedName;
        option.textContent = trimmedName;
        option.selected = true;
      }
      currentLoadedSet = trimmedName;
      instance.currentSandboxSet = trimmedName;
      localStorage.setItem("ggn-upload-templator-sandbox-current", trimmedName);
      instance.showStatus(`Test set renamed to "${trimmedName}" successfully!`);
    });
    toggleCompiledRegexLink?.addEventListener("click", (e) => {
      e.preventDefault();
      showingCompiledRegex = !showingCompiledRegex;
      localStorage.setItem(
        "ggn-upload-templator-show-compiled-regex",
        showingCompiledRegex
      );
      if (showingCompiledRegex) {
        const mask = sandboxMaskInput.value;
        if (!mask) {
          instance.showStatus("Enter a mask first", "error");
          showingCompiledRegex = false;
          localStorage.setItem(
            "ggn-upload-templator-show-compiled-regex",
            "false"
          );
          return;
        }
        updateCompiledRegex();
        sandboxCompiledRegexDisplay.classList.add("visible");
        toggleCompiledRegexLink.textContent = "Hide compiled regex";
      } else {
        sandboxCompiledRegexDisplay.classList.remove("visible");
        toggleCompiledRegexLink.textContent = "Show compiled regex";
      }
    });
    const resetFieldsLink = modal.querySelector("#reset-sandbox-fields");
    resetFieldsLink?.addEventListener("click", (e) => {
      e.preventDefault();
      sandboxMaskInput.value = "";
      sandboxSampleInput.value = "";
      sandboxResultsContainer.innerHTML = '<div class="gut-no-variables">Enter a mask and sample names to see match results.</div>';
      const resultsLabel = modal.querySelector("#sandbox-results-label");
      if (resultsLabel) {
        resultsLabel.textContent = "Match Results:";
      }
      updateMaskHighlighting(sandboxMaskInput, sandboxMaskDisplay, instance.hints);
      if (showingCompiledRegex) {
        updateCompiledRegex();
      }
    });
    if (sandboxMaskInput && currentLoadedSet && currentLoadedSet !== "") {
      const sets = JSON.parse(
        localStorage.getItem("ggn-upload-templator-sandbox-sets") || "{}"
      );
      const data = sets[currentLoadedSet];
      if (data) {
        sandboxMaskInput.value = data.mask || "";
        sandboxSampleInput.value = data.samples || "";
        sandboxMaskInput.dispatchEvent(new Event("change"));
        sandboxSampleInput.dispatchEvent(new Event("change"));
        updateMaskHighlighting(sandboxMaskInput, sandboxMaskDisplay, instance.hints);
        updateSandboxTest();
        if (showingCompiledRegex) {
          updateCompiledRegex();
          sandboxCompiledRegexDisplay.classList.add("visible");
          toggleCompiledRegexLink.textContent = "Hide compiled regex";
        }
      }
    } else if (sandboxMaskInput) {
      updateMaskHighlighting(sandboxMaskInput, sandboxMaskDisplay, instance.hints);
      if (sandboxMaskInput.value && sandboxSampleInput.value) {
        updateSandboxTest();
      }
      if (showingCompiledRegex) {
        updateCompiledRegex();
        sandboxCompiledRegexDisplay.classList.add("visible");
        toggleCompiledRegexLink.textContent = "Hide compiled regex";
      }
    }
    const setupRecordKeybindingHandler = (inputSelector, keybindingSpanIndex, recordBtnSelector) => {
      modal.querySelector(recordBtnSelector)?.addEventListener("click", () => {
        const input = modal.querySelector(inputSelector);
        const keybindingSpans = modal.querySelectorAll(".gut-keybinding-text");
        const keybindingSpan = keybindingSpans[keybindingSpanIndex];
        const recordBtn = modal.querySelector(recordBtnSelector);
        const escapeHandler = (e) => {
          recordBtn.textContent = "Record";
          recordBtn.disabled = false;
          ModalStack.setKeybindingRecorderActive(false);
          ModalStack.popEscapeHandler();
          document.removeEventListener("keydown", handleKeydown);
          return true;
        };
        recordBtn.textContent = "Press keys...";
        recordBtn.disabled = true;
        ModalStack.setKeybindingRecorderActive(true);
        ModalStack.pushEscapeHandler(escapeHandler);
        const handleKeydown = (e) => {
          e.preventDefault();
          const isModifierKey = ["Control", "Alt", "Shift", "Meta"].includes(
            e.key
          );
          if (e.key === "Escape") {
            escapeHandler();
            return;
          }
          if (!isModifierKey) {
            const keybinding = buildKeybindingFromEvent(e);
            input.value = keybinding;
            if (keybindingSpan) {
              keybindingSpan.textContent = keybinding;
            }
            recordBtn.textContent = "Record";
            recordBtn.disabled = false;
            ModalStack.setKeybindingRecorderActive(false);
            ModalStack.popEscapeHandler();
            document.removeEventListener("keydown", handleKeydown);
          }
        };
        document.addEventListener("keydown", handleKeydown);
      });
    };
    setupRecordKeybindingHandler(
      "#custom-submit-keybinding-input",
      0,
      "#record-submit-keybinding-btn"
    );
    setupRecordKeybindingHandler(
      "#custom-apply-keybinding-input",
      1,
      "#record-apply-keybinding-btn"
    );
    modal.addEventListener("click", (e) => {
      if (e.target === modal && !ModalStack.isResizingModal()) {
        ModalStack.pop();
        return;
      }
      const action = e.target.dataset.action;
      const templateName = e.target.dataset.template;
      if (action && templateName) {
        switch (action) {
          case "edit":
            editTemplate(instance, templateName);
            break;
          case "clone":
            cloneTemplate(instance, templateName);
            refreshTemplateManager(instance, modal);
            break;
          case "delete":
            if (confirm(`Delete template "${templateName}"?`)) {
              deleteTemplate(instance, templateName);
              refreshTemplateManager(instance, modal);
            }
            break;
        }
      }
    });
    modal.querySelectorAll('[data-action="delete-hint"]').forEach((btn) => {
      btn.addEventListener("click", (e) => {
        e.preventDefault();
        const hintItem = e.target.closest(".gut-hint-item");
        const hintName = hintItem?.dataset.hint;
        if (hintName && confirm(`Delete hint "${hintName}"?`)) {
          if (isDefaultHint(hintName)) {
            addToDeletedDefaultHints(hintName);
          }
          delete instance.hints[hintName];
          saveHints(instance.hints);
          hintItem.remove();
          const customHintsList = modal.querySelector("#custom-hints-list");
          const customHintsSection = modal.querySelector("#custom-hints-section");
          if (customHintsList && customHintsList.children.length === 0 && customHintsSection) {
            customHintsSection.style.display = "none";
          }
        }
      });
    });
    const editHintButtons = modal.querySelectorAll('[data-action="edit-hint"]');
    editHintButtons.forEach((btn) => {
      btn.addEventListener("click", (e) => {
        e.preventDefault();
        const hintItem = e.target.closest(".gut-hint-item");
        const hintName = hintItem?.dataset.hint;
        if (hintName && instance.hints[hintName]) {
          showHintEditor(instance, modal, hintName, instance.hints[hintName]);
        }
      });
    });
    const importNewHintsBtn = modal.querySelector("#import-new-hints-btn");
    if (importNewHintsBtn) {
      importNewHintsBtn.addEventListener("click", (e) => {
        e.preventDefault();
        try {
          showImportNewHintsModal(instance);
        } catch (error) {
          console.error("Error showing import new hints modal:", error);
          instance.showStatus("Error showing modal: " + error.message, "error");
        }
      });
    }
    const resetDefaultsBtn = modal.querySelector("#reset-defaults-btn");
    if (resetDefaultsBtn) {
      resetDefaultsBtn.addEventListener("click", (e) => {
        e.preventDefault();
        try {
          showResetDefaultsModal(instance);
        } catch (error) {
          console.error("Error showing reset defaults modal:", error);
          instance.showStatus("Error showing modal: " + error.message, "error");
        }
      });
    }
    const deleteAllHintsBtn = modal.querySelector("#delete-all-hints-btn");
    if (deleteAllHintsBtn) {
      deleteAllHintsBtn.addEventListener("click", (e) => {
        e.preventDefault();
        try {
          showDeleteAllHintsModal(instance);
        } catch (error) {
          console.error("Error showing delete all modal:", error);
          instance.showStatus("Error showing modal: " + error.message, "error");
        }
      });
    }
    modal.querySelectorAll('[data-action="import-mappings"]').forEach((btn) => {
      btn.addEventListener("click", (e) => {
        e.preventDefault();
        const hintName = e.target.dataset.hint;
        if (hintName && instance.hints[hintName]) {
          const hintData = instance.hints[hintName];
          showMapImportModal(
            instance,
            hintName,
            hintData.mappings || {},
            "import"
          );
        }
      });
    });
    modal.querySelectorAll('[data-action="mass-edit-mappings"]').forEach((btn) => {
      btn.addEventListener("click", (e) => {
        e.preventDefault();
        const hintName = e.target.dataset.hint;
        if (hintName && instance.hints[hintName]) {
          const hintData = instance.hints[hintName];
          showMapImportModal(
            instance,
            hintName,
            hintData.mappings || {},
            "mass-edit"
          );
        }
      });
    });
    modal.querySelectorAll(".gut-hint-mappings-toggle").forEach((btn) => {
      btn.addEventListener("click", (e) => {
        e.preventDefault();
        const target = e.target.closest(".gut-hint-mappings-toggle");
        const hintName = target.dataset.hint;
        const hintItem = modal.querySelector(
          `.gut-hint-item[data-hint="${hintName}"]`
        );
        const content = hintItem?.querySelector(".gut-hint-mappings-content");
        const caret = target.querySelector(".gut-hint-caret");
        if (content) {
          if (content.style.display === "none") {
            content.style.display = "block";
            content.style.maxHeight = content.scrollHeight + "px";
            if (caret) caret.style.transform = "rotate(90deg)";
          } else {
            content.style.maxHeight = "0";
            if (caret) caret.style.transform = "rotate(0deg)";
            setTimeout(() => {
              content.style.display = "none";
            }, 200);
          }
        }
      });
    });
    const hintFilterInput = modal.querySelector("#hint-filter-input");
    if (hintFilterInput) {
      hintFilterInput.addEventListener("input", (e) => {
        const filterText = e.target.value.toLowerCase().trim();
        const hintsList = modal.querySelector("#hints-list");
        const filterCount = modal.querySelector("#hint-filter-count");
        let visibleCount = 0;
        let totalCount = 0;
        const filterHints = (list) => {
          if (!list) return;
          const hintItems = list.querySelectorAll(".gut-hint-item");
          hintItems.forEach((item) => {
            totalCount++;
            const hintName = item.dataset.hint?.toLowerCase() || "";
            const description = item.querySelector(".gut-hint-description")?.textContent?.toLowerCase() || "";
            const pattern = item.querySelector(".gut-hint-pattern")?.textContent?.toLowerCase() || "";
            const type = item.querySelector(".gut-hint-type-badge")?.textContent?.toLowerCase() || "";
            const matches = !filterText || hintName.includes(filterText) || description.includes(filterText) || pattern.includes(filterText) || type.includes(filterText);
            if (matches) {
              item.style.display = "";
              visibleCount++;
            } else {
              item.style.display = "none";
            }
          });
        };
        filterHints(hintsList);
        if (filterText) {
          filterCount.textContent = `Showing ${visibleCount} of ${totalCount} hints`;
          filterCount.style.display = "block";
        } else {
          filterCount.style.display = "none";
        }
      });
    }
    const addHintBtn = modal.querySelector("#add-hint-btn");
    if (addHintBtn) {
      addHintBtn.addEventListener("click", () => {
        showHintEditor(instance, modal, null, null);
      });
    }
    modal.querySelector("#close-manager").addEventListener("click", () => {
      ModalStack.pop();
    });
    const closeX = modal.querySelector("#modal-close-x");
    if (closeX) {
      closeX.addEventListener("click", () => {
        ModalStack.pop();
      });
    }
  }
  function saveSettingsFromModal(instance, modal) {
    const formSelector = modal.querySelector("#setting-form-selector").value.trim();
    const submitKeybinding = modal.querySelector(
      "#setting-submit-keybinding"
    ).checked;
    const customSubmitKeybinding = modal.querySelector("#custom-submit-keybinding-input").value.trim();
    const applyKeybinding = modal.querySelector(
      "#setting-apply-keybinding"
    ).checked;
    const customApplyKeybinding = modal.querySelector("#custom-apply-keybinding-input").value.trim();
    const customSelectorsText = modal.querySelector("#setting-custom-selectors").value.trim();
    const customSelectors = customSelectorsText.split("\n").map((selector) => selector.trim()).filter((selector) => selector);
    const ignoredFieldsText = modal.querySelector("#setting-ignored-fields").value.trim();
    const ignoredFields = ignoredFieldsText.split("\n").map((field) => field.trim()).filter((field) => field);
    instance.config = {
      TARGET_FORM_SELECTOR: formSelector || DEFAULT_CONFIG.TARGET_FORM_SELECTOR,
      SUBMIT_KEYBINDING: submitKeybinding,
      CUSTOM_SUBMIT_KEYBINDING: customSubmitKeybinding || DEFAULT_CONFIG.CUSTOM_SUBMIT_KEYBINDING,
      APPLY_KEYBINDING: applyKeybinding,
      CUSTOM_APPLY_KEYBINDING: customApplyKeybinding || DEFAULT_CONFIG.CUSTOM_APPLY_KEYBINDING,
      CUSTOM_FIELD_SELECTORS: customSelectors.length > 0 ? customSelectors : DEFAULT_CONFIG.CUSTOM_FIELD_SELECTORS,
      IGNORED_FIELDS_BY_DEFAULT: ignoredFields.length > 0 ? ignoredFields : DEFAULT_CONFIG.IGNORED_FIELDS_BY_DEFAULT
    };
    saveSettings(instance.config);
    instance.showStatus(
      "Settings saved successfully! Reload the page for some changes to take effect."
    );
  }
  function showHintEditor(instance, parentModal, hintName = null, hintData = null) {
    const editorModal = document.createElement("div");
    editorModal.innerHTML = HINT_EDITOR_MODAL_HTML(instance, hintName, hintData);
    const modal = editorModal.firstElementChild;
    ModalStack.push(modal, {
      type: "stack",
      onClose: null,
      metadata: { instance, parentModal, hintName, hintData }
    });
    const closeBtn = modal.querySelector("#modal-close-x");
    const cancelBtn = modal.querySelector("#hint-editor-cancel");
    const saveBtn = modal.querySelector("#hint-editor-save");
    const nameInput = modal.querySelector("#hint-editor-name");
    const typeInputs = modal.querySelectorAll('input[name="hint-type"]');
    const patternGroup = modal.querySelector("#hint-pattern-group");
    const mappingsGroup = modal.querySelector("#hint-mappings-group");
    const patternInput = modal.querySelector("#hint-editor-pattern");
    const patternLabel = modal.querySelector("#hint-pattern-label");
    const descriptionInput = modal.querySelector("#hint-editor-description");
    const strictInput = modal.querySelector("#hint-editor-strict");
    const addMappingBtn = modal.querySelector("#hint-add-mapping");
    const mappingsRows = modal.querySelector("#hint-mappings-rows");
    typeInputs.forEach((input) => {
      input.addEventListener("change", () => {
        modal.querySelectorAll(".gut-radio-label").forEach((label) => {
          label.classList.remove("selected");
        });
        const checkedInput = modal.querySelector(
          'input[name="hint-type"]:checked'
        );
        if (checkedInput) {
          checkedInput.closest(".gut-radio-label").classList.add("selected");
        }
      });
    });
    const initialChecked = modal.querySelector('input[name="hint-type"]:checked');
    if (initialChecked) {
      initialChecked.closest(".gut-radio-label").classList.add("selected");
    }
    setupAutoResize(descriptionInput, { minLines: 1, maxLines: 5 });
    const closeModal = () => {
      ModalStack.pop();
    };
    closeBtn.addEventListener("click", closeModal);
    cancelBtn.addEventListener("click", closeModal);
    modal.addEventListener("click", (e) => {
      if (e.target === modal && !ModalStack.isResizingModal()) {
        closeModal();
      }
    });
    typeInputs.forEach((input) => {
      input.addEventListener("change", (e) => {
        const type = e.target.value;
        if (type === "pattern") {
          patternGroup.style.display = "block";
          mappingsGroup.style.display = "none";
          patternLabel.textContent = "Pattern *";
          patternInput.placeholder = "e.g., ##.##.####";
        } else if (type === "regex") {
          patternGroup.style.display = "block";
          mappingsGroup.style.display = "none";
          patternLabel.textContent = "Regex Pattern *";
          patternInput.placeholder = "e.g., v\\d+(?:\\.\\d+)*";
        } else if (type === "map") {
          patternGroup.style.display = "none";
          mappingsGroup.style.display = "block";
        }
      });
    });
    addMappingBtn.addEventListener("click", () => {
      const newRow = document.createElement("div");
      newRow.className = "gut-mappings-row";
      newRow.innerHTML = `
      <input type="text" class="gut-input gut-mapping-key" placeholder="e.g., en">
      <input type="text" class="gut-input gut-mapping-value" placeholder="e.g., English">
      <button class="gut-btn gut-btn-danger gut-btn-small gut-remove-mapping" title="Remove">\u2212</button>
    `;
      mappingsRows.appendChild(newRow);
      newRow.querySelector(".gut-remove-mapping").addEventListener("click", () => {
        newRow.remove();
      });
    });
    mappingsRows.querySelectorAll(".gut-remove-mapping").forEach((btn) => {
      btn.addEventListener("click", (e) => {
        const row = e.target.closest(".gut-mappings-row");
        if (mappingsRows.querySelectorAll(".gut-mappings-row").length > 1) {
          row.remove();
        } else {
          alert("You must have at least one mapping row.");
        }
      });
    });
    const importBtn = modal.querySelector("#hint-editor-import-btn");
    const massEditBtn = modal.querySelector("#hint-editor-mass-edit-btn");
    importBtn?.addEventListener("click", (e) => {
      e.preventDefault();
      const currentMappings = getCurrentMappingsFromEditor();
      showMapImportModal(
        instance,
        hintName || "new_hint",
        currentMappings,
        "import",
        modal,
        updateEditorMappingsFromImport
      );
    });
    massEditBtn?.addEventListener("click", (e) => {
      e.preventDefault();
      const currentMappings = getCurrentMappingsFromEditor();
      showMapImportModal(
        instance,
        hintName || "new_hint",
        currentMappings,
        "mass-edit",
        modal,
        updateEditorMappingsFromImport
      );
    });
    function getCurrentMappingsFromEditor() {
      const mappings = {};
      mappingsRows.querySelectorAll(".gut-mappings-row").forEach((row) => {
        const key = row.querySelector(".gut-mapping-key").value.trim();
        const value = row.querySelector(".gut-mapping-value").value.trim();
        if (key) {
          mappings[key] = value;
        }
      });
      return mappings;
    }
    function updateEditorMappingsFromImport(newMappings) {
      mappingsRows.innerHTML = "";
      const entries = Object.entries(newMappings);
      if (entries.length === 0) {
        entries.push(["", ""]);
      }
      entries.forEach(([key, value]) => {
        const newRow = document.createElement("div");
        newRow.className = "gut-mappings-row";
        newRow.innerHTML = `
        <input type="text" class="gut-input gut-mapping-key" placeholder="e.g., en" value="${instance.escapeHtml(key)}">
        <input type="text" class="gut-input gut-mapping-value" placeholder="e.g., English" value="${instance.escapeHtml(value)}">
        <button class="gut-btn gut-btn-danger gut-btn-small gut-remove-mapping" title="Remove">\u2212</button>
      `;
        mappingsRows.appendChild(newRow);
        newRow.querySelector(".gut-remove-mapping").addEventListener("click", () => {
          if (mappingsRows.querySelectorAll(".gut-mappings-row").length > 1) {
            newRow.remove();
          } else {
            alert("You must have at least one mapping row.");
          }
        });
      });
    }
    saveBtn.addEventListener("click", () => {
      const name = nameInput.value.trim();
      if (!name || !/^[a-zA-Z0-9_]+$/.test(name)) {
        alert("Invalid hint name. Use only letters, numbers, and underscores.");
        return;
      }
      if (!hintName && instance.hints[name]) {
        alert(`Hint "${name}" already exists.`);
        return;
      }
      const type = modal.querySelector('input[name="hint-type"]:checked').value;
      const description = descriptionInput.value.trim();
      let hintDef = { type, description };
      if (type === "pattern" || type === "regex") {
        const pattern = patternInput.value.trim();
        if (!pattern) {
          alert("Pattern is required.");
          return;
        }
        if (type === "regex") {
          try {
            new RegExp(pattern);
          } catch (e) {
            alert(`Invalid regex: ${e.message}`);
            return;
          }
        }
        hintDef.pattern = pattern;
      } else if (type === "map") {
        const mappings = {};
        const rows = mappingsRows.querySelectorAll(".gut-mappings-row");
        let hasEmptyRow = false;
        rows.forEach((row) => {
          const key = row.querySelector(".gut-mapping-key").value.trim();
          const value = row.querySelector(".gut-mapping-value").value.trim();
          if (key && value) {
            mappings[key] = value;
          } else if (key || value) {
            hasEmptyRow = true;
          }
        });
        if (Object.keys(mappings).length === 0) {
          alert("At least one complete mapping is required.");
          return;
        }
        if (hasEmptyRow) {
          if (!confirm(
            "Some mapping rows are incomplete and will be ignored. Continue?"
          )) {
            return;
          }
        }
        hintDef.mappings = mappings;
        hintDef.strict = strictInput.checked;
      }
      instance.hints[name] = hintDef;
      saveHints(instance.hints);
      ModalStack.pop();
      ModalStack.pop();
      showTemplateAndSettingsManager(instance);
      const hintsTab = document.querySelector('.gut-tab-btn[data-tab="hints"]');
      if (hintsTab) hintsTab.click();
    });
  }
  function resetSettings(instance, modal) {
    removeSettings();
    instance.config = { ...DEFAULT_CONFIG };
    modal.querySelector("#setting-form-selector").value = instance.config.TARGET_FORM_SELECTOR;
    modal.querySelector("#setting-submit-keybinding").checked = instance.config.SUBMIT_KEYBINDING;
    modal.querySelector("#custom-submit-keybinding-input").value = instance.config.CUSTOM_SUBMIT_KEYBINDING;
    modal.querySelector("#setting-apply-keybinding").checked = instance.config.APPLY_KEYBINDING;
    modal.querySelector("#custom-apply-keybinding-input").value = instance.config.CUSTOM_APPLY_KEYBINDING;
    modal.querySelector("#setting-custom-selectors").value = instance.config.CUSTOM_FIELD_SELECTORS.join("\n");
    modal.querySelector("#setting-ignored-fields").value = instance.config.IGNORED_FIELDS_BY_DEFAULT.join("\n");
    const submitKeybindingSpan = modal.querySelector(".gut-keybinding-text");
    submitKeybindingSpan.textContent = instance.config.CUSTOM_SUBMIT_KEYBINDING;
    const applyKeybindingSpans = modal.querySelectorAll(".gut-keybinding-text");
    if (applyKeybindingSpans.length > 1) {
      applyKeybindingSpans[1].textContent = instance.config.CUSTOM_APPLY_KEYBINDING;
    }
    instance.showStatus(
      "Settings reset to defaults! Reload the page for changes to take effect."
    );
  }
  function deleteAllConfig(instance) {
    deleteAllConfig$1();
    instance.templates = {};
    instance.selectedTemplate = null;
    instance.hideUnselectedFields = true;
    instance.config = { ...DEFAULT_CONFIG };
    instance.updateTemplateSelector();
    instance.showStatus(
      "All local configuration deleted! Reload the page for changes to take full effect.",
      "success"
    );
  }
  function saveSandboxSet(instance, name, data) {
    instance.sandboxSets[name] = data;
    saveSandboxSets(instance.sandboxSets);
  }
  function deleteSandboxSet(instance, name) {
    delete instance.sandboxSets[name];
    saveSandboxSets(instance.sandboxSets);
    if (instance.currentSandboxSet === name) {
      instance.currentSandboxSet = "";
      saveCurrentSandboxSet("");
    }
  }
  function showSandboxWithMask(instance, mask, sample) {
    showTemplateAndSettingsManager(instance);
    setTimeout(() => {
      const modal = document.querySelector(".gut-modal");
      if (!modal) return;
      const sandboxTabBtn = modal.querySelector('[data-tab="sandbox"]');
      if (sandboxTabBtn) {
        sandboxTabBtn.click();
      }
      setTimeout(() => {
        const sandboxMaskInput = modal.querySelector("#sandbox-mask-input");
        const sandboxMaskDisplay = modal.querySelector("#sandbox-mask-display");
        const sandboxSampleInput = modal.querySelector("#sandbox-sample-input");
        if (sandboxMaskInput && sandboxSampleInput) {
          sandboxMaskInput.value = mask;
          sandboxSampleInput.value = sample;
          updateMaskHighlighting(sandboxMaskInput, sandboxMaskDisplay, instance.hints);
          sandboxMaskInput.dispatchEvent(new Event("input", { bubbles: true }));
          sandboxSampleInput.dispatchEvent(
            new Event("change", { bubbles: true })
          );
        }
      }, 50);
    }, 50);
  }
  function showMapImportModal(instance, hintName, existingMappings, mode = "import", editorModal = null, onComplete = null) {
    const importModalContainer = document.createElement("div");
    importModalContainer.innerHTML = MAP_IMPORT_MODAL_HTML(
      instance,
      hintName,
      existingMappings,
      mode
    );
    const modal = importModalContainer.firstElementChild;
    ModalStack.push(modal, {
      type: "stack",
      metadata: {
        instance,
        hintName,
        existingMappings,
        mode,
        editorModal,
        onComplete
      }
    });
    const textarea = modal.querySelector("#import-mappings-textarea");
    const separatorSelect = modal.querySelector("#import-separator-select");
    const customSeparatorInput = modal.querySelector("#import-custom-separator");
    const overwriteCheckbox = modal.querySelector("#import-overwrite-checkbox");
    const previewGroup = modal.querySelector("#import-preview-group");
    const previewContent = modal.querySelector("#import-preview-content");
    const previewSummary = modal.querySelector("#import-preview-summary");
    const confirmBtn = modal.querySelector("#import-confirm-btn");
    const cancelBtn = modal.querySelector("#import-cancel-btn");
    const closeBtn = modal.querySelector("#modal-close-x");
    setupAutoResize(textarea, { minLines: 5, maxLines: 15 });
    separatorSelect.addEventListener("change", () => {
      if (separatorSelect.value === "custom") {
        customSeparatorInput.style.display = "block";
      } else {
        customSeparatorInput.style.display = "none";
      }
      updatePreview();
    });
    customSeparatorInput.addEventListener("input", updatePreview);
    textarea.addEventListener("input", updatePreview);
    function getSeparator() {
      if (separatorSelect.value === "custom") {
        return customSeparatorInput.value || ",";
      }
      return separatorSelect.value === "	" ? "	" : separatorSelect.value;
    }
    function parseMappings(text, separator) {
      const lines = text.split("\n").map((l) => l.trim()).filter((l) => l);
      const mappings = {};
      const errors = [];
      lines.forEach((line, idx) => {
        const parts = line.split(separator).map((p) => p.trim());
        if (parts.length >= 2) {
          const key = parts[0];
          const value = parts.slice(1).join(separator).trim();
          if (key && value) {
            mappings[key] = value;
          } else {
            errors.push(`Line ${idx + 1}: Empty key or value`);
          }
        } else if (parts.length === 1 && parts[0]) {
          errors.push(`Line ${idx + 1}: Missing separator or value`);
        }
      });
      return { mappings, errors };
    }
    function updatePreview() {
      const text = textarea.value.trim();
      if (!text) {
        previewGroup.style.display = "none";
        confirmBtn.disabled = true;
        return;
      }
      const separator = getSeparator();
      const { mappings, errors } = parseMappings(text, separator);
      if (Object.keys(mappings).length === 0 && errors.length === 0) {
        previewGroup.style.display = "none";
        confirmBtn.disabled = true;
        return;
      }
      previewGroup.style.display = "block";
      mode === "mass-edit" || overwriteCheckbox && overwriteCheckbox.checked;
      const newKeys = [];
      const updateKeys = [];
      const unchangedKeys = [];
      Object.keys(mappings).forEach((key) => {
        if (existingMappings[key]) {
          if (existingMappings[key] === mappings[key]) {
            unchangedKeys.push(key);
          } else {
            updateKeys.push(key);
          }
        } else {
          newKeys.push(key);
        }
      });
      let html = "";
      if (errors.length > 0) {
        html += `<div style="color: #f44336; margin-bottom: 8px; font-size: 11px;">
        <strong>Errors:</strong><br>${errors.map((e) => `\u2022 ${e}`).join("<br>")}
      </div>`;
      }
      if (Object.keys(mappings).length > 0) {
        html += Object.entries(mappings).map(([key, value]) => {
          let badge = "";
          let style2 = "";
          if (newKeys.includes(key)) {
            badge = '<span style="color: #4caf50; font-size: 10px; margin-left: 4px;">(new)</span>';
            style2 = "border-left: 3px solid #4caf50;";
          } else if (updateKeys.includes(key)) {
            badge = `<span style="color: #ff9800; font-size: 10px; margin-left: 4px;">(update: "${instance.escapeHtml(existingMappings[key])}")</span>`;
            style2 = "border-left: 3px solid #ff9800;";
          } else if (unchangedKeys.includes(key)) {
            badge = '<span style="color: #888; font-size: 10px; margin-left: 4px;">(unchanged)</span>';
            style2 = "border-left: 3px solid #444;";
          }
          return `
          <div class="gut-variable-item" style="${style2}">
            <span class="gut-variable-name">${instance.escapeHtml(key)}${badge}</span>
            <span class="gut-variable-value">${instance.escapeHtml(value)}</span>
          </div>
        `;
        }).join("");
      }
      previewContent.innerHTML = html;
      const summaryParts = [];
      if (newKeys.length > 0) summaryParts.push(`${newKeys.length} new`);
      if (updateKeys.length > 0)
        summaryParts.push(`${updateKeys.length} updates`);
      if (unchangedKeys.length > 0)
        summaryParts.push(`${unchangedKeys.length} unchanged`);
      if (errors.length > 0) summaryParts.push(`${errors.length} errors`);
      previewSummary.textContent = summaryParts.join(", ");
      confirmBtn.disabled = Object.keys(mappings).length === 0 || errors.length > 0;
    }
    function applyImport() {
      const text = textarea.value.trim();
      if (!text) return;
      const separator = getSeparator();
      const { mappings } = parseMappings(text, separator);
      if (Object.keys(mappings).length === 0) {
        alert("No valid mappings to import.");
        return;
      }
      const overwrite = mode === "mass-edit" || overwriteCheckbox && overwriteCheckbox.checked;
      let finalMappings;
      if (mode === "mass-edit") {
        finalMappings = mappings;
      } else if (overwrite) {
        finalMappings = { ...existingMappings, ...mappings };
      } else {
        finalMappings = { ...mappings, ...existingMappings };
      }
      if (onComplete) {
        onComplete(finalMappings);
        ModalStack.pop();
      } else {
        const hintData = instance.hints[hintName] || {};
        instance.hints[hintName] = {
          ...hintData,
          mappings: finalMappings
        };
        saveHints(instance.hints);
        ModalStack.pop();
        ModalStack.pop();
        showTemplateAndSettingsManager(instance);
        const hintsTab = document.querySelector('.gut-tab-btn[data-tab="hints"]');
        if (hintsTab) hintsTab.click();
      }
    }
    confirmBtn.addEventListener("click", applyImport);
    cancelBtn.addEventListener("click", () => ModalStack.pop());
    closeBtn.addEventListener("click", () => ModalStack.pop());
    modal.addEventListener("click", (e) => {
      if (e.target === modal && !ModalStack.isResizingModal()) {
        ModalStack.pop();
      }
    });
    updatePreview();
  }
  function showImportNewHintsModal(instance) {
    const userHints = instance.hints;
    const newHints = getNewDefaultHints(userHints);
    const ignoredHints = loadIgnoredHints();
    if (Object.keys(newHints).length === 0) {
      instance.showStatus("No new hints available to import!", "info");
      return;
    }
    const modal = document.createElement("div");
    modal.innerHTML = IMPORT_NEW_HINTS_MODAL_HTML(
      newHints,
      ignoredHints,
      instance
    );
    const modalElement = modal.firstElementChild;
    ModalStack.push(modalElement, {
      type: "stack",
      metadata: { instance, newHints, ignoredHints }
    });
    const checkboxes = modalElement.querySelectorAll(".hint-select-checkbox");
    const importBtn = modalElement.querySelector("#import-hints-confirm-btn");
    const cancelBtn = modalElement.querySelector("#import-hints-cancel-btn");
    const closeBtn = modalElement.querySelector("#modal-close-x");
    const selectAllBtn = modalElement.querySelector("#import-select-all-btn");
    const selectNoneBtn = modalElement.querySelector("#import-select-none-btn");
    function updateSelectedCount() {
      const checkedCount = Array.from(checkboxes).filter(
        (cb) => cb.checked
      ).length;
      const totalCount = checkboxes.length;
      if (checkedCount === 0) {
        importBtn.textContent = "Import Selected";
        importBtn.disabled = true;
      } else if (checkedCount === totalCount) {
        importBtn.textContent = "Import All";
        importBtn.disabled = false;
      } else {
        importBtn.textContent = `Import ${checkedCount}/${totalCount} Selected`;
        importBtn.disabled = false;
      }
    }
    checkboxes.forEach((checkbox) => {
      checkbox.addEventListener("change", updateSelectedCount);
    });
    selectAllBtn.addEventListener("click", (e) => {
      e.preventDefault();
      checkboxes.forEach((cb) => cb.checked = true);
      updateSelectedCount();
    });
    selectNoneBtn.addEventListener("click", (e) => {
      e.preventDefault();
      checkboxes.forEach((cb) => cb.checked = false);
      updateSelectedCount();
    });
    modalElement.querySelectorAll(".hint-ignore-btn").forEach((btn) => {
      btn.addEventListener("click", (e) => {
        e.preventDefault();
        const hintName = e.target.dataset.hintName;
        const row = e.target.closest(".gut-hint-import-item");
        const checkbox = row.querySelector(".hint-select-checkbox");
        if (isHintIgnored(hintName)) {
          removeFromIgnoredHints(hintName);
          e.target.textContent = "Ignore";
          checkbox.checked = true;
        } else {
          addToIgnoredHints(hintName);
          e.target.textContent = "Unignore";
          checkbox.checked = false;
        }
        updateSelectedCount();
      });
    });
    importBtn.addEventListener("click", () => {
      const selectedHints = Array.from(checkboxes).filter((cb) => cb.checked).map((cb) => cb.dataset.hintName);
      if (selectedHints.length === 0) {
        instance.showStatus("No hints selected for import!", "error");
        return;
      }
      selectedHints.forEach((hintName) => {
        instance.hints[hintName] = newHints[hintName];
        removeFromDeletedDefaultHints(hintName);
      });
      saveHints(instance.hints);
      ModalStack.pop();
      ModalStack.pop();
      showTemplateAndSettingsManager(instance);
      const hintsTab = document.querySelector('.gut-tab-btn[data-tab="hints"]');
      if (hintsTab) hintsTab.click();
      instance.showStatus(
        `Successfully imported ${selectedHints.length} hint(s)!`,
        "success"
      );
    });
    cancelBtn.addEventListener("click", () => ModalStack.pop());
    closeBtn.addEventListener("click", () => ModalStack.pop());
    modalElement.addEventListener("click", (e) => {
      if (e.target === modalElement && !ModalStack.isResizingModal()) {
        ModalStack.pop();
      }
    });
    updateSelectedCount();
  }
  function showResetDefaultsModal(instance) {
    const userHints = instance.hints;
    const ignoredHints = loadIgnoredHints();
    const deletedHints = loadDeletedDefaultHints();
    const modal = document.createElement("div");
    modal.innerHTML = RESET_DEFAULTS_MODAL_HTML(
      userHints,
      ignoredHints,
      deletedHints,
      instance
    );
    const modalElement = modal.firstElementChild;
    ModalStack.push(modalElement, {
      type: "stack",
      metadata: { instance, ignoredHints }
    });
    const checkboxes = modalElement.querySelectorAll(".hint-select-checkbox");
    const resetBtn = modalElement.querySelector("#reset-hints-confirm-btn");
    const cancelBtn = modalElement.querySelector("#reset-hints-cancel-btn");
    const closeBtn = modalElement.querySelector("#modal-close-x");
    const selectAllBtn = modalElement.querySelector("#reset-select-all-btn");
    const selectNoneBtn = modalElement.querySelector("#reset-select-none-btn");
    function updateSelectedCount() {
      const checkedCount = Array.from(checkboxes).filter(
        (cb) => cb.checked
      ).length;
      const totalCount = checkboxes.length;
      if (checkedCount === 0) {
        resetBtn.textContent = "Reset Selected";
        resetBtn.disabled = true;
      } else if (checkedCount === totalCount) {
        resetBtn.textContent = "Reset All";
        resetBtn.disabled = false;
      } else {
        resetBtn.textContent = `Reset ${checkedCount}/${totalCount} Selected`;
        resetBtn.disabled = false;
      }
    }
    checkboxes.forEach((checkbox) => {
      checkbox.addEventListener("change", updateSelectedCount);
    });
    selectAllBtn.addEventListener("click", (e) => {
      e.preventDefault();
      checkboxes.forEach((cb) => cb.checked = true);
      updateSelectedCount();
    });
    selectNoneBtn.addEventListener("click", (e) => {
      e.preventDefault();
      checkboxes.forEach((cb) => cb.checked = false);
      updateSelectedCount();
    });
    modalElement.querySelectorAll(".hint-ignore-btn").forEach((btn) => {
      btn.addEventListener("click", (e) => {
        e.preventDefault();
        const hintName = e.target.dataset.hintName;
        const row = e.target.closest(".gut-hint-import-item");
        const checkbox = row.querySelector(".hint-select-checkbox");
        if (isHintIgnored(hintName)) {
          removeFromIgnoredHints(hintName);
          e.target.textContent = "Ignore";
          checkbox.checked = true;
        } else {
          addToIgnoredHints(hintName);
          e.target.textContent = "Unignore";
          checkbox.checked = false;
        }
        updateSelectedCount();
      });
    });
    resetBtn.addEventListener("click", () => {
      const selectedHints = Array.from(checkboxes).filter((cb) => cb.checked).map((cb) => cb.dataset.hintName);
      if (selectedHints.length === 0) {
        instance.showStatus("No hints selected for reset!", "error");
        return;
      }
      selectedHints.forEach((hintName) => {
        instance.hints[hintName] = DEFAULT_HINTS[hintName];
        removeFromDeletedDefaultHints(hintName);
      });
      saveHints(instance.hints);
      ModalStack.pop();
      ModalStack.pop();
      showTemplateAndSettingsManager(instance);
      const hintsTab = document.querySelector('.gut-tab-btn[data-tab="hints"]');
      if (hintsTab) hintsTab.click();
      instance.showStatus(
        `Successfully reset ${selectedHints.length} hint(s) to defaults!`,
        "success"
      );
    });
    cancelBtn.addEventListener("click", () => ModalStack.pop());
    closeBtn.addEventListener("click", () => ModalStack.pop());
    modalElement.addEventListener("click", (e) => {
      if (e.target === modalElement && !ModalStack.isResizingModal()) {
        ModalStack.pop();
      }
    });
    updateSelectedCount();
  }
  function showDeleteAllHintsModal(instance) {
    const modal = document.createElement("div");
    modal.innerHTML = DELETE_ALL_HINTS_MODAL_HTML();
    const modalElement = modal.firstElementChild;
    ModalStack.push(modalElement, {
      type: "stack",
      metadata: { instance }
    });
    const deleteBtn = modalElement.querySelector("#delete-all-hints-confirm-btn");
    const cancelBtn = modalElement.querySelector("#delete-all-hints-cancel-btn");
    const closeBtn = modalElement.querySelector("#modal-close-x");
    deleteBtn.addEventListener("click", () => {
      if (resetAllHints()) {
        instance.hints = loadHints();
        ModalStack.pop();
        ModalStack.pop();
        showTemplateAndSettingsManager(instance);
        const hintsTab = document.querySelector('.gut-tab-btn[data-tab="hints"]');
        if (hintsTab) hintsTab.click();
        instance.showStatus(
          "All hints deleted and reset to defaults!",
          "success"
        );
      } else {
        instance.showStatus("Failed to delete hints!", "error");
      }
    });
    cancelBtn.addEventListener("click", () => ModalStack.pop());
    closeBtn.addEventListener("click", () => ModalStack.pop());
    modalElement.addEventListener("click", (e) => {
      if (e.target === modalElement && !ModalStack.isResizingModal()) {
        ModalStack.pop();
      }
    });
  }
  function showApplyConfirmationModal(instance, changes, onConfirm) {
    const modalContainer = document.createElement("div");
    modalContainer.innerHTML = APPLY_CONFIRMATION_MODAL_HTML(changes, instance);
    const modal = modalContainer.firstElementChild;
    ModalStack.push(modal, {
      type: "stack",
      metadata: { instance, changes, onConfirm }
    });
    const applyBtn = modal.querySelector("#apply-confirm-apply-btn");
    const cancelBtn = modal.querySelector("#apply-confirm-cancel-btn");
    const closeBtn = modal.querySelector("#modal-close-x");
    const handleConfirm = () => {
      ModalStack.pop();
      if (onConfirm) {
        onConfirm();
      }
    };
    const handleCancel = () => {
      ModalStack.pop();
    };
    applyBtn.addEventListener("click", handleConfirm);
    cancelBtn.addEventListener("click", handleCancel);
    closeBtn.addEventListener("click", handleCancel);
    modal.addEventListener("click", (e) => {
      if (e.target === modal && !ModalStack.isResizingModal()) {
        handleCancel();
      }
    });
    setTimeout(() => {
      applyBtn.focus();
    }, 0);
  }
  async function getCurrentVariables(instance) {
    const commentVariables = {};
    const maskVariables = {};
    let hasBothConditions = false;
    if (instance.selectedTemplate && instance.selectedTemplate !== "none") {
      const template = instance.templates[instance.selectedTemplate];
      if (template) {
        const fileInputs = instance.config.TARGET_FORM_SELECTOR ? document.querySelectorAll(
          `${instance.config.TARGET_FORM_SELECTOR} input[type="file"]`
        ) : document.querySelectorAll('input[type="file"]');
        for (const input of fileInputs) {
          if (input.files && input.files[0] && input.files[0].name.toLowerCase().endsWith(".torrent")) {
            hasBothConditions = true;
            try {
              const torrentData = await TorrentUtils.parseTorrentFile(
                input.files[0]
              );
              Object.assign(
                commentVariables,
                TorrentUtils.parseCommentVariables(torrentData.comment)
              );
              const parseResult = parseTemplateWithOptionals(
                template.mask,
                torrentData.name,
                instance.hints
              );
              const { _matchedOptionals, _optionalCount, ...extracted } = parseResult;
              Object.assign(maskVariables, extracted);
              break;
            } catch (error) {
              console.warn("Could not parse torrent file:", error);
            }
          }
        }
      }
    }
    return {
      all: { ...commentVariables, ...maskVariables },
      comment: commentVariables,
      mask: maskVariables,
      hasBothConditions
    };
  }
  async function updateVariableCount(instance) {
    const variables = await getCurrentVariables(instance);
    const commentCount = Object.keys(variables.comment).length;
    const maskCount = Object.keys(variables.mask).length;
    const totalCount = commentCount + maskCount;
    const variablesRow = document.getElementById("variables-row");
    if (variablesRow) {
      if (!variables.hasBothConditions) {
        variablesRow.style.display = "none";
      } else {
        variablesRow.style.display = "";
        if (totalCount === 0) {
          variablesRow.innerHTML = `Available variables: 0`;
          variablesRow.style.cursor = "default";
          variablesRow.style.opacity = "0.6";
        } else {
          const parts = [];
          if (commentCount > 0) {
            parts.push(`Comment [${commentCount}]`);
          }
          if (maskCount > 0) {
            parts.push(`Mask [${maskCount}]`);
          }
          variablesRow.innerHTML = `Available variables: ${parts.join(", ")}`;
          variablesRow.style.cursor = "pointer";
          variablesRow.style.opacity = "1";
        }
      }
    }
  }
  function applyTemplate(instance, templateName, torrentName, commentVariables = {}) {
    const template = instance.templates[templateName];
    if (!template) return;
    const extracted = parseTemplateWithOptionals(template.mask, torrentName, instance.hints);
    let appliedCount = 0;
    Object.entries(template.fieldMappings).forEach(
      ([fieldName, valueTemplate]) => {
        const firstElement = findElementByFieldName(fieldName, instance.config);
        if (firstElement && firstElement.type === "radio") {
          const formPrefix = instance.config.TARGET_FORM_SELECTOR ? `${instance.config.TARGET_FORM_SELECTOR} ` : "";
          const radioButtons = document.querySelectorAll(
            `${formPrefix}input[name="${fieldName}"][type="radio"]`
          );
          const newValue = interpolate(
            String(valueTemplate),
            extracted,
            commentVariables
          );
          radioButtons.forEach((radio) => {
            if (radio.hasAttribute("disabled")) {
              radio.removeAttribute("disabled");
            }
            const shouldBeChecked = radio.value === newValue;
            if (shouldBeChecked !== radio.checked) {
              radio.checked = shouldBeChecked;
              if (shouldBeChecked) {
                radio.dispatchEvent(new Event("input", { bubbles: true }));
                radio.dispatchEvent(new Event("change", { bubbles: true }));
                appliedCount++;
              }
            }
          });
        } else if (firstElement) {
          if (firstElement.hasAttribute("disabled")) {
            firstElement.removeAttribute("disabled");
          }
          if (firstElement.type === "checkbox") {
            let newChecked;
            if (typeof valueTemplate === "boolean") {
              newChecked = valueTemplate;
            } else {
              const interpolated = interpolate(
                String(valueTemplate),
                extracted,
                commentVariables
              );
              newChecked = /^(true|1|yes|on)$/i.test(interpolated);
            }
            if (newChecked !== firstElement.checked) {
              firstElement.checked = newChecked;
              firstElement.dispatchEvent(new Event("input", { bubbles: true }));
              firstElement.dispatchEvent(
                new Event("change", { bubbles: true })
              );
              appliedCount++;
            }
          } else {
            const interpolated = interpolate(
              String(valueTemplate),
              extracted,
              commentVariables
            );
            if (firstElement.value !== interpolated) {
              firstElement.value = interpolated;
              firstElement.dispatchEvent(new Event("input", { bubbles: true }));
              firstElement.dispatchEvent(
                new Event("change", { bubbles: true })
              );
              appliedCount++;
            }
          }
        }
      }
    );
    if (appliedCount > 0) {
      instance.showStatus(
        `Template "${templateName}" applied to ${appliedCount} field(s)`
      );
    }
  }
  async function checkAndApplyToExistingTorrent(instance, templateName) {
    if (!templateName || templateName === "none") return;
    const fileInputs = instance.config.TARGET_FORM_SELECTOR ? document.querySelectorAll(
      `${instance.config.TARGET_FORM_SELECTOR} input[type="file"]`
    ) : document.querySelectorAll('input[type="file"]');
    for (const input of fileInputs) {
      if (input.files && input.files[0] && input.files[0].name.toLowerCase().endsWith(".torrent")) {
        try {
          const torrentData = await TorrentUtils.parseTorrentFile(
            input.files[0]
          );
          const commentVariables = TorrentUtils.parseCommentVariables(
            torrentData.comment
          );
          applyTemplate(instance, templateName, torrentData.name, commentVariables);
          return;
        } catch (error) {
          console.warn("Could not parse existing torrent file:", error);
        }
      }
    }
  }
  function watchFileInputs(instance) {
    const fileInputs = instance.config.TARGET_FORM_SELECTOR ? document.querySelectorAll(
      `${instance.config.TARGET_FORM_SELECTOR} input[type="file"]`
    ) : document.querySelectorAll('input[type="file"]');
    fileInputs.forEach((input) => {
      input.addEventListener("change", (e) => {
        if (e.target.files[0] && e.target.files[0].name.toLowerCase().endsWith(".torrent")) {
          instance.showStatus(
            "Torrent file selected. Click 'Apply Template' to fill form."
          );
          updateVariableCount(instance);
        }
      });
    });
  }
  async function applyTemplateToCurrentTorrent(instance) {
    if (!instance.selectedTemplate || instance.selectedTemplate === "none") {
      instance.showStatus("No template selected", "error");
      return;
    }
    const fileInputs = instance.config.TARGET_FORM_SELECTOR ? document.querySelectorAll(
      `${instance.config.TARGET_FORM_SELECTOR} input[type="file"]`
    ) : document.querySelectorAll('input[type="file"]');
    for (const input of fileInputs) {
      if (input.files && input.files[0] && input.files[0].name.toLowerCase().endsWith(".torrent")) {
        try {
          const torrentData = await TorrentUtils.parseTorrentFile(
            input.files[0]
          );
          const commentVariables = TorrentUtils.parseCommentVariables(
            torrentData.comment
          );
          const changes = detectFieldChanges(
            instance,
            instance.selectedTemplate,
            torrentData.name,
            commentVariables
          );
          if (changes.length > 0) {
            showApplyConfirmationModal(instance, changes, () => {
              applyTemplate(
                instance,
                instance.selectedTemplate,
                torrentData.name,
                commentVariables
              );
            });
          } else {
            applyTemplate(
              instance,
              instance.selectedTemplate,
              torrentData.name,
              commentVariables
            );
          }
          return;
        } catch (error) {
          console.error("Error processing torrent file:", error);
          instance.showStatus("Error processing torrent file", "error");
        }
      }
    }
    instance.showStatus("No torrent file selected", "error");
  }
  function detectFieldChanges(instance, templateName, torrentName, commentVariables = {}) {
    const template = instance.templates[templateName];
    if (!template) return [];
    const extracted = parseTemplateWithOptionals(
      template.mask,
      torrentName,
      instance.hints
    );
    const changes = [];
    Object.entries(template.fieldMappings).forEach(
      ([fieldName, valueTemplate]) => {
        const firstElement = findElementByFieldName(fieldName, instance.config);
        if (firstElement && firstElement.type === "radio") {
          const formPrefix = instance.config.TARGET_FORM_SELECTOR ? `${instance.config.TARGET_FORM_SELECTOR} ` : "";
          const radioButtons = document.querySelectorAll(
            `${formPrefix}input[name="${fieldName}"][type="radio"]`
          );
          const newValue = interpolate(
            String(valueTemplate),
            extracted,
            commentVariables
          );
          const currentlyChecked = Array.from(radioButtons).find(
            (radio) => radio.checked
          );
          const currentValue = currentlyChecked ? currentlyChecked.value : "";
          if (currentValue !== newValue) {
            changes.push({
              fieldName,
              label: getFieldLabel(firstElement, instance.config),
              currentValue: currentValue || "(empty)",
              newValue,
              fieldType: "radio"
            });
          }
        } else if (firstElement) {
          if (firstElement.type === "checkbox") {
            let newChecked;
            if (typeof valueTemplate === "boolean") {
              newChecked = valueTemplate;
            } else {
              const interpolated = interpolate(
                String(valueTemplate),
                extracted,
                commentVariables
              );
              newChecked = /^(true|1|yes|on)$/i.test(interpolated);
            }
            const currentChecked = firstElement.checked;
            if (currentChecked !== newChecked) {
              changes.push({
                fieldName,
                label: getFieldLabel(firstElement, instance.config),
                currentValue: currentChecked ? "Checked" : "Unchecked",
                newValue: newChecked ? "Checked" : "Unchecked",
                fieldType: "checkbox"
              });
            }
          } else {
            const interpolated = interpolate(
              String(valueTemplate),
              extracted,
              commentVariables
            );
            const currentValue = firstElement.value || "";
            if (currentValue !== interpolated) {
              changes.push({
                fieldName,
                label: getFieldLabel(firstElement, instance.config),
                currentValue: currentValue || "(empty)",
                newValue: interpolated,
                fieldType: firstElement.tagName.toLowerCase() === "select" ? "select" : firstElement.type || "text"
              });
            }
          }
        }
      }
    );
    return changes;
  }
  function setupSubmitKeybinding(instance) {
    const keybinding = instance.config.CUSTOM_SUBMIT_KEYBINDING || "Ctrl+Enter";
    const keys = parseKeybinding(keybinding);
    document.addEventListener("keydown", (e) => {
      if (matchesKeybinding(e, keys)) {
        e.preventDefault();
        const targetForm = document.querySelector(
          instance.config.TARGET_FORM_SELECTOR
        );
        if (targetForm) {
          const submitButton = targetForm.querySelector(
            'input[type="submit"], button[type="submit"]'
          ) || targetForm.querySelector(
            'input[name*="submit"], button[name*="submit"]'
          ) || targetForm.querySelector(".submit-btn, #submit-btn");
          if (submitButton) {
            instance.showStatus(`Form submitted via ${keybinding}`);
            submitButton.click();
          } else {
            instance.showStatus(`Form submitted via ${keybinding}`);
            targetForm.submit();
          }
        }
      }
    });
  }
  function setupApplyKeybinding(instance) {
    const keybinding = instance.config.CUSTOM_APPLY_KEYBINDING || "Ctrl+Shift+A";
    const keys = parseKeybinding(keybinding);
    document.addEventListener("keydown", (e) => {
      if (matchesKeybinding(e, keys)) {
        e.preventDefault();
        instance.applyTemplateToCurrentTorrent();
      }
    });
  }
  const style = `:root {
    --gut-ui-spacing: 10px;
    --gut-ui-gap: 8px;
    --gut-ui-gap-small: 4px;
    --gut-hint-min-width: 320px;
}

#ggn-upload-templator-ui *::-webkit-scrollbar,
.gut-modal *::-webkit-scrollbar,
.gut-hint-editor-modal *::-webkit-scrollbar,
.gut-field-list::-webkit-scrollbar,
.gut-extracted-vars::-webkit-scrollbar,
.gut-template-list::-webkit-scrollbar,
.gut-hints-list::-webkit-scrollbar,
.gut-sandbox-results::-webkit-scrollbar,
.gut-modal-content::-webkit-scrollbar,
.gut-tab-content::-webkit-scrollbar {
    width: 12px;
    height: 12px;
}

#ggn-upload-templator-ui *::-webkit-scrollbar-track,
.gut-modal *::-webkit-scrollbar-track,
.gut-hint-editor-modal *::-webkit-scrollbar-track,
.gut-field-list::-webkit-scrollbar-track,
.gut-extracted-vars::-webkit-scrollbar-track,
.gut-template-list::-webkit-scrollbar-track,
.gut-hints-list::-webkit-scrollbar-track,
.gut-sandbox-results::-webkit-scrollbar-track,
.gut-modal-content::-webkit-scrollbar-track,
.gut-tab-content::-webkit-scrollbar-track {
    background: #1a1a1a;
    border-radius: 6px;
}

#ggn-upload-templator-ui *::-webkit-scrollbar-thumb,
.gut-modal *::-webkit-scrollbar-thumb,
.gut-hint-editor-modal *::-webkit-scrollbar-thumb,
.gut-field-list::-webkit-scrollbar-thumb,
.gut-extracted-vars::-webkit-scrollbar-thumb,
.gut-template-list::-webkit-scrollbar-thumb,
.gut-hints-list::-webkit-scrollbar-thumb,
.gut-sandbox-results::-webkit-scrollbar-thumb,
.gut-modal-content::-webkit-scrollbar-thumb,
.gut-tab-content::-webkit-scrollbar-thumb {
    background: #404040;
    border-radius: 6px;
    border: 2px solid #1a1a1a;
}

#ggn-upload-templator-ui *::-webkit-scrollbar-thumb:hover,
.gut-modal *::-webkit-scrollbar-thumb:hover,
.gut-hint-editor-modal *::-webkit-scrollbar-thumb:hover,
.gut-field-list::-webkit-scrollbar-thumb:hover,
.gut-extracted-vars::-webkit-scrollbar-thumb:hover,
.gut-template-list::-webkit-scrollbar-thumb:hover,
.gut-hints-list::-webkit-scrollbar-thumb:hover,
.gut-sandbox-results::-webkit-scrollbar-thumb:hover,
.gut-modal-content::-webkit-scrollbar-thumb:hover,
.gut-tab-content::-webkit-scrollbar-thumb:hover {
    background: #505050;
}

#ggn-upload-templator-ui {
    background: #1a1a1a;
    border: 1px solid #404040;
    border-radius: 6px;
    padding: 15px;
    margin: 15px 0;
    font-family:
        -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    color: #e0e0e0;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}

.ggn-upload-templator-controls {
    display: flex;
    gap: 10px;
    align-items: center;
    flex-wrap: wrap;
}

.gut-btn {
    padding: 8px 16px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    font-weight: 500;
    transition: all 0.2s ease;
    text-decoration: none;
    outline: none;
    box-sizing: border-box;
    height: auto;
}

.gut-btn-primary {
    background: #0d7377;
    color: #ffffff;
    border: 1px solid #0d7377;
}

.gut-btn-primary:hover {
    background: #0a5d61;
    border-color: #0a5d61;
    transform: translateY(-1px);
}

.gut-btn-danger {
    background: #d32f2f;
    color: #ffffff;
    border: 1px solid #d32f2f;
}

.gut-btn-danger:hover:not(:disabled) {
    background: #b71c1c;
    border-color: #b71c1c;
    transform: translateY(-1px);
}

.gut-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
}

.gut-btn:not(:disabled):active {
    transform: translateY(0);
}

.gut-select {
    padding: 8px 12px;
    border: 1px solid #404040;
    border-radius: 4px;
    font-size: 14px;
    min-width: 200px;
    background: #2a2a2a;
    color: #e0e0e0;
    box-sizing: border-box;
    outline: none;
    height: auto;
    margin: 0 !important;
}

.gut-select:focus {
    border-color: #0d7377;
    box-shadow: 0 0 0 2px rgba(13, 115, 119, 0.2);
}

.gut-modal {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.4);
    backdrop-filter: blur(3px);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 10000;
    padding: 20px;
    box-sizing: border-box;
}

.gut-modal-content {
    background: #1a1a1a;
    border: 1px solid #404040;
    border-radius: 8px;
    max-width: 1200px;
    height: 90vh;
    width: 90%;
    color: #e0e0e0;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
    box-sizing: border-box;
    position: relative;
    display: flex;
    flex-direction: column;
}

.gut-modal-content.gut-hint-editor-modal {
    max-width: 1100px;
    height: 80vh;
    width: 85%;
}

.gut-modal-header {
    flex-shrink: 0;
    padding: 16px 20px;
    border-bottom: 1px solid #404040;
    position: relative;
}

.gut-modal-body {
    flex: 1;
    overflow-y: auto;
    padding: 20px;
}

.gut-modal-footer {
    flex-shrink: 0;
    padding: 16px 20px;
    border-top: 1px solid #404040;
    display: flex;
    justify-content: flex-end;
    gap: 10px;
}

.gut-modal h2 {
    margin: 0;
    color: #ffffff;
    font-size: 20px;
    font-weight: 600;
    text-align: left;
    display: flex;
    align-items: center;
    gap: 10px;
}

.gut-modal-back-btn {
    background: none;
    border: none;
    color: #e0e0e0;
    font-size: 16px;
    cursor: pointer;
    padding: 6px;
    border-radius: 4px;
    transition:
        color 0.2s ease,
        background-color 0.2s ease;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    font-family: monospace;
    font-weight: bold;
}

.gut-modal-back-btn:hover {
    color: #ffffff;
    background-color: #333333;
}

.gut-modal-close-btn {
    position: absolute;
    top: 12px;
    right: 16px;
    background: none;
    border: none;
    color: #e0e0e0;
    font-size: 28px;
    cursor: pointer;
    padding: 4px;
    border-radius: 4px;
    transition:
        color 0.2s ease,
        background-color 0.2s ease;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    line-height: 1;
    font-weight: normal;
    z-index: 1;
}

.gut-modal-close-btn:hover {
    color: #ffffff;
    background-color: #333333;
}

.gut-form-group {
    margin-bottom: 15px;
}

.gut-form-group label {
    display: block;
    margin-bottom: 5px;
    font-weight: 500;
    color: #b0b0b0;
    font-size: 14px;
}

.gut-form-group input,
.gut-form-group textarea {
    width: 100%;
    padding: 8px 12px;
    border: 1px solid #404040;
    border-radius: 4px;
    font-size: 14px;
    box-sizing: border-box;
    background: #2a2a2a;
    color: #e0e0e0;
    outline: none;
    transition: border-color 0.2s ease;
    height: auto;
}

.gut-form-group input:focus,
.gut-form-group textarea:focus {
    border-color: #0d7377;
    box-shadow: 0 0 0 2px rgba(13, 115, 119, 0.2);
}

.gut-form-group input::placeholder,
.gut-form-group textarea::placeholder {
    color: #666666;
}

.gut-field-list {
    max-height: 300px;
    overflow-y: auto;
    border: 1px solid #404040;
    border-radius: 4px;
    padding: 10px;
    background: #0f0f0f;
}

.gut-field-row {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 8px;
    padding: 8px;
    background: #2a2a2a;
    border-radius: 4px;
    border: 1px solid #404040;
    flex-wrap: wrap;
}

.gut-field-row:hover {
    background: #333333;
}

.gut-field-row:not(:has(input[type="checkbox"]:checked)) {
    opacity: 0.6;
}

.gut-field-row.gut-hidden {
    display: none;
}

.gut-field-row input[type="checkbox"] {
    width: auto;
    margin: 0;
    accent-color: #0d7377;
    cursor: pointer;
}

.gut-field-row label {
    min-width: 150px;
    margin: 0;
    font-size: 13px;
    color: #b0b0b0;
    cursor: help;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.gut-field-row input[type="text"],
.gut-field-row select {
    flex: 1;
    margin: 0;
    padding: 6px 8px;
    border: 1px solid #404040;
    border-radius: 3px;
    background: #1a1a1a;
    color: #e0e0e0;
    font-size: 12px;
    outline: none;
    height: auto;
}

.gut-field-row input[type="text"]:focus {
    border-color: #0d7377;
    box-shadow: 0 0 0 1px rgba(13, 115, 119, 0.3);
}

.gut-preview {
    color: #888888;
    font-style: italic;
    font-size: 11px;
    word-break: break-all;
    flex-basis: 100%;
    margin-top: 4px;
    padding-left: 20px;
    white-space: pre-wrap;
    display: none;
}

.gut-preview.active {
    color: #4dd0e1;
    font-weight: bold;
    font-style: normal;
}

.gut-preview.visible {
    display: block;
}

.gut-modal-actions {
    display: flex;
    gap: 10px;
    justify-content: flex-end;
    margin-top: 20px;
    padding-top: 20px;
    border-top: 1px solid #404040;
}

.gut-status {
    position: fixed;
    top: 20px;
    right: 20px;
    background: #2e7d32;
    color: #ffffff;
    padding: 12px 20px;
    border-radius: 6px;
    z-index: 10001;
    font-size: 14px;
    font-weight: 500;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    border: 1px solid #4caf50;
    animation: slideInRight 0.3s ease-out;
}

.gut-status.error {
    background: #d32f2f;
    border-color: #f44336;
}

@keyframes slideInRight {
    from {
        transform: translateX(100%);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

.gut-template-list {
    max-height: 400px;
    overflow-y: auto;
    border: 1px solid #404040;
    border-radius: 4px;
    background: #0f0f0f;
}

.gut-template-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 16px;
    border-bottom: 1px solid #404040;
    background: #2a2a2a;
    transition: background-color 0.2s ease;
}

.gut-template-item:hover {
    background: #333333;
}

.gut-template-item:last-child {
    border-bottom: none;
}

.gut-template-name {
    font-weight: 500;
    color: #e0e0e0;
    flex: 1;
    margin-right: 10px;
}

.gut-template-actions {
    display: flex;
    gap: 8px;
}

.gut-btn-small {
    padding: 6px 12px;
    font-size: 12px;
    min-width: auto;
}

.gut-btn-secondary {
    background: #555555;
    color: #ffffff;
    border: 1px solid #555555;
}

.gut-btn-secondary:hover:not(:disabled) {
    background: #666666;
    border-color: #666666;
    transform: translateY(-1px);
}

.gut-btn-warning {
    background: #ff9800;
    color: #ffffff;
    border: 1px solid #ff9800;
}

.gut-btn-warning:hover:not(:disabled) {
    background: #f57c00;
    border-color: #f57c00;
    transform: translateY(-1px);
}

/* Tab styles for modal */
.gut-modal-tabs {
    display: flex;
    border-bottom: none;
    margin-bottom: 0;
}

.gut-tab-btn {
    padding: 10px 16px;
    background: transparent;
    border: none;
    color: #b0b0b0;
    cursor: pointer;
    font-size: 13px;
    font-weight: 500;
    border-bottom: 2px solid transparent;
    transition: all 0.2s ease;
    height: auto;
}

.gut-tab-btn:hover {
    color: #e0e0e0;
    background: #2a2a2a;
}

.gut-tab-btn.active {
    color: #ffffff;
    background: #2a2a2a;
    border-bottom-color: transparent;
}

.gut-tab-content {
    display: none;
}

.gut-tab-content.active {
    display: block;
}

/* Keybinding controls styling */
.gut-keybinding-controls {
    display: flex !important;
    align-items: center !important;
    gap: 10px !important;
    padding: 8px 12px !important;
    background: #2a2a2a !important;
    border: 1px solid #404040 !important;
    border-radius: 4px !important;
    transition: border-color 0.2s ease !important;
    margin: 0 !important;
}

.gut-keybinding-controls:hover {
    border-color: #0d7377 !important;
}

/* Checkbox label styling */
.gut-checkbox-label {
    display: flex !important;
    align-items: center !important;
    gap: 10px !important;
    cursor: pointer !important;
    margin: 0 !important;
}

.gut-checkbox-label input[type="checkbox"] {
    width: auto !important;
    margin: 0 !important;
    accent-color: #0d7377 !important;
    cursor: pointer !important;
}

.gut-checkbox-text {
    font-size: 14px !important;
    font-weight: 500 !important;
    color: #b0b0b0 !important;
    user-select: none !important;
}

.gut-keybinding-text {
    color: #4dd0e1 !important;
    font-family: monospace !important;
}

.gut-variable-toggle {
    font-size: 11px !important;
    padding: 2px 6px !important;
    white-space: nowrap !important;
}

/* Scrollbar styling for webkit browsers */
.gut-field-list::-webkit-scrollbar,
.gut-modal-content::-webkit-scrollbar {
    width: 8px;
}

.gut-field-list::-webkit-scrollbar-track,
.gut-modal-content::-webkit-scrollbar-track {
    background: #0f0f0f;
    border-radius: 4px;
}

.gut-field-list::-webkit-scrollbar-thumb,
.gut-modal-content::-webkit-scrollbar-thumb {
    background: #404040;
    border-radius: 4px;
}

.gut-field-list::-webkit-scrollbar-thumb:hover,
.gut-modal-content::-webkit-scrollbar-thumb:hover {
    background: #555555;
}

/* Extracted variables section */
.gut-extracted-vars {
    border: 1px solid #404040;
    border-radius: 4px;
    background: #0f0f0f;
    padding: 12px;
    min-height: 80px;
    max-height: 300px;
    overflow-y: auto;
}

.gut-extracted-vars:has(.gut-no-variables) {
    display: flex;
    align-items: center;
    justify-content: center;
}

.gut-no-variables {
    color: #666666;
    font-style: italic;
    text-align: center;
    padding: 20px 10px;
}

.gut-variable-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px 12px;
    margin-bottom: 6px;
    background: #2a2a2a;
    border: 1px solid #404040;
    border-radius: 4px;
    transition: background-color 0.2s ease;
}

.gut-variable-item:last-child {
    margin-bottom: 0;
}

.gut-variable-item:hover {
    background: #333333;
}

.gut-variable-name {
    font-weight: 500;
    color: #4dd0e1;
    font-family: monospace;
    font-size: 13px;
}

.gut-variable-value {
    color: #e0e0e0;
    font-size: 12px;
    max-width: 60%;
    word-break: break-all;
    text-align: right;
}

.gut-variable-value.empty {
    color: #888888;
    font-style: italic;
}

/* Generic hyperlink style for secondary links */
.gut-link {
    font-size: 12px !important;
    color: #b0b0b0 !important;
    text-decoration: underline !important;
    text-underline-offset: 2px !important;
    cursor: pointer !important;
    transition: color 0.2s ease !important;
}

.gut-link:hover {
    color: #4dd0e1 !important;
}

.gut-link-danger {
    color: #ef5350 !important;
}

.gut-link-danger:hover {
    color: #ff7961 !important;
}

.gut-variable-toggle {
    font-size: 11px !important;
    padding: 2px 6px !important;
    margin-left: auto !important;
    align-self: flex-start !important;
    white-space: nowrap !important;
}

#variables-row {
    cursor: pointer;
    color: #b0b0b0;
    transition: color 0.2s ease;
    display: inline-block;
}

#variables-row:hover {
    color: #4dd0e1;
}

#mask-validation-warning {
    display: none;
    background: #b63535;
    color: #ffffff;
    padding: 10px 12px;
    border-radius: 4px;
    margin-top: 8px;
    font-size: 13px;
    border: 1px solid #b71c1c;
}

#mask-validation-warning.visible {
    display: block;
}

.gut-variable-controls {
    display: none;
    gap: 8px;
    align-items: center;
    flex: 1;
}

.gut-variable-controls.visible {
    display: flex;
}

.gut-variable-input {
    flex: 1;
    min-width: 120px;
}

.gut-match-type {
    min-width: 100px;
}

.select-static-mode {
    display: block;
}

.select-static-mode.hidden {
    display: none;
}

.gut-mask-input-container {
    position: relative;
    width: 100%;
}

.gut-mask-highlight-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    padding: 8px 12px;
    border: 1px solid transparent;
    border-radius: 4px;
    font-size: 14px;
    font-family: "Fira Code", monospace !important;
    color: transparent;
    background: #2a2a2a;
    pointer-events: none;
    overflow: hidden;
    white-space: pre;
    word-wrap: normal;
    box-sizing: border-box;
    line-height: normal;
    letter-spacing: normal;
    word-spacing: normal;
    font-variant-ligatures: none;
}

.gut-mask-input {
    position: relative;
    z-index: 1;
    background: transparent !important;
    caret-color: #e0e0e0;
    font-family: "Fira Code", monospace !important;
    font-variant-ligatures: none;
    letter-spacing: normal;
    word-spacing: normal;
}

.gut-highlight-variable {
    color: transparent;
    background: #2d5a5e;
    padding: 2px 0;
    border-radius: 2px;
}

.gut-highlight-optional {
    color: transparent;
    background: #4f2d6a;
    padding: 2px 0;
    border-radius: 2px;
}

.gut-highlight-warning {
    color: transparent;
    background: #4d3419;
    padding: 2px 0;
    border-radius: 2px;
}

.gut-highlight-error {
    color: transparent;
    background: #963a33;
    padding: 2px 0;
    border-radius: 2px;
}

.gut-tooltip {
    position: fixed;
    background: #2a2a2a;
    border: 1px solid #505050;
    border-radius: 4px;
    padding: 6px 10px;
    font-size: 12px;
    color: #e0e0e0;
    font-family: 'Courier New', monospace;
    z-index: 10000;
    pointer-events: none;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
    white-space: nowrap;
    max-width: 400px;
    overflow: hidden;
    text-overflow: ellipsis;
}

.gut-mask-cursor-info {
    font-size: 12px;
    color: #e0e0e0;
    margin-top: 4px;
    min-height: 16px;
    font-family: monospace;
    line-height: 1.5;
}

.gut-mask-cursor-info:empty {
    display: none !important;
}

.gut-mask-status-container {
    display: none;
    margin-top: 8px;
    padding: 8px 12px;
    border-radius: 4px;
    background: #0f0f0f;
    border: 1px solid #404040;
    animation: slideDown 0.2s ease-out;
}

.gut-mask-status-container.visible {
    display: block;
}

.gut-status-message {
    font-size: 13px;
    padding: 4px 0;
    line-height: 1.4;
    display: flex;
    align-items: center;
    gap: 6px;
}

.gut-status-message svg {
    flex-shrink: 0;
    vertical-align: middle;
}

.gut-status-message:not(:last-child) {
    margin-bottom: 6px;
    padding-bottom: 6px;
    border-bottom: 1px solid #2a2a2a;
}

.gut-status-error {
    color: #f44336;
}

.gut-status-warning {
    color: #ff9800;
}

.gut-status-info {
    color: #888888;
}

.gut-status-success {
    color: #4caf50;
}

@keyframes slideDown {
    from {
        opacity: 0;
        transform: translateY(-4px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.gut-compiled-regex-display {
    display: none;
    margin-top: 8px;
    padding: 8px 12px;
    border-radius: 4px;
    background: #0f0f0f;
    border: 1px solid #404040;
    font-family: "Fira Code", monospace;
    font-size: 12px;
    color: #888888;
    word-break: break-all;
    animation: slideDown 0.2s ease-out;
}

.gut-compiled-regex-display.visible {
    display: block;
}

.gut-sandbox-results {
    margin-top: 12px;
    padding: 12px;
    background: #0f0f0f;
    border: 1px solid #404040;
    border-radius: 4px;
    max-height: 400px;
    overflow-y: auto;
}

.gut-sandbox-match,
.gut-sandbox-no-match {
    padding: 10px;
    margin-bottom: 10px;
    border-radius: 4px;
    border-left: 3px solid;
}

.gut-sandbox-match {
    background: rgba(76, 175, 80, 0.1);
    border-left-color: #4caf50;
}

.gut-sandbox-no-match {
    background: rgba(244, 67, 54, 0.1);
    border-left-color: #f44336;
}

.gut-sandbox-sample-name {
    font-family: "Fira Code", monospace;
    font-size: 13px;
    margin-bottom: 6px;
    display: flex;
    align-items: center;
    gap: 8px;
}

.gut-sandbox-sample-name svg {
    flex-shrink: 0;
}

.gut-sandbox-variables {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid #2a2a2a;
}

.gut-sandbox-variables-title {
    font-size: 11px;
    font-weight: 500;
    color: #888;
    margin-bottom: 6px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.gut-sandbox-variable-item {
    font-size: 12px;
    padding: 3px 0;
    display: flex;
    gap: 8px;
    align-items: baseline;
}

.gut-sandbox-variable-name {
    color: #64b5f6;
    font-family: "Fira Code", monospace;
}

.gut-sandbox-variable-value {
    color: #a5d6a7;
    font-family: "Fira Code", monospace;
}

.gut-sandbox-optionals {
    margin-top: 6px;
    font-size: 11px;
    color: #b39ddb;
}

.gut-hints-list {
    display: grid;
    grid-template-columns: repeat(
        auto-fill,
        minmax(var(--gut-hint-min-width), 1fr)
    );
    gap: var(--gut-ui-spacing);
}

.gut-hint-item {
    background: #2a2a2a;
    border: 1px solid #404040;
    border-radius: 4px;
    padding: var(--gut-ui-gap);
    display: flex;
    flex-direction: column;
    gap: var(--gut-ui-gap-small);
}

.gut-hint-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
}

.gut-hint-name-group {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 1;
}

.gut-hint-name {
    font-weight: 500;
    color: #64b5f6;
    font-family: "Fira Code", monospace;
    font-size: 13px;
}

.gut-hint-type-badge {
    font-size: 9px;
    padding: 2px 5px;
    background: #404040;
    border-radius: 3px;
    color: #b0b0b0;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.gut-hint-default-badge {
    font-size: 9px;
    padding: 2px 5px;
    background: #505050;
    border-radius: 3px;
    color: #888;
}

.gut-hint-override-indicator {
    font-size: 9px;
    padding: 2px 5px;
    background: #3d2a0f;
    border-radius: 3px;
    color: #ffa726;
    font-weight: 500;
}

.gut-hint-description {
    font-size: 11px;
    color: #999;
    line-height: 1.4;
}

.gut-hint-pattern {
    font-size: 11px;
    color: #a5d6a7;
    font-family: "Fira Code", monospace;
}

.gut-hint-pattern code {
    background: #1a1a1a;
    padding: 2px 6px;
    border-radius: 3px;
}

.gut-hint-actions {
    display: flex;
    gap: 0;
    font-size: 11px;
    align-items: center;
    white-space: nowrap;
}

.gut-hint-actions .gut-link {
    text-decoration: none !important;
}

.gut-hint-actions .gut-link:hover {
    text-decoration: underline !important;
}

.gut-hint-actions-separator {
    color: #666;
    font-size: 12px;
    margin: 0 6px;
    user-select: none;
}

.gut-hint-mappings {
    font-size: 11px;
    color: #b39ddb;
}

.gut-hint-mappings-inline {
    margin-top: 2px;
}

.gut-hint-mappings-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 10px;
    color: #b39ddb;
    margin-bottom: 4px;
}

.gut-hint-mappings-toggle {
    background: transparent;
    border: 1px solid #404040;
    color: #64b5f6;
    padding: 2px 6px;
    border-radius: 3px;
    cursor: pointer;
    font-size: 9px;
    transition: all 0.2s ease;
}

.gut-hint-mappings-toggle:hover {
    background: #404040;
    border-color: #64b5f6;
}

.gut-hint-mappings-content {
    background: #1a1a1a;
    border: 1px solid #404040;
    border-radius: 4px;
    padding: 6px;
    max-height: 200px;
    overflow-y: auto;
}

.gut-hint-mappings-content .gut-variable-item {
    padding: 3px 6px;
    margin-bottom: 2px;
    font-size: 11px;
}

.gut-hint-mappings-content .gut-variable-item:last-child {
    margin-bottom: 0;
}

.gut-hint-type-selector {
    display: flex;
    flex-direction: row;
    gap: 8px;
    flex-wrap: wrap;
}

.gut-radio-label {
    display: inline-flex !important;
    align-items: center !important;
    cursor: pointer !important;
    padding: 8px 12px !important;
    border: 1px solid #404040 !important;
    border-radius: 4px !important;
    transition: all 0.2s ease !important;
    gap: 8px !important;
}

.gut-radio-label:hover {
    background: #333 !important;
    border-color: #555 !important;
}

.gut-radio-label input[type="radio"] {
    flex-shrink: 0 !important;
    accent-color: #0d7377 !important;
    margin: 0 !important;
    width: auto !important;
    height: auto !important;
}

.gut-radio-label.selected {
    background: rgb(77 208 225 / 15%);
    border-color: rgb(77 208 225 / 67%) !important;
}

.gut-radio-label > span:first-of-type {
    font-weight: 500;
    color: #e0e0e0;
    white-space: nowrap;
}

.gut-mappings-table-header {
    display: flex;
    gap: 8px;
    padding: 8px 0;
    font-size: 12px;
    font-weight: 500;
    color: #b0b0b0;
    border-bottom: 1px solid #404040;
    margin-bottom: 8px;
}

.gut-mappings-row {
    display: flex;
    gap: 8px;
    margin-bottom: 8px;
    align-items: center;
}

.gut-mappings-row .gut-input {
    flex: 1;
    min-width: 0;
}

.gut-remove-mapping {
    width: 32px;
    height: 32px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
    line-height: 1;
}

#hint-mappings-rows {
    margin-bottom: 12px;
}

.gut-hint-autocomplete {
    position: absolute;
    background: #2a2a2a;
    border: 1px solid #404040;
    border-radius: 4px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
    max-height: 180px;
    overflow-y: auto;
    z-index: 10002;
    margin-top: 2px;
}

.gut-hint-autocomplete-item {
    padding: 6px 10px;
    cursor: pointer;
    border-bottom: 1px solid #333;
    transition: background 0.15s ease;
}

.gut-hint-autocomplete-item:last-child {
    border-bottom: none;
}

.gut-hint-autocomplete-item:hover,
.gut-hint-autocomplete-item.selected {
    background: #333;
}

.gut-hint-autocomplete-name {
    font-family: "Fira Code", monospace;
    font-size: 12px;
    font-weight: 500;
    color: #64b5f6;
    margin-bottom: 1px;
}

.gut-hint-autocomplete-type {
    display: inline-block;
    font-size: 9px;
    padding: 1px 4px;
    border-radius: 2px;
    background: #404040;
    color: #b39ddb;
    margin-bottom: 2px;
}

.gut-hint-autocomplete-desc {
    font-size: 10px;
    color: #999;
    margin-top: 1px;
    line-height: 1.2;
}

.gut-resize-handle {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 4px;
    cursor: ew-resize;
    z-index: 10;
    transition: background-color 0.15s ease;
}

.gut-resize-handle-left {
    left: 0;
}

.gut-resize-handle-right {
    right: 0;
}

.gut-resize-handle-hover {
    background-color: rgba(13, 115, 119, 0.3);
}

.gut-resize-handle-active {
    background-color: rgba(13, 115, 119, 0.5);
}

.gut-modal-stacked .gut-resize-handle {
    display: none;
}

.gut-truncate-ellipsis {
    color: #4dd0e1;
    font-weight: bold;
    position: relative;
    transform: translateY(-20%);
    display: inline-block;
    margin: 0 4px;
    letter-spacing: 3px;
}

.gut-match-variables-container {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 8px;
}

.gut-match-variable-item {
    margin: 0;
    flex: 0 0 auto;
    cursor: pointer;
}

.gut-match-separator {
    display: inline-block;
    color: #898989;
    margin: 0 8px;
}

.gut-match-optional-info {
    margin-top: 8px;
    font-size: 11px;
    color: #4caf50;
}

.gut-match-result-item {
    margin-bottom: 12px;
    padding: 8px;
    background: #1e1e1e;
    border-left: 3px solid #4caf50;
    border-radius: 4px;
}

.gut-match-result-item.gut-sandbox-no-match {
    border-left-color: #f44336;
}

.gut-match-result-header {
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

.gut-match-icon {
    font-size: 16px;
    flex-shrink: 0;
}

.gut-match-icon-success {
    color: #4caf50;
}

.gut-match-icon-error {
    color: #f44336;
}

.gut-sandbox-sample-name {
    font-family: "Fira Code", monospace;
    font-size: 13px;
    line-height: 1.4;
    display: inline;
}

.gut-char-span {
    display: inline;
    transition: background 0.2s ease;
}

.gut-match-highlight {
    background: rgba(77, 208, 225, 0.3);
}

.gut-confirmation-modal .gut-modal-body {
    max-height: 70vh;
    overflow-y: auto;
}

.gut-field-changes-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 2px;
}

.gut-field-change-item {
    background: #1e1e1e;
    border: 1px solid #333;
    border-radius: 4px;
    padding: 8px 10px;
}

.gut-field-change-row {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.gut-field-name {
    display: flex;
    justify-content: space-between;
    align-items: center;
    color: #e0e0e0;
    font-size: 13px;
}

.gut-field-type-badge {
    background: #2a2a2a;
    color: #888;
    padding: 1px 6px;
    border-radius: 3px;
    font-size: 10px;
    font-family: 'Fira Code', monospace;
    text-transform: lowercase;
}

.gut-field-values {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    font-family: 'Fira Code', monospace;
}

.gut-value {
    flex: 1;
    padding: 4px 6px;
    border-radius: 3px;
    word-break: break-word;
    min-width: 0;
}

.gut-value-old {
    background: #252525;
    border: 1px solid #444;
    color: #b0b0b0;
}

.gut-value-new {
    background: #1a2f2f;
    border: 1px solid #0d7377;
    color: #4dd0e1;
}

.gut-value-arrow {
    color: #0d7377;
    font-size: 14px;
    font-weight: bold;
    flex-shrink: 0;
}

`;
  const firaCodeFont = `
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:[email protected]&display=swap');
`;
  GM_addStyle(firaCodeFont);
  GM_addStyle(style);
  class GGnUploadTemplator {
    constructor() {
      this.templates = loadTemplates();
      this.selectedTemplate = loadSelectedTemplate();
      this.hideUnselectedFields = loadHideUnselected();
      this.config = {
        ...DEFAULT_CONFIG,
        ...loadSettings()
      };
      this.sandboxSets = loadSandboxSets();
      this.currentSandboxSet = loadCurrentSandboxSet();
      this.hints = loadHints();
      logDebug("Initialized core state", {
        templates: Object.keys(this.templates),
        selectedTemplate: this.selectedTemplate,
        hideUnselectedFields: this.hideUnselectedFields,
        config: this.config,
        hints: Object.keys(this.hints)
      });
      this.init();
    }
    init() {
      logDebug("Initializing...");
      try {
        injectUI(this);
      } catch (error) {
        console.error("UI injection failed:", error);
      }
      try {
        watchFileInputs(this);
      } catch (error) {
        console.error("File input watching setup failed:", error);
      }
      if (this.config.SUBMIT_KEYBINDING) {
        try {
          setupSubmitKeybinding(this);
        } catch (error) {
          console.error("Submit keybinding setup failed:", error);
        }
      }
      if (this.config.APPLY_KEYBINDING) {
        try {
          setupApplyKeybinding(this);
        } catch (error) {
          console.error("Apply keybinding setup failed:", error);
        }
      }
      logDebug("Initialized");
    }
    async showTemplateCreator(editTemplateName = null, editTemplate2 = null) {
      await showTemplateCreator(this, editTemplateName, editTemplate2);
    }
    async getCurrentVariables() {
      return await getCurrentVariables(this);
    }
    async showVariablesModal() {
      const variables = await this.getCurrentVariables();
      const fileInputs = this.config.TARGET_FORM_SELECTOR ? document.querySelectorAll(
        `${this.config.TARGET_FORM_SELECTOR} input[type="file"]`
      ) : document.querySelectorAll('input[type="file"]');
      let torrentName = "";
      for (const input of fileInputs) {
        if (input.files && input.files[0] && input.files[0].name.toLowerCase().endsWith(".torrent")) {
          try {
            const { TorrentUtils: TorrentUtils2 } = await Promise.resolve().then(() => torrent);
            const torrentData = await TorrentUtils2.parseTorrentFile(input.files[0]);
            torrentName = torrentData.name || "";
            break;
          } catch (error) {
            console.warn("Could not parse torrent file:", error);
          }
        }
      }
      const mask = this.selectedTemplate && this.templates[this.selectedTemplate] ? this.templates[this.selectedTemplate].mask : "";
      showVariablesModal(this, variables.all, torrentName, mask);
    }
    async updateVariableCount() {
      await updateVariableCount(this);
    }
    saveTemplate(modal, editingTemplateName = null) {
      saveTemplate(this, modal, editingTemplateName);
    }
    updateTemplateSelector() {
      updateTemplateSelector(this);
    }
    selectTemplate(templateName) {
      selectTemplate(this, templateName);
    }
    applyTemplate(templateName, torrentName, commentVariables = {}) {
      applyTemplate(this, templateName, torrentName, commentVariables);
    }
    async checkAndApplyToExistingTorrent(templateName) {
      await checkAndApplyToExistingTorrent(this, templateName);
    }
    async applyTemplateToCurrentTorrent() {
      await applyTemplateToCurrentTorrent(this);
    }
    showTemplateAndSettingsManager() {
      showTemplateAndSettingsManager(this);
    }
    deleteTemplate(templateName) {
      deleteTemplate(this, templateName);
    }
    cloneTemplate(templateName) {
      cloneTemplate(this, templateName);
    }
    editTemplate(templateName) {
      editTemplate(this, templateName);
    }
    showSandboxWithMask(mask, sample) {
      showSandboxWithMask(this, mask, sample);
    }
    saveHints(hints) {
      this.hints = hints;
      return saveHints(hints);
    }
    getHints() {
      return this.hints;
    }
    showStatus(message, type = "success") {
      const existing = document.querySelector(".gut-status");
      if (existing) existing.remove();
      const status = document.createElement("div");
      status.className = "gut-status";
      status.textContent = message;
      if (type === "error") {
        status.classList.add("error");
      }
      document.body.appendChild(status);
      setTimeout(() => {
        if (status.parentNode) {
          status.parentNode.removeChild(status);
        }
      }, 3e3);
    }
    escapeHtml(text) {
      const div = document.createElement("div");
      div.textContent = text;
      return div.innerHTML;
    }
  }
  logDebug("Script loaded (readyState:", document.readyState, ")");
  let ggnInstance = null;
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", () => {
      logDebug("Initializing after DOMContentLoaded");
      try {
        ggnInstance = new GGnUploadTemplator();
      } catch (error) {
        console.error("Failed to initialize:", error);
      }
    });
  } else {
    logDebug("Initializing immediately (DOM already ready)");
    try {
      ggnInstance = new GGnUploadTemplator();
    } catch (error) {
      console.error("Failed to initialize:", error);
    }
  }
  const GGnUploadTemplatorAPI = {
    version: "0.11",
    getTemplates() {
      if (!ggnInstance) {
        console.warn("GGnUploadTemplator not initialized yet");
        return [];
      }
      return Object.keys(ggnInstance.templates).map((name) => ({
        name,
        mask: ggnInstance.templates[name].mask,
        fieldMappings: ggnInstance.templates[name].fieldMappings,
        variableMatching: ggnInstance.templates[name].variableMatching,
        customUnselectedFields: ggnInstance.templates[name].customUnselectedFields
      }));
    },
    getTemplate(templateName) {
      if (!ggnInstance) {
        console.warn("GGnUploadTemplator not initialized yet");
        return null;
      }
      const template = ggnInstance.templates[templateName];
      if (!template) {
        return null;
      }
      return {
        name: templateName,
        mask: template.mask,
        fieldMappings: template.fieldMappings,
        variableMatching: template.variableMatching,
        customUnselectedFields: template.customUnselectedFields
      };
    },
    extractVariables(templateName, torrentName) {
      if (!ggnInstance) {
        console.warn("GGnUploadTemplator not initialized yet");
        return {};
      }
      const template = ggnInstance.templates[templateName];
      if (!template) {
        console.warn(`Template "${templateName}" not found`);
        return {};
      }
      return parseTemplateWithOptionals(template.mask, torrentName, ggnInstance.hints);
    },
    getInstance() {
      return ggnInstance;
    }
  };
  if (typeof unsafeWindow !== "undefined") {
    unsafeWindow.GGnUploadTemplator = GGnUploadTemplatorAPI;
  } else {
    window.GGnUploadTemplator = GGnUploadTemplatorAPI;
  }
})();