/**
 * @module @twind/preset-tailwind/rules
 */

/* eslint-disable @typescript-eslint/no-unsafe-member-access */

import type {
  MatchResult,
  Rule,
  MaybeArray,
  CSSProperties,
  CSSObject,
  CSSBase,
  ThemeMatchResult,
  ThemeRuleResolver,
  ColorFromThemeValue,
  AutocompleteProvider,
} from '@twind/core'

import { DEV } from 'distilt/env'

import {
  mql,
  match,
  matchTheme,
  matchColor,
  toColorValue,
  toCSS,
  asArray,
  arbitrary,
  withAutocomplete,
} from '@twind/core'

import type { TailwindTheme } from './types'

// indirection wrapper to remove autocomplete functions from production bundles
function withAutocomplete$(
  rule: Rule<TailwindTheme>,
  autocomplete: AutocompleteProvider<TailwindTheme> | false,
): Rule<TailwindTheme> {
  if (DEV) {
    return withAutocomplete(rule, autocomplete)
  }

  return rule
}

const rules: Rule<TailwindTheme>[] = [
  /* arbitrary properties: [paint-order:markers] */
  match('\\[([-\\w]+):(.+)]', ({ 1: $1, 2: $2 }, context) => ({
    '@layer overrides': {
      '&': {
        [$1]: arbitrary(`[${$2}]`, $1, context),
      },
    },
  })),

  /* Styling based on parent and peer state */
  withAutocomplete$(
    match('(group|peer)(~[^-[]+)?', ({ input }, { h }) => [{ c: h(input) }]),
    DEV && (() => ['group', 'peer']),
  ),

  /* LAYOUT */
  matchTheme('aspect-', 'aspectRatio'),

  match('container', (_, { theme }) => {
    const { screens = theme('screens'), center, padding } = theme('container')

    const rules = {
      width: '100%',
      marginRight: center && 'auto',
      marginLeft: center && 'auto',
      ...paddingFor('xs'),
    } as CSSObject

    for (const screen in screens) {
      const value = screens[screen]

      if (typeof value == 'string') {
        rules[mql(value)] = {
          '&': {
            maxWidth: value,
            ...paddingFor(screen),
          },
        }
      }
    }

    return rules

    function paddingFor(screen: string): CSSProperties | undefined {
      const value =
        padding && (typeof padding == 'string' ? padding : padding[screen] || padding.DEFAULT)

      if (value) {
        return {
          paddingRight: value,
          paddingLeft: value,
        }
      }
    }
  }),

  // Content
  matchTheme('content-', 'content', ({ _ }) => ({
    '--tw-content': _,
    content: 'var(--tw-content)',
  })),

  // Box Decoration Break
  match('(?:box-)?decoration-(slice|clone)', 'boxDecorationBreak'),

  // Box Sizing
  match('box-(border|content)', 'boxSizing', ({ 1: $1 }) => $1 + '-box'),

  // Display
  match('hidden', { display: 'none' }),

  // Table Layout
  match('table-(auto|fixed)', 'tableLayout'),

  match(
    [
      '(block|flex|table|grid|inline|contents|flow-root|list-item)',
      '(inline-(block|flex|table|grid))',
      '(table-(caption|cell|column|row|(column|row|footer|header)-group))',
    ],
    'display',
  ),

  // Floats
  '(float)-(left|right|none)',

  // Clear
  '(clear)-(left|right|none|both)',

  // Overflow
  '(overflow(?:-[xy])?)-(auto|hidden|clip|visible|scroll)',

  // Isolation
  '(isolation)-(auto)',

  // Isolation
  match('isolate', 'isolation'),

  // Object Fit
  match('object-(contain|cover|fill|none|scale-down)', 'objectFit'),

  // Object Position
  matchTheme('object-', 'objectPosition'),
  match('object-(top|bottom|center|(left|right)(-(top|bottom))?)', 'objectPosition', spacify),

  // Overscroll Behavior
  match('overscroll(-[xy])?-(auto|contain|none)', ({ 1: $1 = '', 2: $2 }) => ({
    [('overscroll-behavior' + $1) as 'overscroll-behavior-x']: $2 as 'auto',
  })),

  // Position
  match('(static|fixed|absolute|relative|sticky)', 'position'),

  // Top / Right / Bottom / Left
  matchTheme('-?inset(-[xy])?(?:$|-)', 'inset', ({ 1: $1, _ }) => ({
    top: $1 != '-x' && _,
    right: $1 != '-y' && _,
    bottom: $1 != '-x' && _,
    left: $1 != '-y' && _,
  })),

  matchTheme('-?(top|bottom|left|right)(?:$|-)', 'inset'),

  // Visibility
  match('visible', 'visibility'),
  match('invisible', { visibility: 'hidden' }),

  // Z-Index
  matchTheme('-?z-', 'zIndex'),

  /* FLEXBOX */
  // Flex Direction
  match('flex-((row|col)(-reverse)?)', 'flexDirection', columnify),

  match('flex-(wrap|wrap-reverse|nowrap)', 'flexWrap'),
  matchTheme('(flex-(?:grow|shrink))(?:$|-)' /*, 'flex-grow' | flex-shrink */),
  matchTheme('(flex)-' /*, 'flex' */),
  matchTheme('grow(?:$|-)', 'flexGrow'),
  matchTheme('shrink(?:$|-)', 'flexShrink'),
  matchTheme('basis-', 'flexBasis'),

  matchTheme('-?(order)-' /*, 'order' */),
  withAutocomplete$('-?(order)-(\\d+)', DEV && (() => range({ end: 12 }))),

  /* GRID */
  // Grid Template Columns
  matchTheme('grid-cols-', 'gridTemplateColumns'),
  withAutocomplete$(
    match('grid-cols-(\\d+)', 'gridTemplateColumns', gridTemplate),
    DEV && (() => range({ end: 6 })),
  ),

  // Grid Column Start / End
  matchTheme('col-', 'gridColumn'),
  withAutocomplete$(
    match('col-(span)-(\\d+)', 'gridColumn', span),
    DEV && (() => range({ end: 12 })),
  ),

  matchTheme('col-start-', 'gridColumnStart'),
  withAutocomplete$(
    match('col-start-(auto|\\d+)', 'gridColumnStart'),
    DEV && (({ 1: $1 }) => ($1 === 'auto' ? [''] : range({ end: 13 }))),
  ),

  matchTheme('col-end-', 'gridColumnEnd'),
  withAutocomplete$(
    match('col-end-(auto|\\d+)', 'gridColumnEnd'),
    DEV && (({ 1: $1 }) => ($1 === 'auto' ? [''] : range({ end: 13 }))),
  ),

  // Grid Template Rows
  matchTheme('grid-rows-', 'gridTemplateRows'),
  withAutocomplete$(
    match('grid-rows-(\\d+)', 'gridTemplateRows', gridTemplate),
    DEV && (() => range({ end: 6 })),
  ),

  // Grid Row Start / End
  matchTheme('row-', 'gridRow'),
  withAutocomplete$(match('row-(span)-(\\d+)', 'gridRow', span), DEV && (() => range({ end: 6 }))),

  matchTheme('row-start-', 'gridRowStart'),
  withAutocomplete$(
    match('row-start-(auto|\\d+)', 'gridRowStart'),
    DEV && (({ 1: $1 }) => ($1 === 'auto' ? [''] : range({ end: 7 }))),
  ),

  matchTheme('row-end-', 'gridRowEnd'),
  withAutocomplete$(
    match('row-end-(auto|\\d+)', 'gridRowEnd'),
    DEV && (({ 1: $1 }) => ($1 === 'auto' ? [''] : range({ end: 7 }))),
  ),

  // Grid Auto Flow
  match('grid-flow-((row|col)(-dense)?)', 'gridAutoFlow', (match) => spacify(columnify(match))),
  match('grid-flow-(dense)', 'gridAutoFlow'),

  // Grid Auto Columns
  matchTheme('auto-cols-', 'gridAutoColumns'),

  // Grid Auto Rows
  matchTheme('auto-rows-', 'gridAutoRows'),

  // Gap
  matchTheme('gap-x(?:$|-)', 'gap', 'columnGap'),
  matchTheme('gap-y(?:$|-)', 'gap', 'rowGap'),
  matchTheme('gap(?:$|-)', 'gap'),

  /* BOX ALIGNMENT */
  // Justify Items
  // Justify Self
  withAutocomplete$(
    '(justify-(?:items|self))-',
    DEV &&
      (({ 1: $1 }) =>
        $1.endsWith('-items-')
          ? ['start', 'end', 'center', 'stretch']
          : /* '-self-' */ ['auto', 'start', 'end', 'center', 'stretch']),
  ),

  // Justify Content
  withAutocomplete$(
    match('justify-', 'justifyContent', convertContentValue),
    DEV && (() => ['start', 'end', 'center', 'between', 'around', 'evenly']),
  ),

  // Align Content
  // Align Items
  // Align Self
  withAutocomplete$(
    match('(content|items|self)-', (match) => ({
      [('align-' + match[1]) as 'align-content']: convertContentValue(match),
    })),
    DEV &&
      (({ 1: $1 }) =>
        $1 == 'content'
          ? ['center', 'start', 'end', 'between', 'around', 'evenly']
          : $1 == 'items'
          ? ['start', 'end', 'center', 'baseline', 'stretch']
          : /* $1 == 'self' */ ['auto', 'start', 'end', 'center', 'stretch', 'baseline']),
  ),

  // Place Content
  // Place Items
  // Place Self
  withAutocomplete$(
    match('(place-(content|items|self))-', ({ 1: $1, $$ }) => ({
      [$1 as 'place-content']: ('wun'.includes($$[3]) ? 'space-' : '') + $$,
    })),
    DEV &&
      (({ 1: $1 }) =>
        $1 == 'content'
          ? ['center', 'start', 'end', 'between', 'around', 'evenly', 'stretch']
          : $1 == 'items'
          ? ['start', 'end', 'center', 'stretch']
          : /* $1 == 'self' */ ['auto', 'start', 'end', 'center', 'stretch']),
  ),

  /* SPACING */
  // Padding
  matchTheme('p([xytrbl])?(?:$|-)', 'padding', edge('padding')),

  // Margin
  matchTheme('-?m([xytrbl])?(?:$|-)', 'margin', edge('margin')),

  // Space Between
  matchTheme('-?space-(x|y)(?:$|-)', 'space', ({ 1: $1, _ }) => ({
    '&>:not([hidden])~:not([hidden])': {
      [`--tw-space-${$1}-reverse`]: '0',
      ['margin-' +
      { y: 'top', x: 'left' }[
        $1 as 'y' | 'x'
      ]]: `calc(${_} * calc(1 - var(--tw-space-${$1}-reverse)))`,
      ['margin-' +
      { y: 'bottom', x: 'right' }[$1 as 'y' | 'x']]: `calc(${_} * var(--tw-space-${$1}-reverse))`,
    },
  })),

  match('space-(x|y)-reverse', ({ 1: $1 }) => ({
    '&>:not([hidden])~:not([hidden])': {
      [`--tw-space-${$1}-reverse`]: '1',
    },
  })),

  /* SIZING */
  // Width
  matchTheme('w-', 'width'),

  // Min-Width
  matchTheme('min-w-', 'minWidth'),

  // Max-Width
  matchTheme('max-w-', 'maxWidth'),

  // Height
  matchTheme('h-', 'height'),

  // Min-Height
  matchTheme('min-h-', 'minHeight'),

  // Max-Height
  matchTheme('max-h-', 'maxHeight'),

  /* TYPOGRAPHY */
  // Font Weight
  matchTheme('font-', 'fontWeight'),

  // Font Family
  matchTheme('font-', 'fontFamily', 'fontFamily', join),

  // Font Smoothing
  match('antialiased', {
    WebkitFontSmoothing: 'antialiased',
    MozOsxFontSmoothing: 'grayscale',
  }),

  match('subpixel-antialiased', {
    WebkitFontSmoothing: 'auto',
    MozOsxFontSmoothing: 'auto',
  }),

  // Font Style
  match('italic', 'fontStyle'),
  match('not-italic', { fontStyle: 'normal' }),

  // Font Variant Numeric
  match(
    '(ordinal|slashed-zero|(normal|lining|oldstyle|proportional|tabular)-nums|(diagonal|stacked)-fractions)',
    ({ 1: $1, 2: $2 = '', 3: $3 }) =>
      // normal-nums
      $2 == 'normal'
        ? { fontVariantNumeric: 'normal' }
        : {
            [('--tw-' +
              ($3 // diagonal-fractions, stacked-fractions
                ? 'numeric-fraction'
                : 'pt'.includes($2[0]) // proportional-nums, tabular-nums
                ? 'numeric-spacing'
                : $2 // lining-nums, oldstyle-nums
                ? 'numeric-figure'
                : // ordinal, slashed-zero
                  $1)) as 'numeric-spacing']: $1,
            fontVariantNumeric:
              'var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)',
            '@layer defaults': {
              '*,::before,::after,::backdrop': {
                '--tw-ordinal': 'var(--tw-empty,/*!*/ /*!*/)',
                '--tw-slashed-zero': 'var(--tw-empty,/*!*/ /*!*/)',
                '--tw-numeric-figure': 'var(--tw-empty,/*!*/ /*!*/)',
                '--tw-numeric-spacing': 'var(--tw-empty,/*!*/ /*!*/)',
                '--tw-numeric-fraction': 'var(--tw-empty,/*!*/ /*!*/)',
              },
            },
          },
  ),

  // Letter Spacing
  matchTheme('tracking-', 'letterSpacing'),

  // Line Height
  matchTheme('leading-', 'lineHeight'),

  // List Style Position
  match('list-(inside|outside)', 'listStylePosition'),

  // List Style Type
  matchTheme('list-', 'listStyleType'),
  withAutocomplete$(match('list-', 'listStyleType'), DEV && (() => ['none', 'disc', 'decimal'])),

  // Placeholder Opacity
  matchTheme('placeholder-opacity-', 'placeholderOpacity', ({ _ }) => ({
    ['&::placeholder']: { '--tw-placeholder-opacity': _ },
  })),

  // Placeholder Color
  matchColor('placeholder-', { property: 'color', selector: '&::placeholder' }),

  // Text Alignment
  match('text-(left|center|right|justify|start|end)', 'textAlign'),

  match('text-(ellipsis|clip)', 'textOverflow'),

  // Text Opacity
  matchTheme('text-opacity-', 'textOpacity', '--tw-text-opacity'),

  // Text Color
  matchColor('text-', { property: 'color' }),

  // Font Size
  matchTheme('text-', 'fontSize', ({ _ }) =>
    typeof _ == 'string'
      ? { fontSize: _ }
      : {
          fontSize: _[0],
          ...(typeof _[1] == 'string' ? { lineHeight: _[1] } : _[1]),
        },
  ),

  // Text Indent
  matchTheme('indent-', 'textIndent'),

  // Text Decoration
  match('(overline|underline|line-through)', 'textDecorationLine'),
  match('no-underline', { textDecorationLine: 'none' }),

  // Text Underline offset
  matchTheme('underline-offset-', 'textUnderlineOffset'),

  // Text Decoration Color
  matchColor('decoration-', {
    section: 'textDecorationColor',
    opacityVariable: false,
    opacitySection: 'opacity',
  }),

  // Text Decoration Thickness
  matchTheme('decoration-', 'textDecorationThickness'),

  // Text Decoration Style
  withAutocomplete$(
    match('decoration-', 'textDecorationStyle'),
    DEV && (() => ['solid', 'double', 'dotted', 'dashed', 'wavy']),
  ),

  // Text Transform
  match('(uppercase|lowercase|capitalize)', 'textTransform'),
  match('normal-case', { textTransform: 'none' }),

  // Text Overflow
  match('truncate', {
    overflow: 'hidden',
    whiteSpace: 'nowrap',
    textOverflow: 'ellipsis',
  }),

  // Vertical Alignment
  withAutocomplete$(
    match('align-', 'verticalAlign'),
    DEV &&
      (() => ['baseline', 'top', 'middle', 'bottom', 'text-top', 'text-bottom', 'sub', 'super']),
  ),

  // Whitespace
  withAutocomplete$(
    match('whitespace-', 'whiteSpace'),
    DEV && (() => ['normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap']),
  ),

  // Word Break
  match('break-normal', { wordBreak: 'normal', overflowWrap: 'normal' }),
  match('break-words', { overflowWrap: 'break-word' }),
  match('break-all', { wordBreak: 'break-all' }),

  // Caret Color
  matchColor('caret-', {
    // section: 'caretColor',
    opacityVariable: false,
    opacitySection: 'opacity',
  }),

  // Accent Color
  matchColor('accent-', {
    // section: 'accentColor',
    opacityVariable: false,
    opacitySection: 'opacity',
  }),

  // Gradient Color Stops
  match(
    'bg-gradient-to-([trbl]|[tb][rl])',
    'backgroundImage',
    ({ 1: $1 }) => `linear-gradient(to ${position($1, ' ')},var(--tw-gradient-stops))`,
  ),

  matchColor(
    'from-',
    {
      section: 'gradientColorStops',
      opacityVariable: false,
      opacitySection: 'opacity',
    },
    ({ _ }) => ({
      '--tw-gradient-from': _.value,
      '--tw-gradient-to': _.color({ opacityValue: '0' }),
      '--tw-gradient-stops': `var(--tw-gradient-from),var(--tw-gradient-to)`,
    }),
  ),
  matchColor(
    'via-',

    {
      section: 'gradientColorStops',
      opacityVariable: false,
      opacitySection: 'opacity',
    },
    ({ _ }) => ({
      '--tw-gradient-to': _.color({ opacityValue: '0' }),
      '--tw-gradient-stops': `var(--tw-gradient-from),${_.value},var(--tw-gradient-to)`,
    }),
  ),
  matchColor('to-', {
    section: 'gradientColorStops',
    property: '--tw-gradient-to',
    opacityVariable: false,
    opacitySection: 'opacity',
  }),

  /* BACKGROUNDS */
  // Background Attachment
  match('bg-(fixed|local|scroll)', 'backgroundAttachment'),

  // Background Origin
  match('bg-origin-(border|padding|content)', 'backgroundOrigin', ({ 1: $1 }) => $1 + '-box'),

  // Background Repeat
  match(['bg-(no-repeat|repeat(-[xy])?)', 'bg-repeat-(round|space)'], 'backgroundRepeat'),

  // Background Blend Mode
  withAutocomplete$(
    match('bg-blend-', 'backgroundBlendMode'),
    DEV &&
      (() => [
        'normal',
        'multiply',
        'screen',
        'overlay',
        'darken',
        'lighten',
        'color-dodge',
        'color-burn',
        'hard-light',
        'soft-light',
        'difference',
        'exclusion',
        'hue',
        'saturation',
        'color',
        'luminosity',
      ]),
  ),

  // Background Clip
  match(
    'bg-clip-(border|padding|content|text)',
    'backgroundClip',
    ({ 1: $1 }) => $1 + ($1 == 'text' ? '' : '-box'),
  ),

  // Background Opacity
  matchTheme('bg-opacity-', 'backgroundOpacity', '--tw-bg-opacity'),

  // Background Color
  // bg-${backgroundColor}/${backgroundOpacity}
  matchColor('bg-', { section: 'backgroundColor' }),

  // Background Image
  // supported arbitrary types are: length, color, angle, list
  matchTheme('bg-', 'backgroundImage'),

  // Background Position
  matchTheme('bg-', 'backgroundPosition'),
  match('bg-(top|bottom|center|(left|right)(-(top|bottom))?)', 'backgroundPosition', spacify),

  // Background Size
  matchTheme('bg-', 'backgroundSize'),

  /* BORDERS */
  // Border Radius
  matchTheme('rounded(?:$|-)', 'borderRadius'),
  matchTheme('rounded-([trbl]|[tb][rl])(?:$|-)', 'borderRadius', ({ 1: $1, _ }) => {
    const corners = (
      {
        t: ['tl', 'tr'],
        r: ['tr', 'br'],
        b: ['bl', 'br'],
        l: ['bl', 'tl'],
      } as const
    )[$1] || [$1, $1]

    return {
      [`border-${position(corners[0])}-radius` as 'border-top-left-radius']: _,
      [`border-${position(corners[1])}-radius` as 'border-top-right-radius']: _,
    }
  }),

  // Border Collapse
  match('border-(collapse|separate)', 'borderCollapse'),

  // Border Opacity
  matchTheme('border-opacity(?:$|-)', 'borderOpacity', '--tw-border-opacity'),

  // Border Style
  match('border-(solid|dashed|dotted|double|none)', 'borderStyle'),

  // Border Spacing
  matchTheme('border-spacing(-[xy])?(?:$|-)', 'borderSpacing', ({ 1: $1, _ }) => ({
    '@layer defaults': {
      '*,::before,::after,::backdrop': {
        '--tw-border-spacing-x': 0,
        '--tw-border-spacing-y': 0,
      },
    },
    [('--tw-border-spacing' + ($1 || '-x')) as '--tw-border-spacing-x']: _,
    [('--tw-border-spacing' + ($1 || '-y')) as '--tw-border-spacing-y']: _,
    'border-spacing': 'var(--tw-border-spacing-x) var(--tw-border-spacing-y)',
  })),

  // Border Color
  matchColor('border-([xytrbl])-', { section: 'borderColor' }, edge('border', 'Color')),
  matchColor('border-'),

  // Border Width
  matchTheme('border-([xytrbl])(?:$|-)', 'borderWidth', edge('border', 'Width')),
  matchTheme('border(?:$|-)', 'borderWidth'),

  // Divide Opacity
  matchTheme('divide-opacity(?:$|-)', 'divideOpacity', ({ _ }) => ({
    '&>:not([hidden])~:not([hidden])': { '--tw-divide-opacity': _ },
  })),

  // Divide Style
  match('divide-(solid|dashed|dotted|double|none)', ({ 1: $1 }) => ({
    '&>:not([hidden])~:not([hidden])': { borderStyle: $1 },
  })),

  // Divide Width
  match('divide-([xy]-reverse)', ({ 1: $1 }) => ({
    '&>:not([hidden])~:not([hidden])': { ['--tw-divide-' + $1]: '1' },
  })),

  matchTheme('divide-([xy])(?:$|-)', 'divideWidth', ({ 1: $1, _ }) => {
    const edges = (
      {
        x: 'lr',
        y: 'tb',
      } as const
    )[$1 as 'x' | 'y']

    return {
      '&>:not([hidden])~:not([hidden])': {
        [`--tw-divide-${$1}-reverse`]: '0',
        [`border-${position(
          edges[0],
        )}Width`]: `calc(${_} * calc(1 - var(--tw-divide-${$1}-reverse)))`,
        [`border-${position(edges[1])}Width`]: `calc(${_} * var(--tw-divide-${$1}-reverse))`,
      },
    }
  }),

  // Divide Color
  matchColor('divide-', {
    // section: $0.replace('-', 'Color') -> 'divideColor'
    property: 'borderColor',
    // opacityVariable: '--tw-border-opacity',
    // opacitySection: section.replace('Color', 'Opacity') -> 'divideOpacity'
    selector: '&>:not([hidden])~:not([hidden])',
  }),

  // Ring Offset Opacity
  matchTheme('ring-opacity(?:$|-)', 'ringOpacity', '--tw-ring-opacity'),

  // Ring Offset Color
  matchColor('ring-offset-', {
    // section: 'ringOffsetColor',
    property: '--tw-ring-offset-color',
    opacityVariable: false,
    // opacitySection: section.replace('Color', 'Opacity') -> 'ringOffsetOpacity'
  }),

  // Ring Offset Width
  matchTheme('ring-offset(?:$|-)', 'ringOffsetWidth', '--tw-ring-offset-width'),

  // Ring Inset
  match('ring-inset', { '--tw-ring-inset': 'inset' }),

  // Ring Color
  matchColor('ring-', {
    // section: 'ringColor',
    property: '--tw-ring-color',
    // opacityVariable: '--tw-ring-opacity',
    // opacitySection: section.replace('Color', 'Opacity') -> 'ringOpacity'
  }),

  // Ring Width
  matchTheme('ring(?:$|-)', 'ringWidth', ({ _ }, { theme }) => ({
    '--tw-ring-offset-shadow': `var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)`,
    '--tw-ring-shadow': `var(--tw-ring-inset) 0 0 0 calc(${_} + var(--tw-ring-offset-width)) var(--tw-ring-color)`,
    boxShadow: `var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)`,
    '@layer defaults': {
      '*,::before,::after,::backdrop': {
        '--tw-ring-offset-shadow': '0 0 #0000',
        '--tw-ring-shadow': '0 0 #0000',
        '--tw-shadow': '0 0 #0000',
        '--tw-shadow-colored': '0 0 #0000',
        // Within own declaration to have the defaults above to be merged with defaults from shadow
        '&': {
          '--tw-ring-inset': 'var(--tw-empty,/*!*/ /*!*/)',
          '--tw-ring-offset-width': theme('ringOffsetWidth', '', '0px'),
          '--tw-ring-offset-color': toColorValue(theme('ringOffsetColor', '', '#fff')),
          '--tw-ring-color': toColorValue(theme('ringColor', '', '#93c5fd'), {
            opacityVariable: '--tw-ring-opacity',
          }),
          '--tw-ring-opacity': theme('ringOpacity', '', '0.5'),
        },
      },
    },
  })),

  /* EFFECTS */
  // Box Shadow Color
  matchColor(
    'shadow-',
    {
      section: 'boxShadowColor',
      opacityVariable: false,
      opacitySection: 'opacity',
    },
    ({ _ }) => ({
      '--tw-shadow-color': _.value,
      '--tw-shadow': 'var(--tw-shadow-colored)',
    }),
  ),

  // Box Shadow
  matchTheme('shadow(?:$|-)', 'boxShadow', ({ _ }) => ({
    '--tw-shadow': join(_),
    // replace all colors with reference to --tw-shadow-colored
    // this matches colors after non-comma char (keyword, offset) before comma or the end
    '--tw-shadow-colored': (join(_) as string).replace(
      /([^,]\s+)(?:#[a-f\d]+|(?:(?:hsl|rgb)a?|hwb|lab|lch|color|var)\(.+?\)|[a-z]+)(,|$)/g,
      '$1var(--tw-shadow-color)$2',
    ),
    boxShadow: `var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)`,
    '@layer defaults': {
      '*,::before,::after,::backdrop': {
        '--tw-ring-offset-shadow': '0 0 #0000',
        '--tw-ring-shadow': '0 0 #0000',
        '--tw-shadow': '0 0 #0000',
        '--tw-shadow-colored': '0 0 #0000',
      },
    },
  })),

  // Opacity
  matchTheme('(opacity)-' /*, 'opacity' */),

  // Mix Blend Mode
  withAutocomplete$(
    match('mix-blend-', 'mixBlendMode'),
    DEV &&
      (() => [
        'normal',
        'multiply',
        'screen',
        'overlay',
        'darken',
        'lighten',
        'color-dodge',
        'color-burn',
        'hard-light',
        'soft-light',
        'difference',
        'exclusion',
        'hue',
        'saturation',
        'color',
        'luminosity',
      ]),
  ),

  /* FILTERS */
  ...filter(),
  ...filter('backdrop-'),

  /* TRANSITIONS AND ANIMATION */
  // Transition Property
  matchTheme('transition(?:$|-)', 'transitionProperty', (match, { theme }) => ({
    transitionProperty: join(match),
    transitionTimingFunction:
      match._ == 'none' ? undefined : join(theme('transitionTimingFunction', '')),
    transitionDuration: match._ == 'none' ? undefined : join(theme('transitionDuration', '')),
  })),

  // Transition Duration
  matchTheme('duration(?:$|-)', 'transitionDuration', 'transitionDuration', join),

  // Transition Timing Function
  matchTheme('ease(?:$|-)', 'transitionTimingFunction', 'transitionTimingFunction', join),

  // Transition Delay
  matchTheme('delay(?:$|-)', 'transitionDelay', 'transitionDelay', join),

  matchTheme('animate(?:$|-)', 'animation', (match, { theme, h }) => {
    const animation: string = join(match)

    // Try to auto inject keyframes
    const parts = animation.split(' ')
    const keyframeValues = theme('keyframes', parts[0]) as CSSBase

    if (keyframeValues) {
      return {
        [('@keyframes ' + (parts[0] = h(parts[0]))) as '@keyframes xxx']: keyframeValues,
        animation: parts.join(' '),
      }
    }

    return { animation }
  }),

  /* TRANSFORMS */
  // Transform
  '(transform)-(none)',
  match('transform', tranformDefaults),
  match('transform-(cpu|gpu)', ({ 1: $1 }) => ({
    '--tw-transform': transformValue($1 == 'gpu'),
  })),

  // Scale
  matchTheme(
    'scale(-[xy])?-',
    'scale',
    ({ 1: $1, _ }) =>
      ({
        [('--tw-scale' + ($1 || '-x')) as '--tw-scale-x']: _,
        [('--tw-scale' + ($1 || '-y')) as '--tw-scale-y']: _,
        ...tranformDefaults(),
      } as CSSObject),
  ),

  // Rotate
  matchTheme('-?(rotate)-', 'rotate', transform),

  // Translate
  matchTheme('-?(translate-[xy])-', 'translate', transform),

  // Skew
  matchTheme('-?(skew-[xy])-', 'skew', transform),

  // Transform Origin
  match('origin-(center|((top|bottom)(-(left|right))?)|left|right)', 'transformOrigin', spacify),

  /* INTERACTIVITY */
  // Appearance
  withAutocomplete$('(appearance)-', DEV && (() => ['auto', 'none'])),

  // Columns
  matchTheme('(columns)-' /*, 'columns' */),
  withAutocomplete$('(columns)-(\\d+)', DEV && (() => range({ end: 12 }))),

  // Break Before, After and Inside
  withAutocomplete$(
    '(break-(?:before|after|inside))-',
    DEV &&
      (({ 1: $1 }) =>
        $1.endsWith('-inside-')
          ? ['auto', 'avoid', 'avoid-page', 'avoid-column']
          : /* before || after */ [
              'auto',
              'avoid',
              'all',
              'avoid-page',
              'page',
              'left',
              'right',
              'column',
            ]),
  ),

  // Cursor
  matchTheme('(cursor)-' /*, 'cursor' */),
  withAutocomplete$(
    '(cursor)-',
    DEV &&
      (() => [
        'alias',
        'all-scroll',
        'auto',
        'cell',
        'col-resize',
        'context-menu',
        'copy',
        'crosshair',
        'default',
        'e-resize',
        'ew-resize',
        'grab',
        'grabbing',
        'help',
        'move',
        'n-resize',
        'ne-resize',
        'nesw-resize',
        'no-drop',
        'none',
        'not-allowed',
        'ns-resize',
        'nw-resize',
        'nwse-resize',
        'pointer',
        'progress',
        'row-resize',
        's-resize',
        'se-resize',
        'sw-resize',
        'text',
        'vertical-text',
        'w-resize',
        'wait',
        'zoom-in',
        'zoom-out',
      ]),
  ),

  // Scroll Snap Type
  match('snap-(none)', 'scroll-snap-type'),
  match('snap-(x|y|both)', ({ 1: $1 }) => ({
    'scroll-snap-type': $1 + ' var(--tw-scroll-snap-strictness)',
    '@layer defaults': {
      '*,::before,::after,::backdrop': {
        '--tw-scroll-snap-strictness': 'proximity',
      },
    },
  })),
  match('snap-(mandatory|proximity)', '--tw-scroll-snap-strictness'),

  // Scroll Snap Align
  match('snap-(?:(start|end|center)|align-(none))', 'scroll-snap-align'),

  // Scroll Snap Stop
  match('snap-(normal|always)', 'scroll-snap-stop'),

  match('scroll-(auto|smooth)', 'scroll-behavior'),

  // Scroll Margin
  // Padding
  matchTheme('scroll-p([xytrbl])?(?:$|-)', 'padding', edge('scroll-padding')),

  // Margin
  matchTheme<TailwindTheme, 'scrollMargin'>(
    '-?scroll-m([xytrbl])?(?:$|-)',
    'scroll-margin',
    edge('scroll-margin'),
  ),

  // Touch Action
  match('touch-(auto|none|manipulation)', 'touch-action'),
  match('touch-(pinch-zoom|pan-(?:(x|left|right)|(y|up|down)))', ({ 1: $1, 2: $2, 3: $3 }) => ({
    // x, left, right -> pan-x
    // y, up, down -> pan-y
    // -> pinch-zoom
    [`--tw-${$2 ? 'pan-x' : $3 ? 'pan-y' : $1}` as '--tw-pan-x']: $1,
    'touch-action': 'var(--tw-touch-action)',
    '@layer defaults': {
      '*,::before,::after,::backdrop': {
        '--tw-pan-x': 'var(--tw-empty,/*!*/ /*!*/)',
        '--tw-pan-y': 'var(--tw-empty,/*!*/ /*!*/)',
        '--tw-pinch-zoom': 'var(--tw-empty,/*!*/ /*!*/)',
        '--tw-touch-action': 'var(--tw-pan-x) var(--tw-pan-y) var(--tw-pinch-zoom)',
      },
    },
  })),

  // Outline Style
  match('outline-none', {
    outline: '2px solid transparent',
    'outline-offset': '2px',
  }),
  match('outline', { outlineStyle: 'solid' }),
  match('outline-(dashed|dotted|double|hidden)', 'outlineStyle'),

  // Outline Offset
  matchTheme('(outline-offset)-' /*, 'outlineOffset'*/),

  // Outline Color
  matchColor('outline-', {
    opacityVariable: false,
    opacitySection: 'opacity',
  }),

  // Outline Width
  matchTheme('outline-', 'outlineWidth'),

  // Pointer Events
  withAutocomplete$('(pointer-events)-', DEV && (() => ['auto', 'none'])),

  // Will Change
  matchTheme('(will-change)-' /*, 'willChange' */),
  withAutocomplete$('(will-change)-', DEV && (() => ['auto', 'contents', 'transform'])),

  // Resize
  [
    'resize(?:-(none|x|y))?',
    'resize',
    ({ 1: $1 }) => ({ x: 'horizontal', y: 'vertical' }[$1] || $1 || 'both'),
  ],

  // User Select
  match('select-(none|text|all|auto)', 'userSelect'),

  /* SVG */
  // Fill, Stroke
  matchColor('fill-', { section: 'fill', opacityVariable: false, opacitySection: 'opacity' }),
  matchColor('stroke-', { section: 'stroke', opacityVariable: false, opacitySection: 'opacity' }),

  // Stroke Width
  matchTheme('stroke-', 'strokeWidth'),

  /* ACCESSIBILITY */
  // Screen Readers
  match('sr-only', {
    position: 'absolute',
    width: '1px',
    height: '1px',
    padding: '0',
    margin: '-1px',
    overflow: 'hidden',
    whiteSpace: 'nowrap',
    clip: 'rect(0,0,0,0)',
    borderWidth: '0',
  }),

  match('not-sr-only', {
    position: 'static',
    width: 'auto',
    height: 'auto',
    padding: '0',
    margin: '0',
    overflow: 'visible',
    whiteSpace: 'normal',
    clip: 'auto',
  }),
]

export default rules

function spacify(value: string | MatchResult): string {
  return (typeof value == 'string' ? value : value[1]).replace(/-/g, ' ').trim()
}

function columnify(value: string | MatchResult): string {
  return (typeof value == 'string' ? value : value[1]).replace('col', 'column')
}

function position(shorthand: string, separator = '-'): string {
  const longhand: string[] = []

  for (const short of shorthand) {
    longhand.push({ t: 'top', r: 'right', b: 'bottom', l: 'left' }[short] as string)
  }

  return longhand.join(separator)
}

function join(match: ThemeMatchResult<MaybeArray<string>>): string
function join(value: MaybeArray<string> | undefined): string | undefined

function join(
  value: ThemeMatchResult<MaybeArray<string>> | MaybeArray<string> | undefined,
): string | undefined {
  return value && '' + ((value as ThemeMatchResult<MaybeArray<string>>)._ || value)
}

function convertContentValue({ $$ }: MatchResult) {
  return (
    ({
      // /* aut*/ o: '',
      /* sta*/ r /*t*/: 'flex-',
      /* end*/ '': 'flex-',
      // /* cen*/ t /*er*/: '',
      /* bet*/ w /*een*/: 'space-',
      /* aro*/ u /*nd*/: 'space-',
      /* eve*/ n /*ly*/: 'space-',
      // /* str*/ e /*tch*/: '',
      // /* bas*/ l /*ine*/: '',
    }[$$[3] || ''] || '') + $$
  )
}

function edge(
  propertyPrefix: string,
  propertySuffix = '',
): ThemeRuleResolver<string | ColorFromThemeValue, TailwindTheme> {
  return ({ 1: $1, _ }) => {
    const edges =
      {
        x: 'lr',
        y: 'tb',
      }[$1 as 'x' | 'y'] || $1 + $1

    return edges
      ? {
          ...toCSS(propertyPrefix + '-' + position(edges[0]) + propertySuffix, _),
          ...toCSS(propertyPrefix + '-' + position(edges[1]) + propertySuffix, _),
        }
      : toCSS(propertyPrefix + propertySuffix, _)
  }
}

function filter(prefix = ''): Rule<TailwindTheme>[] {
  const filters = [
    'blur',
    'brightness',
    'contrast',
    'grayscale',
    'hue-rotate',
    'invert',
    prefix && 'opacity',
    'saturate',
    'sepia',
    !prefix && 'drop-shadow',
  ].filter(Boolean) as string[]

  let defaults = {} as CSSObject

  // first create properties defaults
  for (const key of filters) {
    defaults[`--tw-${prefix}${key}` as '--tw-blur'] = 'var(--tw-empty,/*!*/ /*!*/)'
  }

  defaults = {
    // add default filter which allows standalone usage
    [`${prefix}filter`]: filters.map((key) => `var(--tw-${prefix}${key})`).join(' '),
    // move defaults
    '@layer defaults': {
      '*,::before,::after,::backdrop': defaults,
    },
  } as CSSObject

  return [
    `(${prefix}filter)-(none)`,

    match(`${prefix}filter`, defaults),

    ...filters.map((key) =>
      matchTheme<TailwindTheme, 'hueRotate' | 'dropShadow'>(
        // hue-rotate can be negated
        `${key[0] == 'h' ? '-?' : ''}(${prefix}${key})(?:$|-)`,

        key as 'hueRotate' | 'dropShadow',
        ({ 1: $1, _ }) =>
          ({
            [`--tw-${$1}`]: asArray(_)
              .map((value) => `${key}(${value})`)
              .join(' '),
            ...defaults,
          } as CSSObject),
      ),
    ),
  ]
}

function transform({ 1: $1, _ }: ThemeMatchResult<string>): CSSObject {
  return {
    ['--tw-' + $1]: _,
    ...tranformDefaults(),
  } as CSSObject
}

function tranformDefaults(): CSSObject {
  return {
    transform: 'var(--tw-transform)',
    '@layer defaults': {
      '*,::before,::after,::backdrop': {
        '--tw-translate-x': '0',
        '--tw-translate-y': '0',
        '--tw-rotate': '0',
        '--tw-skew-x': '0',
        '--tw-skew-y': '0',
        '--tw-scale-x': '1',
        '--tw-scale-y': '1',
        '--tw-transform': transformValue(),
      },
    },
  }
}

function transformValue(gpu?: boolean): string {
  return [
    gpu // -gpu
      ? 'translate3d(var(--tw-translate-x),var(--tw-translate-y),0)'
      : 'translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y))',
    'rotate(var(--tw-rotate))',
    'skewX(var(--tw-skew-x))',
    'skewY(var(--tw-skew-y))',
    'scaleX(var(--tw-scale-x))',
    'scaleY(var(--tw-scale-y))',
  ].join(' ')
}
function span({ 1: $1, 2: $2 }: MatchResult) {
  return `${$1} ${$2} / ${$1} ${$2}`
}

function gridTemplate({ 1: $1 }: MatchResult) {
  return `repeat(${$1},minmax(0,1fr))`
}

function range({
  start = 1,
  end,
  step = 1,
}: {
  start?: number
  end: number
  step?: number
}): string[] {
  const result: string[] = []

  for (let index = start; index <= end; index += step) {
    result.push(`${index}`)
  }

  return result
}
