/**
 * @file Fast minimalist utility-first CSS runtime inspired by Tailwind and Twind.
 * @author Michal Kochel
 */
import { LOGO } from './icons.js'

const SIZES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 72, 80, 96, 128]
const TWELVE = SIZES.slice(1, 13)
const WIDTHS = [0, 1, 2, 4, 8]
const OPACITIES = [0, 5, 10, 20, 25, 30, 40, 50, 60, 70, 75, 80, 90, 95, 100]
const STATES = ['hover', 'focus', 'focus-visible', 'active']
const PALETTES = ['brand1', 'brand2', 'brand3', 'brand4', 'brand5', 'system']
const HIGH_PRIORITY_RULES = ['col-start', 'row-start', 'hidden', '-none']

const SIZES_FRAC = [
  ['1/2', 1 / 2],
  ['1/3', 1 / 3],
  ['2/3', 2 / 3],
  ['1/4', 1 / 4],
  ['3/4', 3 / 4],
  ['1/5', 1 / 5],
  ['2/5', 2 / 5],
  ['3/5', 3 / 5],
  ['4/5', 4 / 5]
]

const RADII = [
  ['-none', 0],
  ['-sm', '3px'],
  ['', '4px'],
  ['-md', '6px'],
  ['-lg', '8px'],
  ['-xl', '12px'],
  ['-2xl', '16px'],
  ['-3xl', '24px'],
  ['-full', '50%']
]

const PIXEL_STEP = 4
export const MEDIUM = 600
export const LARGE = 960

const MEDIA_QUERIES = {
  sm: `@media screen and (max-width: ${MEDIUM - 1}px)`,
  md: `@media screen and (min-width: ${MEDIUM}px) and (max-width: ${LARGE - 1}px)`,
  lg: `@media screen and (min-width: ${LARGE}px)`,
  dark: '@media (prefers-color-scheme: dark)',
  print: '@media print',
}

const CLASS_PARSER = new RegExp(`((?<mq>${Object.keys(MEDIA_QUERIES).join('|')}):)?((?<state>${STATES.join('|')}):)?(?<util>.+)`)

const COLORS = {
  bga: 'var(--color-bga)',
  bgb: 'var(--color-bgb)',
  txa: 'var(--color-txa)',
  txb: 'var(--color-txb)',
  txc: 'var(--color-txc)'
}

const ANIMATIONS = {
  expand: `
    from { opacity: 0; transform: translateY(-8px) }
    to { opacity: 1 }
  `,
  toast: `
    from { opacity: 0; transform: translateY(48px) }
    to { opacity: 1 }
  `,
  fade: `
    0% { opacity: 0 }
    100% { opacity: 1 }
  `
}

const CONFIG = `:root {
  /* Misc */
  --amidala: 1;
  --page-width: 1300px;
  --nav-width: 1400px;
  --anim-time: 250ms;

  /* Fonts */
  --font-default: Arial, Roboto, Helvetica, sans-serif;
  --font-custom: custom-font;
  --font-scale: 1;

  /* Logo */
  --logo: url("data:image/svg+xml;utf-8,${encodeURIComponent(LOGO)}");
  --logo-ratio: 1;

  /* Borders */
  --radius-tl-scale: 1;
  --radius-tr-scale: 1;
  --radius-br-scale: 1;
  --radius-bl-scale: 1;

  /* Transparency */
  --bg-alpha: 1;
  --text-alpha: 1;
  --border-alpha: 1;
  --decoration-alpha: 1;
  --outline-alpha: 1;

  /* Reference colours (paper & ink == background & text) */
  --color-paper1: 0, 0%, 100%;
  --color-paper2: 0, 0%, 96%;
  --color-ink1: 0, 0%, 29%;
  --color-ink2: var(--color-ink1);

  /* Theme colours */
  --color-brand1-bg: 47, 100%, 50%;
  --color-brand1-text: var(--color-ink1);
  --color-brand2-bg: 359, 77%, 49%;
  --color-brand2-text: var(--color-paper1);
  --color-brand3-bg: 212, 49%, 39%;
  --color-brand3-text: var(--color-paper1);
  --color-brand4-bg: 159, 100%, 26%;
  --color-brand4-text: var(--color-paper1);
  --color-brand5-bg: 306, 61%, 33%;
  --color-brand5-text: var(--color-paper1);
  --color-system-bg: 210, 78%, 16%;
  --color-system-text: 0, 0%, 100%;
  --color-black-bg: 0, 0%, 0%;
  --color-black-text: var(--color-paper1);

  /* Set default colour to brand1 */
  --color-bga: var(--color-paper1);
  --color-txa: var(--color-ink1);
  --color-bgb: var(--color-brand1-bg);
  --color-txb: var(--color-brand1-text);
  --color-txc: var(--color-ink2);

  /* Apply default colours */
  color: hsl(var(--color-ink1));
  background: hsl(var(--color-paper1));
}`

const BASE = [
  '*, *::before, *::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; margin: 0; padding: 0 }',
  'ul, ol { list-style: none }',
  'h1,h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit }',
  'input, button, textarea, select { font: inherit }',
  'body, html { height: 100% }',
  'img, video { max-width: 100%; height: auto }',
  'cite { font-style: normal }',
  'a { text-decoration: none; color: inherit }',
  'button { text-align: unset; align-items: unset; background: transparent; color: inherit }',
  'html { overflow: auto }',
  // Stable gutter prevents layout shift when setting overflow to hidden (e.g. when opening a modal).
  'body { overflow-y: scroll; scrollbar-gutter: stable }',
  'svg { display: block }',
  '::placeholder { color: currentColor; opacity: 0.8 }',
  'caption { text-align: left }',
  '* { scrollbar-color: currentColor transparent }',
  // Fix modal and non-modal dialog discrepancies.
  'dialog { position: fixed; inset-block-start: 0; inset-block-end: 0; max-width: none; max-height: none }',
  // Use default scroll for print styles.
  '@media print { html { overflow-y: scroll !important } }',
  '@media print { body { overflow-y: visible !important } }'
]

