[Back]
import { parseColor } from './color'
import { addWhitespaceAroundMathOperators } from './math-operators'
import { parseBoxShadowValue } from './parseBoxShadowValue'
import { splitAtTopLevelOnly } from './splitAtTopLevelOnly'

let cssFunctions = ['min', 'max', 'clamp', 'calc']

// Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types

function isCSSFunction(value) {
  return cssFunctions.some((fn) => new RegExp(`^${fn}\\(.*\\)`).test(value))
}

// These properties accept a `<dashed-ident>` as one of the values. This means that you can use them
// as: `timeline-scope: --tl;`
//
// Without the `var(--tl)`, in these cases we don't want to normalize the value, and you should add
// the `var()` yourself.
//
// More info:
// - https://drafts.csswg.org/scroll-animations/#propdef-timeline-scope
// - https://developer.mozilla.org/en-US/docs/Web/CSS/timeline-scope#dashed-ident
// - https://www.w3.org/TR/css-anchor-position-1
//
const AUTO_VAR_INJECTION_EXCEPTIONS = new Set([
  // Concrete properties
  'scroll-timeline-name',
  'timeline-scope',
  'view-timeline-name',
  'font-palette',
  'anchor-name',
  'anchor-scope',
  'position-anchor',
  'position-try-options',

  // Shorthand properties
  'scroll-timeline',
  'animation-timeline',
  'view-timeline',
  'position-try',
])

// This is not a data type, but rather a function that can normalize the
// correct values.
export function normalize(value, context = null, isRoot = true) {
  let isVarException = context && AUTO_VAR_INJECTION_EXCEPTIONS.has(context.property)
  if (value.startsWith('--') && !isVarException) {
    return `var(${value})`
  }

  // Keep raw strings if it starts with `url(`
  if (value.includes('url(')) {
    return value
      .split(/(url\(.*?\))/g)
      .filter(Boolean)
      .map((part) => {
        if (/^url\(.*?\)$/.test(part)) {
          return part
        }

        return normalize(part, context, false)
      })
      .join('')
  }

  // Convert `_` to ` `, except for escaped underscores `\_`
  value = value
    .replace(
      /([^\\])_+/g,
      (fullMatch, characterBefore) => characterBefore + ' '.repeat(fullMatch.length - 1)
    )
    .replace(/^_/g, ' ')
    .replace(/\\_/g, '_')

  // Remove leftover whitespace
  if (isRoot) {
    value = value.trim()
  }

  value = addWhitespaceAroundMathOperators(value)

  return value
}

export function normalizeAttributeSelectors(value) {
  // Wrap values in attribute selectors with quotes
  if (value.includes('=')) {
    value = value.replace(/(=.*)/g, (_fullMatch, match) => {
      if (match[1] === "'" || match[1] === '"') {
        return match
      }

      // Handle regex flags on unescaped values
      if (match.length > 2) {
        let trailingCharacter = match[match.length - 1]
        if (
          match[match.length - 2] === ' ' &&
          (trailingCharacter === 'i' ||
            trailingCharacter === 'I' ||
            trailingCharacter === 's' ||
            trailingCharacter === 'S')
        ) {
          return `="${match.slice(1, -2)}" ${match[match.length - 1]}`
        }
      }

      return `="${match.slice(1)}"`
    })
  }
  return value
}

export function url(value) {
  return value.startsWith('url(')
}

export function number(value) {
  return !isNaN(Number(value)) || isCSSFunction(value)
}

export function percentage(value) {
  return (value.endsWith('%') && number(value.slice(0, -1))) || isCSSFunction(value)
}

