import {
  Component,
  computed,
  effect,
  inject,
  signal,
  viewChild
} from '@angular/core';
import { CommonModule } from '@angular/common';

import { SettingsModalService } from '../../../../../core/services/settings-modal.service';
import { AppI18nService, APP_TRANSLATE_IMPORTS } from '../../../../../core/i18n';
import { getElectronApi } from '../../../../../core/platform/electron/get-electron-api';
import {
  ThemeContainerKey,
  ThemeElementStyleProperty,
  ThemeRegistryEntry
} from '../../../domain/models/theme.model';
import {
  THEME_ANIMATION_FIELDS as THEME_ANIMATION_FIELD_HINTS,
  THEME_ELEMENT_STYLE_FIELDS,
  createAnimationStarterDefinition,
  getSuggestedFieldDefault
} from '../../../domain/logic/theme-schema.logic';
import { ElementPickerService } from '../../../application/services/element-picker.service';
import { LayoutSyncService } from '../../../application/services/layout-sync.service';
import { ThemeLibraryService } from '../../../application/services/theme-library.service';
import { ThemeRegistryService } from '../../../application/services/theme-registry.service';
import { ThemeService } from '../../../application/services/theme.service';
import { THEME_LLM_GUIDE } from '../../../domain/constants/theme-llm-guide.constants';
import { ThemeGridEditorComponent } from '../theme-grid-editor/theme-grid-editor.component';
import { ThemeJsonCodeEditorComponent } from '../theme-json-code-editor/theme-json-code-editor.component';

type JumpSection = 'elements' | 'layout' | 'animations' | 'css';
type ThemeEditorTab = 'json' | 'cssOnly';
type ThemeStudioWorkspace = 'editor' | 'inspector' | 'layout';

@Component({
  selector: 'app-theme-settings',
  standalone: true,
  imports: [
    CommonModule,
    ThemeGridEditorComponent,
    ThemeJsonCodeEditorComponent,
    ...APP_TRANSLATE_IMPORTS
  ],
  templateUrl: './theme-settings.component.html',
  styleUrl: './theme-settings.component.scss'
})
export class ThemeSettingsComponent {
  readonly modal = inject(SettingsModalService);
  readonly theme = inject(ThemeService);
  readonly themeLibrary = inject(ThemeLibraryService);
  readonly registry = inject(ThemeRegistryService);
  readonly picker = inject(ElementPickerService);
  readonly layoutSync = inject(LayoutSyncService);
  private readonly appI18n = inject(AppI18nService);

  readonly editorRef = viewChild<ThemeJsonCodeEditorComponent>('jsonEditorRef');

  readonly draftText = this.theme.draftText;
  readonly draftErrors = this.theme.draftErrors;
  readonly draftIsValid = this.theme.draftIsValid;
  readonly statusMessage = this.theme.statusMessage;
  readonly isDraftDirty = this.theme.isDraftDirty;
  readonly isFullscreen = this.modal.themeStudioFullscreen;
  readonly activeTheme = this.theme.activeTheme;
  readonly builtInPresets = this.theme.builtInPresets;
  readonly draftTheme = this.theme.draftTheme;
  readonly THEME_ANIMATION_FIELDS = THEME_ANIMATION_FIELD_HINTS;
  readonly animationKeys = this.theme.knownAnimationClasses;
  readonly layoutContainers = this.layoutSync.containers();
  readonly themeEntries = this.registry.entries();
  readonly workspaceTabs = computed(() => [
    {
      key: 'editor' as const,
      label: this.appI18n.instant('theme.studio.workspaces.editor.label'),
      description: this.appI18n.instant('theme.studio.workspaces.editor.description')
    },
    {
      key: 'inspector' as const,
      label: this.appI18n.instant('theme.studio.workspaces.inspector.label'),
      description: this.appI18n.instant('theme.studio.workspaces.inspector.description')
    },
    {
      key: 'layout' as const,
      label: this.appI18n.instant('theme.studio.workspaces.layout.label'),
      description: this.appI18n.instant('theme.studio.workspaces.layout.description')
    }
  ]);
  readonly mountedKeyCounts = computed(() => this.registry.mountedKeyCounts());
  readonly activeWorkspace = signal<ThemeStudioWorkspace>('editor');
  readonly activeEditorTab = signal<ThemeEditorTab>('json');
  readonly cssOnlyText = signal('');
  readonly explorerQuery = signal('');

