// Copyright 2021 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.

/*
 * Copyright (C) 2010 Nikita Vasilyev. All rights reserved.
 * Copyright (C) 2010 Joseph Pecoraro. All rights reserved.
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the #name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import * as Protocol from '../../generated/protocol.js';
import * as SupportedCSSProperties from '../../generated/SupportedCSSProperties.js';
import * as Common from '../common/common.js';
import * as Platform from '../platform/platform.js';

export class CSSMetadata {
  readonly #values: string[];
  readonly #longhands: Map<string, string[]>;
  readonly #shorthands: Map<string, string[]>;
  readonly #inherited: Set<string>;
  readonly #svgProperties: Set<string>;
  readonly #propertyValues: Map<string, string[]>;
  readonly #aliasesFor: Map<string, string>;
  #valuesSet: Set<string>;
  readonly #nameValuePresetsInternal: string[];
  readonly #nameValuePresetsIncludingSVG: string[];

  constructor(properties: CSSPropertyDefinition[], aliasesFor: Map<string, string>) {
    this.#values = [];
    this.#longhands = new Map();
    this.#shorthands = new Map();
    this.#inherited = new Set();
    this.#svgProperties = new Set();
    this.#propertyValues = new Map();
    this.#aliasesFor = aliasesFor;
    for (let i = 0; i < properties.length; ++i) {
      const property = properties[i];
      const propertyName = property.name;
      if (!CSS.supports(propertyName, 'initial')) {
        continue;
      }
      this.#values.push(propertyName);

      if (property.inherited) {
        this.#inherited.add(propertyName);
      }
      if (property.svg) {
        this.#svgProperties.add(propertyName);
      }

      const longhands = properties[i].longhands;
      if (longhands) {
        this.#longhands.set(propertyName, longhands);
        for (let j = 0; j < longhands.length; ++j) {
          const longhandName = longhands[j];
          let shorthands = this.#shorthands.get(longhandName);
          if (!shorthands) {
            shorthands = [];
            this.#shorthands.set(longhandName, shorthands);
          }
          shorthands.push(propertyName);
        }
      }
    }
    this.#values.sort(CSSMetadata.sortPrefixesToEnd);
    this.#valuesSet = new Set(this.#values);

    // Reads in auto-generated property names and #values from blink/public/renderer/core/css/css_properties.json5
    // treats _generatedPropertyValues as basis
    const propertyValueSets = new Map<string, Set<string>>();
    for (const [propertyName, basisValueObj] of Object.entries(SupportedCSSProperties.generatedPropertyValues)) {
      propertyValueSets.set(propertyName, new Set(basisValueObj.values));
    }
    // and add manually maintained map of extra prop-value pairs
    for (const [propertyName, extraValueObj] of Object.entries(extraPropertyValues)) {
      const propertyValueSet = propertyValueSets.get(propertyName);
      if (propertyValueSet) {
        Platform.SetUtilities.addAll(propertyValueSet, extraValueObj.values);
      } else {
        propertyValueSets.set(propertyName, new Set(extraValueObj.values));
      }
    }
    // finally add common keywords to value sets and convert property #values
    // into arrays since callers expect arrays
    for (const [propertyName, values] of propertyValueSets) {
      for (const commonKeyword of CommonKeywords) {
        if (!values.has(commonKeyword) && CSS.supports(propertyName, commonKeyword)) {
          values.add(commonKeyword);
        }
      }

      this.#propertyValues.set(propertyName, [...values]);
    }

    this.#nameValuePresetsInternal = [];
    this.#nameValuePresetsIncludingSVG = [];
    for (const name of this.#valuesSet) {
      const values = this.specificPropertyValues(name)
                         .filter(value => CSS.supports(name, value))
                         .sort(CSSMetadata.sortPrefixesToEnd);
      const presets = values.map(value => `${name}: ${value}`);
      if (!this.isSVGProperty(name)) {
        this.#nameValuePresetsInternal.push(...presets);
      }
      this.#nameValuePresetsIncludingSVG.push(...presets);
    }
  }

  private static sortPrefixesToEnd(a: string, b: string): 1|- 1|0 {
    const aIsPrefixed = a.startsWith('-webkit-');
    const bIsPrefixed = b.startsWith('-webkit-');
    if (aIsPrefixed && !bIsPrefixed) {
      return 1;
    }
    if (!aIsPrefixed && bIsPrefixed) {
      return -1;
    }
    return a < b ? -1 : (a > b ? 1 : 0);
  }

  allProperties(): string[] {
    return this.#values;
  }

  nameValuePresets(includeSVG?: boolean): string[] {
    return includeSVG ? this.#nameValuePresetsIncludingSVG : this.#nameValuePresetsInternal;
  }

  isSVGProperty(name: string): boolean {
    name = name.toLowerCase();
    return this.#svgProperties.has(name);
  }

  getLonghands(shorthand: string): string[]|null {
    return this.#longhands.get(shorthand) || null;
  }

  getShorthands(longhand: string): string[]|null {
    return this.#shorthands.get(longhand) || null;
  }

  isColorAwareProperty(propertyName: string): boolean {
    return colorAwareProperties.has(propertyName.toLowerCase()) || this.isCustomProperty(propertyName.toLowerCase());
  }

  isFontFamilyProperty(propertyName: string): boolean {
    return propertyName.toLowerCase() === 'font-family';
  }

  isAngleAwareProperty(propertyName: string): boolean {
    const lowerCasedName = propertyName.toLowerCase();
    // TODO: @Yisi, parse hsl(), hsla(), hwb() and lch()
    // See also https://drafts.csswg.org/css-color/#hue-syntax
    return colorAwareProperties.has(lowerCasedName) || angleAwareProperties.has(lowerCasedName);
  }

  isGridAreaDefiningProperty(propertyName: string): boolean {
    propertyName = propertyName.toLowerCase();
    return propertyName === 'grid' || propertyName === 'grid-template' || propertyName === 'grid-template-areas';
  }

  isLengthProperty(propertyName: string): boolean {
    propertyName = propertyName.toLowerCase();
    if (propertyName === 'line-height') {
      return false;
    }
    return distanceProperties.has(propertyName) || propertyName.startsWith('margin') ||
        propertyName.startsWith('padding') || propertyName.indexOf('width') !== -1 ||
        propertyName.indexOf('height') !== -1;
  }

  isBezierAwareProperty(propertyName: string): boolean {
    propertyName = propertyName.toLowerCase();
    return bezierAwareProperties.has(propertyName) || this.isCustomProperty(propertyName);
  }

  isFontAwareProperty(propertyName: string): boolean {
    propertyName = propertyName.toLowerCase();
    return fontAwareProperties.has(propertyName) || this.isCustomProperty(propertyName);
  }

  isCustomProperty(propertyName: string): boolean {
    return propertyName.startsWith('--');
  }

  isShadowProperty(propertyName: string): boolean {
    propertyName = propertyName.toLowerCase();
    return propertyName === 'box-shadow' || propertyName === 'text-shadow' || propertyName === '-webkit-box-shadow';
  }

  isStringProperty(propertyName: string): boolean {
    propertyName = propertyName.toLowerCase();
    // TODO(crbug.com/1033910): Generalize this to all CSS properties
    // that accept <string> #values.
    return propertyName === 'content';
  }

  canonicalPropertyName(name: string): string {
    if (this.isCustomProperty(name)) {
      return name;
    }
    name = name.toLowerCase();

    const aliasFor = this.#aliasesFor.get(name);
    if (aliasFor) {
      return aliasFor;
    }

    if (!name || name.length < 9 || name.charAt(0) !== '-') {
      return name;
    }
    const match = name.match(/(?:-webkit-)(.+)/);
    if (!match || !this.#valuesSet.has(match[1])) {
      return name;
    }
    return match[1];
  }

  isCSSPropertyName(propertyName: string): boolean {
    propertyName = propertyName.toLowerCase();
    if ((propertyName.startsWith('--') && propertyName.length > 2) || propertyName.startsWith('-moz-') ||
        propertyName.startsWith('-ms-') || propertyName.startsWith('-o-') || propertyName.startsWith('-webkit-')) {
      return true;
    }
    return this.#valuesSet.has(propertyName);
  }

  isPropertyInherited(propertyName: string): boolean {
    propertyName = propertyName.toLowerCase();
    return propertyName.startsWith('--') || this.#inherited.has(this.canonicalPropertyName(propertyName)) ||
        this.#inherited.has(propertyName);
  }

  private specificPropertyValues(propertyName: string): string[] {
    const unprefixedName = propertyName.replace(/^-webkit-/, '');
    const propertyValues = this.#propertyValues;
    // #propertyValues acts like cache; missing properties are added with possible common keywords
    let keywords: (string[]|undefined) = propertyValues.get(propertyName) || propertyValues.get(unprefixedName);
    if (!keywords) {
      keywords = [];
      for (const commonKeyword of CommonKeywords) {
        if (CSS.supports(propertyName, commonKeyword)) {
          keywords.push(commonKeyword);
        }
      }
      propertyValues.set(propertyName, keywords);
    }
    return keywords;
  }

  getPropertyValues(propertyName: string): string[] {
    const acceptedKeywords = ['inherit', 'initial', 'revert', 'unset'];
    propertyName = propertyName.toLowerCase();
    acceptedKeywords.push(...this.specificPropertyValues(propertyName));
    if (this.isColorAwareProperty(propertyName)) {
      acceptedKeywords.push('currentColor');
      for (const color of Common.Color.Nicknames.keys()) {
        acceptedKeywords.push(color);
      }
    }
    return acceptedKeywords.sort(CSSMetadata.sortPrefixesToEnd);
  }

  propertyUsageWeight(property: string): number {
    return Weight.get(property) || Weight.get(this.canonicalPropertyName(property)) || 0;
  }

  getValuePreset(key: string, value: string): {
    text: string,
    startColumn: number,
    endColumn: number,
  }|null {
    const values = valuePresets.get(key);
    let text: string|(string | null | undefined) = values ? values.get(value) : null;
    if (!text) {
      return null;
    }
    let startColumn: number = text.length;
    let endColumn: number = text.length;
    if (text) {
      startColumn = text.indexOf('|');
      endColumn = text.lastIndexOf('|');
      endColumn = startColumn === endColumn ? endColumn : endColumn - 1;
      text = text.replace(/\|/g, '');
    }
    return {text, startColumn, endColumn};
  }

  isHighlightPseudoType(pseudoType: Protocol.DOM.PseudoType): boolean {
    return (
        pseudoType === Protocol.DOM.PseudoType.Highlight || pseudoType === Protocol.DOM.PseudoType.Selection ||
        pseudoType === Protocol.DOM.PseudoType.TargetText || pseudoType === Protocol.DOM.PseudoType.GrammarError ||
        pseudoType === Protocol.DOM.PseudoType.SpellingError);
  }
}

export const VariableRegex = /(var\(\s*--.*?\))/g;
export const CustomVariableRegex = /(var\(*--[\w\d]+-([\w]+-[\w]+)\))/g;
export const URLRegex = /url\(\s*('.+?'|".+?"|[^)]+)\s*\)/g;

/**
 * Matches an instance of a grid area 'row' definition.
 * 'grid-template-areas', e.g.
 *    "a a ."
 *
 * 'grid', 'grid-template', e.g.
 *    [track-#name] "a a ." minmax(50px, auto) [track-#name]
 */
