// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import * as i18n from '../../../../core/i18n/i18n.js';
import * as ComponentHelpers from '../../../components/helpers/helpers.js';
import * as LitHtml from '../../../lit-html/lit-html.js';
import cssVarSwatchStyles from './cssVarSwatch.css.js';

const UIStrings = {
  /**
  *@description Text displayed in a tooltip shown when hovering over a var() CSS function in the Styles pane when the custom property in this function does not exist. The parameter is the name of the property.
  *@example {--my-custom-property-name} PH1
  */
  sIsNotDefined: '{PH1} is not defined',
};
const str_ = i18n.i18n.registerUIStrings('ui/legacy/components/inline_editor/CSSVarSwatch.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
const {render, html, Directives} = LitHtml;

const VARIABLE_FUNCTION_REGEX = /(var\()(\s*--[^,)]+)(.*)/;

interface SwatchRenderData {
  text: string;
  computedValue: string|null;
  fromFallback: boolean;
  onLinkActivate: (varialeName: string) => void;
}

interface ParsedVariableFunction {
  pre: string;
  name: string;
  post: string;
}

export class CSSVarSwatch extends HTMLElement {
  static readonly litTagName = LitHtml.literal`devtools-css-var-swatch`;
  private readonly shadow = this.attachShadow({mode: 'open'});
  private text: string = '';
  private computedValue: string|null = null;
  private fromFallback: boolean = false;
  private onLinkActivate: (varialeName: string, event: MouseEvent|KeyboardEvent) => void = () => undefined;

  constructor() {
    super();

    this.tabIndex = -1;

    this.addEventListener('focus', () => {
      const link = this.shadow.querySelector<HTMLElement>('[role="link"]');

      if (link) {
        link.focus();
      }
    });
  }

  connectedCallback(): void {
    this.shadow.adoptedStyleSheets = [cssVarSwatchStyles];
  }

  set data(data: SwatchRenderData) {
    this.text = data.text;
    this.computedValue = data.computedValue;
    this.fromFallback = data.fromFallback;
    this.onLinkActivate = (variableName: string, event: MouseEvent|KeyboardEvent): void => {
      if (event instanceof MouseEvent && event.button !== 0) {
        return;
      }

      if (event instanceof KeyboardEvent && !isEnterOrSpaceKey(event)) {
        return;
      }

      data.onLinkActivate(variableName);
      event.consume(true);
    };
    this.render();
  }

  private parseVariableFunctionParts(): ParsedVariableFunction|null {
    // When the value of CSS var() is greater than two spaces, only one is
    // always displayed, and the actual number of spaces is displayed when
    // editing is clicked.
    const result = this.text.replace(/\s{2,}/g, ' ').match(VARIABLE_FUNCTION_REGEX);
    if (!result) {
      return null;
    }

    return {
      pre: result[1],
      name: result[2],
      post: result[3],
    };
  }

  private get variableName(): string {
    const match = this.text.match(/--[^,)]+/);
    if (match) {
      return match[0];
    }
    return '';
  }

  private renderLink(variableName: string): LitHtml.TemplateResult {
    const isDefined = this.computedValue && !this.fromFallback;

    const classes = Directives.classMap({
      'css-var-link': true,
      'undefined': !isDefined,
    });
    const title = isDefined ? this.computedValue : i18nString(UIStrings.sIsNotDefined, {PH1: variableName});
    // The this.variableName's space must be removed, otherwise it cannot be triggered when clicked.
    const onActivate = isDefined ? this.onLinkActivate.bind(this, this.variableName.trim()) : null;

    return html`<span class=${classes} title=${title} @mousedown=${onActivate} @keydown=${
        onActivate} role="link" tabindex="-1">${variableName}</span>`;
  }

  private render(): void {
    const functionParts = this.parseVariableFunctionParts();
    if (!functionParts) {
      render('', this.shadow, {host: this});
      return;
    }

    const link = this.renderLink(functionParts.name);

    // Disabled until https://crbug.com/1079231 is fixed.
    // clang-format off
    render(
      html`<span title=${this.computedValue || ''}>${functionParts.pre}${link}${functionParts.post}</span>`,
      this.shadow, { host: this });
    // clang-format on
  }
}

ComponentHelpers.CustomElements.defineComponent('devtools-css-var-swatch', CSSVarSwatch);

declare global {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  interface HTMLElementTagNameMap {
    'devtools-css-var-swatch': CSSVarSwatch;
  }
}