// Please refer to MDN when updating this list:
// https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Values_and_units
// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries#container_query_length_units
let lengthUnits = [
  'cm',
  'mm',
  'Q',
  'in',
  'pc',
  'pt',
  'px',
  'em',
  'ex',
  'ch',
  'rem',
  'lh',
  'rlh',
  'vw',
  'vh',
  'vmin',
  'vmax',
  'vb',
  'vi',
  'svw',
  'svh',
  'lvw',
  'lvh',
  'dvw',
  'dvh',
  'cqw',
  'cqh',
  'cqi',
  'cqb',
  'cqmin',
  'cqmax',
]
let lengthUnitsPattern = `(?:${lengthUnits.join('|')})`
export function length(value) {
  return (
    value === '0' ||
    new RegExp(`^[+-]?[0-9]*\.?[0-9]+(?:[eE][+-]?[0-9]+)?${lengthUnitsPattern}$`).test(value) ||
    isCSSFunction(value)
  )
}

let lineWidths = new Set(['thin', 'medium', 'thick'])
export function lineWidth(value) {
  return lineWidths.has(value)
}

export function shadow(value) {
  let parsedShadows = parseBoxShadowValue(normalize(value))

  for (let parsedShadow of parsedShadows) {
    if (!parsedShadow.valid) {
      return false
    }
  }

  return true
}

export function color(value) {
  let colors = 0

  let result = splitAtTopLevelOnly(value, '_').every((part) => {
    part = normalize(part)

    if (part.startsWith('var(')) return true
    if (parseColor(part, { loose: true }) !== null) return colors++, true

    return false
  })

  if (!result) return false
  return colors > 0
}

export function image(value) {
  let images = 0
  let result = splitAtTopLevelOnly(value, ',').every((part) => {
    part = normalize(part)

    if (part.startsWith('var(')) return true
    if (
      url(part) ||
      gradient(part) ||
      ['element(', 'image(', 'cross-fade(', 'image-set('].some((fn) => part.startsWith(fn))
    ) {
      images++
      return true
    }

    return false
  })

  if (!result) return false
  return images > 0
}

let gradientTypes = new Set([
  'conic-gradient',
  'linear-gradient',
  'radial-gradient',
  'repeating-conic-gradient',
  'repeating-linear-gradient',
  'repeating-radial-gradient',
])
export function gradient(value) {
  value = normalize(value)

  for (let type of gradientTypes) {
    if (value.startsWith(`${type}(`)) {
      return true
    }
  }
  return false
}

let validPositions = new Set(['center', 'top', 'right', 'bottom', 'left'])
export function position(value) {
  let positions = 0
  let result = splitAtTopLevelOnly(value, '_').every((part) => {
    part = normalize(part)

    if (part.startsWith('var(')) return true
    if (validPositions.has(part) || length(part) || percentage(part)) {
      positions++
      return true
    }

    return false
  })

  if (!result) return false
  return positions > 0
}

export function familyName(value) {
  let fonts = 0
  let result = splitAtTopLevelOnly(value, ',').every((part) => {
    part = normalize(part)

    if (part.startsWith('var(')) return true

    // If it contains spaces, then it should be quoted
    if (part.includes(' ')) {
      if (!/(['"])([^"']+)\1/g.test(part)) {
        return false
      }
    }

    // If it starts with a number, it's invalid
    if (/^\d/g.test(part)) {
      return false
    }

    fonts++

    return true
  })

  if (!result) return false
  return fonts > 0
}

let genericNames = new Set([
  'serif',
  'sans-serif',
  'monospace',
  'cursive',
  'fantasy',
  'system-ui',
  'ui-serif',
  'ui-sans-serif',
  'ui-monospace',
  'ui-rounded',
  'math',
  'emoji',
  'fangsong',
])
export function genericName(value) {
  return genericNames.has(value)
}

let absoluteSizes = new Set([
  'xx-small',
  'x-small',
  'small',
  'medium',
  'large',
  'x-large',
  'xx-large',
  'xxx-large',
])
export function absoluteSize(value) {
  return absoluteSizes.has(value)
}

let relativeSizes = new Set(['larger', 'smaller'])
export function relativeSize(value) {
  return relativeSizes.has(value)
}