// -----------------------------------------------------------------------------
// Utility classes
// -----------------------------------------------------------------------------

export const UTILS = new Map([
  // Amidala
  ...genp('pal'),
  ['font-custom', '{ font-family: var(--font-custom), var(--font-default) }'],
  ['max-w-page', '{ max-width: var(--page-width) }'],
  ['cq-dd-image', '{}'], // Required by AEM for the drag-and-drop to work.
  ['clickable', '{}'], // Analytics clickable identifier.
  ['scrollbar-stable', '{ scrollbar-gutter: stable }'],
  // ---------------------------------------------------------------------------
  // Layout - aspect-ratio
  ['aspect-auto', '{ aspect-ratio: auto }'],
  ['aspect-square', '{ aspect-ratio: 1 / 1 }'],
  ['aspect-video', '{ aspect-ratio: 16 / 9 }'],
  ['aspect-4/3', '{ aspect-ratio: 4 / 3 }'],
  // Layout - columns
  ...gen(ii => [`columns-${ii}`, `columns: ${ii}`], [...TWELVE, 'auto']),
  // Layout - break-after
  ...gen(ii => [`break-after-${ii}`, `break-after: ${ii}`], ['auto', 'avoid', 'all', 'avoid-page', 'page', 'left', 'right', 'column']),
  // Layout - break-before
  ...gen(ii => [`break-before-${ii}`, `break-before: ${ii}`], ['auto', 'avoid', 'all', 'avoid-page', 'page', 'left', 'right', 'column']),
  // Layout - break-inside
  ...gen(ii => [`break-inside-${ii}`, `break-inside: ${ii}`], ['auto', 'avoid', 'avoid-page', 'avoid-column']),
  // Layout - box-decoration-break
  ...gen(ii => [`box-decoration-${ii}`, `box-decoration-break: ${ii}`], ['clone', 'slice']),
  // Layout - box-sizing
  ...gen(ii => [`box-${ii}`, `box-sizing: ${ii}-box`], ['border', 'content']),
  // Layout - display
  ...gen(ii => [`${ii}`, `display: ${ii}`], ['inline', 'block', 'inline-block', 'flow-root', 'flex', 'inline-flex', 'grid', 'inline-grid', 'contents', 'table', 'inline-table', 'list-item']),
  ['hidden', '{ display: none }'],
  ['sr-only', '{ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0 }'],
  ['not-sr-only', '{ position: static; width: auto; height: auto; padding: 0; margin: 0; overflow: visible; clip: auto; white-space: normal }'],
  // Layout - float
  ...gen(ii => [`float-${ii}`, `float: inline-${ii}`], ['start', 'end']),
  ...gen(ii => [`float-${ii}`, `float: ${ii}`], ['left', 'right', 'none']),
  // Layout - clear
  ...gen(ii => [`clear-${ii}`, `clear: inline-${ii}`], ['start', 'end']),
  ...gen(ii => [`clear-${ii}`, `clear: ${ii}`], ['left', 'right', 'both', 'none']),
  // Layout - isolation
  ['isolate', '{ isolation: isolate }'],
  ['isolation-auto', '{ isolation: auto }'],
  // Layout - object-fit
  ...gen(ii => [`object-${ii}`, `object-fit: ${ii}`], ['contain', 'cover', 'fill', 'none', 'scale-down']),
  // Layout - object-position
  ...gen(ii => [`object-${ii.replaceAll(' ', '-')}`, `object-position: ${ii}`], ['bottom', 'center', 'left', 'left bottom', 'left top', 'right', 'right bottom', 'right top', 'top']),
  // Layout - overflow
  ...gen(ii => [`overflow-${ii}`, `overflow: ${ii}`], ['auto', 'hidden', 'clip', 'visible', 'scroll', 'visible']),
  ...gen(ii => [`overflow-x-${ii}`, `overflow-x: ${ii}`], ['auto', 'hidden', 'clip', 'visible', 'scroll', 'visible']),
  ...gen(ii => [`overflow-y-${ii}`, `overflow-y: ${ii}`], ['auto', 'hidden', 'clip', 'visible', 'scroll', 'visible']),
  // Layout - overscroll-behavior
  ...gen(ii => [`overscroll-${ii}`, `overscroll-behavior: ${ii}`], ['auto', 'contain', 'none']),
  ...gen(ii => [`overscroll-x-${ii}`, `overscroll-behavior-x: ${ii}`], ['auto', 'contain', 'none']),
  ...gen(ii => [`overscroll-y-${ii}`, `overscroll-behavior-y: ${ii}`], ['auto', 'contain', 'none']),
  // Layout - position
  ...gen(ii => [`${ii}`, `position: ${ii}`], ['static', 'fixed', 'absolute', 'relative', 'sticky']),
  // Layout - top / right / bottom / left
  ...gens('top'),
  ...gens('right'),
  ...gens('bottom'),
  ...gens('left'),
  ...gens('start', { prop: 'inset-inline-start' }),
  ...gens('end', { prop: 'inset-inline-end' }),
  ...gens('inset', { prop: 'inset' }),
  // Layout - visibility
  ['visible', '{ visibility: visible }'],
  ['invisible', '{ visibility: hidden }'],
  ['collapse', '{ visibility: collapse }'],
  // Layout - z-index
  ...gen(ii => [`z-${ii}`, `z-index: ${ii}`], [0, 10, 20, 30, 40, 50, 'auto']),
  // ---------------------------------------------------------------------------
  // Flexbox & Grid - flex-basis
  ...gens('basis', { prop: 'flex-basis' }),
  // Flexbox & Grid - flex-direction
  ['flex-row', '{ flex-direction: row }'],
  ['flex-row-reverse', '{ flex-direction: row-reverse }'],
  ['flex-col', '{ flex-direction: column }'],
  ['flex-col-reverse', '{ flex-direction: column-reverse }'],
  // Flexbox & Grid - flex-wrap
  ...gen(ii => [`flex-${ii}`, `flex-wrap: ${ii}`], ['nowrap', 'wrap', 'wrap-reverse']),
  // Flexbox & Grid - flex
  ['flex-1', '{ flex: 1 }'],
  ['flex-auto', '{ flex: 1 1 auto }'],
  ['flex-initial', '{ flex: 0 1 auto }'],
  ['flex-none', '{ flex: none }'],
  // Flexbox & Grid - flex-grow
  ['grow', '{ flex-grow: 1 }'],
  ['grow-0', '{ flex-grow: 0 }'],
  // Flexbox & Grid - flex-shrink
  ['shrink', '{ flex-shrink: 1 }'],
  ['shrink-0', '{ flex-shrink: 0 }'],
  // Flexbox & Grid - order
  ...gen(ii => [`order-${ii}`, `order: ${ii}`], TWELVE),
  ['order-first', '{ order: -9999 }'],
  ['order-last', '{ order: 9999 }'],
  ['order-none', '{ order: 0 }'],
  // Flexbox & Grid - grid-template-columns
  ...gen(ii => [`grid-cols-${ii}`, `grid-template-columns: repeat(${ii}, minmax(0, 1fr))`], TWELVE),
  ['grid-cols-none', '{ grid-template-columns: none }'],
  ['grid-cols-subgrid', '{ grid-template-columns: subgrid }'],
  // Flexbox & Grid - grid-column
  ['col-auto', '{ grid-column: auto }'],
  ...gen(ii => [`col-span-${ii}`, `grid-column: span ${ii} / span ${ii}`], TWELVE),
  ['col-span-full', '{ grid-column: 1 / -1 }'],
  ['col-start-auto', '{ grid-column-start: auto }'],
  ...gen(ii => [`col-start-${ii}`, `grid-column-start: ${ii}`], [...TWELVE, 13]),
  // Flexbox & Grid - grid-template-rows
  ...gen(ii => [`grid-rows-${ii}`, `grid-template-rows: repeat(${ii}, auto)`], TWELVE),
  ['grid-rows-none', '{ grid-template-rows: none }'],
  ['grid-rows-subgrid', '{ grid-template-rows: subgrid }'],
  // Flexbox & Grid - grid-row
  ['row-auto', '{ grid-row: auto }'],
  ...gen(ii => [`row-span-${ii}`, `grid-row: span ${ii} / span ${ii}`], TWELVE),
  ['row-span-full', '{ grid-row: 1 / -1 }'],
  ['row-start-auto', '{ grid-row-start: auto }'],
  ...gen(ii => [`row-start-${ii}`, `grid-row-start: ${ii}`], [...TWELVE, 13]),
  // Flexbox & Grid - grid-auto-flow
  ['grid-flow-row', '{ grid-auto-flow: row }'],
  ['grid-flow-col', '{ grid-auto-flow: column }'],
  ['grid-flow-dense', '{ grid-auto-flow: dense }'],
  ['grid-flow-row-dense', '{ grid-auto-flow: row dense }'],
  ['grid-flow-col-dense', '{ grid-auto-flow: column dense }'],
  // Flexbox & Grid - grid-auto-columns
  ['auto-cols-auto', '{ grid-auto-columns: auto }'],
  ['auto-cols-min', '{ grid-auto-columns: min-content }'],
  ['auto-cols-max', '{ grid-auto-columns: max-content }'],
  ['auto-cols-fr', '{ grid-auto-columns: minmax(0, 1fr) }'],
  // Flexbox & Grid - grid-auto-rows
  ['auto-rows-auto', '{ grid-auto-rows: auto }'],
  ['auto-rows-min', '{ grid-auto-rows: min-content }'],
  ['auto-rows-max', '{ grid-auto-rows: max-content }'],
  ['auto-rows-fr', '{ grid-auto-rows: minmax(0, 1fr) }'],
  // Flexbox & Grid - gap
  ...gens('gap', { fractions: false }),
  ['gap-px', '{ gap: 1px }'],
  ...gens('gap-x', { prop: 'column-gap', fractions: false }),
  ['gap-x-px', '{ column-gap: 1px }'],
  ...gens('gap-y', { prop: 'row-gap', fractions: false }),
  ['gap-y-px', '{ row-gap: 1px }'],
  // Flexbox & Grid - justify-content
  ['justify-start', '{ justify-content: flex-start }'],
  ['justify-end', '{ justify-content: flex-end }'],
  ['justify-center', '{ justify-content: center }'],
  ['justify-between', '{ justify-content: space-between }'],
  ['justify-around', '{ justify-content: space-around }'],
  ['justify-evenly', '{ justify-content: space-evenly }'],
  ['justify-stretch', '{ justify-content: stretch }'],
  ['justify-baseline', '{ justify-content: baseline }'],
  ['justify-normal', '{ justify-content: normal }'],
  // Flexbox & Grid - justify-items
  ...gen(ii => [`justify-items-${ii}`, `justify-items: ${ii}`], ['start', 'end', 'center', 'stretch', 'normal']),
  // Flexbox & Grid - justify-self
  ...gen(ii => [`justify-self-${ii}`, `justify-self: ${ii}`], ['auto', 'start', 'end', 'center', 'stretch']),
  // Flexbox & Grid - align-content
  ['content-normal', '{ align-content: normal }'],
  ['content-center', '{ align-content: center }'],
  ['content-start', '{ align-content: flex-start }'],
  ['content-end', '{ align-content: flex-end }'],
  ['content-between', '{ align-content: space-between }'],
  ['content-around', '{ align-content: space-around }'],
  ['content-evenly', '{ align-content: space-evenly }'],
  ['content-baseline', '{ align-content: baseline }'],
  ['content-stretch', '{ align-content: stretch }'],
  // Flexbox & Grid - align-items
  ['items-start', '{ align-items: flex-start }'],
  ['items-end', '{ align-items: flex-end }'],
  ['items-center', '{ align-items: center }'],
  ['items-baseline', '{ align-items: baseline }'],
  ['items-stretch', '{ align-items: stretch }'],
  // Flexbox & Grid - align-self
  ['self-auto', '{ align-self: auto }'],
  ['self-start', '{ align-self: flex-start }'],
  ['self-end', '{ align-self: flex-end }'],
  ['self-center', '{ align-self: center }'],
  ['self-stretch', '{ align-self: stretch }'],
  ['self-baseline', '{ align-self: baseline }'],
  // Flexbox & Grid - place-content
  ['place-content-center', '{ place-content: center }'],
  ['place-content-start', '{ place-content: flex-start }'],
  ['place-content-end', '{ place-content: flex-end }'],
  ['place-content-between', '{ place-content: space-between }'],
  ['place-content-around', '{ place-content: space-around }'],
  ['place-content-evenly', '{ place-content: space-evenly }'],
  ['place-content-baseline', '{ place-content: baseline }'],
  ['place-content-stretch', '{ place-content: stretch }'],
  // Flexbox & Grid - place-items
  ...gen(ii => [`place-items-${ii}`, `place-items: ${ii}`], ['start', 'end', 'center', 'baseline', 'stretch']),
  // Flexbox & Grid - place-self
  ...gen(ii => [`place-self-${ii}`, `place-self: ${ii}`], ['auto', 'start', 'end', 'center', 'stretch']),
  // ---------------------------------------------------------------------------
  // Spacing - padding
  ...gens('p', { prop: 'padding', fractions: true }),
  ...gens('px', { prop: 'padding-inline', fractions: true }),
  ...gens('py', { prop: 'padding-block', fractions: true }),
  ...gens('pt', { prop: 'padding-top', fractions: true }),
  ...gens('pr', { prop: 'padding-right', fractions: true }),
  ...gens('pb', { prop: 'padding-bottom', fractions: true }),
  ...gens('pl', { prop: 'padding-left', fractions: true }),
  ...gens('ps', { prop: 'padding-inline-start', fractions: true }),
  ...gens('pe', { prop: 'padding-inline-end', fractions: true }),
  // Spacing - margin
  ...gens('m', { prop: 'margin', fractions: true }),
  ...gens('mx', { prop: 'margin-inline', fractions: true }),
  ...gens('my', { prop: 'margin-block', fractions: true }),
  ...gens('mt', { prop: 'margin-top', fractions: true }),
  ...gens('mr', { prop: 'margin-right', fractions: true }),
  ...gens('ml', { prop: 'margin-left', fractions: true }),
  ...gens('mb', { prop: 'margin-bottom', fractions: true }),
  ...gens('ms', { prop: 'margin-inline-start', fractions: true }),
  ...gens('me', { prop: 'margin-inline-end', fractions: true }),
  ...gens('space-x', { prop: 'margin-inline-end', fractions: true, next: '> :not(:last-child) ' }),
  ...gens('space-y', { prop: 'margin-bottom', fractions: true, next: '> :not(:last-child) ' }),

  // Sizing - width
  ...gens('w', { prop: 'width', fractions: true }),
  // Sizing - min-width
  ...gens('min-w', { prop: 'min-width', fractions: true }),
  // Sizing - max-width
  ...gens('max-w', { prop: 'max-width', fractions: true }),
  // Sizing - height
  ...gens('h', { prop: 'height', fractions: true }),
  // Sizing - min-height
  ...gens('min-h', { prop: 'min-height', fractions: true }),
  // Sizing - max-height
  ...gens('max-h', { prop: 'max-height', fractions: true }),
  // ---------------------------------------------------------------------------
  // Typography - font-family
  ['font-sans', '{ font-family: var(--font-default) }'],
  ['font-serif', '{ font-family: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif }'],
  ['font-mono', '{ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace }'],
  // Typography - font-size (customised)
  ['text-xs', '{ font-size: calc(11px * var(--font-scale)); line-height: 1.5 }'],
  ['text-sm', '{ font-size: calc(12px * var(--font-scale)); line-height: 1.5 }'],
  ['text-base', '{ font-size: calc(14px * var(--font-scale)); line-height: 1.5 }'],
  ['text-lg', '{ font-size: calc(16px * var(--font-scale)); line-height: 1.5 }'],
  ['text-xl', '{ font-size: calc(18px * var(--font-scale)); line-height: 1.5 }'],
  ['text-2xl', '{ font-size: calc(20px * var(--font-scale)); line-height: 1.5 }'],
  ['text-3xl', '{ font-size: calc(28px * var(--font-scale)); line-height: 1.5 }'],
  ['text-4xl', '{ font-size: calc(32px * var(--font-scale)); line-height: 1.5 }'],
  ['text-5xl', '{ font-size: calc(36px * var(--font-scale)); line-height: 1.5 }'],
  ['text-6xl', '{ font-size: calc(40px * var(--font-scale)); line-height: 1.5 }'],
  ['text-7xl', '{ font-size: calc(44px * var(--font-scale)); line-height: 1.5 }'],
  ['text-8xl', '{ font-size: calc(60px * var(--font-scale)); line-height: 1.5 }'],
  ['text-9xl', '{ font-size: calc(68px * var(--font-scale)); line-height: 1.5 }'],
  // Typography - font-smoothing
  ['antialiased', '{ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale }'],
  ['subpixel-antialiased', '{ -webkit-font-smoothing: auto; -moz-osx-font-smoothing: auto }'],
  // Typography - font-style
  ['italic', '{ font-style: italic }'],
  ['not-italic', '{ font-style: normal }'],
  // Typography - font-weight
  ['font-thin', '{ font-weight: 100 }'],
  ['font-extralight', '{ font-weight: 200 }'],
  ['font-light', '{ font-weight: 300 }'],
  ['font-normal', '{ font-weight: 400 }'],
  ['font-medium', '{ font-weight: 500 }'],
  ['font-semibold', '{ font-weight: 600 }'],
  ['font-bold', '{ font-weight: 700 }'],
  ['font-extrabold', '{ font-weight: 800 }'],
  ['font-black', '{ font-weight: 900 }'],
  // Tpyography - font-stretch
  ...gen(ii => [`font-stretch-${ii}`, `font-stretch: ${ii}`], ['ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'normal', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded']),
  // Typography - font-variant-numeric
  ['normal-nums', '{ font-variant-numeric: normal }'],
  ...gen(ii => [ii, `font-variant-numeric: ${ii}`], ['ordinal', 'slashed-zero', 'lining-nums', 'oldstyle-nums', 'proportional-nums', 'tabular-nums', 'diagonal-fractions', 'stacked-fractions']),
  // Typography - letter-spacing
  ['tracking-tighter', '{ letter-spacing: -0.05em }'],
  ['tracking-tight', '{ letter-spacing: -0.025em }'],
  ['tracking-normal', '{ letter-spacing: 0 }'],
  ['tracking-wide', '{ letter-spacing: 0.025em }'],
  ['tracking-wider', '{ letter-spacing: 0.05em }'],
  ['tracking-widest', '{ letter-spacing: 0.1em }'],
  // Typography - line-clamp
  ...gen(ii => [`line-clamp-${ii}`, `overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: ${ii}`], [1, 2, 3, 4, 5, 6, 'none']),
  // Typography - line-height
  ['leading-none', '{ line-height: 1 }'],
  ['leading-tight', '{ line-height: 1.25 }'],
  ['leading-snug', '{ line-height: 1.375 }'],
  ['leading-normal', '{ line-height: 1.5 }'],
  ['leading-relaxed', '{ line-height: 1.625 }'],
  ['leading-loose', '{ line-height: 2 }'],
  // Typography - list-style
  ['list-image-none', '{ list-style-image: none }'],
  ['list-inside', '{ list-style-position: inside }'],
  ['list-outside', '{ list-style-position: outside }'],
  ['list-disc', '{ list-style-type: disc }'],
  ['list-decimal', '{ list-style-type: decimal }'],
  ['list-none', '{ list-style-type: none }'],
  // Typography - text-align
  ...gen(ii => [`text-${ii}`, `text-align: ${ii}`], ['left', 'center', 'right', 'justify', 'start', 'end']),
  // Typography - text-color
  ...genc('text', 'color'),
  // Typography - text-decoration
  ...gen(ii => [ii, `text-decoration: ${ii}`], ['underline', 'overline', 'line-through']),
  ['no-underline', '{ text-decoration: none }'],
  // Typography - text-decoration-color
  ...genc('decoration', 'text-decoration-color'),
  // Typography = text-decoration-style
  ...gen(ii => [`decoration-${ii}`, `text-decoration-style: ${ii}`], ['solid', 'double', 'dotted', 'dashed', 'wavy']),
  // Typography - text-decoration-thickness
  ['decoration-auto', '{ text-decoration-thickness: auto }'],
  ['decoration-from-font', '{ text-decoration-thickness: from-font }'],
  ...gen(ii => [`decoration-${ii}`, `text-decoration-thickness: ${ii}px`], WIDTHS),
  // Typography - text-underline-offset
  ['underline-offset-auto', '{ text-underline-offset: auto }'],
  ...gen(ii => [`underline-offset-${ii}`, `text-underline-offset: ${ii}px`], WIDTHS),
  // Typography - text-transform
  ...gen(ii => [`${ii}`, `text-transform: ${ii}`], ['uppercase', 'lowercase', 'capitalize']),
  ['normal-case', '{ text-transform: none }'],
  // Typography - text-overflow
  ['truncate', '{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap }'],
  ['overflow-ellipsis', '{ text-overflow: ellipsis }'],
  ['overflow-clip', '{ text-overflow: clip }'],
  // Typography - text-wrap
  ...gen(ii => [`text-${ii}`, `text-wrap: ${ii}`], ['wrap', 'nowrap', 'balance', 'pretty']),
  // Typography - text-indent
  ...gens('indent', { prop: 'text-indent', fractions: false }),
  // Typography - vertical-align
  ...gen(ii => [`align-${ii}`, `vertical-align: ${ii}`], ['baseline', 'top', 'middle', 'bottom', 'text-top', 'text-bottom', 'sub', 'super']),
  // Typography - white-space
  ...gen(ii => [`whitespace-${ii}`, `white-space: ${ii}`], ['normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap', 'break-spaces']),
  // Typography - word-break
  ['break-normal', '{ overflow-wrap: normal; word-break: normal }'],
  ['break-words', '{ overflow-wrap: anywhere; word-break: normal }'],
  ['break-all', '{ word-break: break-all }'],
  ['break-keep', '{ word-break: keep-all }'],
  // Typography - hyphens
  ...gen(ii => [`hyphens-${ii}`, `hyphens: ${ii}`], ['none', 'manual', 'auto']),
  // Typography - content
  ['content-none', '{ content: none }'],
  // ---------------------------------------------------------------------------
  // Backgrounds - background-attachment
  ...gen(ii => [`bg-${ii}`, `background-attachment: ${ii}`], ['fixed', 'local', 'scroll']),
  // Backgrounds - background-clip
  ['bg-clip-border', '{ background-clip: border-box }'],
  ['bg-clip-padding', '{ background-clip: padding-box }'],
  ['bg-clip-content', '{ background-clip: content-box }'],
  ['bg-clip-text', '{ background-clip: text }'],
  // Backgrounds - background-color'
  ...genc('bg', 'background-color'),
  // Backgrounds - background-image - unsupported
  // Backgrounds - background-origin
  ...gen(ii => [`bg-${ii}`, `background-origin: ${ii}`], ['border-box', 'padding-box', 'content-box']),
  // Backgrounds - background-position
  ...gen(ii => [`bg-${ii}`, `background-position: ${ii}`], ['bottom', 'center', 'left', 'left bottom', 'left top', 'right', 'right bottom', 'right top', 'top']),
  // Backgrounds - background-repeat
  ...gen(ii => [`bg-${ii}`, `background-repeat: ${ii}`], ['repeat', 'repeat-x', 'repeat-y', 'space', 'round', 'no-repeat']),
  // Backgrounds - background-size
  ...gen(ii => [`bg-${ii}`, `background-size: ${ii}`], ['auto', 'cover', 'contain']),
  // ---------------------------------------------------------------------------
  // Borders - border-radius
  ['rounded', '{ border-radius: 4px }'],
  ...gen(([name, value]) => [
    `rounded${name}`,
    `border-radius: calc(var(--radius-tl-scale) * ${value}) calc(var(--radius-tr-scale) * ${value}) calc(var(--radius-br-scale) * ${value}) calc(var(--radius-bl-scale) * ${value})`
  ], RADII),
  ...gen(([name, value]) => [`rounded-tl${name}`, `border-top-left-radius: calc(${value} * var(--radius-tl-scale))`], RADII),
  ...gen(([name, value]) => [`rounded-tr${name}`, `border-top-right-radius: calc(${value} * var(--radius-tr-scale))`], RADII),
  ...gen(([name, value]) => [`rounded-bl${name}`, `border-bottom-left-radius: calc(${value} * var(--radius-bl-scale))`], RADII),
  ...gen(([name, value]) => [`rounded-br${name}`, `border-bottom-right-radius: calc(${value} * var(--radius-br-scale))`], RADII),
  // Borders - border-width
  ['border', '{ border-width: 1px }'],
  ...gen(ii => [`border-${ii}`, `border-width: ${ii}px`], WIDTHS),
  ['border-t', '{ border-top-width: 1px }'],
  ...gen(ii => [`border-t-${ii}`, `border-top-width: ${ii}px`], WIDTHS),
  ['border-b', '{ border-bottom-width: 1px }'],
  ...gen(ii => [`border-b-${ii}`, `border-bottom-width: ${ii}px`], WIDTHS),
  ['border-l', '{ border-left-width: 1px }'],
  ...gen(ii => [`border-l-${ii}`, `border-left-width: ${ii}px`], WIDTHS),
  ['border-r', '{ border-right-width: 1px }'],
  ...gen(ii => [`border-r-${ii}`, `border-right-width: ${ii}px`], WIDTHS),
  ['border-s', '{ border-inline-start-width: 1px }'],
  ...gen(ii => [`border-s-${ii}`, `border-inline-start-width: ${ii}px`], WIDTHS),
  ['border-e', '{ border-inline-end-width: 1px }'],
  ...gen(ii => [`border-e-${ii}`, `border-inline-end-width: ${ii}px`], WIDTHS),
  // Borders - border-color
  ...genc('border', 'border-color'),
  // Borders - Border-style
  ...gen(ii => [`border-${ii}`, `border-style: ${ii}`], ['solid', 'dashed', 'dotted', 'double', 'hidden', 'none']),
  // Borders - outline-width
  ...gen(ii => [`outline-${ii}`, `outline-width: ${ii}px`], WIDTHS),
  // Borders - outline-color
  ...genc('outline', 'outline-color'),
  // Borders - outline-style
  ['outline', '{ outline-style: solid }'],
  ...gen(ii => [`outline-${ii}`, `outline-style: ${ii}`], ['none', 'dashed', 'dotted', 'double']),
  // Borders - outline-offset
  ...gen(ii => [`outline-offset-${ii}`, `outline-offset: ${ii}px`], WIDTHS),
  // ---------------------------------------------------------------------------
  // Effects - box-shadow (customised)
  ['shadow', '{ box-shadow: inset 0 0 2px 0 hsla(0, 0%, 100%, 0.2), 0 2px 8px 0 hsla(var(--color-black-bg), 0.2) }'], // Customised.
  ['shadow-md', '{ box-shadow: inset 0 0 2px 0 hsla(0, 0%, 100%, 0.2), 0 0px 16px 0 hsla(var(--color-black-bg), 0.2) }'], // Customised.
  ['shadow-lg', '{ box-shadow: inset 0 0 2px 0 hsla(0, 0%, 100%, 0.2), 0 2px 16px 0 hsla(var(--color-black-bg), 0.2) }'], // Customised.
  ['shadow-none', '{ box-shadow: none }'],
  // Effects - text-shadow
  // Effects - opacity
  ...gen(ii => [`opacity-${ii}`, `opacity: ${ii / 100}`], OPACITIES),
  // Effects - mix-blend-mode - unsupported
  // Effects - background-blend-mode - unsupported
  // ---------------------------------------------------------------------------
  // Filters
  ['filter-none', '{ filter: none }'],
  // Filters - blur
  ['blur-sm', '{ filter: blur(4px) }'],
  ['blur', '{ filter: blur(4px) }'],
  ['blur-md', '{ filter: blur(4px) }'],
  ['blur-lg', '{ filter: blur(16px) }'],
  ['blur-xl', '{ filter: blur(24px) }'],
  ['blur-2xl', '{ filter: blur(40px) }'],
  ['blur-3xl', '{ filter: blur(64px) }'],
  ['blur-none', '{ filter: none }'],
  // Filters - brightness - unsupported
  // Filters - contrast - unsupported
  // Filters - drop-shadow
  ['drop-shadow-sm', '{ filter: drop-shadow(0 1px 1px rgb(0 0 0 / 0.05)) }'],
  ['drop-shadow', '{ filter: drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) drop-shadow(0 1px 1px rgb(0 0 0 / 0.06)) }'],
  ['drop-shadow-md', '{ filter: drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06)) }'],
  ['drop-shadow-lg', '{ filter: drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1)) }'],
  ['drop-shadow-xl', '{ filter: drop-shadow(0 20px 13px rgb(0 0 0 / 0.03)) drop-shadow(0 8px 5px rgb(0 0 0 / 0.08)) }'],
  ['drop-shadow-2xl', '{ filter: drop-shadow(0 25px 25px rgb(0 0 0 / 0.15)) }'],
  ['drop-shadow-none', '{ filter: drop-shadow(0 0 #0000) }'],
  // Filters - grayscale
  ['grayscale-0', '{ filter: grayscale(0) }'],
  ['grayscale', '{ filter: grayscale(100%) }'],
  // Filters - hue-rotate - unsupported
  // Filters - invert - unsupported
  // Filters - saturate - unsupported
  // Filters - sepia - unsupported
  // ---------------------------------------------------------------------------
  // Tables - border-collapse
  ['border-collapse', '{ border-collapse: collapse }'],
  ['border-separate', '{ order-collapse: separate }'],
  // Tables - border-spacing
  ...gens('border-spacing', { prop: 'border-spacing', fractions: false }),
  // Tables - table-layout
  ['table-auto', '{ table-layout: auto }'],
  ['table-fixed', '{ table-layout: fixed }'],
  // Tables - caption-side
  ['caption-top', '{ caption-side: top }'],
  ['caption-bottom', '{ caption-side: bottom }'],
  // ---------------------------------------------------------------------------
  // Transitions & Animations - transition-property
  ['transition', '{ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; transition-duration: var(--anim-time) }'],
  ['transition-none', '{ transition-property: none }'],
  ['transition-all', '{ transition-property: all; transition-duration: var(--anim-time) }'],
  // Transitions & Animations - transition-behavior
  ['transition-normal', '{ transition-behavior: normal }'],
  ['transition-discrete', '{ transition-behavior: discrete }'],
  // Transitions & Animations - transition-duration - unsupported
  // Transitions & Animations - transition-timing - unsupported
  // Transitions & Animations - transition-delay - unsupported
  // Transitions & Animations - animation
  ...gen(ii => [`animate-${ii}`, `animation: ${ii} calc(var(--anim-time) * ${ii === 'delay' ? 3 : 1})`], Object.keys(ANIMATIONS)),
  // ---------------------------------------------------------------------------
  // Transforms - blackface-visibility - unsupported
  // Transforms - perspective - unsupported
  // Transforms - perspective-origin - unsupported
  // Transforms - rotate
  ...gen(ii => [`rotate-${ii}`, `rotate: ${ii}deg`], [90, 180, 270]),
  // Transforms - scale - unsupported
  // Transforms - skew - unsupported
  // Transforms - transform - unsupported
  // Transforms - transform-origin - unsupported
  // Transforms - transform-style - unsupported
  // Transforms - translate - unsupported
  // ---------------------------------------------------------------------------
  // Interactivity - accent-color - unsupported
  // Interactivity - appearance
  ['appearance-none', '{ appearance: none }'],
  ['appearance-auto', '{ appearance: auto }'],
  // Interactivity - caret-color - unsupported
  // Interactivity - color-scheme - unsupported
  // Interactivity - cursor
  ...gen(ii => [`cursor-${ii}`, `cursor: ${ii}`], ['auto', 'default', 'pointer', 'wait', 'text', 'move', 'not-allowed', 'grab', 'grabbing']),
  // Interactivity - field-sizing - unsupported
  // Interactivity - pointer-events
  ['pointer-events-none', '{ pointer-events: none }'],
  ['pointer-events-auto', '{ pointer-events: auto }'],
  // Interactivity - resize
  ['resize-none', '{ resize: none }'],
  ['resize-x', '{ resize: horizontal }'],
  ['resize-y', '{ resize: vertical }'],
  ['resize-both', '{ resize: both }'],
  // Interactivity - scroll-behavior
  ['scroll-auto', '{ scroll-behavior: auto }'],
  ['scroll-smooth', '{ scroll-behavior: smooth }'],
  // Interactivity - scroll-margin - unsupported
  // Interactivity - scroll-padding - unsupported
  // Interactivity - scroll-snap-align
  ['snap-start', '{ scroll-snap-align: start }'],
  ['snap-end', '{ scroll-snap-align: end }'],
  ['snap-center', '{ scroll-snap-align: center }'],
  ['snap-align-none', '{ scroll-snap-align: none }'],
  // Interactivity - scroll-snap-stop
  ['snap-stop-normal', '{ scroll-snap-stop: normal }'],
  ['snap-stop-always', '{ scroll-snap-stop: always }'],
  // Interactivity - scroll-snap-type
  ['snap-none', '{ scroll-snap-type: none }'],
  ['snap-x', '{ scroll-snap-type: x mandatory }'],
  ['snap-y', '{ scroll-snap-type: y proximity }'],
  ['snap-both', '{ scroll-snap-type: both proximity }'],
  // Interactivity - touch-action - unsupported
  // Interactivity - user-select
  ['select-none', '{ user-select: none }'],
  ['select-text', '{ user-select: text }'],
  ['select-all', '{ user-select: all }'],
  ['select-auto', '{ user-select: auto }']
  // Interactivity - will-change - unsupported
  // ---------------------------------------------------------------------------
  // SVG - fill - unsupported
  // SVG - stroke - unsupported
  // SVG - stroke-width - unsupported
  // ---------------------------------------------------------------------------
  // Accessibility - forced-color-adjust - unsupported
])

