import hljs from 'highlight.js';
import { createStyleMixin, draggableMixin, componentNameValidationMixin } from '../../mixins';
import { compose } from '../../helpers';
import { getComponentName, observeChildren } from '../../helpers/componentHelpers';
import { createBaseClass } from '../../baseClasses/createBaseClass';
import { decode, tpl } from './helpers';
import { injectStyle } from '@descope-ui/common/components-helpers';

export const componentName = getComponentName('code-snippet');

class CodeSnippet extends createBaseClass({ componentName, baseSelector: ':host > code' }) {
  static get observedAttributes() {
    return ['lang', 'inline'];
  }

  constructor() {
    super();

    this.attachShadow({ mode: 'open' }).innerHTML = `
    <code class="hljs"></code>
    `;

    injectStyle(
      `
      :host {
        display: inline-block;
        width: 100%;
      }
      code {
        display: block;
        width: 100%;
        min-height: 1em;
        overflow-x: scroll;
        overflow-y: scroll;
      }
      pre {
        margin: 0;
      }
      `,
      this
    );
  }

  init() {
    super.init?.();

    this.lang = this.getAttribute('lang');
    this.isInline = this.getAttribute('inline') === 'true';

    observeChildren(this, this.#renderSnippet.bind(this));
  }

  get contentNode() {
    return this.shadowRoot.querySelector(this.baseSelector);
  }

  attributeChangedCallback(attrName, oldValue, newValue) {
    super.attributeChangedCallback?.(attrName, oldValue, newValue);

    if (oldValue !== newValue) {
      if (attrName === 'inline') {
        this.isInline = newValue;
      }

      if (attrName === 'lang') {
        this.lang = newValue;
      }

      this.#renderSnippet();
    }
  }

  #renderSnippet() {
    const sanitized = decode(this.textContent);
    const language = this.lang;

    if (!language) {
      this.contentNode.textContent = sanitized;
      return;
    }

    const { value: highlighted } = hljs.highlight(sanitized, { language });

    this.contentNode.innerHTML = tpl(highlighted, this.isInline);
  }
}

const {
  root,
  docTag,
  keyword,
  metaKeyword,
  templateTag,
  templateVariable,
  type,
  variableLanguage,
  title,
  titleClass,
  titleClassInherited,
  titleFunction,
  attr,
  attribute,
  literal,
  meta,
  number,
  operator,
  variable,
  selectorAttr,
  selectorClass,
  selectorId,
  regexp,
  string,
  metaString,
  builtIn,
  symbol,
  comment,
  code,
  formula,
  name,
  quote,
  selectorTag,
  selectorPseudo,
  subst,
  section,
  bullet,
  emphasis,
  strong,
  addition,
  deletion,
  charEscape,
  link,
  params,
  property,
  punctuation,
  tag,
} = {
  root: { selector: () => '.hljs' },
  docTag: { selector: () => '.hljs-doctag' },
  keyword: { selector: () => '.hljs-keyword' },
  metaKeyword: { selector: () => '.hljs-meta .hljs-keyword' },
  templateTag: { selector: () => '.hljs-template-tag' },
  templateVariable: { selector: () => '.hljs-template-variable' },
  type: { selector: () => '.hljs-type' },
  variableLanguage: { selector: () => '.hljs-variable.language' },
  title: { selector: () => '.hljs-title' },
  titleClass: { selector: () => '.hljs-title.class_' },
  titleClassInherited: { selector: () => '.hljs-title.class_.inherited__' },
  titleFunction: { selector: () => '.hljs-title.function_' },
  attr: { selector: () => '.hljs-attr' },
  attribute: { selector: () => '.hljs-attribute' },
  literal: { selector: () => '.hljs-literal' },
  meta: { selector: () => '.hljs-meta' },
  number: { selector: () => '.hljs-number' },
  operator: { selector: () => '.hljs-operator' },
  variable: { selector: () => '.hljs-variable' },
  selectorAttr: { selector: () => '.hljs-selector-attr' },
  selectorClass: { selector: () => '.hljs-selector-class' },
  selectorId: { selector: () => '.hljs-selector-id' },
  regexp: { selector: () => '.hljs-regexp' },
  string: { selector: () => '.hljs-string' },
  metaString: { selector: () => '.hljs-meta .hljs-string' },
  builtIn: { selector: () => '.hljs-built_in' },
  symbol: { selector: () => '.hljs-symbol' },
  comment: { selector: () => '.hljs-comment' },
  code: { selector: () => '.hljs-code' },
  formula: { selector: () => '.hljs-formula' },
  name: { selector: () => '.hljs-name' },
  quote: { selector: () => '.hljs-quote' },
  selectorTag: { selector: () => '.hljs-selector-tag' },
  selectorPseudo: { selector: () => '.hljs-selector-pseudo' },
  subst: { selector: () => '.hljs-subst' },
  section: { selector: () => '.hljs-section' },
  bullet: { selector: () => '.hljs-bullet' },
  emphasis: { selector: () => '.hljs-emphasis' },
  strong: { selector: () => '.hljs-strong' },
  addition: { selector: () => '.hljs-addition' },
  deletion: { selector: () => '.hljs-deletion' },
  charEscape: { selector: () => '.hljs-char.escape' },
  link: { selector: () => '.hljs-link' },
  params: { selector: () => '.hljs-params' },
  property: { selector: () => '.hljs-property' },
  punctuation: { selector: () => '.hljs-punctuation' },
  tag: { selector: () => '.hljs-tag' },
};

