import { LitElement } from 'lit';

// Styles
import FontStyles from './styles/font-styles.js';
import InputStyles from './styles/input-styles.js';
import SchemaStyles from './styles/schema-styles.js';
import FlexStyles from './styles/flex-styles.js';
import TableStyles from './styles/table-styles.js';
import KeyFrameStyles from './styles/key-frame-styles.js';
import EndpointStyles from './styles/endpoint-styles.js';
import PrismStyles from './styles/prism-styles.js';
import TagInputStyles from './styles/tag-input-styles.js';
import TabStyles from './styles/tab-styles.js';
import NavStyles from './styles/nav-styles.js';
import InfoStyles from './styles/info-styles.js';
import advancedSearchStyles from './styles/advanced-search-styles.js';
import MainBodyStyles from './styles/main-body-styles.js';

import { advancedSearch, getCurrentElement, replaceState, sleep } from './utils/common-utils.js';
import { initI18n } from './languages/index.js';
import ProcessSpec from './utils/spec-parser.js';
import mainBodyTemplate from './templates/mainBodyTemplate.js';
import apiRequestStyles from './styles/api-request-styles.js';
import { checkForAuthToken } from './templates/security-scheme-template.js';
import './components/syntax-highlighter.js';
export default class OpenApiExplorer extends LitElement {
  constructor() {
    super();
    this.loading = true;
    const intersectionObserverOptions = {
      root: this.getRootNode().host,
      rootMargin: '-50px 0px -50px 0px', // when the element is visible 100px from bottom
      threshold: 0,
    };
    this.isIntersectionObserverActive = true;
    if (typeof IntersectionObserver !== 'undefined') {
      this.intersectionObserver = new IntersectionObserver((entries) => { this.onIntersect(entries); }, intersectionObserverOptions);
    } else {
      this.intersectionObserver = { disconnect() {}, observe() {} };
    }
  }

  static get properties() {
    return {
      // Heading
      headingText: { type: String, attribute: 'heading-text' },
      explorerLocation: { type: String, attribute: 'explorer-location' },

      // Spec
      specUrl: { type: String, attribute: 'spec-url' },

      // UI Layouts
      layout: { type: String },

      collapsed: {
        type: Boolean, attribute: 'collapse',
        converter(value) {
          return value !== 'false' && value !== false;
        }
      },
      operationsCollapsed: { type: Boolean },
      componentsCollapsed: { type: Boolean },

      defaultSchemaTab: { type: String, attribute: 'default-schema-tab' },
      responseAreaHeight: { type: String, attribute: 'response-area-height' },
      hideDefaults: {
        type: Boolean, attribute: 'hide-defaults',
        converter(value) {
          return value !== 'false' && value !== false;
        }
      },

      // Schema Styles
      displaySchemaAsTree: {
        type: Boolean, attribute: 'tree',
        converter(value) {
          return value !== 'false' && value !== false;
        }
      },
      schemaExpandLevel: { type: Number, attribute: 'schema-expand-level' },

      // API Server
      serverUrl: { type: String, attribute: 'server-url' },

      // Hide/Show Sections & Enable Disable actions
      hideInfo: {
        type: Boolean, attribute: 'hide-info',
        converter(value) {
          return value !== 'false' && value !== false;
        }
      },
      hideAuthentication: {
        type: Boolean, attribute: 'hide-authentication',
        converter(value) {
          return value !== 'false' && value !== false;
        }
      },
      hideExecution: {
        type: Boolean, attribute: 'hide-console',
        converter(value) {
          return value !== 'false' && value !== false;
        }
      },
      includeNulls: {
        type: Boolean, attribute: 'display-nulls',
        converter(value) {
          return value !== 'false' && value !== false;
        }
      },
      hideSearch: {
        type: Boolean, attribute: 'hide-search',
        converter(value) {
          return value !== 'false' && value !== false;
        }
      },
      hideServerSelection: {
        type: Boolean, attribute: 'hide-server-selection',
        converter(value) {
          return value !== 'false' && value !== false;
        }
      },
      hideComponents: {
        type: Boolean, attribute: 'hide-components',
        converter(value) {
          return value !== 'false' && value !== false;
        }
      },

      // Main Colors and Font
      primaryColor: { type: String, attribute: 'primary-color' },
      secondaryColor: { type: String, attribute: 'secondary-color' },
      bgColor: { type: String, attribute: 'bg-color' },
      bgHeaderColor: { type: String, attribute: 'header-bg-color' },
      textColor: { type: String, attribute: 'text-color' },
      headerColor: { type: String, attribute: 'header-color' },

      // Nav Bar Colors
      navBgColor: { type: String, attribute: 'nav-bg-color' },
      navTextColor: { type: String, attribute: 'nav-text-color' },
      navHoverBgColor: { type: String, attribute: 'nav-hover-bg-color' },
      navHoverTextColor: { type: String, attribute: 'nav-hover-text-color' },
      usePathInNavBar: {
        type: Boolean, attribute: 'use-path-in-nav-bar',
        converter(value) {
          return value !== 'false' && value !== false;
        }
      },

      // Fetch Options
      fetchCredentials: { type: String, attribute: 'fetch-credentials' },

      // Filters
      matchPaths: { type: String, attribute: 'match-paths' },

      // Internal Properties
      loading: { type: Boolean }, // indicates spec is being loaded
      showAdvancedSearchDialog: { type: Boolean },
      advancedSearchMatches: { type: Object }
    };
  }