  readonly selectedContainer = signal<ThemeContainerKey>('roomLayout');
  readonly selectedElementKey = signal<string>('chatRoomMainPanel');
  readonly selectedElement = computed(() => this.registry.getDefinition(this.selectedElementKey()));
  readonly selectedElementCapabilities = computed(() => {
    const selected = this.selectedElement();

    if (!selected) {
      return [] as string[];
    }

    return [
      selected.layoutEditable ? this.appI18n.instant('theme.studio.capabilities.layoutEditable') : null,
      selected.supportsTextOverride ? this.appI18n.instant('theme.studio.capabilities.textOverride') : null,
      selected.supportsLink ? this.appI18n.instant('theme.studio.capabilities.safeExternalLink') : null,
      selected.supportsIcon ? this.appI18n.instant('theme.studio.capabilities.iconSlot') : null
    ].filter((value): value is string => value !== null);
  });
  readonly selectedContainerItems = computed(() => this.layoutSync.itemsForContainer(this.selectedContainer()));
  readonly selectedLayoutContainer = computed(() => {
    return this.layoutContainers.find((container) => container.key === this.selectedContainer()) ?? this.layoutContainers[0];
  });
  readonly selectedElementGrid = computed(() => {
    return this.selectedContainerItems().find((item) => item.key === this.selectedElementKey()) ?? null;
  });
  readonly activeWorkspaceInfo = computed(() => {
    return this.workspaceTabs().find((workspace) => workspace.key === this.activeWorkspace()) ?? this.workspaceTabs()[0];
  });
  readonly localizedFilteredEntries = computed(() =>
    this.filteredEntries().map((entry) => this.localizeRegistryEntry(entry))
  );
  readonly localizedSelectedElement = computed(() => {
    const selected = this.selectedElement();

    return selected ? this.localizeRegistryEntry(selected) : null;
  });
  readonly visiblePropertyHints = computed(() => {
    const selected = this.selectedElement();

    return THEME_ELEMENT_STYLE_FIELDS.filter((field) => {
      if (field.key === 'textOverride' && !selected?.supportsTextOverride) {
        return false;
      }

      if (field.key === 'link' && !selected?.supportsLink) {
        return false;
      }

      if (field.key === 'icon' && !selected?.supportsIcon) {
        return false;
      }

      return true;
    });
  });
  readonly mountedEntries = computed(() => {
    return this.themeEntries.filter((entry) => entry.pickerVisible || entry.layoutEditable);
  });
  readonly filteredEntries = computed(() => {
    const query = this.explorerQuery().trim()
      .toLowerCase();

    if (!query) {
      return this.mountedEntries();
    }

    return this.mountedEntries().filter((entry) => {
      const haystack = `${entry.label} ${entry.key} ${entry.description} ${entry.category}`.toLowerCase();

      return haystack.includes(query);
    });
  });
  readonly draftLineCount = computed(() => this.draftText().split('\n').length);
  readonly draftCharacterCount = computed(() => this.draftText().length);
  readonly draftErrorCount = computed(() => this.draftErrors().length);
  readonly mountedEntryCount = computed(() => this.mountedEntries().length);
  readonly llmGuideCopyMessage = signal<string | null>(null);
  readonly savedThemesAvailable = this.themeLibrary.isAvailable;
  readonly savedThemes = this.themeLibrary.entries;
  readonly savedThemesBusy = this.themeLibrary.isBusy;
  readonly savedThemesPath = this.themeLibrary.savedThemesPath;
  readonly selectedSavedTheme = this.themeLibrary.selectedEntry;

  private llmGuideCopyTimeoutId: ReturnType<typeof setTimeout> | null = null;

  constructor() {
    this.syncCssOnlyTextFromTheme();

    if (this.savedThemesAvailable()) {
      void this.themeLibrary.refresh();
    }

    effect(() => {
      const pickedKey = this.picker.selectedKey();

      if (!pickedKey) {
        return;
      }

      this.openPickedElementInJson(pickedKey);
      this.picker.clearSelection();
    });

    effect(() => {
      if (this.activeEditorTab() !== 'json') {
        return;
      }

      this.cssOnlyText.set(this.draftTheme().css);
    });

    effect(() => {
      if (!this.isFullscreen()) {
        return;
      }

      queueMicrotask(() => {
        this.focusEditor();
      });
    });
  }

