Perplexity helper

Simple script that adds buttons to Perplexity website for repeating request using Copilot.

נכון ליום 11-05-2025. ראה הגרסה האחרונה.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        Perplexity helper
// @namespace   Tiartyos
// @match       https://www.perplexity.ai/*
// @grant       none
// @version     5.6
// @author      Tiartyos, monnef
// @description Simple script that adds buttons to Perplexity website for repeating request using Copilot.
// @require     https://code.jquery.com/jquery-3.6.0.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.1/jquery.modal.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/lodash-fp/0.10.4/lodash-fp.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.8/clipboard.min.js
// @require     https://cdn.jsdelivr.net/npm/[email protected]/dist/index.unpkg.umd.js
// @require     https://cdn.jsdelivr.net/npm/[email protected]/dist/showdown.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/jsondiffpatch/0.4.1/jsondiffpatch.umd.min.js
// @require     https://cdn.jsdelivr.net/npm/[email protected]/dist/lib/perplex-plus.js
// @homepageURL https://www.perplexity.ai/
// @license     GPL-3.0-or-later
// ==/UserScript==

const PP = window.PP.noConflict();
const jq = PP.jq;
const $c = (cls, parent) => jq(`.${cls}`, parent);
const $i = (id, parent) => jq(`#${id}`, parent);
const takeStr = n => str => str.slice(0, n);
const dropStr = n => str => str.slice(n);
const dropRightStr = n => str => str.slice(0, -n);
const filter = pred => xs => xs.filter(pred);
const nl = '\n';
const markdownConverter = new showdown.Converter({ tables: true });

let debugMode = false;
const enableDebugMode = () => {
  debugMode = true;
};

const userscriptName = 'Perplexity helper';
const logPrefix = `[${userscriptName}]`;

const debugLog = (...args) => {
  if (debugMode) {
    console.debug(logPrefix, ...args);
  }
};

let debugTags = false;
const debugLogTags = (...args) => {
  if (debugTags) {
    console.debug(logPrefix, '[tags]', ...args);
  }
};

const enableTagsDebugging = () => {
  debugTags = true;
};

($ => {
  $.fn.nthParent = function (n) {
    let $p = $(this);
    if (!(n > -0)) { return $(); }
    let p = 1 + n;
    while (p--) { $p = $p.parent(); }
    return $p;
  };
})(jq);

// unpkg had quite often problems, tens of seconds to load, sometime 503 fails
// const getLucideIconUrl = iconName => `https://unpkg.com/lucide-static@latest/icons/${iconName}.svg`;
const getLucideIconUrl = (iconName) => `https://cdn.jsdelivr.net/npm/lucide-static@latest/icons/${iconName}.svg`;

const getTDesignIconUrl = iconName => `https://api.iconify.design/tdesign:${iconName}.svg`;

const parseIconName = iconName => {
  if (!iconName.includes(':')) return { typePrefix: 'l', processedIconName: iconName };
  const [typePrefix, processedIconName] = iconName.split(':');
  return { typePrefix, processedIconName };
};

const getIconUrl = iconName => {
  const { typePrefix, processedIconName } = parseIconName(iconName);
  if (typePrefix === 'td') {
    return getTDesignIconUrl(processedIconName);
  }
  if (typePrefix === 'l') {
    return getLucideIconUrl(processedIconName);
  }
  throw new Error(`Unknown icon type: ${typePrefix}`);
};

const pplxHelperTag = 'pplx-helper';
const genCssName = x => `${pplxHelperTag}--${x}`;

const button = (id, icoName, title, extraClass) => `<button title="${title}" type="button" id="${id}" class="btn-helper bg-super dark:bg-superDark dark:text-backgroundDark text-white hover:opacity-80 font-sans focus:outline-none outline-none outline-transparent transition duration-300 ease-in-out font-sans  select-none items-center relative group  justify-center text-center items-center rounded-full cursor-point active:scale-95 origin-center whitespace-nowrap inline-flex text-base aspect-square h-10 ${extraClass}"  >
<div class="flex items-center leading-none justify-center gap-xs">
    ${icoName}
</div></button>`;

const upperButton = (id, icoName, title) => `
<div title="${title}" id="${id}" class="border rounded-full px-sm py-xs flex items-center gap-x-sm  border-borderMain/60 dark:border-borderMainDark/60 divide-borderMain dark:divide-borderMainDark ring-borderMain dark:ring-borderMainDark  bg-transparent cursor-pointer"><div class="border-borderMain/60 dark:border-borderMainDark/60 divide-borderMain dark:divide-borderMainDark ring-borderMain dark:ring-borderMainDark  bg-transparent"><div class="flex items-center gap-x-xs transition duration-300 select-none hover:text-superAlt light font-sans text-sm font-medium text-textOff dark:text-textOffDark selection:bg-super selection:text-white dark:selection:bg-opacity-50 selection:bg-opacity-70"><div class="">${icoName}<path fill="currentColor" d="M64 288L39.8 263.8C14.3 238.3 0 203.8 0 167.8C0 92.8 60.8 32 135.8 32c36 0 70.5 14.3 96 39.8L256 96l24.2-24.2c25.5-25.5 60-39.8 96-39.8C451.2 32 512 92.8 512 167.8c0 36-14.3 70.5-39.8 96L448 288 256 480 64 288z"></path></svg></div><div></div></div></div></div>
`;

const textButton = (id, text, title) => `
<button title="${title}" id="${id}" type="button" class="bg-super text-white hover:opacity-80 font-sans focus:outline-none outline-none transition duration-300 ease-in-out font-sans  select-none items-center relative group justify-center rounded-md cursor-point active:scale-95 origin-center whitespace-nowrap inline-flex text-sm px-sm font-medium h-8">
<div class="flex items-center leading-none justify-center gap-xs"><span class="flex items-center relative ">${text}</span></div></button>
`;
const icoColor = '#1F1F1F';
const robotIco = `<svg style="width: 23px; fill: ${icoColor};" viewBox="0 0 640 512" xmlns="http://www.w3.org/2000/svg"><path d="m32 224h32v192h-32a31.96166 31.96166 0 0 1 -32-32v-128a31.96166 31.96166 0 0 1 32-32zm512-48v272a64.06328 64.06328 0 0 1 -64 64h-320a64.06328 64.06328 0 0 1 -64-64v-272a79.974 79.974 0 0 1 80-80h112v-64a32 32 0 0 1 64 0v64h112a79.974 79.974 0 0 1 80 80zm-280 80a40 40 0 1 0 -40 40 39.997 39.997 0 0 0 40-40zm-8 128h-64v32h64zm96 0h-64v32h64zm104-128a40 40 0 1 0 -40 40 39.997 39.997 0 0 0 40-40zm-8 128h-64v32h64zm192-128v128a31.96166 31.96166 0 0 1 -32 32h-32v-192h32a31.96166 31.96166 0 0 1 32 32z"/></svg>`;
const robotRepeatIco = `<svg style="width: 23px; fill: ${icoColor};"  viewBox="0 0 640 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/">    <path d="M442.179,325.051L442.179,459.979C442.151,488.506 418.685,511.972 390.158,512L130.053,512C101.525,511.972 78.06,488.506 78.032,459.979L78.032,238.868C78.032,203.208 107.376,173.863 143.037,173.863L234.095,173.863L234.095,121.842C234.095,107.573 245.836,95.832 260.105,95.832C274.374,95.832 286.116,107.573 286.116,121.842L286.116,173.863L309.247,173.863C321.515,245.71 373.724,304.005 442.179,325.051ZM26.011,277.905L52.021,277.905L52.021,433.968L25.979,433.968C11.727,433.968 -0,422.241 -0,407.989L-0,303.885C-0,289.633 11.727,277.905 25.979,277.905L26.011,277.905ZM468.19,331.092C478.118,332.676 488.289,333.497 498.65,333.497C505.935,333.497 513.126,333.091 520.211,332.299L520.211,407.989C520.211,422.241 508.483,433.968 494.231,433.968L468.19,433.968L468.19,331.092ZM208.084,407.958L156.063,407.958L156.063,433.968L208.084,433.968L208.084,407.958ZM286.116,407.958L234.095,407.958L234.095,433.968L286.116,433.968L286.116,407.958ZM364.147,407.958L312.126,407.958L312.126,433.968L364.147,433.968L364.147,407.958ZM214.587,303.916C214.587,286.08 199.91,271.403 182.074,271.403C164.238,271.403 149.561,286.08 149.561,303.916C149.561,321.752 164.238,336.429 182.074,336.429C182.075,336.429 182.075,336.429 182.076,336.429C199.911,336.429 214.587,321.753 214.587,303.918C214.587,303.917 214.587,303.917 214.587,303.916ZM370.65,303.916C370.65,286.08 355.973,271.403 338.137,271.403C320.301,271.403 305.624,286.08 305.624,303.916C305.624,321.752 320.301,336.429 338.137,336.429C338.138,336.429 338.139,336.429 338.139,336.429C355.974,336.429 370.65,321.753 370.65,303.918C370.65,303.917 370.65,303.917 370.65,303.916Z" style="fill-rule:nonzero;"/>
    <g transform="matrix(14.135,0,0,14.135,329.029,-28.2701)">
        <path d="M12,2C6.48,2 2,6.48 2,12C2,17.52 6.48,22 12,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 12,2ZM17.19,15.94C17.15,16.03 17.1,16.11 17.03,16.18L15.34,17.87C15.19,18.02 15,18.09 14.81,18.09C14.62,18.09 14.43,18.02 14.28,17.87C13.99,17.58 13.99,17.1 14.28,16.81L14.69,16.4L9.1,16.4C7.8,16.4 6.75,15.34 6.75,14.05L6.75,12.28C6.75,11.87 7.09,11.53 7.5,11.53C7.91,11.53 8.25,11.87 8.25,12.28L8.25,14.05C8.25,14.52 8.63,14.9 9.1,14.9L14.69,14.9L14.28,14.49C13.99,14.2 13.99,13.72 14.28,13.43C14.57,13.14 15.05,13.14 15.34,13.43L17.03,15.12C17.1,15.19 17.15,15.27 17.19,15.36C17.27,15.55 17.27,15.76 17.19,15.94ZM17.25,11.72C17.25,12.13 16.91,12.47 16.5,12.47C16.09,12.47 15.75,12.13 15.75,11.72L15.75,9.95C15.75,9.48 15.37,9.1 14.9,9.1L9.31,9.1L9.72,9.5C10.01,9.79 10.01,10.27 9.72,10.56C9.57,10.71 9.38,10.78 9.19,10.78C9,10.78 8.81,10.71 8.66,10.56L6.97,8.87C6.9,8.8 6.85,8.72 6.81,8.63C6.73,8.45 6.73,8.24 6.81,8.06C6.85,7.97 6.9,7.88 6.97,7.81L8.66,6.12C8.95,5.83 9.43,5.83 9.72,6.12C10.01,6.41 10.01,6.89 9.72,7.18L9.31,7.59L14.9,7.59C16.2,7.59 17.25,8.65 17.25,9.94L17.25,11.72Z" style="fill-rule:nonzero;"/>
    </g></svg>`;

const cogIco = `<svg style="width: 23px; fill: rgb(141, 145, 145);" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"viewBox="0 0 38.297 38.297"
\t xml:space="preserve">
<g>
\t<path d="M25.311,18.136l2.039-2.041l-2.492-2.492l-2.039,2.041c-1.355-0.98-2.941-1.654-4.664-1.934v-2.882H14.63v2.883
\t\tc-1.722,0.278-3.308,0.953-4.662,1.934l-2.041-2.041l-2.492,2.492l2.041,2.041c-0.98,1.354-1.656,2.941-1.937,4.662H2.658v3.523
\t\tH5.54c0.279,1.723,0.955,3.309,1.937,4.664l-2.041,2.039l2.492,2.492l2.041-2.039c1.354,0.979,2.94,1.653,4.662,1.936v2.883h3.524
\t\tv-2.883c1.723-0.279,3.309-0.955,4.664-1.936l2.039,2.039l2.492-2.492l-2.039-2.039c0.98-1.355,1.654-2.941,1.934-4.664h2.885
\t\tv-3.524h-2.885C26.967,21.078,26.293,19.492,25.311,18.136z M16.393,30.869c-3.479,0-6.309-2.83-6.309-6.307
\t\tc0-3.479,2.83-6.308,6.309-6.308c3.479,0,6.307,2.828,6.307,6.308C22.699,28.039,19.871,30.869,16.393,30.869z M35.639,8.113v-2.35
\t\th-0.965c-0.16-0.809-0.474-1.561-0.918-2.221l0.682-0.683l-1.664-1.66l-0.68,0.683c-0.658-0.445-1.41-0.76-2.217-0.918V0h-2.351
\t\tv0.965c-0.81,0.158-1.562,0.473-2.219,0.918L24.625,1.2l-1.662,1.66l0.683,0.683c-0.445,0.66-0.761,1.412-0.918,2.221h-0.966v2.35
\t\th0.966c0.157,0.807,0.473,1.559,0.918,2.217l-0.681,0.68l1.658,1.664l0.685-0.682c0.657,0.443,1.409,0.758,2.219,0.916v0.967h2.351
\t\tv-0.968c0.807-0.158,1.559-0.473,2.217-0.916l0.682,0.68l1.662-1.66l-0.682-0.682c0.444-0.658,0.758-1.41,0.918-2.217H35.639
\t\tL35.639,8.113z M28.701,10.677c-2.062,0-3.74-1.678-3.74-3.74c0-2.064,1.679-3.742,3.74-3.742c2.064,0,3.742,1.678,3.742,3.742
\t\tC32.443,9,30.766,10.677,28.701,10.677z"/>
</g>
</svg>`;


const perplexityHelperModalId = 'perplexityHelperModal';
const getPerplexityHelperModal = () => $i(perplexityHelperModalId);

const modalSettingsTitleCls = genCssName('modal-settings-title');

const gitlabLogo = classes => `
<svg class="${classes}" fill="#000000" width="800px" height="800px" viewBox="0 0 512 512" id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg"><path d="M494.07,281.6l-25.18-78.08a11,11,0,0,0-.61-2.1L417.78,44.48a20.08,20.08,0,0,0-19.17-13.82A19.77,19.77,0,0,0,379.66,44.6L331.52,194.15h-152L131.34,44.59a19.76,19.76,0,0,0-18.86-13.94h-.11a20.15,20.15,0,0,0-19.12,14L42.7,201.73c0,.14-.11.26-.16.4L16.91,281.61a29.15,29.15,0,0,0,10.44,32.46L248.79,476.48a11.25,11.25,0,0,0,13.38-.07L483.65,314.07a29.13,29.13,0,0,0,10.42-32.47m-331-64.51L224.8,408.85,76.63,217.09m209.64,191.8,59.19-183.84,2.55-8h86.52L300.47,390.44M398.8,59.31l43.37,134.83H355.35M324.16,217l-43,133.58L255.5,430.14,186.94,217M112.27,59.31l43.46,134.83H69M40.68,295.58a6.19,6.19,0,0,1-2.21-6.9l19-59L197.08,410.27M470.34,295.58,313.92,410.22l.52-.69L453.5,229.64l19,59a6.2,6.2,0,0,1-2.19,6.92"/></svg>
`;

const modalLargeIconAnchorClasses = 'hover:scale-110 opacity-50 hover:opacity-100 transition-all duration-300';

const modalTabGroupTabsCls = genCssName('modal-tab-group-tabs');
const modalTabGroupActiveCls = genCssName('modal-tab-group-active');
const modalTabGroupContentCls = genCssName('modal-tab-group-content');
const modalTabGroupSeparatorCls = genCssName('modal-tab-group-separator');

const modalHTML = `
<div id="${perplexityHelperModalId}" class="modal">
  <div class="modal-content">
    <span class="close">&times;</span>
    <h1 class="flex items-center gap-4">
      <span class="mr-4 ${modalSettingsTitleCls}">Perplexity Helper</span>
      <a href="https://gitlab.com/Tiartyos/perplexity-helper"
        target="_blank" title="GitLab Repository"
        class="${modalLargeIconAnchorClasses}"
      >
        ${gitlabLogo('w-8 h-8 invert')}
      </a>
      <a href="https://tiartyos.gitlab.io/perplexity-helper/"
        target="_blank" title="Web Page"
        class="${modalLargeIconAnchorClasses}"
      >
        <img src="${getLucideIconUrl('globe')}" class="w-8 h-8 invert">
      </a>
    </h1>
    <p class="text-xs opacity-30 mt-1 mb-3">Changes may require page refresh.</p>
    <div class="${modalTabGroupTabsCls}">
    </div>
    <hr class="!mt-0 !mb-0 ${modalTabGroupSeparatorCls}">
  </div>
</div>
`;

const tagsContainerCls = genCssName('tags-container');
const tagContainerCompactCls = genCssName('tag-container-compact');
const tagContainerWiderCls = genCssName('tag-container-wider');
const tagContainerWideCls = genCssName('tag-container-wide');
const tagContainerExtraWideCls = genCssName('tag-container-extra-wide');
const threadTagContainerCls = genCssName('thread-tag-container');
const newTagContainerCls = genCssName('new-tag-container');
const newTagContainerInCollectionCls = genCssName('new-tag-container-in-collection');
const tagCls = genCssName('tag');
const tagDarkTextCls = genCssName('tag-dark-text');
const tagIconCls = genCssName('tag-icon');
const tagPaletteCls = genCssName('tag-palette');
const tagPaletteItemCls = genCssName('tag-palette-item');
const tagTweakNoBorderCls = genCssName('tag-tweak-no-border');
const tagTweakSlimPaddingCls = genCssName('tag-tweak-slim-padding');
const tagsPreviewCls = genCssName('tags-preview');
const tagsPreviewNewCls = genCssName('tags-preview-new');
const tagsPreviewThreadCls = genCssName('tags-preview-thread');
const tagsPreviewNewInCollectionCls = genCssName('tags-preview-new-in-collection');
const tagTweakTextShadowCls = genCssName('tag-tweak-text-shadow');
const tagFenceCls = genCssName('tag-fence');
const tagAllFencesWrapperCls = genCssName('tag-all-fences-wrapper');
const tagRestOfTagsWrapperCls = genCssName('tag-rest-of-tags-wrapper');
const tagFenceContentCls = genCssName('tag-fence-content');
const tagDirectoryCls = genCssName('tag-directory');
const tagDirectoryContentCls = genCssName('tag-directory-content');
const helpTextCls = genCssName('help-text');
const queryBoxCls = genCssName('query-box');
const controlsAreaCls = genCssName('controls-area');
const textAreaCls = genCssName('text-area');
const standardButtonCls = genCssName('standard-button');
const lucideIconParentCls = genCssName('lucide-icon-parent');
const roundedMD = genCssName('rounded-md');
const leftPanelSlimCls = genCssName('left-panel-slim');
const modelIconButtonCls = genCssName('model-icon-button');
const modelLabelCls = genCssName('model-label');
const modelLabelStyleJustTextCls = genCssName('model-label-style-just-text');
const modelLabelStyleButtonSubtleCls = genCssName('model-label-style-button-subtle');
const modelLabelStyleButtonWhiteCls = genCssName('model-label-style-button-white');
const modelLabelStyleButtonCyanCls = genCssName('model-label-style-button-cyan');
const modelLabelOverwriteCyanIconToGrayCls = genCssName('model-label-overwrite-cyan-icon-to-gray');
const modelLabelRemoveCpuIconCls = genCssName('model-label-remove-cpu-icon');
const reasoningModelCls = genCssName('reasoning-model');
const notReasoningModelCls = genCssName('not-reasoning-model');
const iconColorCyanCls = genCssName('icon-color-cyan');
const iconColorGrayCls = genCssName('icon-color-gray');
const iconColorWhiteCls = genCssName('icon-color-white');
const iconColorGoldCls = genCssName('icon-color-gold');
const customJsAppliedCls = genCssName('customJsApplied');
const customCssAppliedCls = genCssName('customCssApplied');
const customWidgetsHtmlAppliedCls = genCssName('customWidgetsHtmlApplied');
const sideMenuHiddenCls = genCssName('side-menu-hidden');
const sideMenuLabelsHiddenCls = genCssName('side-menu-labels-hidden');
const topSettingsButtonId = genCssName('settings-button-top');
const leftSettingsButtonId = genCssName('settings-button-left');
const leftSettingsButtonWrapperId = genCssName('settings-button-left-wrapper');