export const GridAreaRowRegex = /((?:\[[\w\- ]+\]\s*)*(?:"[^"]+"|'[^']+'))[^'"\[]*\[?[^'"\[]*/;

let cssMetadataInstance: CSSMetadata|null = null;

export function cssMetadata(): CSSMetadata {
  if (!cssMetadataInstance) {
    const supportedProperties = (SupportedCSSProperties.generatedProperties as CSSPropertyDefinition[]);
    cssMetadataInstance = new CSSMetadata(supportedProperties, SupportedCSSProperties.generatedAliasesFor);
  }
  return cssMetadataInstance;
}

/**
 * The pipe character '|' indicates where text selection should be set.
 */
const imageValuePresetMap = new Map([
  ['linear-gradient', 'linear-gradient(|45deg, black, transparent|)'],
  ['radial-gradient', 'radial-gradient(|black, transparent|)'],
  ['repeating-linear-gradient', 'repeating-linear-gradient(|45deg, black, transparent 100px|)'],
  ['repeating-radial-gradient', 'repeating-radial-gradient(|black, transparent 100px|)'],
  ['url', 'url(||)'],
]);

const filterValuePresetMap = new Map([
  ['blur', 'blur(|1px|)'],
  ['brightness', 'brightness(|0.5|)'],
  ['contrast', 'contrast(|0.5|)'],
  ['drop-shadow', 'drop-shadow(|2px 4px 6px black|)'],
  ['grayscale', 'grayscale(|1|)'],
  ['hue-rotate', 'hue-rotate(|45deg|)'],
  ['invert', 'invert(|1|)'],
  ['opacity', 'opacity(|0.5|)'],
  ['saturate', 'saturate(|0.5|)'],
  ['sepia', 'sepia(|1|)'],
  ['url', 'url(||)'],
]);

const valuePresets = new Map([
  ['filter', filterValuePresetMap],
  ['backdrop-filter', filterValuePresetMap],
  ['background', imageValuePresetMap],
  ['background-image', imageValuePresetMap],
  ['-webkit-mask-image', imageValuePresetMap],
  [
    'transform',
    new Map([
      ['scale', 'scale(|1.5|)'],
      ['scaleX', 'scaleX(|1.5|)'],
      ['scaleY', 'scaleY(|1.5|)'],
      ['scale3d', 'scale3d(|1.5, 1.5, 1.5|)'],
      ['rotate', 'rotate(|45deg|)'],
      ['rotateX', 'rotateX(|45deg|)'],
      ['rotateY', 'rotateY(|45deg|)'],
      ['rotateZ', 'rotateZ(|45deg|)'],
      ['rotate3d', 'rotate3d(|1, 1, 1, 45deg|)'],
      ['skew', 'skew(|10deg, 10deg|)'],
      ['skewX', 'skewX(|10deg|)'],
      ['skewY', 'skewY(|10deg|)'],
      ['translate', 'translate(|10px, 10px|)'],
      ['translateX', 'translateX(|10px|)'],
      ['translateY', 'translateY(|10px|)'],
      ['translateZ', 'translateZ(|10px|)'],
      ['translate3d', 'translate3d(|10px, 10px, 10px|)'],
      ['matrix', 'matrix(|1, 0, 0, 1, 0, 0|)'],
      ['matrix3d', 'matrix3d(|1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1|)'],
      ['perspective', 'perspective(|10px|)'],
    ]),
  ],
]);

const distanceProperties = new Set<string>([
  'background-position',
  'border-spacing',
  'bottom',
  'font-size',
  'height',
  'left',
  'letter-spacing',
  'max-height',
  'max-width',
  'min-height',
  'min-width',
  'right',
  'text-indent',
  'top',
  'width',
  'word-spacing',
  'grid-row-gap',
  'grid-column-gap',
  'row-gap',
]);

const bezierAwareProperties = new Set<string>([
  'animation',
  'animation-timing-function',
  'transition',
  'transition-timing-function',
  '-webkit-animation',
  '-webkit-animation-timing-function',
  '-webkit-transition',
  '-webkit-transition-timing-function',
]);

const fontAwareProperties =
    new Set<string>(['font-size', 'line-height', 'font-weight', 'font-family', 'letter-spacing']);

const colorAwareProperties = new Set<string>([
  'accent-color',
  'background',
  'background-color',
  'background-image',
  'border',
  'border-color',
  'border-image',
  'border-image-source',
  'border-bottom',
  'border-bottom-color',
  'border-left',
  'border-left-color',
  'border-right',
  'border-right-color',
  'border-top',
  'border-top-color',
  'box-shadow',
  'caret-color',
  'color',
  'column-rule',
  'column-rule-color',
  'content',
  'fill',
  'list-style-image',
  'outline',
  'outline-color',
  'stroke',
  'text-decoration-color',
  'text-shadow',
  '-webkit-border-after',
  '-webkit-border-after-color',
  '-webkit-border-before',
  '-webkit-border-before-color',
  '-webkit-border-end',
  '-webkit-border-end-color',
  '-webkit-border-start',
  '-webkit-border-start-color',
  '-webkit-box-reflect',
  '-webkit-box-shadow',
  '-webkit-column-rule-color',
  '-webkit-mask',
  '-webkit-mask-box-image',
  '-webkit-mask-box-image-source',
  '-webkit-mask-image',
  '-webkit-tap-highlight-color',
  '-webkit-text-decoration-color',
  '-webkit-text-emphasis',
  '-webkit-text-emphasis-color',
  '-webkit-text-fill-color',
  '-webkit-text-stroke',
  '-webkit-text-stroke-color',
]);

// In addition to `_colorAwareProperties`, the following properties contain CSS <angle> units.
const angleAwareProperties = new Set<string>([
  '-webkit-border-image',
  'transform',
  '-webkit-transform',
  'rotate',
  'filter',
  '-webkit-filter',
  'backdrop-filter',
  'offset',
  'offset-rotate',
  'font-style',
]);

// manually maintained list of property #values to add into autocomplete list
const extraPropertyValues = {
  'background-repeat': {values: ['repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'space', 'round']},
  'content': {values: ['normal', 'close-quote', 'no-close-quote', 'no-open-quote', 'open-quote']},
  'baseline-shift': {values: ['baseline']},
  'max-height': {values: ['min-content', 'max-content', '-webkit-fill-available', 'fit-content']},
  'color': {values: ['black']},
  'background-color': {values: ['white']},
  'box-shadow': {values: ['inset']},
  'text-shadow': {values: ['0 0 black']},
  '-webkit-writing-mode': {values: ['horizontal-tb', 'vertical-rl', 'vertical-lr']},
  'writing-mode': {values: ['lr', 'rl', 'tb', 'lr-tb', 'rl-tb', 'tb-rl']},
  'page-break-inside': {values: ['avoid']},
  'cursor': {values: ['-webkit-zoom-in', '-webkit-zoom-out', '-webkit-grab', '-webkit-grabbing']},
  'border-width': {values: ['medium', 'thick', 'thin']},
  'border-style': {values: ['hidden', 'inset', 'groove', 'ridge', 'outset', 'dotted', 'dashed', 'solid', 'double']},
  'size': {values: ['a3', 'a4', 'a5', 'b4', 'b5', 'landscape', 'ledger', 'legal', 'letter', 'portrait']},
  'overflow': {values: ['hidden', 'visible', 'overlay', 'scroll']},
  'overscroll-behavior': {values: ['contain']},
  'text-rendering': {values: ['optimizeSpeed', 'optimizeLegibility', 'geometricPrecision']},
  'text-align': {values: ['-webkit-auto', '-webkit-match-parent']},
  'clip-path': {values: ['circle', 'ellipse', 'inset', 'polygon', 'url']},
  'color-interpolation': {values: ['sRGB', 'linearRGB']},
  'word-wrap': {values: ['normal', 'break-word']},
  'font-weight': {values: ['100', '200', '300', '400', '500', '600', '700', '800', '900']},
  '-webkit-text-emphasis': {values: ['circle', 'filled', 'open', 'dot', 'double-circle', 'triangle', 'sesame']},
  'color-rendering': {values: ['optimizeSpeed', 'optimizeQuality']},
  '-webkit-text-combine': {values: ['horizontal']},
  'text-orientation': {values: ['sideways-right']},
  'outline': {
    values: ['inset', 'groove', 'ridge', 'outset', 'dotted', 'dashed', 'solid', 'double', 'medium', 'thick', 'thin'],
  },
  'font': {
    values: [
      'caption',
      'icon',
      'menu',
      'message-box',
      'small-caption',
      '-webkit-mini-control',
      '-webkit-small-control',
      '-webkit-control',
      'status-bar',
    ],
  },
  'dominant-baseline': {values: ['text-before-edge', 'text-after-edge', 'use-script', 'no-change', 'reset-size']},
  '-webkit-text-emphasis-position': {values: ['over', 'under']},
  'alignment-baseline': {values: ['before-edge', 'after-edge', 'text-before-edge', 'text-after-edge', 'hanging']},
  'page-break-before': {values: ['left', 'right', 'always', 'avoid']},
  'border-image': {values: ['repeat', 'stretch', 'space', 'round']},
  'text-decoration':
      {values: ['blink', 'line-through', 'overline', 'underline', 'wavy', 'double', 'solid', 'dashed', 'dotted']},
  // List taken from https://drafts.csswg.org/css-fonts-4/#generic-font-families
  'font-family': {
    values: [
      'serif',
      'sans-serif',
      'cursive',
      'fantasy',
      'monospace',
      'system-ui',
      'emoji',
      'math',
      'fangsong',
      'ui-serif',
      'ui-sans-serif',
      'ui-monospace',
      'ui-rounded',
      '-webkit-body',
    ],
  },
  'zoom': {values: ['normal']},
  'max-width': {values: ['min-content', 'max-content', '-webkit-fill-available', 'fit-content']},
  '-webkit-font-smoothing': {values: ['antialiased', 'subpixel-antialiased']},
  'border': {
    values: [
      'hidden',
      'inset',
      'groove',
      'ridge',
      'outset',
      'dotted',
      'dashed',
      'solid',
      'double',
      'medium',
      'thick',
      'thin',
    ],
  },
  'font-variant': {
    values: [
      'small-caps',
      'normal',
      'common-ligatures',
      'no-common-ligatures',
      'discretionary-ligatures',
      'no-discretionary-ligatures',
      'historical-ligatures',
      'no-historical-ligatures',
      'contextual',
      'no-contextual',
      'all-small-caps',
      'petite-caps',
      'all-petite-caps',
      'unicase',
      'titling-caps',
      'lining-nums',
      'oldstyle-nums',
      'proportional-nums',
      'tabular-nums',
      'diagonal-fractions',
      'stacked-fractions',
      'ordinal',
      'slashed-zero',
      'jis78',
      'jis83',
      'jis90',
      'jis04',
      'simplified',
      'traditional',
      'full-width',
      'proportional-width',
      'ruby',
    ],
  },
  'vertical-align': {values: ['top', 'bottom', '-webkit-baseline-middle']},
  'page-break-after': {values: ['left', 'right', 'always', 'avoid']},
  '-webkit-text-emphasis-style': {values: ['circle', 'filled', 'open', 'dot', 'double-circle', 'triangle', 'sesame']},
  'transform': {
    values: [
      'scale',      'scaleX',     'scaleY',      'scale3d', 'rotate',   'rotateX',     'rotateY',
      'rotateZ',    'rotate3d',   'skew',        'skewX',   'skewY',    'translate',   'translateX',
      'translateY', 'translateZ', 'translate3d', 'matrix',  'matrix3d', 'perspective',
    ],
  },
  'align-content': {
    values: [
      'normal',
      'baseline',
      'space-between',
      'space-around',
      'space-evenly',
      'stretch',
      'center',
      'start',
      'end',
      'flex-start',
      'flex-end',
    ],
  },
  'justify-content': {
    values: [
      'normal',
      'space-between',
      'space-around',
      'space-evenly',
      'stretch',
      'center',
      'start',
      'end',
      'flex-start',
      'flex-end',
      'left',
      'right',
    ],
  },
  'place-content': {
    values: [
      'normal',
      'space-between',
      'space-around',
      'space-evenly',
      'stretch',
      'center',
      'start',
      'end',
      'flex-start',
      'flex-end',
      'baseline',
    ],
  },
  'align-items': {
    values:
        ['normal', 'stretch', 'baseline', 'center', 'start', 'end', 'self-start', 'self-end', 'flex-start', 'flex-end'],
  },
  'justify-items': {
    values: [
      'normal',
      'stretch',
      'baseline',
      'center',
      'start',
      'end',
      'self-start',
      'self-end',
      'flex-start',
      'flex-end',
      'left',
      'right',
      'legacy',
    ],
  },
  'place-items': {
    values:
        ['normal', 'stretch', 'baseline', 'center', 'start', 'end', 'self-start', 'self-end', 'flex-start', 'flex-end'],
  },
  'align-self': {
    values:
        ['normal', 'stretch', 'baseline', 'center', 'start', 'end', 'self-start', 'self-end', 'flex-start', 'flex-end'],
  },
  'justify-self': {
    values: [
      'normal',
      'stretch',
      'baseline',
      'center',
      'start',
      'end',
      'self-start',
      'self-end',
      'flex-start',
      'flex-end',
      'left',
      'right',
    ],
  },
  'place-self': {
    values:
        ['normal', 'stretch', 'baseline', 'center', 'start', 'end', 'self-start', 'self-end', 'flex-start', 'flex-end'],
  },
  'perspective-origin': {values: ['left', 'center', 'right', 'top', 'bottom']},
  'transform-origin': {values: ['left', 'center', 'right', 'top', 'bottom']},
  'transition-timing-function': {values: ['cubic-bezier', 'steps']},
  'animation-timing-function': {values: ['cubic-bezier', 'steps']},
  '-webkit-backface-visibility': {values: ['visible', 'hidden']},
  '-webkit-column-break-after': {values: ['always', 'avoid']},
  '-webkit-column-break-before': {values: ['always', 'avoid']},
  '-webkit-column-break-inside': {values: ['avoid']},
  '-webkit-column-span': {values: ['all']},
  '-webkit-column-gap': {values: ['normal']},
  'filter': {
    values: [
      'url',
      'blur',
      'brightness',
      'contrast',
      'drop-shadow',
      'grayscale',
      'hue-rotate',
      'invert',
      'opacity',
      'saturate',
      'sepia',
    ],
  },
  'backdrop-filter': {
    values: [
      'url',
      'blur',
      'brightness',
      'contrast',
      'drop-shadow',
      'grayscale',
      'hue-rotate',
      'invert',
      'opacity',
      'saturate',
      'sepia',
    ],
  },
  'mix-blend-mode': {values: ['unset']},
  'background-blend-mode': {values: ['unset']},
  'grid-template-columns': {values: ['min-content', 'max-content']},
  'grid-template-rows': {values: ['min-content', 'max-content']},
  'grid-auto-flow': {values: ['dense']},
  'background': {
    values: [
      'repeat',
      'repeat-x',
      'repeat-y',
      'no-repeat',
      'top',
      'bottom',
      'left',
      'right',
      'center',
      'fixed',
      'local',
      'scroll',
      'space',
      'round',
      'border-box',
      'content-box',
      'padding-box',
      'linear-gradient',
      'radial-gradient',
      'repeating-linear-gradient',
      'repeating-radial-gradient',
      'url',
    ],
  },
  'background-image':
      {values: ['linear-gradient', 'radial-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'url']},
  'background-position': {values: ['top', 'bottom', 'left', 'right', 'center']},
  'background-position-x': {values: ['left', 'right', 'center']},
  'background-position-y': {values: ['top', 'bottom', 'center']},
  'background-repeat-x': {values: ['repeat', 'no-repeat']},
  'background-repeat-y': {values: ['repeat', 'no-repeat']},
  'border-bottom': {
    values: [
      'hidden',
      'inset',
      'groove',
      'outset',
      'ridge',
      'dotted',
      'dashed',
      'solid',
      'double',
      'medium',
      'thick',
      'thin',
    ],
  },
  'border-left': {
    values: [
      'hidden',
      'inset',
      'groove',
      'outset',
      'ridge',
      'dotted',
      'dashed',
      'solid',
      'double',
      'medium',
      'thick',
      'thin',
    ],
  },
  'border-right': {
    values: [
      'hidden',
      'inset',
      'groove',
      'outset',
      'ridge',
      'dotted',
      'dashed',
      'solid',
      'double',
      'medium',
      'thick',
      'thin',
    ],
  },
  'border-top': {
    values: [
      'hidden',
      'inset',
      'groove',
      'outset',
      'ridge',
      'dotted',
      'dashed',
      'solid',
      'double',
      'medium',
      'thick',
      'thin',
    ],
  },
  'buffered-rendering': {values: ['static', 'dynamic']},
  'color-interpolation-filters': {values: ['srgb', 'linearrgb']},
  'column-rule': {
    values: [
      'hidden',
      'inset',
      'groove',
      'outset',
      'ridge',
      'dotted',
      'dashed',
      'solid',
      'double',
      'medium',
      'thick',
      'thin',
    ],
  },
  'flex-flow': {values: ['nowrap', 'row', 'row-reverse', 'column', 'column-reverse', 'wrap', 'wrap-reverse']},
  'height': {values: ['-webkit-fill-available']},
  'inline-size': {values: ['-webkit-fill-available', 'min-content', 'max-content', 'fit-content']},
  'list-style': {
    values: [
      'outside',
      'inside',
      'disc',
      'circle',
      'square',
      'decimal',
      'decimal-leading-zero',
      'arabic-indic',
      'bengali',
      'cambodian',
      'khmer',
      'devanagari',
      'gujarati',
      'gurmukhi',
      'kannada',
      'lao',
      'malayalam',
      'mongolian',
      'myanmar',
      'oriya',
      'persian',
      'urdu',
      'telugu',
      'tibetan',
      'thai',
      'lower-roman',
      'upper-roman',
      'lower-greek',
      'lower-alpha',
      'lower-latin',
      'upper-alpha',
      'upper-latin',
      'cjk-earthly-branch',
      'cjk-heavenly-stem',
      'ethiopic-halehame',
      'ethiopic-halehame-am',
      'ethiopic-halehame-ti-er',
      'ethiopic-halehame-ti-et',
      'hangul',
      'hangul-consonant',
      'korean-hangul-formal',
      'korean-hanja-formal',
      'korean-hanja-informal',
      'hebrew',
      'armenian',
      'lower-armenian',
      'upper-armenian',
      'georgian',
      'cjk-ideographic',
      'simp-chinese-formal',
      'simp-chinese-informal',
      'trad-chinese-formal',
      'trad-chinese-informal',
      'hiragana',
      'katakana',
      'hiragana-iroha',
      'katakana-iroha',
    ],
  },
  'max-block-size': {values: ['-webkit-fill-available', 'min-content', 'max-content', 'fit-content']},
  'max-inline-size': {values: ['-webkit-fill-available', 'min-content', 'max-content', 'fit-content']},
  'min-block-size': {values: ['-webkit-fill-available', 'min-content', 'max-content', 'fit-content']},
  'min-height': {values: ['-webkit-fill-available', 'min-content', 'max-content', 'fit-content']},
  'min-inline-size': {values: ['-webkit-fill-available', 'min-content', 'max-content', 'fit-content']},
  'min-width': {values: ['-webkit-fill-available', 'min-content', 'max-content', 'fit-content']},
  'object-position': {values: ['top', 'bottom', 'left', 'right', 'center']},
  'shape-outside': {values: ['border-box', 'content-box', 'padding-box', 'margin-box']},
  '-webkit-appearance': {
    values: [
      'checkbox',
      'radio',
      'push-button',
      'square-button',
      'button',
      'inner-spin-button',
      'listbox',
      'media-slider',
      'media-sliderthumb',
      'media-volume-slider',
      'media-volume-sliderthumb',
      'menulist',
      'menulist-button',
      'meter',
      'progress-bar',
      'slider-horizontal',
      'slider-vertical',
      'sliderthumb-horizontal',
      'sliderthumb-vertical',
      'searchfield',
      'searchfield-cancel-button',
      'textfield',
      'textarea',
    ],
  },
  '-webkit-border-after': {
    values: [
      'hidden',
      'inset',
      'groove',
      'outset',
      'ridge',
      'dotted',
      'dashed',
      'solid',
      'double',
      'medium',
      'thick',
      'thin',
    ],
  },
  '-webkit-border-after-style':
      {values: ['hidden', 'inset', 'groove', 'outset', 'ridge', 'dotted', 'dashed', 'solid', 'double']},
  '-webkit-border-after-width': {values: ['medium', 'thick', 'thin']},
  '-webkit-border-before': {
    values: [
      'hidden',
      'inset',
      'groove',
      'outset',
      'ridge',
      'dotted',
      'dashed',
      'solid',
      'double',
      'medium',
      'thick',
      'thin',
    ],
  },
  '-webkit-border-before-style':
      {values: ['hidden', 'inset', 'groove', 'outset', 'ridge', 'dotted', 'dashed', 'solid', 'double']},
  '-webkit-border-before-width': {values: ['medium', 'thick', 'thin']},
  '-webkit-border-end': {
    values: [
      'hidden',
      'inset',
      'groove',
      'outset',
      'ridge',
      'dotted',
      'dashed',
      'solid',
      'double',
      'medium',
      'thick',
      'thin',
    ],
  },
  '-webkit-border-end-style':
      {values: ['hidden', 'inset', 'groove', 'outset', 'ridge', 'dotted', 'dashed', 'solid', 'double']},
  '-webkit-border-end-width': {values: ['medium', 'thick', 'thin']},
  '-webkit-border-start': {
    values: [
      'hidden',
      'inset',
      'groove',
      'outset',
      'ridge',
      'dotted',
      'dashed',
      'solid',
      'double',
      'medium',
      'thick',
      'thin',
    ],
  },
  '-webkit-border-start-style':
      {values: ['hidden', 'inset', 'groove', 'outset', 'ridge', 'dotted', 'dashed', 'solid', 'double']},
  '-webkit-border-start-width': {values: ['medium', 'thick', 'thin']},
  '-webkit-logical-height': {values: ['-webkit-fill-available', 'min-content', 'max-content', 'fit-content']},
  '-webkit-logical-width': {values: ['-webkit-fill-available', 'min-content', 'max-content', 'fit-content']},
  '-webkit-margin-collapse': {values: ['collapse', 'separate', 'discard']},
  '-webkit-mask-box-image': {values: ['repeat', 'stretch', 'space', 'round']},
  '-webkit-mask-box-image-repeat': {values: ['repeat', 'stretch', 'space', 'round']},
  '-webkit-mask-clip': {values: ['text', 'border', 'border-box', 'content', 'content-box', 'padding', 'padding-box']},
  '-webkit-mask-composite': {
    values: [
      'clear',
      'copy',
      'source-over',
      'source-in',
      'source-out',
      'source-atop',
      'destination-over',
      'destination-in',
      'destination-out',
      'destination-atop',
      'xor',
      'plus-lighter',
    ],
  },
  '-webkit-mask-image':
      {values: ['linear-gradient', 'radial-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'url']},
  '-webkit-mask-origin': {values: ['border', 'border-box', 'content', 'content-box', 'padding', 'padding-box']},
  '-webkit-mask-position': {values: ['top', 'bottom', 'left', 'right', 'center']},
  '-webkit-mask-position-x': {values: ['left', 'right', 'center']},
  '-webkit-mask-position-y': {values: ['top', 'bottom', 'center']},
  '-webkit-mask-repeat': {values: ['repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'space', 'round']},
  '-webkit-mask-size': {values: ['contain', 'cover']},
  '-webkit-max-logical-height': {values: ['-webkit-fill-available', 'min-content', 'max-content', 'fit-content']},
  '-webkit-max-logical-width': {values: ['-webkit-fill-available', 'min-content', 'max-content', 'fit-content']},
  '-webkit-min-logical-height': {values: ['-webkit-fill-available', 'min-content', 'max-content', 'fit-content']},
  '-webkit-min-logical-width': {values: ['-webkit-fill-available', 'min-content', 'max-content', 'fit-content']},
  '-webkit-perspective-origin-x': {values: ['left', 'right', 'center']},
  '-webkit-perspective-origin-y': {values: ['top', 'bottom', 'center']},
  '-webkit-text-decorations-in-effect': {values: ['blink', 'line-through', 'overline', 'underline']},
  '-webkit-text-stroke': {values: ['medium', 'thick', 'thin']},
  '-webkit-text-stroke-width': {values: ['medium', 'thick', 'thin']},
  '-webkit-transform-origin-x': {values: ['left', 'right', 'center']},
  '-webkit-transform-origin-y': {values: ['top', 'bottom', 'center']},
  'width': {values: ['-webkit-fill-available']},
};

// Weight of CSS properties based on their usage from https://www.chromestatus.com/metrics/css/popularity
const Weight = new Map([
  ['align-content', 57],
  ['align-items', 129],
  ['align-self', 55],
  ['animation', 175],
  ['animation-delay', 114],
  ['animation-direction', 113],
  ['animation-duration', 137],
  ['animation-fill-mode', 132],
  ['animation-iteration-count', 124],
  ['animation-name', 139],
  ['animation-play-state', 104],
  ['animation-timing-function', 141],
  ['backface-visibility', 123],
  ['background', 260],
  ['background-attachment', 119],
  ['background-clip', 165],
  ['background-color', 259],
  ['background-image', 246],
  ['background-origin', 107],
  ['background-position', 237],
  ['background-position-x', 108],
  ['background-position-y', 93],
  ['background-repeat', 234],
  ['background-size', 203],
  ['border', 263],
  ['border-bottom', 233],
  ['border-bottom-color', 190],
  ['border-bottom-left-radius', 186],
  ['border-bottom-right-radius', 185],
  ['border-bottom-style', 150],
  ['border-bottom-width', 179],
  ['border-collapse', 209],
  ['border-color', 226],
  ['border-image', 89],
  ['border-image-outset', 50],
  ['border-image-repeat', 49],
  ['border-image-slice', 58],
  ['border-image-source', 32],
  ['border-image-width', 52],
  ['border-left', 221],
  ['border-left-color', 174],
  ['border-left-style', 142],
  ['border-left-width', 172],
  ['border-radius', 224],
  ['border-right', 223],
  ['border-right-color', 182],
  ['border-right-style', 130],
  ['border-right-width', 178],
  ['border-spacing', 198],
  ['border-style', 206],
  ['border-top', 231],
  ['border-top-color', 192],
  ['border-top-left-radius', 187],
  ['border-top-right-radius', 189],
  ['border-top-style', 152],
  ['border-top-width', 180],
  ['border-width', 214],
  ['bottom', 227],
  ['box-shadow', 213],
  ['box-sizing', 216],
  ['caption-side', 96],
  ['clear', 229],
  ['clip', 173],
  ['clip-rule', 5],
  ['color', 256],
  ['content', 219],
  ['counter-increment', 111],
  ['counter-reset', 110],
  ['cursor', 250],
  ['direction', 176],
  ['display', 262],
  ['empty-cells', 99],
  ['fill', 140],
  ['fill-opacity', 82],
  ['fill-rule', 22],
  ['filter', 160],
  ['flex', 133],
  ['flex-basis', 66],
  ['flex-direction', 85],
  ['flex-flow', 94],
  ['flex-grow', 112],
  ['flex-shrink', 61],
  ['flex-wrap', 68],
  ['float', 252],
  ['font', 211],
  ['font-family', 254],
  ['font-kerning', 18],
  ['font-size', 264],
  ['font-stretch', 77],
  ['font-style', 220],
  ['font-variant', 161],
  ['font-weight', 257],
  ['height', 266],
  ['image-rendering', 90],
  ['justify-content', 127],
  ['left', 248],
  ['letter-spacing', 188],
  ['line-height', 244],
  ['list-style', 215],
  ['list-style-image', 145],
  ['list-style-position', 149],
  ['list-style-type', 199],
  ['margin', 267],
  ['margin-bottom', 241],
  ['margin-left', 243],
  ['margin-right', 238],
  ['margin-top', 253],
  ['mask', 20],
  ['max-height', 205],
  ['max-width', 225],
  ['min-height', 217],
  ['min-width', 218],
  ['object-fit', 33],
  ['opacity', 251],
  ['order', 117],
  ['orphans', 146],
  ['outline', 222],
  ['outline-color', 153],
  ['outline-offset', 147],
  ['outline-style', 151],
  ['outline-width', 148],
  ['overflow', 255],
  ['overflow-wrap', 105],
  ['overflow-x', 184],
  ['overflow-y', 196],
  ['padding', 265],
  ['padding-bottom', 230],
  ['padding-left', 235],
  ['padding-right', 232],
  ['padding-top', 240],
  ['page', 8],
  ['page-break-after', 120],
  ['page-break-before', 69],
  ['page-break-inside', 121],
  ['perspective', 92],
  ['perspective-origin', 103],
  ['pointer-events', 183],
  ['position', 261],
  ['quotes', 158],
  ['resize', 168],
  ['right', 245],
  ['shape-rendering', 38],
  ['size', 64],
  ['speak', 118],
  ['src', 170],
  ['stop-color', 42],
  ['stop-opacity', 31],
  ['stroke', 98],
  ['stroke-dasharray', 36],
  ['stroke-dashoffset', 3],
  ['stroke-linecap', 30],
  ['stroke-linejoin', 21],
  ['stroke-miterlimit', 12],
  ['stroke-opacity', 34],
  ['stroke-width', 87],
  ['table-layout', 171],
  ['tab-size', 46],
  ['text-align', 260],
  ['text-anchor', 35],
  ['text-decoration', 247],
  ['text-indent', 207],
  ['text-overflow', 204],
  ['text-rendering', 155],
  ['text-shadow', 208],
  ['text-transform', 202],
  ['top', 258],
  ['touch-action', 80],
  ['transform', 181],
  ['transform-origin', 162],
  ['transform-style', 86],
  ['transition', 193],
  ['transition-delay', 134],
  ['transition-duration', 135],
  ['transition-property', 131],
  ['transition-timing-function', 122],
  ['unicode-bidi', 156],
  ['unicode-range', 136],
  ['vertical-align', 236],
  ['visibility', 242],
  ['-webkit-appearance', 191],
  ['-webkit-backface-visibility', 154],
  ['-webkit-background-clip', 164],
  ['-webkit-background-origin', 40],
  ['-webkit-background-size', 163],
  ['-webkit-border-end', 9],
  ['-webkit-border-horizontal-spacing', 81],
  ['-webkit-border-image', 75],
  ['-webkit-border-radius', 212],
  ['-webkit-border-start', 10],
  ['-webkit-border-start-color', 16],
  ['-webkit-border-start-width', 13],
  ['-webkit-border-vertical-spacing', 43],
  ['-webkit-box-align', 101],
  ['-webkit-box-direction', 51],
  ['-webkit-box-flex', 128],
  ['-webkit-box-ordinal-group', 91],
  ['-webkit-box-orient', 144],
  ['-webkit-box-pack', 106],
  ['-webkit-box-reflect', 39],
  ['-webkit-box-shadow', 210],
  ['-webkit-column-break-inside', 60],
  ['-webkit-column-count', 84],
  ['-webkit-column-gap', 76],
  ['-webkit-column-rule', 25],
  ['-webkit-column-rule-color', 23],
  ['-webkit-columns', 44],
  ['-webkit-column-span', 29],
  ['-webkit-column-width', 47],
  ['-webkit-filter', 159],
  ['-webkit-font-feature-settings', 59],
  ['-webkit-font-smoothing', 177],
  ['-webkit-highlight', 1],
  ['-webkit-line-break', 45],
  ['-webkit-line-clamp', 126],
  ['-webkit-margin-after', 67],
  ['-webkit-margin-before', 70],
  ['-webkit-margin-collapse', 14],
  ['-webkit-margin-end', 65],
  ['-webkit-margin-start', 100],
  ['-webkit-margin-top-collapse', 78],
  ['-webkit-mask', 19],
  ['-webkit-mask-box-image', 72],
  ['-webkit-mask-image', 88],
  ['-webkit-mask-position', 54],
  ['-webkit-mask-repeat', 63],
  ['-webkit-mask-size', 79],
  ['-webkit-padding-after', 15],
  ['-webkit-padding-before', 28],
  ['-webkit-padding-end', 48],
  ['-webkit-padding-start', 73],
  ['-webkit-print-color-adjust', 83],
  ['-webkit-rtl-ordering', 7],
  ['-webkit-tap-highlight-color', 169],
  ['-webkit-text-emphasis-color', 11],
  ['-webkit-text-fill-color', 71],
  ['-webkit-text-security', 17],
  ['-webkit-text-stroke', 56],
  ['-webkit-text-stroke-color', 37],
  ['-webkit-text-stroke-width', 53],
  ['-webkit-user-drag', 95],
  ['-webkit-user-modify', 62],
  ['-webkit-user-select', 194],
  ['-webkit-writing-mode', 4],
  ['white-space', 228],
  ['widows', 115],
  ['width', 268],
  ['will-change', 74],
  ['word-break', 166],
  ['word-spacing', 157],
  ['word-wrap', 197],
  ['writing-mode', 41],
  ['z-index', 239],
  ['zoom', 200],
]);

// Common keywords to CSS properties
const CommonKeywords = ['auto', 'none'];

export interface CSSPropertyDefinition {
  name: string;
  longhands: string[]|null;
  inherited: boolean|null;
  svg: boolean|null;
}