  onDraftEditorValueChange(value: string): void {
    this.theme.updateDraftText(value);
  }

  onCssOnlyEditorValueChange(value: string): void {
    this.cssOnlyText.set(value);
  }

  applyDraft(): void {
    if (this.activeEditorTab() === 'cssOnly') {
      this.applyCssOnlyTheme();
      return;
    }

    this.theme.applyDraft();
  }

  applyCssOnlyTheme(): void {
    this.theme.applyCssOnlyTheme(this.cssOnlyText());
  }

  setEditorTab(tab: ThemeEditorTab): void {
    this.activeEditorTab.set(tab);

    if (tab === 'json') {
      this.focusEditor();
      return;
    }

    this.syncCssOnlyTextFromTheme();
    this.focusEditor();
  }

  setWorkspace(workspace: ThemeStudioWorkspace): void {
    this.activeWorkspace.set(workspace);

    if (workspace === 'layout') {
      const selected = this.selectedElement();

      if (selected?.container) {
        this.selectedContainer.set(selected.container);
      }
    }

    if (workspace === 'editor') {
      this.activeEditorTab.set('json');
      this.focusEditor();
    }
  }

  onWorkspaceSelect(event: Event): void {
    const select = event.target as HTMLSelectElement;

    this.setWorkspace(select.value as ThemeStudioWorkspace);
  }

  layoutContainerLabel(key: string): string {
    return this.appI18n.instant(`theme.registry.${key}.label`);
  }

  onExplorerQueryInput(event: Event): void {
    const input = event.target as HTMLInputElement;

    this.explorerQuery.set(input.value);
  }

  formatDraft(): void {
    this.theme.formatDraft();
    this.focusEditor();
  }

  async exportThemeFile(): Promise<void> {
    const exportText = this.getExportThemeText();

    if (!exportText) {
      return;
    }

    const fileName = `${this.sanitizeThemeFileName(this.draftTheme().meta.name)}.json`;
    const saved = await this.saveTextAsFile(fileName, exportText);

    this.theme.announceStatus(saved
      ? this.appI18n.instant('theme.studio.exported', { fileName })
      : this.appI18n.instant('theme.studio.exportCancelled'));
  }

  importThemeFile(event: Event): void {
    const input = event.target as HTMLInputElement;
    const file = input.files?.[0] ?? null;

    input.value = '';

    if (!file) {
      return;
    }

    void this.loadThemeFile(file);
  }

  async copyLlmThemeGuide(): Promise<void> {
    const copied = await this.copyTextToClipboard(THEME_LLM_GUIDE);

    this.setLlmGuideCopyMessage(copied
      ? this.appI18n.instant('theme.studio.llmGuideCopied')
      : this.appI18n.instant('theme.studio.llmGuideManualCopy'));
  }

  startPicker(): void {
    this.picker.start('theme');
  }

  selectSavedTheme(fileName: string): void {
    this.themeLibrary.select(fileName);
  }

  async refreshSavedThemes(): Promise<void> {
    await this.themeLibrary.refresh();
  }

  async saveDraftAsNewTheme(): Promise<void> {
    await this.themeLibrary.saveDraftAsNewTheme();
  }

  async saveDraftToSelectedTheme(): Promise<void> {
    await this.themeLibrary.saveDraftToSelectedTheme();
  }

  async useSelectedSavedTheme(): Promise<void> {
    await this.themeLibrary.useSelectedTheme();
  }

  async editSelectedSavedTheme(): Promise<void> {
    const opened = await this.themeLibrary.openSelectedThemeInDraft();

    if (!opened) {
      return;
    }

    this.setWorkspace('editor');
    this.focusEditor();
  }

  async removeSelectedSavedTheme(): Promise<void> {
    const selectedSavedTheme = this.selectedSavedTheme();

    if (!selectedSavedTheme) {
      return;
    }

    const confirmed = window.confirm(`Delete saved theme "${selectedSavedTheme.themeName}"?`);

    if (!confirmed) {
      return;
    }

    await this.themeLibrary.removeSelectedTheme();
  }

  applyBuiltInPreset(name: string): void {
    this.theme.applyBuiltInPreset(name);
    this.activeWorkspace.set('editor');
    this.activeEditorTab.set('json');
    this.syncCssOnlyTextFromTheme();
    this.focusEditor();
  }