  static finalizeStyles() {
    return [
      FontStyles,
      SchemaStyles,
      InputStyles,
      FlexStyles,
      TableStyles,
      KeyFrameStyles,
      EndpointStyles,
      PrismStyles,
      TabStyles,
      NavStyles,
      InfoStyles,
      TagInputStyles,
      advancedSearchStyles,
      apiRequestStyles,
      MainBodyStyles
    ];
  }

  // Startup
  connectedCallback() {
    super.connectedCallback();
    this.handleResize = this.handleResize.bind(this);
    window.addEventListener('resize', this.handleResize);
    this.loading = true;

    const parent = this.parentElement;
    if (parent) {
      if (parent.offsetWidth === 0 && parent.style.width === '') {
        parent.style.width = '100vw';
      }
      if (parent.offsetHeight === 0 && parent.style.height === '') {
        parent.style.height = '100vh';
      }
      if (parent.tagName === 'BODY') {
        if (!parent.style.marginTop) { parent.style.marginTop = '0'; }
        if (!parent.style.marginRight) { parent.style.marginRight = '0'; }
        if (!parent.style.marginBottom) { parent.style.marginBottom = '0'; }
        if (!parent.style.marginLeft) { parent.style.marginLeft = '0'; }
      }
    }

    this.renderStyle = 'focused';
    this.operationsCollapsed = this.collapsed;
    this.componentsCollapsed = this.collapsed;
    this.explorerLocation = this.explorerLocation || getCurrentElement();

    if (!this.defaultSchemaTab || !'body, model, form,'.includes(`${this.defaultSchemaTab},`)) { this.defaultSchemaTab = 'model'; }
    if (!this.schemaExpandLevel || this.schemaExpandLevel < 1) { this.schemaExpandLevel = 99999; }
    this.schemaHideReadOnly = ['post', 'put', 'patch', 'query'].join(',');
    this.schemaHideWriteOnly = true;
    if (!this.responseAreaHeight) {
      this.responseAreaHeight = '300px';
    }

    if (!this.fetchCredentials || !'omit, same-origin, include,'.includes(`${this.fetchCredentials},`)) { this.fetchCredentials = ''; }

    if (!this.showAdvancedSearchDialog) { this.showAdvancedSearchDialog = false; }

    window.addEventListener('hashchange', () => {
      this.scrollTo(getCurrentElement());
    }, true);
    this.handleResize();
  }

  // Cleanup
  disconnectedCallback() {
    this.intersectionObserver.disconnect();
    window.removeEventListener('resize', this.handleResize);
    super.disconnectedCallback();
  }

  render() {
    return mainBodyTemplate.call(this);
  }

  observeExpandedContent() {
    // Main Container
    const observeOverviewEls = this.shadowRoot.querySelectorAll('.observe-me');
    observeOverviewEls.forEach((targetEl) => {
      this.intersectionObserver.observe(targetEl);
    });
  }

  handleResize() {
    const mediaQueryResult = window.matchMedia('(min-width: 768px)');
    const newDisplay = mediaQueryResult.matches ? 'focused' : 'view';
    if (this.renderStyle !== newDisplay) {
      this.renderStyle = newDisplay;
      this.requestUpdate();
    }
  }