// -----------------------------------------------------------------------------
// Rule generators
// -----------------------------------------------------------------------------

// Generate with a list.
function * gen (func, list) {
  for (const item of list) {
    const [name, value] = func(item)
    yield [name, `{ ${value} }`]
  }
}

// Generate with sizes.
function * gens (cls, options = {}) {
  const { prop = cls, fractions = true, next = '' } = options

  yield [`${cls}-auto`, `${next}{ ${prop}: auto }`]
  yield [`${cls}-full`, `${next}{ ${prop}: 100% }`]
  yield [`${cls}-min`, `${next}{ ${prop}: min-content }`]
  yield [`${cls}-max`, `${next}{ ${prop}: max-content }`]
  yield [`${cls}-fit`, `${next}{ ${prop}: fit-content }`]
  yield [`${cls}-px`, `${next}{ ${prop}: 1px }`]

  for (const unit of ['svh', 'lvh', 'dvh', 'svw', 'lvw', 'dvw']) {
    yield [`${cls}-${unit}`, `${next}{ ${prop}: 100${unit} }`]
  }

  if (fractions) {
    for (const [s, frac] of SIZES_FRAC) {
      yield [`${cls}-${s}`, `${next}{ ${prop}: ${frac * 100}% }`]
    }
  }

  for (const s of SIZES) {
    yield [`${cls}-${s}`, `${next}{ ${prop}: ${s * PIXEL_STEP}px }`]
    yield [`-${cls}-${s}`, `${next}{ ${prop}: -${s * PIXEL_STEP}px }`]
  }
}

