import hljs from 'highlight.js';
import { injectStyle } from '@descope-ui/common/components-helpers';
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 copyIconSrc from './copy-icon.svg';
import checkIconSrc from './check-icon.svg';

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

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

  constructor() {
    super();

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

    injectStyle(
      `
      :host {
        display: inline-block;
        width: 100%;
      }
      .wrapper {
        display: grid;
        width: 100%;
      }
      code {
        grid-area: 1 / 1;
        display: block;
        width: 100%;
        min-height: 1em;
        overflow-x: auto;
      }
      pre {
        margin: 0;
      }
      .copy-btn {
        grid-area: 1 / 1;
        place-self: start end;
        margin: 8px;
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 0;
        border-style: solid;
        cursor: pointer;
        opacity: 0;
        transition: opacity 150ms ease, background 150ms ease;
      }
      .wrapper:hover .copy-btn,
      .copy-btn:focus-visible {
        opacity: 1;
      }
      .copy-btn descope-icon {
        width: 16px;
        height: 16px;
        pointer-events: none;
        flex-shrink: 0;
      }
      .copy-btn .check-icon {
        display: none;
      }
      .copy-btn.copied .check-icon {
        display: block;
      }
      .copy-btn.copied descope-icon:not(.check-icon) {
        display: none;
      }
      `,
      this
    );
  }

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

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

    if (this.getAttribute('copy-button') === 'true') {
      this.#initCopyButton();
    }

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

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

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

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

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

      if (attrName === 'copy-button') {
        if (newValue === 'true') {
          this.#initCopyButton();
        } else {
          this.#destroyCopyButton();
        }
      }

      this.#renderSnippet();
    }
  }

  // ── Copy button ────────────────────────────────────────────────────────────

  #initCopyButton() {
    if (this.shadowRoot.querySelector('.copy-btn')) return;

    const btn = document.createElement('button');
    btn.className = 'copy-btn';
    btn.type = 'button';
    btn.setAttribute('aria-label', 'Copy code');
    const copyIcon = document.createElement('descope-icon');
    copyIcon.setAttribute('src', copyIconSrc);

    const checkIcon = document.createElement('descope-icon');
    checkIcon.setAttribute('src', checkIconSrc);
    checkIcon.classList.add('check-icon');

    btn.appendChild(copyIcon);
    btn.appendChild(checkIcon);
    btn.addEventListener('click', () => this.#handleCopyClick());

    this.shadowRoot.querySelector('.wrapper').appendChild(btn);
  }

  #destroyCopyButton() {
    this.shadowRoot.querySelector('.copy-btn')?.remove();
  }

  #handleCopyClick() {
    const btn = this.shadowRoot.querySelector('.copy-btn');
    navigator.clipboard
      .writeText(decode(this.textContent))
      .then(() => {
        btn.classList.add('copied');
        setTimeout(() => btn.classList.remove('copied'), 2000);
      })
      .catch(() => {});
  }

  // ── Snippet rendering ──────────────────────────────────────────────────────

  #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 copyBtn = { selector: () => '.copy-btn' };

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' },
      copyButtonSize: [
        { ...copyBtn, property: 'width' },
        { ...copyBtn, property: 'height' },
      ],
      copyButtonBorderRadius: { ...copyBtn, property: 'border-radius' },
      copyButtonBorderWidth: { ...copyBtn, property: 'border-width' },
      copyButtonBorderColor: { ...copyBtn, property: 'border-color' },
      copyButtonBgColor: { ...copyBtn, property: 'background-color' },
      copyButtonHoverBgColor: { selector: () => '.copy-btn:hover', property: 'background-color' },
      copyButtonColor: { ...copyBtn, property: 'color' },
    },
  }),
  draggableMixin,
  componentNameValidationMixin
)(CodeSnippet);