const cyanPerplexityColor = '#1fb8cd';
const cyanMediumPerplexityColor = '#204b51';
const cyanDarkPerplexityColor = '#203133';

const styles = `
.textarea_wrapper {
  display: flex;
  flex-direction: column;
}

@import url('https://fonts.googleapis.com/css2?family=Fira+Sans:wght@400;500;600&display=swap');

.textarea_wrapper > textarea {
  width: 100%;
  background-color: rgba(0, 0, 0, 0.8);
  padding: 0 5px;
  border-radius: 0.5em;
}

.textarea_label {
}

.${helpTextCls} {
  background-color: #225;
  padding: 0.3em 0.7em;
  border-radius: 0.5em;
  margin: 1em 0;
}
.${helpTextCls} {
  cursor: text;
}

.${helpTextCls} a {
  text-decoration: underline;
}
.${helpTextCls} a:hover {
  color: white;
}

.${helpTextCls} code {
  font-size: 80%;
  background-color: rgba(255, 255, 255, 0.1);
  border-radius: 0.3em;
  padding: 0.1em;
}
.${helpTextCls} pre > code {
  background: none;
}
.${helpTextCls} pre {
  font-size: 80%;
  overflow: auto;
  background-color: rgba(255, 255, 255, 0.1);
  border-radius: 0.3em;
  padding: 0.1em 1em;
}
.${helpTextCls} li {
  list-style: circle;
  margin-left: 1em;
}
.${helpTextCls} hr {
  margin: 1em 0 0.5em 0;
  border-color: rgba(255, 255, 255, 0.1);
}

.${helpTextCls} table {
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 0.5em;
  display: inline-block;
}
.${helpTextCls} table td, .${helpTextCls} table th {
  padding: 0.1em 0.5em;
}

.btn-helper {
margin-left: 20px
}

.modal {
  display: none;
  position: fixed;
  z-index: 1000;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: auto;
  background-color: rgba(0, 0, 0, 0.8)
}

.modal-content {
  display: flex;
  margin: 1em auto;
  width: calc(100vw - 2em);
  padding: 20px;
  border: 1px solid #333;
  background: linear-gradient(135deg, #151517, #202025);
  border-radius: 6px;
  color: rgb(206, 206, 210);
  flex-direction: column;
  position: relative;
  overflow-y: auto;
  cursor: default;
  font-family: 'Fira Sans', sans-serif;
}

.${modalTabGroupTabsCls} {
  display: flex;
  flex-direction: row;
}

.modal-content .${modalTabGroupTabsCls} > button {
  border-radius: 0.5em 0.5em 0 0;
  border-bottom: 0;
  padding: 0.2em 0.5em 0 0.5em;
  background-color: #1e293b;
  color: rgba(255, 255, 255, 0.5);
  outline-bottom: none;
  white-space: nowrap;
}

.modal-content .${modalTabGroupTabsCls} > button.${modalTabGroupActiveCls} {
  /* background-color: #3b82f6; */
  color: white;
  text-shadow: 0 0 1px currentColor;
  padding: 0.3em 0.5em 0.2em 0.5em;
}

.modal-content .${modalTabGroupContentCls} {
  display: flex;
  flex-direction: column;
  gap: 1em;
  padding-top: 1em;
}

.${modalSettingsTitleCls} {
  background: linear-gradient(to bottom, white, gray);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  font-weight: bold;
  font-size: 3em;
  text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
  user-select: none;
  margin-top: -0.33em;
  margin-bottom: -0.33em;
}

.${modalSettingsTitleCls} .animate-letter {
  display: inline-block;
  background: inherit;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  transition: transform 0.3s ease-out;
}

.${modalSettingsTitleCls} .animate-letter.active {
  /* Move and highlight on active */
  transform: translateY(-10px) rotate(5deg);
  -webkit-text-fill-color: #4dabff;
  text-shadow: 0 0 5px #4dabff, 0 0 10px #4dabff;
}

.modal-content .hover\\:scale-110:hover {
  transform: scale(1.1);
}

.modal-content label {
  padding-right: 10px;
}

.modal-content hr {
  height: 1px;
  margin: 1em 0;
  border-color: rgba(255, 255, 255, 0.1);
}

.modal-content hr.${modalTabGroupSeparatorCls} {
  margin: 0 -1em 0 -1em;
}

.modal-content input[type="checkbox"] {
  appearance: none;
  width: 1.2em;
  height: 1.2em;
  border: 2px solid #ffffff80;
  border-radius: 0.25em;
  background-color: transparent;
  transition: all 0.2s ease;
  cursor: pointer;
  position: relative;
}

.modal-content input[type="checkbox"]:checked {
  background-color: #3b82f6;
  border-color: #3b82f6;
}

.modal-content input[type="checkbox"]:checked::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0.4em;
  height: 0.7em;
  border: solid white;
  border-width: 0 2px 2px 0;
  transform: translate(-50%, -60%) rotate(45deg);
}

.modal-content input[type="checkbox"]:hover {
  border-color: #ffffff;
}

.modal-content input[type="checkbox"]:focus {
  outline: 2px solid #3b82f680;
  outline-offset: 2px;
}

.modal-content .checkbox_label {
  color: white;
  line-height: 1.5;
}

.modal-content .checkbox_wrapper {
  display: flex;
  align-items: center;
  gap: 0.5em;
}

.modal-content .number_label {
  margin-left: 0.5em;
}

.modal-content .color_wrapper {
  display: flex;
  align-items: center;
}

.modal-content .color_label {
  margin-left: 0.5em;
}

.modal-content input, .modal-content button {
  background-color: #1e293b;
  border: 2px solid #ffffff80;
  border-radius: 0.5em;
  color: white;
  padding: 0.5em;
  transition: border-color 0.3s ease, outline 0.3s ease;
}

.modal-content input:hover, .modal-content button:hover {
  border-color: #ffffff;
}

.modal-content input:focus, .modal-content button:focus {
  outline: 2px solid #3b82f680;
  outline-offset: 2px;
}

.modal-content input[type="number"] {
  padding: 0.5em;
  transition: border-color 0.3s ease, outline 0.3s ease;
}

.modal-content input[type="color"] {
  padding: 0;
  height: 2em;
}

.modal-content input[type="color"]:hover {
  border-color: #ffffff;
}

.modal-content input[type="color"]:focus {
  outline: 2px solid #3b82f680;
  outline-offset: 2px;
}

.modal-content h1 + hr {
  margin-top: 0.5em;
}


.modal-content select {
  appearance: none;
  background-color: #1e293b; /* Dark blue background */
  border: 2px solid #ffffff80;
  border-radius: 0.5em;
  padding: 0.3em 2em 0.3em 0.5em;
  color: white;
  font-size: 1em;
  cursor: pointer;
  transition: all 0.2s ease;
  background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3e%3cpath d='M7 10l5 5 5-5z'/%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: right 0.5em center;
  background-size: 1.2em;
}

.modal-content select option {
  background-color: #1e293b; /* Match select background */
  color: white;
  padding: 0.5em;
}

.modal-content select:hover {
  border-color: #ffffff;
}

.modal-content select:focus {
  outline: 2px solid #3b82f680;
  outline-offset: 2px;
}

.modal-content .select_label {
  color: white;
  margin-left: 0.5em;
}

.modal-content .select_wrapper {
  display: flex;
  align-items: center;
}

.close {
  color: rgb(206, 206, 210);
  float: right;
  font-size: 28px;
  font-weight: bold;
  position: absolute;
  right: 20px;
  top: 5px;
}

.close:hover,
.close:focus {
  color: white;
  text-decoration: none;
  cursor: pointer;
}

#copied-modal,#copied-modal-2 {
  padding: 5px 5px;
  background:gray;
  position:absolute;
  display: none;
  color: white;
  font-size: 15px;
}

label > div.select-none {
  user-select: text;
  cursor: initial;
}

.${tagsContainerCls} {
  display: flex;
  flex-direction: row;
  margin: 5px 0;
}
.${tagsContainerCls}.${threadTagContainerCls} {
  margin-left: 0.5em;
  margin-right: 0.5em;
  margin-bottom: 2px;
}

.${tagContainerCompactCls} {
  margin-top: -2em;
  margin-bottom: 1px;
}
.${tagContainerCompactCls} .${tagFenceCls} {
  margin: 0;
  padding: 1px;
}
.${tagContainerCompactCls} .${tagCls} {
}
.${tagContainerCompactCls} .${tagAllFencesWrapperCls} {
  gap: 1px;
}
.${tagContainerCompactCls} .${tagRestOfTagsWrapperCls} {
  margin: 1px;
}
.${tagContainerCompactCls} .${tagRestOfTagsWrapperCls},
.${tagContainerCompactCls} .${tagFenceContentCls},
.${tagContainerCompactCls} .${tagDirectoryContentCls} {
  gap: 1px;
}

.${tagContainerWiderCls} {
  margin-left: -6em;
  margin-right: -6em;
}
.${tagContainerWiderCls} .${tagCls} {
}

.${tagContainerWideCls} {
  margin-left: -12em;
  margin-right: -12em;
}

.${tagContainerExtraWideCls} {
  margin-left: -16em;
  margin-right: -16em;
  max-width: 100vw;
}

.${tagsContainerCls} {
  @media (max-width: 768px) {
    margin-left: 0 !important;
    margin-right: 0 !important;
  }
}


.${tagCls} {
  border: 1px solid #3b3b3b;
  background-color: #282828;
  /*color: rgba(255, 255, 255, 0.482);*/ /* equivalent of #909090; when on #282828 background */
  padding: 0px 8px 0 8px;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.2s, color 0.2s;
  display: inline-block;
  color: #E8E8E6;
  user-select: none;
}
.${tagCls}.${tagDarkTextCls} {
  color: #171719;
}
.${tagCls} span {
  display: inline-block;
}

.${tagCls}.${tagTweakNoBorderCls} {
  border: none;
}

.${tagCls}.${tagTweakSlimPaddingCls} {
  padding: 0px 4px 0 4px;
}

.${tagCls} .${tagIconCls} {
  width: 16px;
  height: 16px;
  margin-right: 2px;
  margin-left: -4px;
  margin-top: -4px;
  vertical-align: middle;
  display: inline-block;
  filter: invert(1);
}
.${tagCls}.${tagDarkTextCls} .${tagIconCls} {
  filter: none;
}
.${tagCls}.${tagTweakSlimPaddingCls} .${tagIconCls} {
  margin-left: -2px;
}
.${tagCls} span {
  position: relative;
  top: 1.5px;
}
.${tagCls}.${tagTweakTextShadowCls} span {
  text-shadow: 1px 0 0.5px black, -1px 0 0.5px black, 0 1px 0.5px black, 0 -1px 0.5px black;
}
.${tagCls}.${tagTweakTextShadowCls}.${tagDarkTextCls} span {
  text-shadow: 1px 0 0.5px white, -1px 0 0.5px white, 0 1px 0.5px white, 0 -1px 0.5px white;
}
.${tagCls}:hover {
  background-color: #333;
  color: #fff;
  transform: scale(1.02);
}
.${tagCls}.${tagDarkTextCls}:hover {
  /* color: #171717; */
  color: #2f2f2f;
}
.${tagCls}:active {
  transform: scale(0.98);
}

.${tagPaletteCls} {
  display: flex;
  flex-wrap: wrap;
  gap: 1px;
}
.${tagPaletteCls} .${tagPaletteItemCls} {
  text-shadow: 1px 0 1px black, -1px 0 1px black, 0 1px 1px black, 0 -1px 1px black;
  width: 40px;
  height: 25px;
  display: inline-block;
  text-align: center;
  padding: 0 2px;
  transition: color 0.2s, border 0.1s;
  border: 2px solid transparent;
}

.${tagPaletteItemCls}:hover {
  cursor: pointer;
  color: white;
  border: 2px solid white;
}

.${tagsPreviewCls} {
  background-color: #191a1a;
  padding: 0.5em 1em;
  border-radius: 1em;
}

.${tagAllFencesWrapperCls} {
  display: flex;
  flex-direction: row;
  gap: 5px;
}

.${tagRestOfTagsWrapperCls} {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: 5px;
  margin: 8px;
}

.${tagFenceCls} {
  display: flex;
  margin: 5px 0;
  padding: 5px;
  border-radius: 4px;
}

.${tagFenceContentCls} {
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
  gap: 5px;
}

.${tagDirectoryCls} {
  position: relative;
  display: flex;
  z-index: 100;
}
.${tagDirectoryCls}:hover .${tagDirectoryContentCls} {
  display: flex;
}
.${tagDirectoryContentCls} {
  position: absolute;
  display: none;
  flex-direction: column;
  gap: 5px;
  top: 0px;
  padding-bottom: 1px;
  left: -5px;
  transform: translateY(-100%);
  background: rgba(0, 0, 0, 0.5);
  padding: 5px;
  border-radius: 4px;
  flex-wrap: nowrap;
  width: max-content;
}
.${tagDirectoryContentCls} .${tagCls} {
  white-space: nowrap;
  width: fit-content;
}

.${queryBoxCls} {
  flex-wrap: wrap;
}

.${controlsAreaCls} {
  grid-template-columns: repeat(4,minmax(0,1fr))
}

.${textAreaCls} {
  grid-column-end: 5;
}

.${standardButtonCls} {
  grid-column-start: 4;
}

.${roundedMD} {
  border-radius: 0.375rem!important;
}

#${leftSettingsButtonId} svg {
  transition: fill 0.2s;
}
#${leftSettingsButtonId}:hover svg {
  fill: #fff !important;
}

.w-collapsedSideBarWidth #${leftSettingsButtonId} span {
  display: none;
}

.w-collapsedSideBarWidth #${leftSettingsButtonId} {
  width: 100%;
  border-radius: 0.25rem;
  height: 40px;
}

#${leftSettingsButtonWrapperId} {
  display: flex;
  padding: 0.1em 0.2em;
  justify-content: flex-start;
}

.w-collapsedSideBarWidth #${leftSettingsButtonWrapperId} {
  justify-content: center;
}

.${lucideIconParentCls} > img {
  transition: opacity 0.2s ease;
}

.${lucideIconParentCls}:hover > img, a.dark\\:text-textMainDark .${lucideIconParentCls} > img {
  opacity: 1;
}

.${leftPanelSlimCls} > .py-md {
  margin-left: -0.1em;
}

.${leftPanelSlimCls} > .py-md > div.flex-col > * {
/*   background: red; */
  margin-right: 0;
  max-width: 40px;
}

.${modelLabelCls} {
  color: #888;
  /* padding is from style attr */
  transition: color 0.2s, background-color 0.2s, border 0.2s;
/*   margin-right: 0.5em;
  margin-left: 0.5em;
  margin-right: 0.5em;
 */  padding-top: 2px;
}
button.${modelIconButtonCls} {
  padding-right: 1.0em;
  padding-left: 1.0em;
  gap: 5px;
}
button:hover > .${modelLabelCls} {
  color: #fff;
}
button.${modelIconButtonCls} > .min-w-0 {
  min-width: 16px;
  margin-right: 0.0em;
}
button.${modelLabelRemoveCpuIconCls} {
  margin-left: 0.5em;
  padding-right: 1.0em;
}
button.${modelLabelRemoveCpuIconCls} .${modelLabelCls} {
  margin-right: 0.5em;
}
button.${modelLabelRemoveCpuIconCls}:has(.${reasoningModelCls}) .${modelLabelCls} {
  margin-right: 0.5em;
}
button.${modelLabelRemoveCpuIconCls}.${notReasoningModelCls} .${modelLabelCls} {
  margin-right: 0.0em;
}
.${modelLabelRemoveCpuIconCls} div:has(div > svg.tabler-icon-cpu) {
  display: none;
}

button:has(> .${modelLabelCls}.${modelLabelStyleButtonSubtleCls}) {
  border: 1px solid #333;
}
button:hover:has(> .${modelLabelCls}.${modelLabelStyleButtonSubtleCls}) {
  background: #333 !important;
}

.${modelLabelCls}.${modelLabelStyleButtonWhiteCls} {
  color: #8D9191 !important;
}
button:hover > .${modelLabelCls}.${modelLabelStyleButtonWhiteCls} {
  color: #fff !important;
}
.${modelIconButtonCls} svg[stroke] {
  stroke: #8D9191 !important;
}
.${modelIconButtonCls}:hover svg[stroke] {
  stroke: #fff !important;
}
button:has(> .${modelLabelCls}.${modelLabelStyleButtonWhiteCls}) {
  background: #191A1A !important;
  color: #2D2F2F !important;
}
button:has(> .${modelLabelCls}.${modelLabelStyleButtonWhiteCls}):hover {
  color: #8D9191 !important;
}

.${modelLabelCls}.${modelLabelStyleButtonCyanCls} {
  color: ${cyanPerplexityColor};
}
button:has(> .${modelLabelCls}.${modelLabelStyleButtonCyanCls}) {
  border: 1px solid ${cyanMediumPerplexityColor};
  background: ${cyanDarkPerplexityColor} !important;
}
button:hover:has(> .${modelLabelCls}.${modelLabelStyleButtonCyanCls}) {
  border: 1px solid ${cyanPerplexityColor};
}
.${modelIconButtonCls}:has(> .${modelLabelCls}.${modelLabelStyleButtonCyanCls}) svg[stroke] {
  stroke: ${cyanPerplexityColor} !important;
}
.${modelIconButtonCls}:has(> .${modelLabelCls}.${modelLabelStyleButtonCyanCls}):hover svg[stroke] {
  stroke: #fff !important;
}

button:has(> .${modelLabelCls}.${modelLabelOverwriteCyanIconToGrayCls}) {
  color: #888 !important;
}
button:has(> .${modelLabelCls}.${modelLabelOverwriteCyanIconToGrayCls}):hover {
  color: #fff !important;
}

.${reasoningModelCls} {
  width: 16px;
  height: 16px;
  margin-right: 0px;
  margin-left: 0;
  margin-top: -2px;
  filter: invert();
}
/* button:has(.${reasoningModelCls}) > div > div > svg {
  width: 32px;
  height: 16px;
  margin-left: 8px;
  margin-right: 12px;
  margin-top: 0px;
  min-width: 16px;
  background-color: cyan;
}
button:has(.${reasoningModelCls}) > div > div:has(svg) {
  width: 16px;
  height: 16px;
  min-width: 30px;
  background-color: purple;
} */


.${iconColorCyanCls} {
  filter: invert(54%) sepia(84%) saturate(431%) hue-rotate(139deg) brightness(97%) contrast(90%);
  transition: filter 0.2s;
}

button:hover:has(> .${modelLabelCls}.${modelLabelStyleButtonCyanCls}) .${iconColorCyanCls} {
  filter: invert(100%);
}

.${iconColorGrayCls} {
  filter: invert(50%);
  transition: filter 0.2s;
}
button:has(.${reasoningModelCls}):hover .${iconColorGrayCls} {
  filter: invert(100%);
}

.${iconColorWhiteCls} {
  filter: invert(50%);
  transition: filter 0.2s;
}
button:has(.${reasoningModelCls}):hover .${iconColorWhiteCls} {
  filter: invert(100%);
}

.${iconColorGoldCls} {
  filter: brightness(0) saturate(100%) invert(88%) sepia(75%) saturate(2577%) hue-rotate(324deg) brightness(96%) contrast(99%);
  transition: filter 0.2s;
}

.${sideMenuHiddenCls} {
  display: none;
}

.${sideMenuLabelsHiddenCls} div.text-xs {
  display: none;
}
`;