  attributeChangedCallback(name, oldVal, newVal) {
    if (name === 'spec-url') {
      if (oldVal !== newVal) {
        window.setTimeout(async () => {
          await this.loadSpec(newVal);
          // If the initial location is set, then attempt to scroll there
          if (this.explorerLocation) {
            this.scrollTo(this.explorerLocation);
          }
        }, 0);
      }
    }

    if (name === 'server-url' && newVal) {
      this.selectedServer = this.resolvedSpec?.servers.find((s) => s.url === newVal || !newVal) || { url: newVal, computedUrl: newVal };
    }

    if (name === 'render-style') {
      if (newVal === 'read') {
        window.setTimeout(() => {
          this.observeExpandedContent();
        }, 100);
      } else {
        this.intersectionObserver.disconnect();
      }
    }
    if (name === 'explorer-location') {
      window.setTimeout(() => {
        this.scrollTo(newVal);
      }, 0);
    }

    if (name === 'collapsed') {
      this.operationsCollapsed = newVal;
      this.componentsCollapsed = newVal;
    }
    super.attributeChangedCallback(name, oldVal, newVal);
  }

  onSearchChange(e) {
    this.matchPaths = e.target.value;
    const expand = !!this.matchPaths?.trim();
    this.operationsCollapsed = !expand;
    this.componentsCollapsed = !expand;
    this.resolvedSpec.tags.forEach((tag) => { tag.expanded = expand; });
    this.resolvedSpec.components.forEach((component) => { component.expanded = expand; });
    this.requestUpdate();
  }

  onClearSearch() {
    const searchEl = this.shadowRoot.getElementById('nav-bar-search');
    searchEl.value = '';
    this.matchPaths = '';
  }

  async onShowSearchModalClicked() {
    this.showAdvancedSearchDialog = true;
    // wait for the dialog to render
    await sleep(10);
    const inputEl = this.shadowRoot.getElementById('advanced-search-dialog-input');
    if (inputEl) {
      inputEl.focus();
    }
  }

  // Public Method
  async loadSpec(specUrlOrObject) {
    if (!specUrlOrObject) {
      return;
    }
    this.matchPaths = '';
    try {
      this.resolvedSpec = null;
      this.loading = true;
      this.loadingFailedError = null;
      const spec = await ProcessSpec(specUrlOrObject, this.serverUrl);
      this.loading = false;
      if (spec === undefined || spec === null) {
        console.error('Unable to resolve the API spec. '); // eslint-disable-line no-console
        return;
      }
      initI18n(spec.info?.['x-locale']);
      if (!this.serverUrl) {
        this.serverUrl = spec.servers[0]?.computedUrl || spec.servers[0]?.url;
      }
      this.selectedServer = spec.servers.find((s) => s.url === this.serverUrl || !this.serverUrl) || spec.servers[0];
      this.afterSpecParsedAndValidated(spec);
    } catch (err) {
      this.loading = false;
      this.loadingFailedError = err.message;
      this.resolvedSpec = null;
      console.error('OpenAPI Explorer: Unable to resolve the API spec..', err); // eslint-disable-line no-console
    }

    try {
      await checkForAuthToken.call(this);
    } catch (error) {
      // eslint-disable-next-line no-console
      console.error('Failed to check for authentication token', error);
    }
  }

  // Public Method
  async setAuthenticationConfiguration(apiKeyId, { token, clientId, clientSecret, redirectUri }) {
    const securityObj = this.resolvedSpec && this.resolvedSpec.securitySchemes.find((v) => (v.apiKeyId === apiKeyId));
    if (!securityObj) {
      throw Error('SecuritySchemeNotFound');
    }

    let authorizationToken = token && token.replace(/^(Bearer|Basic)\s+/i, '').trim();
    if (authorizationToken && securityObj.type && securityObj.type === 'http' && securityObj.scheme && securityObj.scheme.toLowerCase() === 'basic') {
      authorizationToken = `Basic ${btoa(authorizationToken)}`;
    } else if (authorizationToken && securityObj.scheme && securityObj.scheme.toLowerCase() === 'bearer') {
      authorizationToken = `Bearer ${authorizationToken}`;
    }

    securityObj.clientId = clientId && clientId.trim();
    securityObj.clientSecret = clientSecret && clientSecret.trim();
    securityObj.redirectUri = new URL(redirectUri && redirectUri.trim() || '', window.location.href).toString();

    securityObj.finalKeyValue = authorizationToken;
    await checkForAuthToken.call(this);
    this.requestUpdate();
  }