// Generate colours.
function * genc (cls, prop) {
  yield [`${cls}-inherit`, `{ ${prop}: inherit }`]
  yield [`${cls}-transparent`, `{ ${prop}: transparent }`]
  yield [`${cls}-current`, `{ ${prop}: currentColor }`]

  for (const [colorName, color] of Object.entries(COLORS)) {
    yield [`${cls}-${colorName}`, `{ ${prop}: hsla(${color}, var(--${cls}-alpha)) }`]

    for (const value of OPACITIES) {
      yield [`${cls}-${colorName}/${value}`, `{ ${prop}: hsla(${color}, ${value / 100}) }`]
    }
  }
}

// Generate palettes.
function generatePalettesTemplate (c1, c2, c3, c4, c5, alpha) {
  return `{
    --color-bga: var(--color-${c1});
    --color-txa: var(--color-${c2});
    --color-bgb: var(--color-${c3});
    --color-txb: var(--color-${c4});
    --color-txc: var(--color-${c5});
    --bg-alpha: ${alpha};
  }`
}

function * genp (cls) {
  const tpl = generatePalettesTemplate

  for (const name of PALETTES) {
    yield [`${cls}-${name}`, tpl('paper1', 'ink1', `${name}-bg`, `${name}-text`, 'ink2', '1')]
    yield [`${cls}-${name}-subtle`, tpl('paper2', 'ink1', `${name}-bg`, `${name}-text`, 'ink2', '1')]
    yield [`${cls}-${name}-solid`, tpl(`${name}-bg`, `${name}-text`, `${name}-text`, `${name}-bg`, `${name}-text`, '1')]
  }

  yield [`${cls}-transparent`, tpl('black-bg', 'black-text', 'brand1-bg', 'brand1-text', 'black-text', '0')]
  yield [`${cls}-semitransparent`, tpl('black-bg', 'black-text', 'brand1-bg', 'brand1-text', 'black-text', '0.6')]
  // AEM outputs pal-inherited if nothing is selected. No colour change.
  yield [`${cls}-inherited`, '{}']
}