const TAG_POSITION = {
  BEFORE: 'before',
  AFTER: 'after',
  CARET: 'caret',
  WRAP: 'wrap',
};

const TAG_CONTAINER_TYPE = {
  NEW: 'new',
  NEW_IN_COLLECTION: 'new-in-collection',
  THREAD: 'thread',
  ALL: 'all',
};

const tagsHelpText = `
Each line is one tag.
Non-field text is what will be inserted into prompt.
Field is denoted by \`<\` and \`>\`, field name is before \`:\`, field value after \`:\`.

Supported fields:
- \`label\`: tag label shown on tag "box" (new items around prompt input area)
- \`position\`: where the tag text will be inserted, default is \`before\`; valid values are \`before\`/\`after\` (existing text) or \`caret\` (at cursor position) or \`wrap\` (wrap text around \`$$wrap$$\` marker)
- \`color\`: tag color; CSS colors supported, you can use colors from a pre-generated palette via \`%\` syntax, e.g. \`<color:%5>\`. See palette bellow.
- \`tooltip\`: shown on hover (aka title); (default) tooltip can be disabled when this field is set to empty string - \`<tooltip:>\`
- \`target\`: where the tag will be inserted, default is \`new\`; valid values are \`new\` (on home page or when clicking on "New Thread" button) / \`thread\` (on thread page) / \`all\` (everywhere)
- \`hide\`: hide the tag from the tag list
- \`link\`: link to a URL, e.g. \`<link:https://example.com>\`, can be used for collections. only one link per tag is supported.
- \`link-target\`: target of the link, e.g. \`<link-target:_blank>\` (opens in new tab), default is \`_self\` (same tab).
- \`icon\`: Lucide icon name, e.g. \`<icon:arrow-right>\`. see [lucide icons](https://lucide.dev/icons). prefix \`td:\` is used for [TDesign icons](https://tdesign.tencent.com/design/icon-en#header-69). prefix \`l:\` for Lucide icons is implicit and can be omitted.
- \`set-mode\`: set the query mode: \`pro\` or \`research\`, e.g. \`<set-mode:pro>\`
- \`set-model\`: set the model, e.g. \`<set-model:claude-3-7-sonnet-thinking>\`
- \`set-sources\`: set the sources, e.g. \`<set-sources:001>\` for disabled first source (web), disabled second source (academic), enabled third source (social)
- \`auto-submit\`: automatically submit the query after the tag is clicked (applies after other tag actions like \`set-mode\` or \`set-model\`), e.g. \`<auto-submit>\`
- \`dir\`: unique identifier for a directory tag (it will not insert text into prompt)
- \`in-dir\`: identifier of the parent directory this tag belongs to
- \`fence\`: unique identifier for a fence definition (hidden by default)
- \`in-fence\`: identifier of the fence this tag belongs to
- \`fence-width\`: CSS width for a fence, e.g. \`<fence-width:10em>\`
- \`fence-border-style\`: CSS border style for a fence (e.g., solid, dashed, dotted)
- \`fence-border-color\`: CSS color or a palette \`%\` syntax for a fence border
- \`fence-border-width\`: CSS width for a fence border

---

| String | Replacement | Example |
|---|---|---|
| \`\\n\` | newline | |
| \`$$time$$\` | current time | \`23:05\` |
| \`$$wrap$$\` | sets position where existing text will be inserted | |

---

Examples:
\`\`\`
stable diffusion web ui - <label:SDWU>
, prefer concise modern syntax and style, <position:caret><label:concise modern>
tell me a joke<label:Joke><tooltip:>
tell me a joke<label:Joke & Submit><auto-submit>
\`\`\`

Directory example:
\`\`\`
<dir:games>Games<icon:gamepad-2>
<in-dir:games>FFXIV: <color:%15><label:FFXIV>
<in-dir:games>Vintage Story - <label:VS>
\`\`\`

Fence example:
\`\`\`
<fence:anime><fence-border-style:dashed><fence-border-color:%10>
<in-fence:anime>Shounen
<in-fence:anime>Seinen
<in-fence:anime>Shoujo
\`\`\`

Another fence example:
\`\`\`
<fence:programming><fence-border-style:solid><fence-border-color:%20>
<in-fence:programming>Haskell
<in-fence:programming>Raku<label:🦋>
\`\`\`
`.trim();

const defaultTagColor = '#282828';

const changeValueUsingEvent = (selector, value) => {
  debugLog('changeValueUsingEvent', value, selector);

  const nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
  nativeTextareaValueSetter.call(selector, value);
  const inputEvent = new Event('input', { bubbles: true });
  selector.dispatchEvent(inputEvent);
};

const TAGS_PALETTE_COLORS_NUM = 16;
const TAGS_PALETTE_CLASSIC = Object.freeze((() => {
  const step = 360 / TAGS_PALETTE_COLORS_NUM;
  const [startH, startS, startL, startA] = color2k.parseToHsla(cyanPerplexityColor);
  return _.flow(
    _.map(x => startH + x * step, _),
    _.map(h => color2k.hsla(h, startS, startL, startA), _),
    _.sortBy(x => color2k.parseToHsla(x)[0], _)
  )(_.range(0, TAGS_PALETTE_COLORS_NUM));
})());

const TAGS_PALETTE_PASTEL = Object.freeze((() => {
  const step = 360 / TAGS_PALETTE_COLORS_NUM;
  const [startH, startS, startL, startA] = color2k.parseToHsla(cyanPerplexityColor);
  return _.flow(
    _.map(x => startH + x * step, _),
    _.map(h => color2k.hsla(h, startS - 0.2, startL + 0.2, startA), _),
    _.sortBy(x => color2k.parseToHsla(x)[0], _)
  )(_.range(0, TAGS_PALETTE_COLORS_NUM));
})());

const TAGS_PALETTE_GRIM = Object.freeze((() => {
  const step = 360 / TAGS_PALETTE_COLORS_NUM;
  const [startH, startS, startL, startA] = color2k.parseToHsla(cyanPerplexityColor);
  return _.flow(
    _.map(x => startH + x * step, _),
    _.map(h => color2k.hsla(h, startS - 0.6, startL - 0.3, startA), _),
    _.sortBy(x => color2k.parseToHsla(x)[0], _)
  )(_.range(0, TAGS_PALETTE_COLORS_NUM));
})());

const TAGS_PALETTE_DARK = Object.freeze((() => {
  const step = 360 / TAGS_PALETTE_COLORS_NUM;
  const [startH, startS, startL, startA] = color2k.parseToHsla(cyanPerplexityColor);
  return _.flow(
    _.map(x => startH + x * step, _),
    _.map(h => color2k.hsla(h, startS, startL - 0.4, startA), _),
    _.sortBy(x => color2k.parseToHsla(x)[0], _)
  )(_.range(0, TAGS_PALETTE_COLORS_NUM));
})());

const TAGS_PALETTE_GRAY = Object.freeze((() => {
  const step = 1 / TAGS_PALETTE_COLORS_NUM;
  return _.range(0, TAGS_PALETTE_COLORS_NUM).map(x => color2k.hsla(0, 0, step * x, 1));
})());

const TAGS_PALETTE_CYAN = Object.freeze((() => {
  const step = 1 / TAGS_PALETTE_COLORS_NUM;
  const [startH, startS, startL, startA] = color2k.parseToHsla(cyanPerplexityColor);
  return _.range(0, TAGS_PALETTE_COLORS_NUM).map(x => color2k.hsla(startH, startS, step * x, 1));
})());

const TAGS_PALETTE_TRANSPARENT = Object.freeze((() => {
  const step = 1 / TAGS_PALETTE_COLORS_NUM;
  return _.range(0, TAGS_PALETTE_COLORS_NUM).map(x => color2k.hsla(0, 0, 0, step * x));
})());

const TAGS_PALETTE_HACKER = Object.freeze((() => {
  const step = 1 / TAGS_PALETTE_COLORS_NUM;
  return _.range(0, TAGS_PALETTE_COLORS_NUM).map(x => color2k.hsla(120, step * x, step * x * 0.5, 1));
})());

const TAGS_PALETTES = Object.freeze({
  CLASSIC: TAGS_PALETTE_CLASSIC,
  PASTEL: TAGS_PALETTE_PASTEL,
  GRIM: TAGS_PALETTE_GRIM,
  DARK: TAGS_PALETTE_DARK,
  GRAY: TAGS_PALETTE_GRAY,
  CYAN: TAGS_PALETTE_CYAN,
  TRANSPARENT: TAGS_PALETTE_TRANSPARENT,
  HACKER: TAGS_PALETTE_HACKER,
  CUSTOM: 'CUSTOM',
});

const convertColorInPaletteFormat = currentPalette => value => currentPalette[parseInt(dropStr(1)(value), 10)] ?? defaultTagColor;

const TAG_HOME_PAGE_LAYOUT = {
  DEFAULT: 'default',
  COMPACT: 'compact',
  WIDER: 'wider',
  WIDE: 'wide',
  EXTRA_WIDE: 'extra-wide',
};

const parseBinaryState = binaryStr => {
  if (!/^[01-]+$/.test(binaryStr)) {
    throw new Error('Invalid binary state: ' + binaryStr);
  }
  return binaryStr.split('').map(bit => bit === '1' ? true : bit === '0' ? false : null);
};

const processTagField = currentPalette => name => value => {
  if (name === 'color' && value.startsWith('%')) return convertColorInPaletteFormat(currentPalette)(value);
  if (name === 'hide') return true;
  if (name === 'auto-submit') return true;
  if (name === 'set-sources') return parseBinaryState(value);
  return value;
};

const tagLineRegex = /<(label|position|color|tooltip|target|hide|link|link-target|icon|dir|in-dir|fence|in-fence|fence-border-style|fence-border-color|fence-border-width|fence-width|set-mode|set-model|auto-submit|set-sources)(?::([^<>]*))?>/g;
const parseOneTagLine = currentPalette => line =>
  Array.from(line.matchAll(tagLineRegex)).reduce(
    (acc, match) => {
      const [fullMatch, field, value] = match;
      const processedValue = processTagField(currentPalette)(field)(value);
      return {
        ...acc,
        [_.camelCase(field)]: processedValue,
        text: acc.text.replace(fullMatch, '').replace(/\\n/g, '\n'),
      };
    },
    {
      text: line,
      color: defaultTagColor,
      target: TAG_CONTAINER_TYPE.NEW,
      hide: false,
      'link-target': '_self',
    }
  );

const parseTagsText = text => {
  const lines = text.split('\n').filter(tag => tag.trim().length > 0);
  const palette = getPalette(loadConfig()?.tagPalette);
  return lines.map(parseOneTagLine(palette)).map((x, i) => ({ ...x, originalIndex: i }));
};

const getTagsContainer = () => $c(tagsContainerCls);

const posFromTag = tag => Object.values(TAG_POSITION).includes(tag.position) ? tag.position : TAG_POSITION.BEFORE;

const splitTextAroundWrap = (text) => {
  const parts = text.split('$$wrap$$');
  return {
    before: parts[0] || '',
    after: parts[1] || '',
  };
};

const applyTagToString = (tag, val, caretPos) => {
  const { text } = tag;
  const timeString = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
  const textAfterTime = text.replace(/\$\$time\$\$/g, timeString);
  const { before: processedTextBefore, after: processedTextAfter } = splitTextAroundWrap(textAfterTime);
  const processedText = processedTextBefore;

  switch (posFromTag(tag)) {
    case TAG_POSITION.BEFORE:
      return `${processedText}${val}`;
    case TAG_POSITION.AFTER:
      return `${val}${processedText}`;
    case TAG_POSITION.CARET:
      return `${takeStr(caretPos)(val)}${processedText}${dropStr(caretPos)(val)}`;
    case TAG_POSITION.WRAP:
      return `${processedTextBefore}${val}${processedTextAfter}`;
    default:
      throw new Error(`Invalid position: ${tag.position}`);
  }
};

const getPromptAreaFromTagsContainer = tagsContainerEl => PP.getAnyPromptArea(tagsContainerEl.parent());

const getPalette = paletteName => {
  // Add this check for 'CUSTOM'
  if (paletteName === TAGS_PALETTES.CUSTOM) {
    // Use tagPaletteCustom from config or default if not found
    return loadConfigOrDefault()?.tagPaletteCustom ?? defaultConfig.tagPaletteCustom;
  }
  // Fallback to predefined palettes or CLASSIC as default
  const palette = TAGS_PALETTES[paletteName];
  // Check if palette is an array before returning, otherwise return default
  return Array.isArray(palette) ? palette : TAGS_PALETTES.CLASSIC;
};

const createTag = containerEl => isPreview => tag => {
  if (tag.hide) return null;

  const labelString = tag.label ?? tag.text;
  const isTagLight = color2k.getLuminance(tag.color) > loadConfig().tagLuminanceThreshold;
  const colorMod = isTagLight ? color2k.darken : color2k.lighten;
  const hoverBgColor = color2k.toRgba(colorMod(tag.color, 0.1));
  const borderColor = color2k.toRgba(colorMod(tag.color, loadConfig().tagTweakRichBorderColor ? 0.2 : 0.1));

  const clickHandler = evt => {
    debugLog('clicked', tag, evt);
    if (tag.link) return;
    if (tag.setMode) {
      switch (tag.setMode) {
        case 'pro':
          PP.getModeProButton().click();
          break;
        case 'research':
        case 'deep-research':
        case 'dr':
          PP.getModeResearchButton().click();
          break;
        default:
          throw new Error(`Invalid set-mode: ${tag.setMode}`);
      }
    }
    if (tag.setModel) {
      setTimeout(() => { // delay for model button to be available after setting mode
        const modelDescriptor = PP.getModelDescriptorFromId(tag.setModel);
        debugLog('[createTag] clickHandler: set model=', tag.setModel, ' modelDescriptor=', modelDescriptor);
        PP.doSelectModel(modelDescriptor.index);
      }, 50);
    }
    if (tag.setSources) {
      setTimeout(() => {
        PP.getAnySourcesButton().click();
        setTimeout(() => {
          PP.setSourcesSelectionListValues()(tag.setSources, {
            callback: () => {
              debugLogTags('[createTag] clickHandler: setSources callback');
              setTimeout(() => {
                PP.getAnySourcesButton().click();
              }, 5);
            },
          });
          debugLogTags('[createTag] clickHandler: setSources=', tag.setSources);
        }, 10);
      }, 80);
    }
    if (tag.autoSubmit) {
      setTimeout(() => {
        const submitButton = PP.submitButtonAny();
        debugLogTags('[createTag] clickHandler: submitButton=', submitButton);
        if (submitButton.length) {
          if (submitButton.length > 1) {
            debugLogTags('[createTag] clickHandler: multiple submit buttons found, using first one');
          }
          submitButton.first().click();
        } else {
          debugLogTags('[createTag] clickHandler: no submit button found');
        }
      }, 300);
    }
    const el = jq(evt.currentTarget);
    const tagsContainer = el.closest(`.${tagsContainerCls}`);
    if (!tagsContainer.length) {
      debugLogTags('[clickHandler] no tags container found');
      return;
    }
    const promptArea = getPromptAreaFromTagsContainer(tagsContainer);
    if (!promptArea.length) {
      debugLogTags('[clickHandler] no prompt area found', promptArea);
      return;
    }
    const promptAreaRaw = promptArea[0];
    const newText = applyTagToString(tag, promptArea.val(), promptAreaRaw.selectionStart);
    changeValueUsingEvent(promptAreaRaw, newText);
    promptAreaRaw.focus();
  };

  const tagFont = loadConfig().tagFont;

  const defaultTooltip = tag.link ? `${logPrefix} Open link: ${tag.link}` : `${logPrefix} Insert \`${tag.text}\` at position \`${posFromTag(tag)}\``;
  const tagEl = jq(`<div/>`)
    .addClass(tagCls)
    .prop('title', tag.tooltip ?? defaultTooltip)
    .attr('data-tag', JSON.stringify(tag))
    .css({
      backgroundColor: tag.color,
      borderColor,
      fontFamily: tagFont,
      borderRadius: `${loadConfig().tagRoundness}px`,
    })
    .attr('data-color', color2k.toHex(tag.color))
    .attr('data-hoverBgColor', color2k.toHex(hoverBgColor))
    .attr('data-font', tagFont)
    .on('mouseenter', event => {
      jq(event.currentTarget).css('background-color', hoverBgColor);
    })
    .on('mouseleave', event => {
      jq(event.currentTarget).css('background-color', tag.color);
    });

  if (isTagLight) {
    tagEl.addClass(tagDarkTextCls);
  }

  if (loadConfig()?.tagTweakNoBorder) {
    tagEl.addClass(tagTweakNoBorderCls);
  }
  if (loadConfig()?.tagTweakSlimPadding) {
    tagEl.addClass(tagTweakSlimPaddingCls);
  }
  if (loadConfig()?.tagTweakTextShadow) {
    tagEl.addClass(tagTweakTextShadowCls);
  }

  const textEl = jq('<span/>')
    .text(labelString)
    .css({
      'font-weight': loadConfig().tagBold ? 'bold' : 'normal',
      'font-style': loadConfig().tagItalic ? 'italic' : 'normal',
      'font-size': `${loadConfig().tagFontSize}px`,
      'transform': `translateY(${loadConfig().tagTextYOffset}px)`,
    });

  if (tag.icon) {
    const iconEl = jq('<img/>')
      .attr('src', getIconUrl(tag.icon))
      .addClass(tagIconCls)
      .css({
        'width': `${loadConfig().tagIconSize}px`,
        'height': `${loadConfig().tagIconSize}px`,
        'transform': `translateY(${loadConfig().tagIconYOffset}px)`,
      });
    if (!labelString) {
      iconEl.css({
        marginLeft: '0',
        marginRight: '0',
      });
    }
    textEl.prepend(iconEl);
  }

  tagEl.append(textEl);

  if (tag.link) {
    const linkEl = jq('<a/>')
      .attr('href', tag.link)
      .attr('target', tag.linkTarget)
      .css({
        textDecoration: 'none',
        color: 'inherit'
      });
    textEl.wrap(linkEl);
  }

  if (!isPreview && !tag.link && !tag.dir) {
    tagEl.click(clickHandler);
  }
  containerEl.append(tagEl);

  return tagEl;
};