  afterSpecParsedAndValidated(spec) {
    this.resolvedSpec = spec;

    if (this.operationsCollapsed) {
      this.resolvedSpec.tags.forEach(t => t.expanded = false);
    }
    if (this.componentsCollapsed) {
      this.resolvedSpec.components.forEach(c => c.expanded = false);
    }

    this.dispatchEvent(new CustomEvent('spec-loaded', { bubbles: true, detail: spec }));
    this.requestUpdate();

    // Initiate IntersectionObserver and put it at the end of event loop, to allow loading all the child elements (must for larger specs)
    this.intersectionObserver.disconnect();

    if (this.renderStyle === 'focused') {
      const defaultElementId = !this.hideInfo ? 'overview' : this.resolvedSpec.tags && this.resolvedSpec.tags[0] && this.resolvedSpec.tags[0].paths[0];
      this.scrollTo(this.explorerLocation || defaultElementId);
    }

    if (this.renderStyle === 'view' && this.explorerLocation) {
      this.expandAndGotoOperation(this.explorerLocation);
    }
  }

  expandAndGotoOperation(elementId) {
    // Expand full operation and tag
    let isExpandingNeeded = false;

    const tag = this.resolvedSpec.tags.find(t => t.paths && t.paths.find(p => p.elementId === elementId));
    const path = tag?.paths?.find((p) => p.elementId === elementId);
    if (path && (!path.expanded || !tag.expanded)) {
      isExpandingNeeded = true;
      path.expanded = true;
      tag.expanded = true;
      this.requestUpdate();
    }

    // requestUpdate() and delay required, else we cant find element because it won't exist immediately
    const tmpElementId = elementId.indexOf('#') === -1 ? elementId : elementId.substring(1);
    window.setTimeout(() => {
      const gotoEl = this.shadowRoot.getElementById(tmpElementId);
      if (gotoEl) {
        gotoEl.scrollIntoView({ behavior: 'auto', block: 'start' });
        replaceState(tmpElementId);
      }
    }, isExpandingNeeded ? 150 : 0);
  }

  onIntersect(entries) {
    if (this.isIntersectionObserverActive === false) {
      return;
    }
    entries.forEach((entry) => {
      if (entry.isIntersecting && entry.intersectionRatio > 0) {
        const oldNavEl = this.shadowRoot.querySelector('.nav-bar-tag.active, .nav-bar-path.active, .nav-bar-info.active, .nav-bar-h1.active, .nav-bar-h2.active');
        const newNavEl = this.shadowRoot.getElementById(`link-${entry.target.id}`);

        // Add active class in the new element
        if (newNavEl) {
          replaceState(entry.target.id);
          newNavEl.scrollIntoView({ behavior: 'auto', block: 'center' });
          newNavEl.classList.add('active');
        }
        // Remove active class from previous element
        if (oldNavEl) {
          oldNavEl.classList.remove('active');
        }
      }
    });
  }

  // Called by anchor tags created using markdown
  handleHref(e) {
    if (e.target.tagName.toLowerCase() === 'a') {
      const anchor = e.target.getAttribute('href');
      if (anchor && anchor.startsWith('#')) {
        const gotoEl = this.shadowRoot.getElementById(anchor.replace('#', ''));
        if (gotoEl) {
          gotoEl.scrollIntoView({ behavior: 'auto', block: 'start' });
        }
      }
    }
  }

  /**
   * Called by
   *  - onClick of Navigation Bar
   *  - onClick of Advanced Search items
   *
   * Functionality:
   *  1. First deactivate IntersectionObserver
   *  2. Scroll to the element
   *  3. Activate IntersectionObserver (after little delay)
   *
  */
  scrollToEventTarget(event, scrollNavItemToView = true) {
    const navEl = event.currentTarget;
    if (!navEl.dataset.contentId) {
      return;
    }

    this.isIntersectionObserverActive = false;
    this.scrollTo(navEl.dataset.contentId, scrollNavItemToView);
    setTimeout(() => {
      this.isIntersectionObserverActive = true;
    }, 300);
  }