  restoreDefaultTheme(): void {
    this.theme.resetToDefault('button');
    this.activeWorkspace.set('editor');
    this.activeEditorTab.set('json');
    this.selectedContainer.set('roomLayout');
    this.selectedElementKey.set('chatRoomMainPanel');
    this.focusEditor();
  }

  selectThemeEntry(key: string, section: JumpSection = 'elements'): void {
    const definition = this.registry.getDefinition(key);

    if (!definition) {
      return;
    }

    if (section === 'layout') {
      this.activeWorkspace.set('layout');
    } else if (section === 'animations' || section === 'css') {
      this.activeWorkspace.set('editor');
    } else {
      this.activeWorkspace.set('inspector');
    }

    this.selectedElementKey.set(key);

    if (definition.container) {
      this.selectedContainer.set(definition.container);
    }
  }

  selectContainer(containerKey: ThemeContainerKey): void {
    this.activeWorkspace.set('layout');
    this.selectedContainer.set(containerKey);

    const matchingItem = this.layoutSync.itemsForContainer(containerKey)[0];

    if (matchingItem) {
      this.selectedElementKey.set(matchingItem.key);
    }
  }

  applySuggestedProperty(property: ThemeElementStyleProperty): void {
    if (!this.draftIsValid()) {
      return;
    }

    const value = getSuggestedFieldDefault(property, this.animationKeys());

    this.theme.setElementStyle(this.selectedElementKey(), property, value);
    this.activeWorkspace.set('editor');
    this.activeEditorTab.set('json');
    this.focusJsonProperty('elements', this.selectedElementKey(), property);
  }

  addStarterAnimation(): void {
    if (!this.draftIsValid()) {
      return;
    }

    this.theme.setAnimation('theme-fade-in', createAnimationStarterDefinition());
  }

  handleGridChange(event: { key: string; grid: { x: number; y: number; w: number; h: number } }): void {
    this.layoutSync.updateGrid(event.key, event.grid);
  }

  handleGridSelection(key: string): void {
    this.selectThemeEntry(key, 'layout');
  }

  resetSelectedContainer(): void {
    this.layoutSync.resetContainer(this.selectedContainer());
  }

  jumpToLayout(): void {
    this.activeWorkspace.set('editor');
    this.activeEditorTab.set('json');
    this.focusJsonAnchor('layout', this.selectedElementKey());
  }

  jumpToStyles(): void {
    this.activeWorkspace.set('editor');
    this.activeEditorTab.set('json');
    this.focusJsonAnchor('elements', this.selectedElementKey());
  }

  jumpToAnimation(animationKey: string): void {
    this.activeWorkspace.set('editor');
    this.activeEditorTab.set('json');
    this.focusJsonAnchor('animations', animationKey);
  }

  jumpToCss(): void {
    this.activeWorkspace.set('editor');
    this.activeEditorTab.set('cssOnly');
    this.syncCssOnlyTextFromTheme();
    this.focusEditor();
  }

  isMounted(entry: ThemeRegistryEntry): boolean {
    return (this.mountedKeyCounts()[entry.key] ?? 0) > 0;
  }

  private focusEditor(): void {
    this.withEditorReady((editor) => {
      editor.focus();
    });
  }

  private withEditorReady(action: (editor: ThemeJsonCodeEditorComponent) => void, attempts = 8): void {
    queueMicrotask(() => {
      const editor = this.editorRef();

      if (editor) {
        action(editor);
        return;
      }

      if (attempts <= 0) {
        return;
      }

      setTimeout(() => {
        this.withEditorReady(action, attempts - 1);
      });
    });
  }

  private syncCssOnlyTextFromTheme(): void {
    this.cssOnlyText.set(this.activeTheme().css || this.draftTheme().css);
  }

  private getExportThemeText(): string | null {
    if (this.activeEditorTab() === 'cssOnly') {
      return this.theme.buildDraftTextWithCss(this.cssOnlyText());
    }

    if (!this.draftIsValid()) {
      this.theme.announceStatus(this.appI18n.instant('theme.studio.fixJsonBeforeExport'));
      return null;
    }

    return this.draftText();
  }