const genDebugFakeTags = () =>
  _.times(TAGS_PALETTE_COLORS_NUM, x => `Fake ${x} ${_.times(x / 3).map(() => 'x').join('')}<color:%${x % TAGS_PALETTE_COLORS_NUM}>`)
    .join('\n');

const getTagContainerType = containerEl => {
  if (containerEl.hasClass(threadTagContainerCls) || containerEl.hasClass(tagsPreviewThreadCls)) return TAG_CONTAINER_TYPE.THREAD;
  if (containerEl.hasClass(newTagContainerCls) || containerEl.hasClass(tagsPreviewNewCls)) return TAG_CONTAINER_TYPE.NEW;
  if (containerEl.hasClass(newTagContainerInCollectionCls) || containerEl.hasClass(tagsPreviewNewInCollectionCls)) return TAG_CONTAINER_TYPE.NEW_IN_COLLECTION;
  return null;
};

const getPromptWrapperTagContainerType = promptWrapper => {
  if (PP.getPromptAreaOfNewThread(promptWrapper).length) return TAG_CONTAINER_TYPE.NEW;
  if (PP.getPromptAreaOnThread(promptWrapper).length) return TAG_CONTAINER_TYPE.THREAD;
  if (PP.getPromptAreaOnCollection(promptWrapper).length) return TAG_CONTAINER_TYPE.NEW_IN_COLLECTION;
  return null;
};

const isTagRelevantForContainer = containerType => tag =>
  containerType === tag.target
  || (containerType === TAG_CONTAINER_TYPE.NEW_IN_COLLECTION && tag.target === TAG_CONTAINER_TYPE.NEW)
  || tag.target === TAG_CONTAINER_TYPE.ALL;

const tagContainerTypeToTagContainerClass = {
  [TAG_CONTAINER_TYPE.THREAD]: threadTagContainerCls,
  [TAG_CONTAINER_TYPE.NEW]: newTagContainerCls,
  [TAG_CONTAINER_TYPE.NEW_IN_COLLECTION]: newTagContainerInCollectionCls,
};

const currentUrlIsSettingsPage = () => window.location.pathname.includes('/settings/');

const refreshTags = ({ force = false } = {}) => {
  if (!loadConfigOrDefault()?.tagsEnabled) return;
  const promptWrapper = PP.getPromptAreaWrapperOfNewThread()
    .add(PP.getPromptAreaWrapperOnThread())
    .add(PP.getPromptAreaWrapperOnCollection().nthParent(2))
    .filter((_, rEl) => {
      const isPreview = Boolean(jq(rEl).attr('data-preview'));
      return isPreview || !currentUrlIsSettingsPage();
    });
  if (!promptWrapper.length) {
    debugLogTags('no prompt area found');
  }
  // debugLogTags('promptWrappers', promptWrapper);
  const allTags = _.flow(
    x => x + (unsafeWindow.phFakeTags ? `${nl}${genDebugFakeTags()}${nl}` : ''),
    parseTagsText,
  )(loadConfig()?.tagsText ?? defaultConfig.tagsText);
  debugLogTags('refreshing allTags', allTags);

  const createContainer = (promptWrapper) => {
    const el = jq(`<div/>`).addClass(tagsContainerCls);
    const tagContainerType = getPromptWrapperTagContainerType(promptWrapper);
    if (tagContainerType) {
      const clsToAdd = tagContainerTypeToTagContainerClass[tagContainerType];
      if (!clsToAdd) {
        console.error('Unexpected tagContainerType:', tagContainerType, { promptWrapper });
      }
      el.addClass(clsToAdd);
    }
    return el;
  };
  promptWrapper.each((_, rEl) => {
    const el = jq(rEl);
    if (el.parent().find(`.${tagsContainerCls}`).length) {
      el.parent().addClass(queryBoxCls);
      return;
    }
    el.before(createContainer(el));
  });

  const currentPalette = getPalette(loadConfig().tagPalette);

  const createFence = (fence) => {
    const fenceEl = jq('<div/>')
      .addClass(tagFenceCls)
      .css({
        'border-style': fence.fenceBorderStyle ?? 'solid',
        'border-color': fence.fenceBorderColor?.startsWith('%')
          ? convertColorInPaletteFormat(currentPalette)(fence.fenceBorderColor)
          : fence.fenceBorderColor ?? defaultTagColor,
        'border-width': fence.fenceBorderWidth ?? '1px',
      })
      .attr('data-tag', JSON.stringify(fence))
      ;
    const fenceContentEl = jq('<div/>')
      .addClass(tagFenceContentCls)
      .css({
        'width': fence.fenceWidth ?? '',
      })
      ;
    fenceEl.append(fenceContentEl);
    return { fenceEl, fenceContentEl };
  };

  const createDirectory = () => {
    const directoryEl = jq('<div/>').addClass(tagDirectoryCls);
    const directoryContentEl = jq('<div/>').addClass(tagDirectoryContentCls);
    directoryEl.append(directoryContentEl);
    return { directoryEl, directoryContentEl };
  };

  const containerEls = getTagsContainer();
  containerEls.each((_i, rEl) => {
    const containerEl = jq(rEl);
    const isPreview = Boolean(containerEl.attr('data-preview'));

    const tagContainerTypeFromPromptWrapper = getPromptWrapperTagContainerType(containerEl.nthParent(2));
    const prelimTagContainerType = getTagContainerType(containerEl);
    if (tagContainerTypeFromPromptWrapper !== prelimTagContainerType && !isPreview) {
      debugLog('tagContainerTypeFromPromptWrapper !== prelimTagContainerType', { tagContainerTypeFromPromptWrapper, prelimTagContainerType, containerEl, isPreview });
      containerEl
        .empty()
        .removeClass(threadTagContainerCls, newTagContainerCls, newTagContainerInCollectionCls)
        .addClass(tagContainerTypeToTagContainerClass[tagContainerTypeFromPromptWrapper])
        ;
    } else {
      if (!isPreview) {
        debugLogTags('tagContainerTypeFromPromptWrapper === prelimTagContainerType', { tagContainerTypeFromPromptWrapper, prelimTagContainerType, containerEl, isPreview });
      }
    }

    // TODO: use something else than lodash/fp. in following functions it behaved randomly very weirdly 
    // e.g. partial application of map resulting in an empty array or sortBy sorting field name instead 
    // of input array. possibly inconsistent normal FP order of arguments
    const mapParseAttrTag = xs => xs.map(el => JSON.parse(el.dataset.tag));
    const sortByOriginalIndex = xs => [...xs].sort((a, b) => a.originalIndex - b.originalIndex);
    const tagElsInCurrentContainer = containerEl.find(`.${tagCls}, .${tagFenceCls}`).toArray();
    const filterOutHidden = filter(x => !x.hide);
    const currentTags = _.flow(
      mapParseAttrTag,
      sortByOriginalIndex,
      filterOutHidden,
      _.uniq,
    )(tagElsInCurrentContainer);
    const tagContainerType = getTagContainerType(containerEl);
    const tagsForThisContainer = _.flow(
      filter(isTagRelevantForContainer(tagContainerType)),
      filterOutHidden,
      sortByOriginalIndex,
    )(allTags);
    debugLogTags('tagContainerType =', tagContainerType, ', current tags =', currentTags, ', tagsForThisContainer =', tagsForThisContainer, ', tagElsInCurrentContainer =', tagElsInCurrentContainer);
    if (_.isEqual(currentTags, tagsForThisContainer) && !force) {
      debugLogTags('no tags changed');
      return;
    }
    const diff = jsondiffpatch.diff(currentTags, tagsForThisContainer);
    const changedTags = jsondiffpatch.formatters.console.format(diff);
    debugLogTags('changedTags', changedTags);
    containerEl.empty();
    const tagHomePageLayout = loadConfig()?.tagHomePageLayout;
    if (!isPreview) {
      if ((tagContainerType === TAG_CONTAINER_TYPE.NEW || tagContainerType === TAG_CONTAINER_TYPE.NEW_IN_COLLECTION)) {
        if (tagContainerType === TAG_CONTAINER_TYPE.NEW_IN_COLLECTION) {
          // only compact layout is supported for new in collection
          if (tagHomePageLayout === TAG_HOME_PAGE_LAYOUT.COMPACT) {
            containerEl.addClass(tagContainerCompactCls);
          }
        } else if (tagHomePageLayout === TAG_HOME_PAGE_LAYOUT.COMPACT) {
          containerEl.addClass(tagContainerCompactCls);
        } else if (tagHomePageLayout === TAG_HOME_PAGE_LAYOUT.WIDER) {
          containerEl.addClass(tagContainerWiderCls);
        } else if (tagHomePageLayout === TAG_HOME_PAGE_LAYOUT.WIDE) {
          containerEl.addClass(tagContainerWideCls);
        } else if (tagHomePageLayout === TAG_HOME_PAGE_LAYOUT.EXTRA_WIDE) {
          containerEl.addClass(tagContainerExtraWideCls);
        } else {
          containerEl.removeClass(`${tagContainerCompactCls} ${tagContainerWiderCls} ${tagContainerWideCls} ${tagContainerExtraWideCls}`);
        }
        const extraMargin = loadConfig()?.tagContainerExtraBottomMargin || 0;
        containerEl.css('margin-bottom', `${extraMargin}em`);
      }
    }

    const fences = {};
    const directories = {};

    const fencesWrapperEl = jq('<div/>').addClass(tagAllFencesWrapperCls);
    const restWrapperEl = jq('<div/>').addClass(tagRestOfTagsWrapperCls);

    tagsForThisContainer.forEach(tag => {
      const { fence, dir, inFence, inDir } = tag;

      const getOrCreateDirectory = dirName => {
        if (!directories[dirName]) directories[dirName] = createDirectory();
        return directories[dirName];
      };

      const getTagContainer = () => {
        if (fence) {
          if (!fences[fence]) fences[fence] = createFence(tag);
          return fences[fence].fenceContentEl;
        } else if (dir && inFence) {
          if (!fences[inFence]) {
            console.error(`fence ${inFence} for tag not found`, tag);
            return null;
          }
          const { directoryEl } = getOrCreateDirectory(dir);
          fences[inFence].fenceContentEl.append(directoryEl);
          return directoryEl;
        } else if (dir) {
          const { directoryEl } = getOrCreateDirectory(dir);
          restWrapperEl.append(directoryEl);
          return directoryEl;
        } else if (inFence) {
          if (!fences[inFence]) {
            console.error(`fence ${inFence} for tag not found`, tag);
            return null;
          }
          return fences[inFence].fenceContentEl;
        } else if (inDir) {
          if (!directories[inDir]) {
            console.error(`directory ${inDir} for tag not found`, tag);
            return null;
          }
          return directories[inDir].directoryContentEl;
        } else {
          return restWrapperEl;
        }
      };

      const tagContainer = getTagContainer();
      if (tagContainer && !fence) {
        createTag(tagContainer)(isPreview)(tag);
      }
    });

    Object.values(fences).forEach(({ fenceEl }) => fencesWrapperEl.append(fenceEl));
    containerEl.append(fencesWrapperEl).append(restWrapperEl);
  });
};

const setupTags = () => {
  debugLog('setting up tags');
  setInterval(refreshTags, 500);
};

const ICON_REPLACEMENT_MODE = Object.freeze({
  OFF: 'Off',
  LUCIDE1: 'Lucide 1',
  LUCIDE2: 'Lucide 2',
  LUCIDE3: 'Lucide 3',
  TDESIGN1: 'TDesign 1',
  TDESIGN2: 'TDesign 2',
  TDESIGN3: 'TDesign 3',
});

const leftPanelIconMappingsToLucide1 = Object.freeze({
  'search': 'search',
  'discover': 'telescope',
  'spaces': 'shapes',
});

const leftPanelIconMappingsToLucide2 = Object.freeze({
  'search': 'house',
  'discover': 'compass',
  'spaces': 'square-stack',
  'library': 'archive',
});

const leftPanelIconMappingsToLucide3 = Object.freeze({
  'search': 'search',
  'discover': 'telescope',
  'spaces': 'bot',
  'library': 'folder-open',
});

const leftPanelIconMappingsToTDesign1 = Object.freeze({
  'search': 'search',
  'discover': 'compass-filled',
  'spaces': 'grid-view',
  'library': 'book',
});

const leftPanelIconMappingsToTDesign2 = Object.freeze({
  'search': 'search',
  'discover': 'shutter-filled',
  'spaces': 'palette-1',
  'library': 'folder-open-1-filled',
});

const leftPanelIconMappingsToTDesign3 = Object.freeze({
  'search': 'search',
  'discover': 'banana-filled',
  'spaces': 'chili-filled',
  'library': 'barbecue-filled',
});

const iconMappings = {
  LUCIDE1: leftPanelIconMappingsToLucide1,
  LUCIDE2: leftPanelIconMappingsToLucide2,
  LUCIDE3: leftPanelIconMappingsToLucide3,
  TDESIGN1: leftPanelIconMappingsToTDesign1,
  TDESIGN2: leftPanelIconMappingsToTDesign2,
  TDESIGN3: leftPanelIconMappingsToTDesign3,
};

const MODEL_LABEL_TEXT_MODE = Object.freeze({
  OFF: 'Off',
  FULL_NAME: 'Full Name',
  SHORT_NAME: 'Short Name',
  PP_MODEL_ID: 'PP Model ID',
  OWN_NAME_VERSION_SHORT: 'Own Name + Version Short',
});

const MODEL_LABEL_STYLE = Object.freeze({
  OFF: 'Off',
  JUST_TEXT: 'Just Text',
  BUTTON_SUBTLE: 'Button Subtle',
  BUTTON_WHITE: 'Button White',
  BUTTON_CYAN: 'Button Cyan',
});

const CUSTOM_MODEL_POPOVER_MODE = Object.freeze({
  OFF: 'Off',
  SIMPLE_LIST: 'Simple List',
  COMPACT_LIST: 'Compact List',
  SIMPLE_GRID: 'Simple 2x Grid',
  COMPACT_GRID: 'Compact 2x Grid',
});

const MODEL_LABEL_ICON_REASONING_MODEL = Object.freeze({
  OFF: 'Off',
  LIGHTBULB: 'Lightbulb',
  BRAIN: 'Brain',
  MICROCHIP: 'Microchip',
});

const defaultConfig = Object.freeze({
  // General
  hideSideMenu: false,
  slimLeftMenu: false,
  hideSideMenuLabels: false,
  hideHomeWidgets: false,
  hideDiscoverButton: false,
  fixImageGenerationOverlay: false,
  extraSpaceBellowLastAnswer: false,
  replaceIconsInMenu: ICON_REPLACEMENT_MODE.OFF,

  // Model
  modelLabelTextMode: MODEL_LABEL_TEXT_MODE.OFF,
  modelLabelStyle: MODEL_LABEL_STYLE.OFF,
  modelLabelOverwriteCyanIconToGray: false,
  modelLabelUseIconForReasoningModels: MODEL_LABEL_ICON_REASONING_MODEL.OFF,
  modelLabelReasoningModelIconGold: false,
  modelLabelRemoveCpuIcon: false,
  customModelPopover: CUSTOM_MODEL_POPOVER_MODE.SIMPLE_GRID,
  
  // Legacy
  showCopilot: true,
  showCopilotNewThread: true,
  showCopilotRepeatLast: true,
  showCopilotCopyPlaceholder: true,
  
  // Tags
  tagsEnabled: true,
  tagsText: '',
  tagPalette: 'CLASSIC',
  tagPaletteCustom: ['#000', '#fff', '#ff0', '#f00', '#0f0', '#00f', '#0ff', '#f0f'],
  tagFont: 'Roboto',
  tagHomePageLayout: TAG_HOME_PAGE_LAYOUT.DEFAULT,
  tagContainerExtraBottomMargin: 0,
  tagLuminanceThreshold: 0.35,
  tagBold: false,
  tagItalic: false,
  tagFontSize: 16,
  tagIconSize: 16,
  tagRoundness: 4,
  tagTextYOffset: 0,
  tagIconYOffset: 0,

  // Raw
  mainCaptionHtml: '',
  customJs: '',
  customCss: '',
  customWidgetsHtml: '',

  // Settings

  // Debug
  debugMode: false,
  debugTagsMode: false,
});

// TODO: if still using local storage, at least it should be prefixed with user script name
const storageKey = 'checkBoxStates';

const loadConfig = () => {
  try {
    // TODO: use storage from GM API
    const val = JSON.parse(localStorage.getItem(storageKey));
    // debugLog('loaded config', val);
    return val;
  } catch (e) {
    console.error('Failed to load config, using default', e);
    return defaultConfig;
  }
};

const loadConfigOrDefault = () => loadConfig() ?? defaultConfig;

const saveConfig = cfg => {
  debugLog('saving config', cfg);
  localStorage.setItem(storageKey, JSON.stringify(cfg));
};

const createCheckbox = (id, labelText, onChange) => {
  debugLog("createCheckbox", id);
  const checkbox = jq(`<input type="checkbox" id=${id}>`);
  const label = jq(`<label class="checkbox_label" for="${id}">${labelText}</label>`);
  const checkboxWithLabel = jq('<div class="checkbox_wrapper"></div>').append(checkbox).append(' ').append(label);
  debugLog('checkboxwithlabel', checkboxWithLabel);

  getSettingsLastTabGroupContent().append(checkboxWithLabel);
  checkbox.on('change', onChange);
  return checkbox;
};