  scrollToCustomNavSectionTarget(event, scrollNavItemToView = true) {
    const navEl = event.currentTarget;
    if (!navEl.dataset.contentId) {
      return;
    }

    const navSectionSlot = this.shadowRoot.querySelector('slot.custom-nav-section');
    const assignedNodes = navSectionSlot?.assignedNodes();

    // clicked child node could be multiple levels deep in a custom nav
    const hasChildNode = node => {
      return node === event.target || node.children && [...node.children].some(c => hasChildNode(c));
    };

    let repeatedElementIndex = assignedNodes && [].findIndex.call(assignedNodes, (slot) => hasChildNode(slot));
    if (repeatedElementIndex === -1 && navEl.dataset.contentId.match(/^section--\d+/)) {
      repeatedElementIndex = Number(navEl.dataset.contentId.split('--')[1]) - 1;
    }

    this.isIntersectionObserverActive = false;
    this.scrollTo(navEl.dataset.contentId, scrollNavItemToView, repeatedElementIndex);
    setTimeout(() => {
      this.isIntersectionObserverActive = true;
    }, 300);
  }

  async scrollToSchemaComponentByName(schemaComponentNameEvent) {
    const schemaComponentName = schemaComponentNameEvent.detail;
    const schemaComponent = this.resolvedSpec?.components?.find(c => c.componentKeyId === 'schemas')?.subComponents?.find(s => s.name === schemaComponentName);
    if (schemaComponent) {
      await this.scrollTo(`cmp--${schemaComponent.id}`, true);
    }
  }

  // Public Method (scrolls to a given path and highlights the left-nav selection)
  async scrollTo(elementId, scrollNavItemToView = true, repeatedElementIndex) {
    try {
      await this.scrollToOrThrowException(elementId, scrollNavItemToView, repeatedElementIndex);
    } catch (error) {
      // There's an issue for lit elements for some browsers which are causing this issue we'll log here and still throw
      console.error('Failed to scroll to target', elementId, scrollNavItemToView, repeatedElementIndex, error); // eslint-disable-line no-console
      throw error;
    }
  }