export const CodeSnippetClass = compose(
  createStyleMixin({
    mappings: {
      rootBgColor: { ...root, property: 'background-color' },
      rootTextColor: { ...root, property: 'color' },
      docTagTextColor: { ...docTag, property: 'color' },
      keywordTextColor: { ...keyword, property: 'color' },
      metaKeywordTextColor: { ...metaKeyword, property: 'color' },
      templateTagTextColor: { ...templateTag, property: 'color' },
      templateVariableTextColor: { ...templateVariable, property: 'color' },
      typeTextColor: { ...type, property: 'color' },
      variableLanguageTextColor: { ...variableLanguage, property: 'color' },
      titleTextColor: { ...title, property: 'color' },
      titleClassTextColor: { ...titleClass, property: 'color' },
      titleClassInheritedTextColor: { ...titleClassInherited, property: 'color' },
      titleFunctionTextColor: { ...titleFunction, property: 'color' },
      attrTextColor: { ...attr, property: 'color' },
      attributeTextColor: { ...attribute, property: 'color' },
      literalTextColor: { ...literal, property: 'color' },
      metaTextColor: { ...meta, property: 'color' },
      numberTextColor: { ...number, property: 'color' },
      operatorTextColor: { ...operator, property: 'color' },
      variableTextColor: { ...variable, property: 'color' },
      selectorAttrTextColor: { ...selectorAttr, property: 'color' },
      selectorClassTextColor: { ...selectorClass, property: 'color' },
      selectorIdTextColor: { ...selectorId, property: 'color' },
      regexpTextColor: { ...regexp, property: 'color' },
      stringTextColor: { ...string, property: 'color' },
      metaStringTextColor: { ...metaString, property: 'color' },
      builtInTextColor: { ...builtIn, property: 'color' },
      symbolTextColor: { ...symbol, property: 'color' },
      commentTextColor: { ...comment, property: 'color' },
      codeTextColor: { ...code, property: 'color' },
      formulaTextColor: { ...formula, property: 'color' },
      nameTextColor: { ...name, property: 'color' },
      quoteTextColor: { ...quote, property: 'color' },
      selectorTagTextColor: { ...selectorTag, property: 'color' },
      selectorPseudoTextColor: { ...selectorPseudo, property: 'color' },
      substTextColor: { ...subst, property: 'color' },
      sectionTextColor: { ...section, property: 'color' },
      bulletTextColor: { ...bullet, property: 'color' },
      emphasisTextColor: { ...emphasis, property: 'color' },
      strongTextColor: { ...strong, property: 'color' },
      additionTextColor: { ...addition, property: 'color' },
      additionBgColor: { ...addition, property: 'background-color' },
      deletionTextColor: { ...deletion, property: 'color' },
      deletionBgColor: { ...deletion, property: 'background-color' },
      charEscapeTextColor: { ...charEscape, property: 'color' },
      linkTextColor: { ...link, property: 'color' },
      paramsTextColor: { ...params, property: 'color' },
      propertyTextColor: { ...property, property: 'color' },
      punctuationTextColor: { ...punctuation, property: 'color' },
      tagTextColor: { ...tag, property: 'color' },
    },
  }),
  draggableMixin,
  componentNameValidationMixin
)(CodeSnippet);