const createTextArea = (id, labelText, onChange, helpText, links) => {
  debugLog("createTextArea", id);
  const textarea = jq(`<textarea id=${id}></textarea>`);
  const bookIconHtml = `<img src="${getLucideIconUrl('book-text')}" class="w-4 h-4 invert inline-block"/>`;
  const labelTextHtml = `<span class="opacity-100">${labelText}</span>`;
  const label = jq(`<label class="textarea_label">${labelTextHtml}${helpText ? ' ' + bookIconHtml : ''}</label>`);
  const labelWithLinks = jq('<div/>').addClass('flex flex-row gap-2 mb-2').append(label);
  const textareaWrapper = jq('<div class="textarea_wrapper"></div>').append(labelWithLinks);
  if (links) {
    links.forEach(({ icon, label, url, tooltip }) => {
      const iconHtml = `<img src="${getIconUrl(icon)}" class="w-4 h-4 invert opacity-50 hover:opacity-100 transition-opacity duration-300 ease-in-out"/>`;
      const link = jq(`<a href="${url}" target="_blank" class="flex flex-row gap-2 items-center">${icon ? iconHtml : ''}${label ? ' ' + label : ''}</a>`);
      link.attr('title', tooltip);
      labelWithLinks.append(link);
    });
  }
  if (helpText) {
    const help = jq(`<div/>`).addClass(helpTextCls).html(markdownConverter.makeHtml(helpText)).append(jq('<br/>'));
    help.find('a').each((_, a) => jq(a).attr('target', '_blank'));
    help.append(jq('<button/>').text('[Close help]').on('click', () => help.hide()));
    textareaWrapper.append(help);
    label
      .css({ cursor: 'pointer' })
      .on('click', () => help.toggle())
      .prop('title', 'Click to toggle help')
      ;
    help.hide();
  }
  textareaWrapper.append(textarea);
  debugLog('textareaWithLabel', textareaWrapper);

  getSettingsLastTabGroupContent().append(textareaWrapper);
  textarea.on('change', onChange);
  return textarea;
};

const createSelect = (id, labelText, options, onChange) => {
  const select = jq(`<select id=${id}>`);
  options.forEach(({ value, label }) => {
    jq('<option>').val(value).text(label).appendTo(select);
  });
  const label = jq(`<label class="select_label">${labelText}</label>`);
  const selectWithLabel = jq('<div class="select_wrapper"></div>').append(select).append(label);
  debugLog('selectWithLabel', selectWithLabel);

  getSettingsLastTabGroupContent().append(selectWithLabel);
  select.on('change', onChange);
  return select;
};

const createPaletteLegend = paletteName => {
  const wrapper = jq('<div/>')
    .addClass(tagPaletteCls)
    .append(jq('<span>').html('Palette of color codes:&nbsp;'))
    ;
  const palette = getPalette(paletteName);
  palette.forEach((color, i) => {
    const colorCode = `%${i}`;
    const colorPart = genColorPart(colorCode);
    // console.log('createPaletteLegend', {i, colorCode, colorPart, color});
    jq('<span/>')
      .text(colorCode)
      .addClass(tagPaletteItemCls)
      .css({
        'background-color': color,
      })
      .prop('title', `Copy ${colorPart} to clipboard`)
      .click(() => {
        copyTextToClipboard(colorPart);
      })
      .appendTo(wrapper);
  });
  return wrapper;
};

const createColorInput = (id, labelText, onChange) => {
  debugLog("createColorInput", id);
  const input = jq(`<input type="color" id=${id}>`);
  const label = jq(`<label class="color_label">${labelText}</label>`);
  const inputWithLabel = jq('<div class="color_wrapper"></div>').append(input).append(label);
  debugLog('inputWithLabel', inputWithLabel);

  getSettingsLastTabGroupContent().append(inputWithLabel);
  input.on('change', onChange);
  return input;
};

const createNumberInput = (id, labelText, onChange, { step = 1, min = 0, max = 100 } = {}) => {
  debugLog("createNumberInput", id);
  const input = jq(`<input type="number" id=${id}>`)
    .prop('step', step)
    .prop('min', min)
    .prop('max', max)
    ;
  const label = jq(`<label class="number_label">${labelText}</label>`);
  const inputWithLabel = jq('<div class="number_wrapper"></div>').append(input).append(label);
  debugLog('inputWithLabel', inputWithLabel);

  getSettingsLastTabGroupContent().append(inputWithLabel);
  input.on('change', onChange);
  return input;
};

const createTagsPreview = () => {
  const wrapper = jq('<div/>')
    .addClass(tagsPreviewCls)
    .append(jq('<div>').text('Preview').addClass('text-lg font-bold'))
    .append(jq('<div>').text('Target New:'))
    .append(jq('<div>').addClass(tagsPreviewNewCls).addClass(tagsContainerCls).attr('data-preview', 'true'))
    .append(jq('<div>').text('Target Thread:'))
    .append(jq('<div>').addClass(tagsPreviewThreadCls).addClass(tagsContainerCls).attr('data-preview', 'true'))
    ;
  getSettingsLastTabGroupContent().append(wrapper);
};

const coPilotNewThreadAutoSubmitCheckboxId = 'coPilotNewThreadAutoSubmit';
const getCoPilotNewThreadAutoSubmitCheckbox = () => $i(coPilotNewThreadAutoSubmitCheckboxId);

const coPilotRepeatLastAutoSubmitCheckboxId = 'coPilotRepeatLastAutoSubmit';
const getCoPilotRepeatLastAutoSubmitCheckbox = () => $i(coPilotRepeatLastAutoSubmitCheckboxId);

const hideSideMenuCheckboxId = 'hideSideMenu';
const getHideSideMenuCheckbox = () => $i(hideSideMenuCheckboxId);

const tagsEnabledId = genCssName('tagsEnabled');
const getTagsEnabledCheckbox = () => $i(tagsEnabledId);

const tagsTextAreaId = 'tagsText';
const getTagsTextArea = () => $i(tagsTextAreaId);

const tagColorPickerId = genCssName('tagColorPicker');
const getTagColorPicker = () => $i(tagColorPickerId);

const enableDebugCheckboxId = genCssName('enableDebug');
const getEnableDebugCheckbox = () => $i(enableDebugCheckboxId);

const enableTagsDebugCheckboxId = genCssName('enableTagsDebug');
const getEnableTagsDebugCheckbox = () => $i(enableTagsDebugCheckboxId);

const tagPaletteSelectId = genCssName('tagPaletteSelect');
const getTagPaletteSelect = () => $i(tagPaletteSelectId);

const tagFontSelectId = genCssName('tagFontSelect');
const getTagFontSelect = () => $i(tagFontSelectId);

const tagTweakNoBorderCheckboxId = genCssName('tagTweakNoBorder');
const getTagTweakNoBorderCheckbox = () => $i(tagTweakNoBorderCheckboxId);

const tagTweakSlimPaddingCheckboxId = genCssName('tagTweakSlimPadding');
const getTagTweakSlimPaddingCheckbox = () => $i(tagTweakSlimPaddingCheckboxId);

const tagTweakRichBorderColorCheckboxId = genCssName('tagTweakRichBorderColor');
const getTagTweakRichBorderColorCheckbox = () => $i(tagTweakRichBorderColorCheckboxId);

const tagTweakTextShadowCheckboxId = genCssName('tagTweakTextShadow');
const getTagTweakTextShadowCheckbox = () => $i(tagTweakTextShadowCheckboxId);

const tagHomePageLayoutSelectId = genCssName('tagHomePageLayout');
const getTagHomePageLayoutSelect = () => $i(tagHomePageLayoutSelectId);

const tagContainerExtraBottomMarginInputId = genCssName('tagContainerExtraBottomMargin');
const getTagContainerExtraBottomMarginInput = () => $i(tagContainerExtraBottomMarginInputId);

const tagLuminanceThresholdInputId = genCssName('tagLuminanceThreshold');
const getTagLuminanceThresholdInput = () => $i(tagLuminanceThresholdInputId);

const tagBoldCheckboxId = genCssName('tagBold');
const getTagBoldCheckbox = () => $i(tagBoldCheckboxId);

const tagItalicCheckboxId = genCssName('tagItalic');
const getTagItalicCheckbox = () => $i(tagItalicCheckboxId);

const tagFontSizeInputId = genCssName('tagFontSize');
const getTagFontSizeInput = () => $i(tagFontSizeInputId);

const tagIconSizeInputId = genCssName('tagIconSize');
const getTagIconSizeInput = () => $i(tagIconSizeInputId);

const tagRoundnessInputId = genCssName('tagRoundness');
const getTagRoundnessInput = () => $i(tagRoundnessInputId);

const tagTextYOffsetInputId = genCssName('tagTextYOffset');
const getTagTextYOffsetInput = () => $i(tagTextYOffsetInputId);

const tagIconYOffsetInputId = genCssName('tagIconYOffset');
const getTagIconYOffsetInput = () => $i(tagIconYOffsetInputId);

const tagPaletteCustomTextAreaId = genCssName('tagPaletteCustomTextArea');
const getTagPaletteCustomTextArea = () => $i(tagPaletteCustomTextAreaId);

const replaceIconsInMenuId = genCssName('replaceIconsInMenu');
const getReplaceIconsInMenu = () => $i(replaceIconsInMenuId);

const slimLeftMenuCheckboxId = genCssName('slimLeftMenu');
const getSlimLeftMenuCheckbox = () => $i(slimLeftMenuCheckboxId);

const hideHomeWidgetsCheckboxId = genCssName('hideHomeWidgets');
const getHideHomeWidgetsCheckbox = () => $i(hideHomeWidgetsCheckboxId);

const hideDiscoverButtonCheckboxId = genCssName('hideDiscoverButton');
const getHideDiscoverButtonCheckbox = () => $i(hideDiscoverButtonCheckboxId);

const fixImageGenerationOverlayCheckboxId = genCssName('fixImageGenerationOverlay');
const getFixImageGenerationOverlayCheckbox = () => $i(fixImageGenerationOverlayCheckboxId);

const extraSpaceBellowLastAnswerCheckboxId = genCssName('extraSpaceBellowLastAnswer');
const getExtraSpaceBellowLastAnswerCheckbox = () => $i(extraSpaceBellowLastAnswerCheckboxId);

const modelLabelTextModeSelectId = genCssName('modelLabelTextModeSelect');
const getModelLabelTextModeSelect = () => $i(modelLabelTextModeSelectId);

const modelLabelStyleSelectId = genCssName('modelLabelStyleSelect');
const getModelLabelStyleSelect = () => $i(modelLabelStyleSelectId);

const modelLabelOverwriteCyanIconToGrayCheckboxId = genCssName('modelLabelOverwriteCyanIconToGray');
const getModelLabelOverwriteCyanIconToGrayCheckbox = () => $i(modelLabelOverwriteCyanIconToGrayCheckboxId);

const modelLabelUseIconForReasoningModelsSelectId = genCssName('modelLabelUseIconForReasoningModelsSelect');
const getModelLabelUseIconForReasoningModelsSelect = () => $i(modelLabelUseIconForReasoningModelsSelectId);

const modelLabelReasoningModelIconGoldCheckboxId = genCssName('modelLabelReasoningModelIconGold');
const getModelLabelReasoningModelIconGoldCheckbox = () => $i(modelLabelReasoningModelIconGoldCheckboxId);

const modelLabelRemoveCpuIconCheckboxId = genCssName('modelLabelRemoveCpuIcon');
const getModelLabelRemoveCpuIconCheckbox = () => $i(modelLabelRemoveCpuIconCheckboxId);

const customModelPopoverSelectId = genCssName('customModelPopoverSelect');
const getCustomModelPopoverSelect = () => $i(customModelPopoverSelectId);

const mainCaptionHtmlTextAreaId = genCssName('mainCaptionHtmlTextArea');
const getMainCaptionHtmlTextArea = () => $i(mainCaptionHtmlTextAreaId);

const customJsTextAreaId = genCssName('customJsTextArea');
const getCustomJsTextArea = () => $i(customJsTextAreaId);

const customCssTextAreaId = genCssName('customCssTextArea');
const getCustomCssTextArea = () => $i(customCssTextAreaId);

const customWidgetsHtmlTextAreaId = genCssName('customWidgetsHtmlTextArea');
const getCustomWidgetsHtmlTextArea = () => $i(customWidgetsHtmlTextAreaId);

const hideSideMenuLabelsId = genCssName('hideSideMenuLabels');
const getHideSideMenuLabels = () => $i(hideSideMenuLabelsId);

const copyTextToClipboard = async text => {
  try {
    await navigator.clipboard.writeText(text);
    console.log('Text copied to clipboard', { text });
  } catch (err) {
    console.error('Failed to copy text: ', err);
  }
};

const genColorPart = color => `<color:${color}>`;

const loadCurrentConfigToSettingsForm = () => {
  const savedStatesRaw = JSON.parse(localStorage.getItem(storageKey));
  if (savedStatesRaw === null) { return; }
  const savedStates = { ...defaultConfig, ...savedStatesRaw };

  getCoPilotNewThreadAutoSubmitCheckbox().prop('checked', savedStates.coPilotNewThreadAutoSubmit);
  getCoPilotRepeatLastAutoSubmitCheckbox().prop('checked', savedStates.coPilotRepeatLastAutoSubmit);
  getHideSideMenuCheckbox().prop('checked', savedStates.hideSideMenu);
  getTagsEnabledCheckbox().prop('checked', savedStates.tagsEnabled);
  getTagsTextArea().val(savedStates.tagsText);
  getEnableDebugCheckbox().prop('checked', savedStates.enableDebug);
  getEnableTagsDebugCheckbox().prop('checked', savedStates.debugTagsMode);
  getTagPaletteSelect().val(savedStates.tagPalette);
  getTagFontSelect().val(savedStates.tagFont);
  getTagTweakNoBorderCheckbox().prop('checked', savedStates.tagTweakNoBorder);
  getTagTweakSlimPaddingCheckbox().prop('checked', savedStates.tagTweakSlimPadding);
  getTagTweakRichBorderColorCheckbox().prop('checked', savedStates.tagTweakRichBorderColor);
  getTagTweakTextShadowCheckbox().prop('checked', savedStates.tagTweakTextShadow);
  getTagHomePageLayoutSelect().val(savedStates.tagHomePageLayout);
  getTagContainerExtraBottomMarginInput().val(savedStates.tagContainerExtraBottomMargin);
  getTagLuminanceThresholdInput().val(savedStates.tagLuminanceThreshold);
  getTagBoldCheckbox().prop('checked', savedStates.tagBold);
  getTagItalicCheckbox().prop('checked', savedStates.tagItalic);
  getTagFontSizeInput().val(savedStates.tagFontSize);
  getTagIconSizeInput().val(savedStates.tagIconSize);
  getTagTextYOffsetInput().val(savedStates.tagTextYOffset);
  getTagIconYOffsetInput().val(savedStates.tagIconYOffset);
  getTagRoundnessInput().val(savedStates.tagRoundness);
  getReplaceIconsInMenu().val(savedStates.replaceIconsInMenu);
  getSlimLeftMenuCheckbox().prop('checked', savedStates.slimLeftMenu);
  getHideHomeWidgetsCheckbox().prop('checked', savedStates.hideHomeWidgets);
  getHideDiscoverButtonCheckbox().prop('checked', savedStates.hideDiscoverButton);
  getFixImageGenerationOverlayCheckbox().prop('checked', savedStates.fixImageGenerationOverlay);
  getExtraSpaceBellowLastAnswerCheckbox().prop('checked', savedStates.extraSpaceBellowLastAnswer);
  getModelLabelTextModeSelect().val(savedStates.modelLabelTextMode);
  getModelLabelStyleSelect().val(savedStates.modelLabelStyle);
  getModelLabelRemoveCpuIconCheckbox().prop('checked', savedStates.modelLabelRemoveCpuIcon);
  getModelLabelOverwriteCyanIconToGrayCheckbox().prop('checked', savedStates.modelLabelOverwriteCyanIconToGray);
  getModelLabelUseIconForReasoningModelsSelect().val(savedStates.modelLabelUseIconForReasoningModels ?? MODEL_LABEL_ICON_REASONING_MODEL.OFF);
  getModelLabelReasoningModelIconGoldCheckbox().prop('checked', savedStates.modelLabelReasoningModelIconGold);
  getCustomModelPopoverSelect().val(savedStates.customModelPopover ?? CUSTOM_MODEL_POPOVER_MODE.SIMPLE_GRID);
  getTagPaletteCustomTextArea().val((savedStates.tagPaletteCustom || []).join(', '));
  getMainCaptionHtmlTextArea().val(savedStates.mainCaptionHtml);
  getCustomJsTextArea().val(savedStates.customJs);
  getCustomCssTextArea().val(savedStates.customCss);
  getCustomWidgetsHtmlTextArea().val(savedStates.customWidgetsHtml);
  getHideSideMenuLabels().prop('checked', savedStates.hideSideMenuLabels);
};