  async scrollToOrThrowException(elementId, scrollNavItemToView = true, forcedRepeatedElementIndex) {
    if (!this.resolvedSpec) {
      return;
    }

    this.emitOperationChangedEvent(elementId);

    if (this.renderStyle === 'view') {
      this.expandAndGotoOperation(elementId);
      return;
    }

    // explorerLocation will get validated in the focused-endpoint-template
    this.explorerLocation = elementId;
    const tagFoundByPath = this.resolvedSpec.tags.find(t => t.paths.some(p => p.elementId === elementId));
    if (tagFoundByPath) {
      tagFoundByPath.expanded = true;
    }

    // Convert to Async and to the background, so that we can be sure that the operation has been expanded and put into view before trying to directly scroll to it (or it won't be found in the next line and even if it is, it might not be able to be scrolled into view)
    await sleep(0);

    // In the case of section scrolling, these are hard swaps, so just load "section". In the case of `tags` the headers have the element html Id in the last `--id`, so split that off and check for it
    // NOTE: Really this whole nonsense is because Marked, inserts -- between the prefix and the type and we cannot control it at all. When upgrading to Node 20, marked 16+, we will have to change this and we might even be able to make it work correctly. The biggest problem is that both the separator `--` and invalid character replacement `-`, can stack up.
    const contentEl = this.shadowRoot.getElementById(elementId?.startsWith('section') ? 'section' : elementId)
      // Remove the prefix of the section as headers in sub sections are not prefixed with the type.
      || elementId.split('--').length > 1 && this.shadowRoot.getElementById(elementId.split('--').slice(1).join('--'))
      // Remove the prefix of the operation (tag--) and the tag name (tag--NAME--) from header, as headers in sub sections are not prefixed with the type.
      || elementId.split('--').length > 2 && this.shadowRoot.getElementById(elementId.split('--').slice(2).join('--'))
      || this.shadowRoot.getElementById(elementId.split('--').slice(-1)[0]);

    if (!contentEl) {
      return;
    }

    // For focused APIs, always scroll to the top of the component
    let newNavEl;
    let waitForComponentToExpand = false;
    const elementIndex = forcedRepeatedElementIndex || forcedRepeatedElementIndex === 0 ? forcedRepeatedElementIndex : (Number(elementId.split('--')[1]) - 1);
    if (elementId.match(/^section/)) {
      const customSections = this.shadowRoot.querySelector('slot.custom-section');
      const assignedNodesToCustomSections = customSections?.assignedNodes();
      if (assignedNodesToCustomSections) {
        try {
          assignedNodesToCustomSections.map(customSection => {
            customSection.classList.remove('active');
          });
          const newActiveCustomSection = assignedNodesToCustomSections[(elementIndex)];
          if (newActiveCustomSection && !newActiveCustomSection.classList.contains('active')) {
            newActiveCustomSection.classList.add('active');
          }
        } catch (error) {
          // eslint-disable-next-line no-console
          console.error('Failed to switch between custom sections, usually happens because the DOM is not ready and has not loaded these sections yet.', error);
        }
      }

      const navSectionSlot = this.shadowRoot.querySelector('slot.custom-nav-section');
      const assignedNodes = navSectionSlot?.assignedNodes();
      newNavEl = assignedNodes?.[elementIndex];

      // Update Location Hash
      replaceState(`section--${elementIndex + 1}`);
    } else if (elementId.match('cmp--')) {
      const component = this.resolvedSpec.components.find(c => c.subComponents.find(sub => elementId.includes(sub.id)));
      if (component && !component.expanded) {
        waitForComponentToExpand = true;
        component.expanded = true;
      }
      contentEl.scrollIntoView({ behavior: 'auto', block: 'start' });

      // Update Location Hash
      replaceState(elementId);
      newNavEl = this.shadowRoot.getElementById(`link-${elementId}`);
    } else if (elementId.match('cmp--') || elementId.match('tag--') || elementId.match('overview--') || elementId.match('auth--') || elementId.match('servers--')) {
      contentEl.scrollIntoView({ behavior: 'auto', block: 'start' });

      // Update Location Hash
      replaceState(elementId);
      newNavEl = this.shadowRoot.getElementById(`link-${elementId}`);
    } else {
      this.shadowRoot.getElementById('operations-root').scrollIntoView({ behavior: 'auto', block: 'start' });

      // Update Location Hash
      replaceState(elementId);
      newNavEl = this.shadowRoot.getElementById(`link-${elementId}`);
    }

    // for focused style it is important to reset request-body-selection and response selection which maintains the state for in case of multiple req-body or multiple response mime-type
    const requestEl = this.shadowRoot.querySelector('api-request');
    if (requestEl) {
      requestEl.resetRequestBodySelection();
    }
    const responseEl = this.shadowRoot.querySelector('api-response');
    if (responseEl) {
      responseEl.resetSelection();
    }

    // Update NavBar View and Styles
    if (!newNavEl) {
      return;
    }

    if (scrollNavItemToView) {
      newNavEl.scrollIntoView({ behavior: 'auto', block: 'center' });
      // Also force it into view again if for some reason it isn't there
      if (waitForComponentToExpand) {
        setTimeout(() => newNavEl.scrollIntoView({ behavior: 'auto', block: 'center' }), 600);
      }
    }

    await sleep(0);
    const oldNavEl = this.shadowRoot.querySelector('.nav-bar-tag.active, .nav-bar-path.active, .nav-bar-info.active, .nav-bar-h1.active, .nav-bar-h2.active');
    if (oldNavEl) {
      oldNavEl.classList.remove('active');
    }
    const navSectionSlot = this.shadowRoot.querySelector('slot.custom-nav-section');
    const assignedNodes = navSectionSlot?.assignedNodes();
    (assignedNodes || []).filter((n, nodeIndex) => isNaN(elementIndex) || nodeIndex !== elementIndex).forEach((node) => {
      node.classList.remove('active');
    });

    newNavEl.classList.add('active'); // must add the class after scrolling
    this.requestUpdate();
  }

  // Event handler for Advanced Search text-inputs and checkboxes
  onAdvancedSearch(ev) {
    const eventTargetEl = ev.target;
    clearTimeout(this.timeoutId);
    this.timeoutId = setTimeout(() => {
      let searchInputEl;
      if (eventTargetEl.type === 'text') {
        searchInputEl = eventTargetEl;
      } else {
        searchInputEl = eventTargetEl.closest('.advanced-search-options').querySelector('input[type=text]');
      }
      const searchOptions = [...eventTargetEl.closest('.advanced-search-options').querySelectorAll('input:checked')].map((v) => v.id);
      this.advancedSearchMatches = advancedSearch(searchInputEl.value, this.resolvedSpec.tags, searchOptions);
    }, 0);
  }

  emitOperationChangedEvent(elementId) {
    const operation = this.resolvedSpec.tags.map(t => t.paths).flat(1).find(p => p.elementId === elementId);
    const event = { bubbles: true, composed: true, detail: { explorerLocation: elementId, operation, type: 'OperationChanged' } };
    this.dispatchEvent(new CustomEvent('event', event));
  }
}

if (!customElements.get('openapi-explorer')) {
  customElements.define('openapi-explorer', OpenApiExplorer);
}
import './openapi-explorer-oauth-handler.js';