// -----------------------------------------------------------------------------
// Runtime builder
// -----------------------------------------------------------------------------

const usedRules = new Set()
const sheet = createSheet()
let mqRulesStartIndex = sheet.cssRules.length

function createSheet () {
  const result = new CSSStyleSheet()
  for (const css of BASE) { result.insertRule(css, result.cssRules.length) }
  for (const [name, keyframes] of Object.entries(ANIMATIONS)) { result.insertRule(`@keyframes ${name} { ${keyframes} }`) }
  return result
}

// Rules are added in the following order.
// 1. Standard rules.
// 2. High priority standard rules.
// 3. Media query rules.
// 4. High priority media query rules.
function addRule (cls) {
  // Skip if empty or already present.
  if (!cls || usedRules.has(cls)) {
    return
  }

  const { mq, state, util } = cls.match(CLASS_PARSER).groups

  const css = UTILS.get(util)
  if (!css) {
    console.warn(`Unknown utility class: [${cls}]`)
    return
  }

  const isHighPriority = Boolean(HIGH_PRIORITY_RULES.some(r => util.includes(r)))

  // Resolve rule and index.
  const escapedUtil = util.replaceAll('/', String.raw`\/`)
  const utilWithState = state ? String.raw`${state}\:${escapedUtil}:${state}` : escapedUtil
  let rule
  let index
  if (mq) {
    index = isHighPriority ? sheet.cssRules.length : mqRulesStartIndex
    rule = String.raw`${MEDIA_QUERIES[mq]} { .${mq}\:${utilWithState} ${css} }`
  } else {
    rule = `.${utilWithState} ${css}`
    index = isHighPriority ? mqRulesStartIndex : 0
    mqRulesStartIndex += 1
  }

  usedRules.add(cls)
  sheet.insertRule(rule, index)
}

export function injectStyles (root) {
  root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet]

  // Add config.
  // In order for it to be overrideable by themes, it needs to be inlined.
  // This is because constructible styles will take precedence.
  const hasConfig = Boolean(globalThis.getComputedStyle(document.documentElement).getPropertyValue('--amidala'))
  if (!hasConfig) {
    const config = document.createElement('style')
    config.id = 'tw-config'
    config.textContent = CONFIG
    root.head.prepend(config)
  }
}

export function tw (className) {
  for (const cls of (className || '').trim().split(/ +/)) {
    addRule(cls)
  }

  return className
}