function handleSettingsInit() {
  const modalExists = getPerplexityHelperModal().length > 0;
  const firstCheckboxExists = getCoPilotNewThreadAutoSubmitCheckbox().length > 0;

  if (!modalExists || firstCheckboxExists) { return; }

  const $tabButtons = $c(modalTabGroupTabsCls).addClass('flex gap-2 items-end');

  const setActiveTab = (tabName) => {
    $c(modalTabGroupTabsCls).find('> button').each((_, tab) => {
      const $tab = jq(tab);
      if ($tab.attr('data-tab') === tabName) {
        $tab.addClass(modalTabGroupActiveCls);
      } else {
        $tab.removeClass(modalTabGroupActiveCls);
      }
    });
    $c(modalTabGroupContentCls).each((_, tab) => {
      const $tab = jq(tab);
      if ($tab.attr('data-tab') === tabName) {
        $tab.show();
      } else {
        $tab.hide();
      }
    });
  };

  const createTabContent = (tabName, tabLabel) => {
    const $tabButton = jq('<button/>').text(tabLabel).attr('data-tab', tabName).on('click', () => setActiveTab(tabName));
    $tabButtons.append($tabButton);
    const $tabContent = jq('<div/>')
      .addClass(modalTabGroupContentCls)
      .attr('data-tab', tabName);
    getSettingsModalContent().append($tabContent);
    return $tabContent;
  };

  const insertSeparator = () => getSettingsLastTabGroupContent().append('<hr/>');

  // -------------------------------------------------------------------------------------------------------------------
  createTabContent('general', 'General');

  createCheckbox(hideSideMenuCheckboxId, 'Hide Side Menu', saveConfigFromForm);
  createCheckbox(slimLeftMenuCheckboxId, 'Slim Left Menu', saveConfigFromForm);
  createCheckbox(hideHomeWidgetsCheckboxId, 'Hide Home Page Widgets', saveConfigFromForm);
  createCheckbox(hideSideMenuLabelsId, 'Hide Side Menu Labels', saveConfigFromForm);
  createCheckbox(hideDiscoverButtonCheckboxId, 'Hide Discover Button', saveConfigFromForm);
  createCheckbox(fixImageGenerationOverlayCheckboxId, 'Fix Image Generation Overlay Position (Experimental; only use if you encounter the submit button in a custom image prompt outside of the viewport)', saveConfigFromForm);
  createCheckbox(extraSpaceBellowLastAnswerCheckboxId, 'Add extra space bellow last answer', saveConfigFromForm);
  createSelect(
    replaceIconsInMenuId,
    'Replace menu icons',
    Object.values(ICON_REPLACEMENT_MODE).map(value => ({ value, label: value })),
    () => {
      saveConfigFromForm();
      replaceIconsInMenu();
    }
  );


  // -------------------------------------------------------------------------------------------------------------------
  createTabContent('model', 'Model');

  createSelect(
    modelLabelStyleSelectId,
    'Model Label Style',
    Object.values(MODEL_LABEL_STYLE).map(value => ({ value, label: value })),
    saveConfigFromForm
  );
  createSelect(
    modelLabelTextModeSelectId,
    'Model Label Text',
    Object.values(MODEL_LABEL_TEXT_MODE).map(value => ({ value, label: value })),
    saveConfigFromForm
  );
  createCheckbox(modelLabelOverwriteCyanIconToGrayCheckboxId, 'Overwrite Model Icon: Cyan -> Gray', saveConfigFromForm);
  createSelect(
    modelLabelUseIconForReasoningModelsSelectId,
    'Use icon for reasoning models',
    Object.values(MODEL_LABEL_ICON_REASONING_MODEL).map(value => ({ value, label: value })),
    saveConfigFromForm
  );
  createCheckbox(modelLabelReasoningModelIconGoldCheckboxId, 'Use gold color for reasoning model icon', saveConfigFromForm);
  createSelect(
    customModelPopoverSelectId,
    'Custom Model Popover (Experimental)',
    Object.values(CUSTOM_MODEL_POPOVER_MODE).map(value => ({ value, label: value })),
    saveConfigFromForm
  );
  createCheckbox(modelLabelRemoveCpuIconCheckboxId, 'Remove CPU icon', saveConfigFromForm);

  // -------------------------------------------------------------------------------------------------------------------
  createTabContent('tags', 'Tags');

  createCheckbox(tagsEnabledId, 'Enable Tags', saveConfigFromForm);

  createTextArea(tagsTextAreaId, 'Tags', saveConfigFromForm, tagsHelpText, [
    { icon: 'l:images', tooltip: 'Lucide Icons', url: 'https://lucide.dev/icons' },
    { icon: 'td:image', tooltip: 'TDesign Icons', url: 'https://tdesign.tencent.com/design/icon-en#header-69' }
  ])
    .prop('rows', 12).css('min-width', '700px').prop('wrap', 'off');

  const paletteLegendContainer = jq('<div/>').attr('id', 'palette-legend-container');
  getSettingsLastTabGroupContent().append(paletteLegendContainer);

  const updatePaletteLegend = () => {
    paletteLegendContainer.empty().append(createPaletteLegend(loadConfig()?.tagPalette));
  };

  updatePaletteLegend();

  createSelect(
    tagPaletteSelectId,
    'Tag color palette',
    Object.keys(TAGS_PALETTES).map(key => ({ value: key, label: key })),
    () => {
      saveConfigFromForm();
      updatePaletteLegend();
      refreshTags();
    }
  );

  createTextArea(
    tagPaletteCustomTextAreaId,
    'Custom Palette Colors (comma-separated):',
    () => {
      saveConfigFromForm();
      // Update legend and tags only if CUSTOM is the selected palette
      if (getTagPaletteSelect().val() === TAGS_PALETTES.CUSTOM) {
        updatePaletteLegend();
        refreshTags();
      }
    }
  ).prop('rows', 2); // Make it a bit smaller than the main tags text area

  createTagsPreview();

  const FONTS = Object.keys(fontUrls);

  createSelect(
    tagFontSelectId,
    'Tag font',
    FONTS.map(font => ({ value: font, label: font })),
    () => {
      saveConfigFromForm();
      loadFont(loadConfigOrDefault().tagFont);
      refreshTags({ force: true });
    }
  );
  createColorInput(tagColorPickerId, 'Custom color - copy field for tag to clipboard', () => {
    const color = getTagColorPicker().val();
    debugLog('color', color);
    copyTextToClipboard(genColorPart(color));
  });
  const saveConfigFromFormAndForceRefreshTags = () => {
    saveConfigFromForm();
    refreshTags({ force: true });
  };

  createCheckbox(tagBoldCheckboxId, 'Bold text', saveConfigFromFormAndForceRefreshTags);
  createCheckbox(tagItalicCheckboxId, 'Italic text', saveConfigFromFormAndForceRefreshTags);

  createNumberInput(
    tagFontSizeInputId,
    'Font size',
    saveConfigFromFormAndForceRefreshTags,
    { min: 4, max: 64 }
  );

  createNumberInput(
    tagIconSizeInputId,
    'Icon size',
    saveConfigFromFormAndForceRefreshTags,
    { min: 4, max: 64 }
  );

  createNumberInput(
    tagRoundnessInputId,
    'Tag Roundness (px)',
    saveConfigFromFormAndForceRefreshTags,
    { min: 0, max: 32 }
  );

  createNumberInput(
    tagTextYOffsetInputId,
    'Text Y offset',
    saveConfigFromFormAndForceRefreshTags,
    { step: 1, min: -50, max: 50 }
  );

  createNumberInput(
    tagIconYOffsetInputId,
    'Icon Y offset',
    saveConfigFromFormAndForceRefreshTags,
    { step: 1, min: -50, max: 50 }
  );

  createCheckbox(tagTweakNoBorderCheckboxId, 'No border', saveConfigFromFormAndForceRefreshTags);
  createCheckbox(tagTweakSlimPaddingCheckboxId, 'Slim padding', saveConfigFromFormAndForceRefreshTags);
  createCheckbox(tagTweakRichBorderColorCheckboxId, 'Rich Border Color', saveConfigFromFormAndForceRefreshTags);
  createCheckbox(tagTweakTextShadowCheckboxId, 'Text shadow', saveConfigFromFormAndForceRefreshTags);
  createNumberInput(
    tagLuminanceThresholdInputId,
    'Tag Luminance Threshold (determines if tag is light or dark)',
    saveConfigFromFormAndForceRefreshTags,
    { step: 0.01, min: 0, max: 1 }
  );
  createSelect(
    tagHomePageLayoutSelectId,
    'Tag container layout on home page (requires page refresh)',
    Object.values(TAG_HOME_PAGE_LAYOUT).map(value => ({ value, label: value })),
    saveConfigFromForm
  );
  createNumberInput(
    tagContainerExtraBottomMarginInputId,
    'Extra bottom margin on home page (em)',
    saveConfigFromFormAndForceRefreshTags,
    { min: 0, max: 10, step: 0.5 }
  );

  const $modelsList = jq('<div/>').text('Model IDs: ');
  const modelIds = PP.modelDescriptors.map(md => md.ppModelId).join(', ');
  $modelsList.append(modelIds);
  getSettingsLastTabGroupContent().append($modelsList);

  // -------------------------------------------------------------------------------------------------------------------
  createTabContent('raw', 'Raw (HTML, CSS, JS)');

  createTextArea(mainCaptionHtmlTextAreaId, 'Main Caption HTML', saveConfigFromForm)
    .prop('rows', 8).css('min-width', '700px');

  createTextArea(customWidgetsHtmlTextAreaId, 'Custom Widgets HTML', saveConfigFromForm)
    .prop('rows', 8).css('min-width', '700px');

  createTextArea(customCssTextAreaId, 'Custom CSS', saveConfigFromForm)
    .prop('rows', 8).css('min-width', '700px');

  createTextArea(customJsTextAreaId, 'Custom JS', saveConfigFromForm)
    .prop('rows', 8).css('min-width', '700px');

  // -------------------------------------------------------------------------------------------------------------------
  createTabContent('settings', 'Settings');

  getSettingsLastTabGroupContent().append(jq('<div/>').text('Settings are stored in your browser\'s local storage. It is recommended to backup your settings via the export button below after every change.'));

  const buttonsContainer = jq('<div/>').addClass('flex gap-2');
  getSettingsLastTabGroupContent().append(buttonsContainer);

  const createExportButton = () => {
    const exportButton = jq('<button>')
      .text('Export Settings')
      .on('click', () => {
        const settings = JSON.stringify(getSavedStates(), null, 2);
        const blob = new Blob([settings], { type: 'application/json' });
        const date = new Date().toISOString().replace(/[:]/g, '-').replace(/T/g, '--').split('.')[0]; // Format: YYYY-MM-DD--HH-MM-SS
        const filename = `perplexity-helper-settings_${date}.json`;
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
      });
    buttonsContainer.append(exportButton);
  };
  createExportButton();

  const createImportButton = () => {
    const importButton = jq('<button>')
      .text('Import Settings')
      .on('click', () => {
        const input = jq('<input type="file" accept=".json">');
        input.on('change', async (event) => {
          const file = event.target.files[0];
          if (file) {
            // this is a dangerous operation, so we need to confirm it
            const confirmOverwrite = confirm('This will overwrite your current settings. Do you want to continue?');
            if (confirmOverwrite) {
              const reader = new FileReader();
              reader.onload = (e) => {
                try {
                  const settings = JSON.parse(e.target.result);
                  saveConfig(settings);
                  loadCurrentConfigToSettingsForm();
                  refreshTags();
                  alert('Settings imported successfully!');
                } catch (error) {
                  console.error('Error importing settings:', error);
                  alert('Error importing settings. Please check the file format.');
                }
              };
              reader.readAsText(file);
            }
          }
        });
        input.trigger('click');
      });
    buttonsContainer.append(importButton);
  };
  createImportButton();

  // -------------------------------------------------------------------------------------------------------------------
  createTabContent('legacy', 'Legacy');

  createCheckbox(coPilotNewThreadAutoSubmitCheckboxId, 'Auto Submit New Thread With CoPilot', saveConfigFromForm);
  createCheckbox(coPilotRepeatLastAutoSubmitCheckboxId, 'Auto Submit Repeat With CoPilot', saveConfigFromForm);
  
  // -------------------------------------------------------------------------------------------------------------------
  createTabContent('debug', 'Debug'); // debug options at the bottom (do NOT add more normal options bellow this!)

  createCheckbox(enableDebugCheckboxId, 'Debug Mode', () => {
    saveConfigFromForm();
    const checked = getEnableDebugCheckbox().prop('checked');
    if (checked) {
      enableDebugMode();
    }
  });

  createCheckbox(enableTagsDebugCheckboxId, 'Debug Tags Mode', () => {
    saveConfigFromForm();
    const checked = getEnableTagsDebugCheckbox().prop('checked');
    if (checked) {
      enableTagsDebugging();
      refreshTags();
    }
  });

  // -------------------------------------------------------------------------------------------------------------------
  setActiveTab('general');
  loadCurrentConfigToSettingsForm();
}

debugLog(jq.fn.jquery);
const getSavedStates = () => JSON.parse(localStorage.getItem(storageKey));

const getModal = () => jq("[data-testid='quick-search-modal'] > div");
const getCopilotToggleButton = textarea => textarea.parent().parent().find('[data-testid="copilot-toggle"]');
const upperControls = () => jq('svg[data-icon="lock"] ~ div:contains("Share")').nthParent(5).closest('.flex.justify-between:not(.grid-cols-3)');

const getControlsArea = () => jq('textarea[placeholder="Ask follow-up"]').parent().parent().children().last();

const getCopilotNewThreadButton = () => jq('#copilot_new_thread');
const getCopilotRepeatLastButton = () => jq('#copilot_repeat_last');
const getSelectAllButton = () => jq('#perplexity_helper_select_all');
const getSelectAllAndSubmitButton = () => jq('#perplexity_helper_select_all_and_submit');
const getCopyPlaceholder = () => jq('#perplexity_helper_copy_placeholder');
const getCopyAndFillInPlaceholder = () => jq('#perplexity_helper_copy_placeholder_and_fill_in');
const getTopSettingsButtonEl = () => $i(topSettingsButtonId);
const getLeftSettingsButtonEl = () => $i(leftSettingsButtonId);
const getSettingsModalContent = () => getPerplexityHelperModal().find(`.modal-content`);
const getSettingsLastTabGroupContent = () => getSettingsModalContent().find(`.${modalTabGroupContentCls}`).last();

const getSubmitBtn0 = () => jq('svg[data-icon="arrow-up"]').last().parent().parent();
const getSubmitBtn1 = () => jq('svg[data-icon="arrow-right"]').last().parent().parent();
const getSubmitBtn2 = () => jq('svg[data-icon="code-fork"]').last().parent().parent();

const isStandardControlsAreaFc = () => !getControlsArea().hasClass('bottom-0');
const getCurrentControlsArea = () => isStandardControlsAreaFc() ? getControlsArea() : getControlsArea().find('.bottom-0');

const getDashedCheckboxButton = () => jq('svg[data-icon="square-dashed"]').parent().parent();
const getStarSVG = () => jq('svg[data-icon="star-christmas"]');
const getSpecifyQuestionBox = () => jq('svg[data-icon="star-christmas"]').parent().parent().parent().last();

const getNumberOfDashedSVGs = () => getSpecifyQuestionBox().find('svg[data-icon="square-dashed"]').length;
const getSpecifyQuestionControlsWrapper = () => getSpecifyQuestionBox().find('button:contains("Continue")').parent();
const getCopiedModal = () => jq('#copied-modal');
const getCopiedModal2 = () => jq('#copied-modal-2');
const getCopyPlaceholderInput = () => getSpecifyQuestionBox().find('textarea');

const getSubmitButton0or2 = () => getSubmitBtn0().length < 1 ? getSubmitBtn2() : getSubmitBtn0();

const questionBoxWithPlaceholderExists = () => getSpecifyQuestionBox().find('textarea')?.attr('placeholder')?.length > 0 ?? false;

// TODO: no longer used? was this for agentic questions?
const selectAllCheckboxes = () => {
  const currentCheckboxes = getDashedCheckboxButton();
  debugLog('checkboxes', currentCheckboxes);

  const removeLastObject = (arr) => {
    if (!_.isEmpty(arr)) {
      debugLog('arr', arr);
      const newArr = _.dropRight(arr, 1);
      debugLog("newArr", newArr);
      getDashedCheckboxButton().last().click();

      return setTimeout(() => {
        removeLastObject(newArr);
      }, 1);

    }
  };

  removeLastObject(currentCheckboxes);
};

const isCopilotOn = (el) => el.hasClass('text-super');

const toggleBtnDot = (btnDot, value) => {
  debugLog(' toggleBtnDot btnDot', btnDot);

  const btnDotInner = btnDot.find('.rounded-full');

  debugLog('btnDotInner', btnDotInner);

  if (!btnDotInner.hasClass('bg-super') && value === true) {
    btnDot.click();
  }
};

const checkForCopilotToggleState = (timer, checkCondition, submitWhenTrue, submitButtonVersion) => {
  debugLog("checkForCopilotToggleState run", timer, checkCondition(), submitWhenTrue, submitButtonVersion);
  if (checkCondition()) {
    clearInterval(timer);
    debugLog("checkForCopilotToggleState condition met, interval cleared");
    const submitBtn = submitButtonVersion === 0 ? getSubmitButton0or2() : getSubmitBtn1();

    debugLog('submitBtn', submitBtn);
    if (submitWhenTrue) {
      submitBtn.click();
    }
  }
};

const openNewThreadModal = (lastQuery) => {
  debugLog('openNewThreadModal', lastQuery);

  const newThreadText = jq(".sticky div").filter(function () {
    return /^New Thread$/i.test(jq(this).text());
  });
  if (!newThreadText.length) {
    debugLog('newThreadText.length should be 1', newThreadText.length);
    return;
  }
  debugLog('newThreadText', newThreadText);

  newThreadText.click();
  setTimeout(() => {
    debugLog('newThreadText.click()');
    const modal = getModal();

    if (modal.length > 0) {
      const textArea = modal.find('textarea');
      if (textArea.length !== 1) debugLog('textArea.length should be 1', textArea.length);

      const newTextArea = textArea.last();
      const textareaElement = newTextArea[0];
      debugLog('textareaElement', textareaElement);
      changeValueUsingEvent(textareaElement, lastQuery);

      const copilotButton = getCopilotToggleButton(newTextArea);

      toggleBtnDot(copilotButton, true);
      const isCopilotOnBtn = () => isCopilotOn(copilotButton);

      const coPilotNewThreadAutoSubmit =
        getSavedStates()
          ? getSavedStates().coPilotNewThreadAutoSubmit
          : getCoPilotNewThreadAutoSubmitCheckbox().prop('checked');

      const copilotCheck = () => {
        const ctx = { timer: null };
        ctx.timer = setInterval(() => checkForCopilotToggleState(ctx.timer, isCopilotOnBtn, coPilotNewThreadAutoSubmit, 1), 500);
      };

      copilotCheck();
    } else {
      debugLog('else of modal.length > 0');
    }
  },
    2000);
};

const getLastQuery = () => {
  // wrapper around prompt + response
  const lastQueryBox = jq('svg[data-icon="repeat"]').last().nthParent(7);
  if (lastQueryBox.length === 0) {
    debugLog('lastQueryBox not found');
  }

  const wasCopilotUsed = lastQueryBox.find('svg[data-icon="star-christmas"]').length > 0;
  const lastQueryBoxText = lastQueryBox.find('.whitespace-pre-line').text();

  debugLog('[getLastQuery]', { lastQueryBox, wasCopilotUsed, lastQueryBoxText });
  return lastQueryBoxText ?? null;
};