  private async loadThemeFile(file: File): Promise<void> {
    try {
      const text = await file.text();

      if (file.name.toLowerCase().endsWith('.css')) {
        this.activeWorkspace.set('editor');
        this.activeEditorTab.set('cssOnly');
        this.cssOnlyText.set(text);
        this.theme.announceStatus(`${file.name} loaded into the CSS editor.`);
        this.focusEditor();
        return;
      }

      const loaded = this.theme.loadThemeText(text, 'draft', `${file.name} imported into the draft editor.`, 'imported theme file');

      if (!loaded) {
        return;
      }

      this.activeWorkspace.set('editor');
      this.activeEditorTab.set('json');
      this.syncCssOnlyTextFromTheme();
      this.focusEditor();
    } catch {
      this.theme.announceStatus(this.appI18n.instant('theme.status.importFailed', { fileName: file.name }));
    }
  }

  private async saveTextAsFile(fileName: string, text: string): Promise<boolean> {
    const electronApi = getElectronApi();

    if (electronApi) {
      const result = await electronApi.saveFileAs(fileName, this.encodeBase64(text));

      return result.saved;
    }

    const url = URL.createObjectURL(new Blob([text], { type: 'application/json' }));
    const link = document.createElement('a');

    link.href = url;
    link.download = fileName;
    link.style.display = 'none';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    URL.revokeObjectURL(url);

    return true;
  }

  private encodeBase64(value: string): string {
    const bytes = new TextEncoder().encode(value);

    let binary = '';

    for (const byte of bytes) {
      binary += String.fromCharCode(byte);
    }

    return btoa(binary);
  }

  private sanitizeThemeFileName(value: string): string {
    const normalized = value
      .trim()
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, '-')
      .replace(/^-+|-+$/g, '');