const saveConfigFromForm = () => {
  const checkBoxStates = {
    coPilotNewThreadAutoSubmit: getCoPilotNewThreadAutoSubmitCheckbox().prop('checked'),
    coPilotRepeatLastAutoSubmit: getCoPilotRepeatLastAutoSubmitCheckbox().prop('checked'),
    hideSideMenu: getHideSideMenuCheckbox().prop('checked'),
    tagsText: getTagsTextArea().val(),
    tagsEnabled: getTagsEnabledCheckbox().prop('checked'),
    enableDebug: getEnableDebugCheckbox().prop('checked'),
    debugTagsMode: getEnableTagsDebugCheckbox().prop('checked'),
    tagPalette: getTagPaletteSelect().val(),
    tagFont: getTagFontSelect().val(),
    tagTweakNoBorder: getTagTweakNoBorderCheckbox().prop('checked'),
    tagTweakSlimPadding: getTagTweakSlimPaddingCheckbox().prop('checked'),
    tagTweakRichBorderColor: getTagTweakRichBorderColorCheckbox().prop('checked'),
    tagTweakTextShadow: getTagTweakTextShadowCheckbox().prop('checked'),
    tagHomePageLayout: getTagHomePageLayoutSelect().val(),
    tagContainerExtraBottomMargin: parseFloat(getTagContainerExtraBottomMarginInput().val()) || 0,
    tagLuminanceThreshold: parseFloat(getTagLuminanceThresholdInput().val()),
    tagBold: getTagBoldCheckbox().prop('checked'),
    tagItalic: getTagItalicCheckbox().prop('checked'),
    tagFontSize: parseInt(getTagFontSizeInput().val()),
    tagIconSize: parseInt(getTagIconSizeInput().val()),
    tagRoundness: parseInt(getTagRoundnessInput().val()),
    tagTextYOffset: parseInt(getTagTextYOffsetInput().val()),
    tagIconYOffset: parseInt(getTagIconYOffsetInput().val()),
    replaceIconsInMenu: getReplaceIconsInMenu().val(),
    slimLeftMenu: getSlimLeftMenuCheckbox().prop('checked'),
    hideSideMenuLabels: getHideSideMenuLabels().prop('checked'),
    hideHomeWidgets: getHideHomeWidgetsCheckbox().prop('checked'),
    hideDiscoverButton: getHideDiscoverButtonCheckbox().prop('checked'),
    fixImageGenerationOverlay: getFixImageGenerationOverlayCheckbox().prop('checked'),
    extraSpaceBellowLastAnswer: getExtraSpaceBellowLastAnswerCheckbox().prop('checked'),
    modelLabelTextMode: getModelLabelTextModeSelect().val(),
    modelLabelStyle: getModelLabelStyleSelect().val(),
    modelLabelOverwriteCyanIconToGray: getModelLabelOverwriteCyanIconToGrayCheckbox().prop('checked'),
    modelLabelUseIconForReasoningModels: getModelLabelUseIconForReasoningModelsSelect().val(),
    modelLabelReasoningModelIconGold: getModelLabelReasoningModelIconGoldCheckbox().prop('checked'),
    customModelPopover: getCustomModelPopoverSelect().val(),
    modelLabelRemoveCpuIcon: getModelLabelRemoveCpuIconCheckbox().prop('checked'),
    tagPaletteCustom: getTagPaletteCustomTextArea().val().split(',').map(s => s.trim()).filter(Boolean),
    mainCaptionHtml: getMainCaptionHtmlTextArea().val(),
    customJs: getCustomJsTextArea().val(),
    customCss: getCustomCssTextArea().val(),
    customWidgetsHtml: getCustomWidgetsHtmlTextArea().val(),
  };
  saveConfig(checkBoxStates);
};

const showPerplexityHelperSettingsModal = () => {
  loadCurrentConfigToSettingsForm();
  getPerplexityHelperModal().show().css('display', 'flex');
};

const hidePerplexityHelperSettingsModal = () => {
  getPerplexityHelperModal().hide();
};

const handleTopSettingsButtonInsertion = () => {
  const copilotHelperSettings = getTopSettingsButtonEl();
  // TODO: no longer works
  // debugLog('upperControls().length > 0', upperControls().length, 'copilotHelperSettings.length', copilotHelperSettings.length, 'upperControls().children().length', upperControls().children().length);
  if (upperControls().length > 0 && copilotHelperSettings.length < 1 && upperControls().children().length >= 1) {
    debugLog('inserting settings button');
    upperControls().children().eq(0).children().eq(0).append(upperButton(topSettingsButtonId, cogIco, 'Perplexity Helper Settings'));
  }
};

const handleTopSettingsButtonSetup = () => {
  const settingsButtonEl = getTopSettingsButtonEl();

  if (settingsButtonEl.length === 1 && !settingsButtonEl.attr('data-has-custom-click-event')) {
    debugLog('handleTopSettingsButtonSetup: setting up the button');
    if (settingsButtonEl.length === 0) {
      debugLog('handleTopSettingsButtonSetup: settingsButtonEl.length === 0');
    }

    settingsButtonEl.on("click", () => {
      debugLog('perplexity_helper_settings open click');
      showPerplexityHelperSettingsModal();
    });

    settingsButtonEl.attr('data-has-custom-click-event', true);
  }
};

const applySideMenuHiding = () => {
  const config = loadConfigOrDefault();
  if (!config.hideSideMenu) return;
  const $sideMenu = PP.getLeftPanel();
  if ($sideMenu.hasClass(sideMenuHiddenCls)) return;
  $sideMenu.addClass(sideMenuHiddenCls);
  console.log(logPrefix, '[applySideMenuHiding] User requested hiding of side menu (left panel). You can open Perplexity Helper settings modal via typing (copy&paste):\n\nph.showPerplexityHelperSettingsModal()\n\nin Console in DevTools and executing via enter key.', { $sideMenu });
};

const handleModalCreation = () => {
  if (getPerplexityHelperModal().length > 0) return;
  debugLog('handleModalCreation: creating modal');
  jq("body").append(modalHTML);

  getPerplexityHelperModal().find('.close').on('click', () => {
    debugLog('perplexity_helper_settings close  click');
    hidePerplexityHelperSettingsModal();
  });

  // Setup title animation
  setTimeout(() => {
    const $titleEl = getPerplexityHelperModal().find(`.${modalSettingsTitleCls}`);
    if ($titleEl.length) {
      const text = $titleEl.text();
      const wrappedText = text
        .split('')
        .map((char, i) => {
          if (i === 0 || i === 11) { // P and H positions
            return `<span class="animate-letter" data-letter="${char}">${char}</span>`;
          }
          return char;
        })
        .join('');

      $titleEl.html(wrappedText);

      $titleEl.on('click', () => {
        const $firstLetter = $titleEl.find('.animate-letter').eq(0);
        const $secondLetter = $titleEl.find('.animate-letter').eq(1);

        // Staggered animation
        $firstLetter.addClass('active');
        setTimeout(() => {
          $firstLetter.removeClass('active');
          $secondLetter.addClass('active');
          setTimeout(() => {
            $secondLetter.removeClass('active');
          }, 500);
        }, 250);
      });
    }
  }, 500);
};

const lucideIconMappings = {
  LUCIDE1: leftPanelIconMappingsToLucide1,
  LUCIDE2: leftPanelIconMappingsToLucide2,
};

const findKeyByValue = (obj, value) =>
  Object.keys(obj).find(key => obj[key] === value);

const SUPPORTED_ICON_REPLACEMENT_MODES = [
  ICON_REPLACEMENT_MODE.LUCIDE1,
  ICON_REPLACEMENT_MODE.LUCIDE2,
  ICON_REPLACEMENT_MODE.LUCIDE3,
  ICON_REPLACEMENT_MODE.TDESIGN1,
  ICON_REPLACEMENT_MODE.TDESIGN2,
  ICON_REPLACEMENT_MODE.TDESIGN3,
];

const replaceIconsInMenu = () => {
  const config = loadConfigOrDefault();
  const replacementMode = findKeyByValue(ICON_REPLACEMENT_MODE, config.replaceIconsInMenu);

  if (SUPPORTED_ICON_REPLACEMENT_MODES.includes(config.replaceIconsInMenu)) {
    const processedAttr = `data-${pplxHelperTag}-processed`;
    const iconMapping = iconMappings[replacementMode];
    if (!iconMapping) {
      console.error(logPrefix, '[replaceIconsInMenu] iconMapping not found', { config, iconMappings });
      return;
    }

    const $iconButtons = PP.getIconsInLeftPanel().find('a:has(> div.grid > svg)');
    // debugLog('[replaceIconsInMenu] svgEls', svgEls);
    $iconButtons.each((idx, rawIconButton) => {
      const $iconButton = jq(rawIconButton);
      const $svg = $iconButton.find('svg');
      const processed = $iconButton.attr(processedAttr);
      if (processed) return;
      if ($iconButton.attr('id') === leftSettingsButtonId) return;

      const iconName = dropStr(1)($iconButton.attr('href')) || 'search';
      debugLog('[replaceIconsInMenu] iconName', iconName);
      const replacementIconName = iconMapping[iconName];
      debugLog('[replaceIconsInMenu] replacementIconName', replacementIconName);

      $iconButton.attr(processedAttr, true);

      if (replacementIconName) {
        const isTDesign = config.replaceIconsInMenu.startsWith('TDesign');
        const newIconUrl = (isTDesign ? getTDesignIconUrl : getLucideIconUrl)(replacementIconName);

        debugLog('[replaceIconsInMenu] replacing icon', { iconName, replacementIconName, $svg, newIconUrl });
        $svg.hide();
        const newIconEl = jq('<img>')
        .attr('src', newIconUrl)
        .addClass('invert opacity-50')
        .addClass('relative duration-150 [grid-area:1/-1] group-hover:scale-110 text-text-200')
        ;
        if (isTDesign) newIconEl.addClass('h-6');
        $svg.parent().addClass(lucideIconParentCls);
        $svg.after(newIconEl);
      } else {
        if (!['plus', 'thread'].includes(iconName)) {
          console.error('[replaceIconsInMenu] no replacement icon found', { iconName, replacementIconName });
        }
      }
    });
  }
};

const createSidebarButton = (options) => {
  const { svgHtml, label, testId, href } = options;
  return jq('<a>', {
    'data-testid': testId,
    'class': 'p-sm group flex w-full flex-col items-center justify-center gap-0.5',
    'href': href ?? '#',
  }).append(
    jq('<div>', {
      'class': 'grid size-[40px] place-items-center border-borderMain/50 ring-borderMain/50 divide-borderMain/50 dark:divide-borderMainDark/50 dark:ring-borderMainDark/50 dark:border-borderMainDark/50 bg-transparent'
    }).append(
      jq('<div>', {
        'class': 'size-[90%] rounded-md duration-150 [grid-area:1/-1] group-hover:opacity-100 opacity-0 border-borderMain/50 ring-borderMain/50 divide-borderMain/50 dark:divide-borderMainDark/50 dark:ring-borderMainDark/50 dark:border-borderMainDark/50 bg-offsetPlus dark:bg-offsetPlusDark'
      }),
      jq(svgHtml).addClass('relative duration-150 [grid-area:1/-1] group-hover:scale-110 text-text-200'),
    ),
    jq('<div>', {
      'class': 'light font-sans text-xs text-textOff dark:text-textOffDark selection:bg-super/50 selection:text-textMain dark:selection:bg-superDuper/10 dark:selection:text-superDark text-center',
      'text': label ?? 'MISSING LABEL'
    })
  );
}

const handleLeftSettingsButtonSetup = () => {
  const existingLeftSettingsButton = getLeftSettingsButtonEl();
  if (existingLeftSettingsButton.length === 1) {
    // const wrapper = existingLeftSettingsButton.parent();
    // if (!wrapper.is(':last-child')) {
    //   wrapper.appendTo(wrapper.parent());
    // }
    return;
  }

  const $leftPanel = PP.getIconsInLeftPanel();

  if ($leftPanel.length === 0) {
    debugLog('handleLeftSettingsButtonSetup: leftPanel not found');
  }

  const $sidebarButton = createSidebarButton({
    svgHtml: cogIco,
    label: 'Perplexity Helper',
    testId: 'perplexity-helper-settings',
    href: '#',
  })
  .attr('id', leftSettingsButtonId)
  .on('click', () => {
    debugLog('left settings button clicked');
    if (!PP.isBreakpoint('md')) {
      PP.getLeftPanel().hide();
    }
    showPerplexityHelperSettingsModal();
  });

  $leftPanel.append($sidebarButton);
};

const handleSlimLeftMenu = () => {
  const config = loadConfigOrDefault();
  if (!config.slimLeftMenu) return;

  const $leftPanel = PP.getLeftPanel();
  if ($leftPanel.length === 0) {
    // debugLog('handleSlimLeftMenu: leftPanel not found');
  }

  $leftPanel.addClass(leftPanelSlimCls);
  $leftPanel.find('.py-md').css('width', '45px');
};

const handleHideHomeWidgets = () => {
  const config = loadConfigOrDefault();
  if (!config.hideHomeWidgets) return;

  const homeWidgets = PP.getHomeWidgets();
  if (homeWidgets.length === 0) {
    debugLog('handleHideHomeWidgets: homeWidgets not found');
    return;
  }
  if (homeWidgets.length > 1) {
    console.warn(logPrefix, '[handleHideHomeWidgets] too many homeWidgets found', homeWidgets);
  }

  homeWidgets.hide();
};

const handleFixImageGenerationOverlay = () => {
  const config = loadConfigOrDefault();
  if (!config.fixImageGenerationOverlay) return;

  const imageGenerationOverlay = PP.getImageGenerationOverlay();
  if (imageGenerationOverlay.length === 0) {
    // debugLog('handleFixImageGenerationOverlay: imageGenerationOverlay not found');
    return;
  }

  // only if wrench button is cyan (we are in custom prompt)
  if (!imageGenerationOverlay.find('button').hasClass('bg-super')) return;

  const transform = imageGenerationOverlay.css('transform');
  if (!transform) return;

  // Handle both matrix and translate formats
  const matrixMatch = transform.match(/matrix\(.*,\s*([\d.]+),\s*([\d.]+)\)/);
  const translateMatch = transform.match(/translate\(([\d.]+)px(?:,\s*([\d.]+)px)?\)/);

  const currentX = matrixMatch
    ? matrixMatch[1]  // Matrix format: 5th value is X translation
    : translateMatch?.[1] || 0;  // Translate format: first value

  debugLog('[handleFixImageGenerationOverlay] currentX', currentX, 'transform', transform);
  imageGenerationOverlay.css({
    transform: `translate(${currentX}px, 0px)`
  });
};

const handleExtraSpaceBellowLastAnswer = () => {
  const config = loadConfigOrDefault();
  if (!config.extraSpaceBellowLastAnswer) return;
  jq('body')
    .find(`.erp-sidecar\\:h-fit .md\\:pt-md.isolate > .max-w-threadContentWidth`)
    .last()
    .css({
      // backgroundColor: 'magenta',
      paddingBottom: '15em',
    })
    ;
};

const handleSearchPage = () => {
  const controlsArea = getCurrentControlsArea();
  controlsArea.addClass(controlsAreaCls);
  controlsArea.parent().find('textarea').first().addClass(textAreaCls);
  controlsArea.addClass(roundedMD);
  controlsArea.parent().addClass(roundedMD);


  if (controlsArea.length === 0) {
    debugLog('controlsArea not found', {
      controlsArea,
      currentControlsArea: getCurrentControlsArea(),
      isStandardControlsAreaFc: isStandardControlsAreaFc()
    });
  }

  const lastQueryBoxText = getLastQuery();

  const mainTextArea = isStandardControlsAreaFc() ? controlsArea.prev().prev() : controlsArea.parent().prev();

  if (mainTextArea.length === 0) {
    debugLog('mainTextArea not found', mainTextArea);
  }


  debugLog('lastQueryBoxText', { lastQueryBoxText });
  if (lastQueryBoxText) {
    const copilotNewThread = getCopilotNewThreadButton();
    const copilotRepeatLast = getCopilotRepeatLastButton();

    if (controlsArea.length > 0 && copilotNewThread.length < 1) {
      controlsArea.append(button('copilot_new_thread', robotIco, "Starts new thread for with last query text and Copilot ON", standardButtonCls));
    }

    // Due to updates in Perplexity, this is unnecessary for now
    // if (controlsArea.length > 0 && copilotRepeatLast.length < 1) {
    //   controlsArea.append(button('copilot_repeat_last', robotRepeatIco, "Repeats last query with Copilot ON"));
    // }

    if (!copilotNewThread.attr('data-has-custom-click-event')) {
      copilotNewThread.on("click", function () {
        debugLog('copilotNewThread Button clicked!');
        openNewThreadModal(getLastQuery());
      });
      copilotNewThread.attr('data-has-custom-click-event', true);
    }

    if (!copilotRepeatLast.attr('data-has-custom-click-event')) {
      copilotRepeatLast.on("click", function () {
        const controlsArea = getCurrentControlsArea();
        const textAreaElement = controlsArea.parent().find('textarea')[0];

        const coPilotRepeatLastAutoSubmit =
          getSavedStates()
            ? getSavedStates().coPilotRepeatLastAutoSubmit
            : getCoPilotRepeatLastAutoSubmitCheckbox().prop('checked');

        debugLog('coPilotRepeatLastAutoSubmit', coPilotRepeatLastAutoSubmit);
        changeValueUsingEvent(textAreaElement, getLastQuery());
        const copilotToggleButton = getCopilotToggleButton(mainTextArea);
        debugLog('mainTextArea', mainTextArea);
        debugLog('copilotToggleButton', copilotToggleButton);

        toggleBtnDot(copilotToggleButton, true);
        const isCopilotOnBtn = () => isCopilotOn(copilotToggleButton);

        const copilotCheck = () => {
          const ctx = { timer: null };
          ctx.timer = setInterval(() => checkForCopilotToggleState(ctx.timer, isCopilotOnBtn, coPilotRepeatLastAutoSubmit, 0), 500);
        };

        copilotCheck();
        debugLog('copilot_repeat_last Button clicked!');
      });
      copilotRepeatLast.attr('data-has-custom-click-event', true);
    }
  }

  if (getNumberOfDashedSVGs() > 0 && getNumberOfDashedSVGs() === getDashedCheckboxButton().length
    && getSelectAllButton().length < 1 && getSelectAllAndSubmitButton().length < 1) {
    debugLog('getNumberOfDashedSVGs() === getNumberOfDashedSVGs()', getNumberOfDashedSVGs());
    debugLog('getSpecifyQuestionBox', getSpecifyQuestionBox());

    const specifyQuestionControlsWrapper = getSpecifyQuestionControlsWrapper();
    debugLog('specifyQuestionControlsWrapper', specifyQuestionControlsWrapper);
    const selectAllButton = textButton('perplexity_helper_select_all', 'Select all', 'Selects all options');
    const selectAllAndSubmitButton = textButton('perplexity_helper_select_all_and_submit', 'Select all & submit', 'Selects all options and submits');

    specifyQuestionControlsWrapper.append(selectAllButton);
    specifyQuestionControlsWrapper.append(selectAllAndSubmitButton);

    getSelectAllButton().on("click", function () {
      selectAllCheckboxes();
    });

    getSelectAllAndSubmitButton().on("click", function () {
      selectAllCheckboxes();
      setTimeout(() => {
        getSpecifyQuestionControlsWrapper().find('button:contains("Continue")').click();
      }, 200);
    });
  }

  const constructClipBoard = (buttonId, buttonGetter, modalGetter, copiedModalId, elementGetter) => {
    const placeholderValue = getSpecifyQuestionBox().find('textarea').attr('placeholder');

    const clipboardInstance = new ClipboardJS(`#${buttonId}`, {
      text: () => placeholderValue
    });

    const copiedModal = `<span id="${copiedModalId}">Copied!</span>`;
    debugLog('copiedModalId', copiedModalId);
    debugLog('copiedModal', copiedModal);

    jq('main').append(copiedModal);

    clipboardInstance.on('success', _ => {
      var buttonPosition = buttonGetter().position();
      jq(`#${copiedModalId}`).css({
        top: buttonPosition.top - 30,
        left: buttonPosition.left + 50
      }).show();

      if (elementGetter !== undefined) {
        changeValueUsingEvent(elementGetter()[0], placeholderValue);
      }

      setTimeout(() => {
        modalGetter().hide();
      }, 5000);
    });
  };

  if (questionBoxWithPlaceholderExists() && getCopyPlaceholder().length < 1) {
    const copyPlaceholder = textButton('perplexity_helper_copy_placeholder', 'Copy placeholder', 'Copies placeholder value');
    const copyPlaceholderAndFillIn = textButton('perplexity_helper_copy_placeholder_and_fill_in', 'Copy placeholder and fill in',
      'Copies placeholder value and fills in input');

    const specifyQuestionControlsWrapper = getSpecifyQuestionControlsWrapper();

    specifyQuestionControlsWrapper.append(copyPlaceholder);
    specifyQuestionControlsWrapper.append(copyPlaceholderAndFillIn);

    constructClipBoard('perplexity_helper_copy_placeholder', getCopyPlaceholder, getCopiedModal, 'copied-modal');
    constructClipBoard('perplexity_helper_copy_placeholder_and_fill_in', getCopyAndFillInPlaceholder, getCopiedModal2, 'copied-modal-2', getCopyPlaceholderInput);
  }
};