    return normalized.length > 0 ? normalized : 'theme';
  }

  private openPickedElementInJson(key: string): void {
    const definition = this.registry.getDefinition(key);

    if (!definition) {
      return;
    }

    this.selectedElementKey.set(key);

    if (definition.container) {
      this.selectedContainer.set(definition.container);
    }

    if (this.draftIsValid()) {
      this.theme.ensureElementEntry(key);
    }

    this.activeWorkspace.set('editor');
    this.activeEditorTab.set('json');
    this.focusJsonElementEntry(key);
  }

  private focusJsonElementEntry(key: string): void {
    this.withEditorReady((editor) => {
      let text = this.draftText();
      let anchorIndex = this.findAnchorIndex(text, 'elements', key);

      if (anchorIndex === -1 && this.draftIsValid()) {
        this.theme.ensureElementEntry(key);
        text = this.draftText();
        anchorIndex = this.findAnchorIndex(text, 'elements', key);
      }

      if (anchorIndex === -1) {
        editor.focus();
        return;
      }

      const colonIndex = text.indexOf(':', anchorIndex);
      const objectStart = colonIndex === -1 ? -1 : text.indexOf('{', colonIndex);

      if (objectStart === -1) {
        editor.focusRange(anchorIndex, Math.min(anchorIndex + key.length + 2, text.length));
        return;
      }

      editor.focusRange(objectStart + 1, objectStart + 1);
    });
  }

  private focusJsonAnchor(section: JumpSection, key: string): void {
    this.withEditorReady((editor) => {
      let text = this.draftText();
      let anchorIndex = this.findAnchorIndex(text, section, key);

      if (anchorIndex === -1 && this.draftIsValid()) {
        if (section === 'elements') {
          this.theme.ensureElementEntry(key);
        } else if (section === 'layout') {
          this.theme.ensureLayoutEntry(key);
        } else if (section === 'animations') {
          this.theme.setAnimation(key, createAnimationStarterDefinition(), false);
        }

        text = this.draftText();
        anchorIndex = this.findAnchorIndex(text, section, key);
      }

      if (anchorIndex === -1) {
        editor.focus();
        return;
      }

      const selectionEnd = Math.min(anchorIndex + key.length + 2, text.length);

      editor.focusRange(anchorIndex, selectionEnd);
    });
  }

  private focusJsonProperty(section: JumpSection, key: string, property: string): void {
    this.withEditorReady((editor) => {
      const text = this.draftText();
      const keyIndex = this.findAnchorIndex(text, section, key);

      if (keyIndex === -1) {
        editor.focus();
        return;
      }

      const propertyIndex = text.indexOf(`"${property}"`, keyIndex);

      if (propertyIndex === -1) {
        editor.focusRange(keyIndex, Math.min(keyIndex + key.length + 2, text.length));
        return;
      }

      const valueRange = this.findJsonPropertyValueRange(text, propertyIndex);

      editor.focusRange(valueRange.start, valueRange.end);
    });
  }

  private findJsonPropertyValueRange(text: string, propertyIndex: number): { start: number; end: number } {
    const colonIndex = text.indexOf(':', propertyIndex);

    if (colonIndex === -1) {
      return {
        start: propertyIndex,
        end: propertyIndex
      };
    }

    let valueStart = colonIndex + 1;

    while (valueStart < text.length && /\s/.test(text[valueStart])) {
      valueStart += 1;
    }

    if (text[valueStart] === '"') {
      const stringContentStart = valueStart + 1;

      let stringContentEnd = stringContentStart;

      while (stringContentEnd < text.length) {
        if (text[stringContentEnd] === '"' && text[stringContentEnd - 1] !== '\\') {
          break;
        }

        stringContentEnd += 1;
      }

      return {
        start: stringContentStart,
        end: stringContentEnd
      };
    }

    let valueEnd = valueStart;

    while (valueEnd < text.length && ![
      ',',
      '\n',
      '}'
    ].includes(text[valueEnd])) {
      valueEnd += 1;
    }

    return {
      start: valueStart,
      end: Math.max(valueStart, valueEnd)
    };
  }

  private focusJsonSection(section: JumpSection): void {
    this.withEditorReady((editor) => {
      const text = this.draftText();
      const anchorIndex = text.indexOf(`"${section}"`);

      if (anchorIndex === -1) {
        editor.focus();
        return;
      }

      editor.focusRange(anchorIndex, Math.min(anchorIndex + section.length + 2, text.length));
    });
  }

  private async copyTextToClipboard(value: string): Promise<boolean> {
    if (navigator.clipboard?.writeText) {
      try {
        await navigator.clipboard.writeText(value);
        return true;
      } catch {}
    }

    const textarea = document.createElement('textarea');

    textarea.value = value;
    textarea.setAttribute('readonly', 'true');
    textarea.style.position = 'fixed';
    textarea.style.opacity = '0';
    textarea.style.pointerEvents = 'none';
    document.body.appendChild(textarea);
    textarea.select();

    try {
      const copied = document.execCommand('copy');

      if (copied) {
        return true;
      }
    } catch {
      window.prompt(this.appI18n.instant('theme.studio.llmGuidePrompt'), value);
      return false;
    } finally {
      document.body.removeChild(textarea);
    }

    window.prompt(this.appI18n.instant('theme.studio.llmGuidePrompt'), value);
    return false;
  }

  private setLlmGuideCopyMessage(message: string): void {
    this.llmGuideCopyMessage.set(message);

    if (this.llmGuideCopyTimeoutId) {
      clearTimeout(this.llmGuideCopyTimeoutId);
    }

    this.llmGuideCopyTimeoutId = setTimeout(() => {
      this.llmGuideCopyMessage.set(null);
      this.llmGuideCopyTimeoutId = null;
    }, 4000);
  }

  presetDisplayName(presetKey: string, fallbackName: string): string {
    const localized = this.appI18n.instant(`theme.presets.${presetKey}.name`);

    return localized === `theme.presets.${presetKey}.name` ? fallbackName : localized;
  }

  presetDescription(presetKey: string, fallbackDescription?: string): string | undefined {
    if (!fallbackDescription) {
      return undefined;
    }

    const localized = this.appI18n.instant(`theme.presets.${presetKey}.description`);

    return localized === `theme.presets.${presetKey}.description` ? fallbackDescription : localized;
  }

  isDefaultPresetName(name: string): boolean {
    return name === this.appI18n.instant('theme.presets.toju-default-dark-11.name');
  }

  private localizeRegistryEntry(entry: ThemeRegistryEntry): ThemeRegistryEntry {
    return {
      ...entry,
      label: this.appI18n.instant(`theme.registry.${entry.key}.label`),
      description: this.appI18n.instant(`theme.registry.${entry.key}.description`)
    };
  }

  private findAnchorIndex(text: string, section: JumpSection, key: string): number {
    const sectionAnchor = `"${section}": {`;
    const sectionIndex = text.indexOf(sectionAnchor);

    if (sectionIndex === -1) {
      return -1;
    }

    return text.indexOf(`"${key}"`, sectionIndex);
  }
}