const getLabelFromModelDescription = modelLabelStyle => modelLabelFromAriaLabel => modelDescription => {
  if (!modelDescription) return modelLabelFromAriaLabel;
  switch (modelLabelStyle) {
    case MODEL_LABEL_TEXT_MODE.FULL_NAME:
      return modelDescription.nameEn;
    case MODEL_LABEL_TEXT_MODE.SHORT_NAME:
      return modelDescription.nameEnShort ?? modelDescription.nameEn;
    case MODEL_LABEL_TEXT_MODE.PP_MODEL_ID:
      return modelDescription.ppModelId;
    case MODEL_LABEL_TEXT_MODE.OWN_NAME_VERSION_SHORT:
      const nameText = modelDescription.ownNameEn ?? modelDescription.nameEn;
      const versionTextRaw = modelDescription.ownVersionEnShort ?? modelDescription.ownVersionEn;
      const versionText = versionTextRaw?.replace(/ P$/, ' Pro'); // HACK: Gemini 2.5 Pro
      return [nameText, versionText].filter(Boolean).join(modelDescription.ownNameVersionSeparator ?? ' ');
    default:
      throw new Error(`Unknown model label style: ${modelLabelStyle}`);
  }
};

const getExtraClassesFromModelLabelStyle = modelLabelStyle => {
  switch (modelLabelStyle) {
    case MODEL_LABEL_STYLE.BUTTON_SUBTLE:
      return modelLabelStyleButtonSubtleCls;
    case MODEL_LABEL_STYLE.BUTTON_WHITE:
      return modelLabelStyleButtonWhiteCls;
    case MODEL_LABEL_STYLE.BUTTON_CYAN:
      return modelLabelStyleButtonCyanCls;
    default:
      return '';
  }
};

const handleModelLabel = () => {
  const config = loadConfigOrDefault();
  if (!config.modelLabelStyle || config.modelLabelStyle === MODEL_LABEL_STYLE.OFF) return;

  const $modelIcons = PP.getAnyModelButton();
  $modelIcons.each((_, el) => {
    const $el = jq(el);
    if (!$el.find(`.${modelLabelCls}`).length) {
      $el.prepend(jq(`<span class="${modelLabelCls}"></span>`));
    }
    if (!$el.hasClass(modelIconButtonCls)) {
      $el.addClass(modelIconButtonCls);
    }
    if (config.modelLabelRemoveCpuIcon) {
      $el.addClass(modelLabelRemoveCpuIconCls);
    }
    const $label = $el.find(`.${modelLabelCls}`);
    const modelDescription = PP.getModelDescriptionFromModelButton($el);
    const modelLabelFromAriaLabel = $el.attr('aria-label');
    const modelLabel = getLabelFromModelDescription(config.modelLabelTextMode)(modelLabelFromAriaLabel)(modelDescription);
    if (!modelLabel) {
      console.error('[handleModelLabel] modelLabel is null', { modelDescription, modelLabelFromAriaLabel, $el });
      return;
    }
    const extraClasses = [
      getExtraClassesFromModelLabelStyle(config.modelLabelStyle),
      config.modelLabelOverwriteCyanIconToGray ? modelLabelOverwriteCyanIconToGrayCls : '',
    ].filter(Boolean).join(' ');
    $label.attr('data-extra-classes', extraClasses);

    const isReasoningModel = modelDescription?.modelType === 'reasoning';
    const hasReasoningIcon = isReasoningModel && 
      config.modelLabelUseIconForReasoningModels !== MODEL_LABEL_ICON_REASONING_MODEL.OFF;
    const reasoningIconRemoved = !isReasoningModel && 
      config.modelLabelUseIconForReasoningModels !== MODEL_LABEL_ICON_REASONING_MODEL.OFF;
    const chipIconRemoved = config.modelLabelRemoveCpuIcon;

    if ($label.text() !== modelLabel) {
      $label.attr('data-model-description', JSON.stringify(modelDescription));
      $label.text(modelLabel);
      $label.addClass(extraClasses);
    }

    // Reasoning model icon
    if (config.modelLabelUseIconForReasoningModels !== MODEL_LABEL_ICON_REASONING_MODEL.OFF) {
      const prevReasoningModelIcon = $el.find(`.${reasoningModelCls}`);
      const noReasoningIconAndModelIsNotReasoningModel = prevReasoningModelIcon.length === 0 && !isReasoningModel;
      const reasoningIconAndModelIsReasoningModel = prevReasoningModelIcon.length > 0 && isReasoningModel;
      if (noReasoningIconAndModelIsNotReasoningModel || reasoningIconAndModelIsReasoningModel) {
        if (noReasoningIconAndModelIsNotReasoningModel) {
          $el.addClass(notReasoningModelCls);
        }
        if (reasoningIconAndModelIsReasoningModel) {
          $el.removeClass(notReasoningModelCls);
        }
        return;
      }
      if (prevReasoningModelIcon.length === 0) {
        const iconUrl = getLucideIconUrl(config.modelLabelUseIconForReasoningModels.toLowerCase());
        const $icon = jq(`<img src="${iconUrl}" alt="Reasoning model" class="${reasoningModelCls}" />`);
        if (config.modelLabelReasoningModelIconGold) {
          $icon.addClass(iconColorGoldCls);
        }
        $el.prepend($icon);
      }
      const $reasoningModelIcon = $el.find(`.${reasoningModelCls}`);
      if (isReasoningModel) {
        $reasoningModelIcon.css({ display: 'inline-block' });
        if (config.modelLabelStyle === MODEL_LABEL_STYLE.JUST_TEXT) {
          $reasoningModelIcon.addClass(iconColorGrayCls);
        } else if (config.modelLabelStyle === MODEL_LABEL_STYLE.BUTTON_CYAN) {
          $reasoningModelIcon.addClass(iconColorCyanCls);
        } else if (config.modelLabelStyle === MODEL_LABEL_STYLE.BUTTON_SUBTLE) {
          $reasoningModelIcon.addClass(iconColorGrayCls);
        } else if (config.modelLabelStyle === MODEL_LABEL_STYLE.BUTTON_WHITE) {
          $reasoningModelIcon.addClass(iconColorWhiteCls);
        }
      } else {
        $reasoningModelIcon.remove();
      }
    } // End of reasoning model icon
  });
};

const handleHideDiscoverButton = () => {
  const config = loadConfigOrDefault();
  if (!config.hideDiscoverButton) return;
  const $iconsInLeftPanel = PP.getIconsInLeftPanel().find('a[href="/discover"]');
  $iconsInLeftPanel.hide();
};

const handleCustomModelPopover = () => {
  const config = loadConfigOrDefault();
  const mode = config.customModelPopover;
  if (mode === CUSTOM_MODEL_POPOVER_MODE.OFF) return;

  const $modelSelectionList = PP.getModelSelectionList();
  if ($modelSelectionList.length === 0) return;
  const processedAttr = 'ph-processed-custom-model-popover';
  if ($modelSelectionList.attr(processedAttr)) return;
  $modelSelectionList.attr(processedAttr, true);

  $modelSelectionList.nthParent(2).css({ maxHeight: 'initial' });

  const $reasoningDelim = $modelSelectionList.children(".sm\\:px-sm.relative");

  const markListItemAsReasoningModel = (el) => {
    const $el = jq(el);
    const $icon = jq('<img>', {
      src: getLucideIconUrl(config.modelLabelUseIconForReasoningModels.toLowerCase()),
      alt: 'Reasoning model',
      class: reasoningModelCls,
    }).css({ marginLeft: '0px' });
    $el.find('.default,.super').parent().find('>.font-sans').first().prepend($icon);
  };
  const modelSelectionListType = PP.getModelSelectionListType($modelSelectionList);
  if (config.modelLabelUseIconForReasoningModels !== MODEL_LABEL_ICON_REASONING_MODEL.OFF) {
    if (modelSelectionListType === 'new') {
      const $delimIndex = $modelSelectionList.children().index($reasoningDelim);
      $modelSelectionList.children().slice($delimIndex + 1).each((_idx, el) => {
        markListItemAsReasoningModel(el);
      });
    } else {
      $modelSelectionList
      .children()
      .filter((_idx, rEl) => jq(rEl).find('span').text().includes('Reasoning'))
      .each((_idx, el) => markListItemAsReasoningModel(el));
    }
  }
  const $delims = $modelSelectionList.children(".sm\\:mx-sm");

  const removeAllDelims = () => {
    $delims.hide();
    $reasoningDelim.hide();
  };

  const removeAllModelDescriptions = () => {
    $modelSelectionList.find('div.light.text-textOff').hide();
    $modelSelectionList.find('.group\\/item > .relative > .gap-sm').css({ alignItems: 'center' });
  };

  if (mode === CUSTOM_MODEL_POPOVER_MODE.COMPACT_LIST) {
    removeAllDelims();
    removeAllModelDescriptions();
    return;
  }
  if (mode === CUSTOM_MODEL_POPOVER_MODE.SIMPLE_LIST) {
    // it is already a list, we forced the height to grow
    return;
  }

  $modelSelectionList.css({
    display: 'grid',
    gridTemplateColumns: '1fr 1fr',
    gap: mode === CUSTOM_MODEL_POPOVER_MODE.COMPACT_GRID ? '0px' : '10px',
    'grid-auto-rows': 'min-content',
  });

  if (mode === CUSTOM_MODEL_POPOVER_MODE.COMPACT_GRID) {
    removeAllDelims();
    removeAllModelDescriptions();
  }

  $delims.hide();
  $reasoningDelim.css({ gridColumn: 'span 2', });
};

const mainCaptionAppliedCls = genCssName('mainCaptionApplied');
const handleMainCaptionHtml = () => {
  const config = loadConfigOrDefault();
  if (!config.mainCaptionHtml) return;
  if (PP.getMainCaption().hasClass(mainCaptionAppliedCls)) return;
  PP.setMainCaptionHtml(config.mainCaptionHtml);
  PP.getMainCaption().addClass(mainCaptionAppliedCls);
};

const handleCustomJs = () => {
  const config = loadConfigOrDefault();
  if (!config.customJs) return;

  try {
    // Use a static key to ensure we only run once per page load
    const dataKey = 'data-' + genCssName('custom-js-applied');
    if (!jq('body').attr(dataKey)) {
      jq('body').attr(dataKey, true);
      // Use Function constructor to evaluate the JS code
      const customJsFn = new Function(config.customJs);
      customJsFn();
    }
  } catch (error) {
    console.error('Error executing custom JS:', error);
  }
};

const handleCustomCss = () => {
  const config = loadConfigOrDefault();
  if (!config.customCss) return;

  try {
    // Check if custom CSS has already been applied
    const dataKey = 'data-' + genCssName('custom-css-applied');
    if (!jq('head').attr(dataKey)) {
      jq('head').attr(dataKey, true);
      const styleElement = jq('<style></style>')
        .addClass(customCssAppliedCls)
        .text(config.customCss);
      jq('head').append(styleElement);
    }
  } catch (error) {
    console.error('Error applying custom CSS:', error);
  }
};

const handleCustomWidgetsHtml = () => {
  const config = loadConfigOrDefault();
  if (!config.customWidgetsHtml) return;

  try {
    // Check if custom widgets have already been applied
    const dataKey = 'data-' + genCssName('custom-widgets-html-applied');
    if (!jq('body').attr(dataKey)) {
      jq('body').attr(dataKey, true);
      const widgetContainer = jq('<div></div>')
        .addClass(customWidgetsHtmlAppliedCls)
        .html(config.customWidgetsHtml);
      PP.getPromptAreaWrapperOfNewThread().after(widgetContainer);

    }
  } catch (error) {
    console.error('Error applying custom widgets HTML:', error);
  }
};

const handleHideSideMenuLabels = () => {
  const config = loadConfigOrDefault();
  if (!config.hideSideMenuLabels) return;
  const $sideMenu = PP.getLeftPanel();
  if ($sideMenu.hasClass(sideMenuLabelsHiddenCls)) return;
  $sideMenu.addClass(sideMenuLabelsHiddenCls);
};

const work = () => {
  handleModalCreation();
  handleTopSettingsButtonInsertion();
  handleTopSettingsButtonSetup();
  handleSettingsInit();
  handleLeftSettingsButtonSetup();
  handleExtraSpaceBellowLastAnswer();
  handleHideDiscoverButton();
  handleHideSideMenuLabels();

  const regex = /^https:\/\/www\.perplexity\.ai\/search\/?.*/;
  const currentUrl = jq(location).attr('href');
  const matchedCurrentUrlAsSearchPage = regex.test(currentUrl);

  // debugLog("currentUrl", currentUrl);
  // debugLog("matchedCurrentUrlAsSearchPage", matchedCurrentUrlAsSearchPage);

  if (matchedCurrentUrlAsSearchPage) {
    handleSearchPage();
  }
};

const fastWork = () => {
  handleCustomModelPopover();
  handleSlimLeftMenu();
  handleHideHomeWidgets();
  applySideMenuHiding();
  replaceIconsInMenu();
  handleModelLabel();
  handleMainCaptionHtml();
  handleCustomJs();
  handleCustomCss();
  handleCustomWidgetsHtml();
};

const fontUrls = {
  Roboto: 'https://fonts.cdnfonts.com/css/roboto',
  Montserrat: 'https://fonts.cdnfonts.com/css/montserrat',
  Lato: 'https://fonts.cdnfonts.com/css/lato',
  Oswald: 'https://fonts.cdnfonts.com/css/oswald-4',
  Raleway: 'https://fonts.cdnfonts.com/css/raleway-5',
  'Ubuntu Mono': 'https://fonts.cdnfonts.com/css/ubuntu-mono',
  Nunito: 'https://fonts.cdnfonts.com/css/nunito',
  Poppins: 'https://fonts.cdnfonts.com/css/poppins',
  'Playfair Display': 'https://fonts.cdnfonts.com/css/playfair-display',
  Merriweather: 'https://fonts.cdnfonts.com/css/merriweather',
  'Fira Sans': 'https://fonts.cdnfonts.com/css/fira-sans',
  Quicksand: 'https://fonts.cdnfonts.com/css/quicksand',
  Comfortaa: 'https://fonts.cdnfonts.com/css/comfortaa-3',
  'Almendra': 'https://fonts.cdnfonts.com/css/almendra',
  'Enchanted Land': 'https://fonts.cdnfonts.com/css/enchanted-land',
  'Cinzel Decorative': 'https://fonts.cdnfonts.com/css/cinzel-decorative',
  'Orbitron': 'https://fonts.cdnfonts.com/css/orbitron',
  'Exo 2': 'https://fonts.cdnfonts.com/css/exo-2',
  'Chakra Petch': 'https://fonts.cdnfonts.com/css/chakra-petch',
  'Open Sans Condensed': 'https://fonts.cdnfonts.com/css/open-sans-condensed',
  'Saira Condensed': 'https://fonts.cdnfonts.com/css/saira-condensed',
  Inter: 'https://cdn.jsdelivr.net/npm/@fontsource/[email protected]/index.min.css',
};

const loadFont = (fontName) => {
  const fontUrl = fontUrls[fontName];
  debugLog('loadFont', { fontName, fontUrl });
  if (fontUrl) {
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = fontUrl;
    document.head.appendChild(link);
  }
};

const setupFixImageGenerationOverlay = () => {
  const config = loadConfigOrDefault();
  if (config.fixImageGenerationOverlay) {
    setInterval(handleFixImageGenerationOverlay, 250);
  }
};

(function () {
  if (loadConfigOrDefault()?.enableDebug) {
    enableDebugMode();
  }

  debugLog('TAGS_PALETTES', TAGS_PALETTES);
  if (loadConfigOrDefault()?.debugTagsMode) {
    enableTagsDebugging();
  }

  'use strict';
  jq("head").append(`<style>${styles}</style>`);

  setupTags();
  setupFixImageGenerationOverlay();

  const mainInterval = setInterval(work, 1000);
  const fastInterval = setInterval(fastWork, 100);
  window.ph = {
    stopWork: () => { clearInterval(mainInterval); clearInterval(fastInterval); },
    work,
    fastWork,
    jq,
    showPerplexityHelperSettingsModal,
    enableTagsDebugging: () => { debugTags = true; },
    disableTagsDebugging: () => { debugTags = false; },
  };

  loadFont(loadConfigOrDefault().tagFont);

  console.log(`%c${userscriptName}%c\n  %cTiartyos%c & %cmonnef%c\n ... loaded`,
    'color: #aaffaa; font-size: 1.5rem; background-color: rgba(0, 0, 0, 0.5); padding: 2px;',
    '',
    'color: #6b02ff; font-weight: bold; background-color: rgba(0, 0, 0, 0.5); padding: 2px;',
    '',
    'color: #aa2cc3; font-weight: bold; background-color: rgba(0, 0, 0, 0.5); padding: 2px;',
    '',
    '');
  console.log('to show settings use:\nph.showPerplexityHelperSettingsModal()');
}());