<commit_before>import { PlainObject, StyliStyle } from '../types'
import { kebab, canUseDom, hash, memorize } from '../utils'

function generateStyliTag(name: string) {
  const tag = document.createElement('style')
  tag.dataset.styli = name
  document.querySelector('head')?.append(tag)
  return tag
}

const getMediaStyliTag = memorize((value: number) => {
  const tag = generateStyliTag(`media-styli-${value}`)
  tag.innerHTML = `@media (min-width: ${value}px}) {}`
  return tag
})

const generateClassName = memorize((key: string) => `styli-${hash(key)}`)

function styliStyleToCss(style: StyliStyle, breakpoints: number[]) {
  let className = generateClassName(JSON.stringify(style))
  let cssFragment = ''
  let cssFragmentList: string[] = []

  for (let [key, value] of Object.entries(style)) {
    const cssKeyName = kebab(key)
    // media queries
    if (Array.isArray(value)) {
      if (!breakpoints.length) {
        throw new Error('breakpoints is not provide, array value is not support')
      }
      for (let i = 0; i < breakpoints.length; i++) {
        cssFragmentList[i] = `${cssFragmentList[i] || ''} ${cssKeyName}: ${value[i]};`
      }
    } else {
      cssFragment = `${cssFragment} ${cssKeyName}: ${value};`
    }
  }

  cssFragmentList = cssFragmentList.map((fragment) => `.${className} { ${fragment} }`)
  cssFragment = `.${className} { ${cssFragment} }`

  return { cssFragment, className, cssFragmentList }
}

function getMediaTagContent(tag: any) {
  const content = tag.innerHTML
  const [, match = ''] = content.match(/{(.*)}/) || []
  return match
}

const setMediaTagContent = memorize((tag: any, breakpoint: number, content: string) => {
  tag.innerHTML = `@media (min-width: ${breakpoint}px) {${content}}`
  return true
})

const setStyliTagContent = memorize((tag: any, content: string) => {
  tag.innerHTML = tag.innerHTML + content
  return true
})

interface ToCSSConfig {
  breakpoints?: number[]
}

export function toCss(config?: ToCSSConfig) {
  const breakpoints = config?.breakpoints || [640, 768, 1024, 1280]
  const styliTag = generateStyliTag('styli')

  return function (finalProps: PlainObject, styliStyle: StyliStyle, props: PlainObject) {
    if (!canUseDom) {
      throw new Error('current environment is not support this plugin')
    }

    const { cssFragment, className, cssFragmentList } = styliStyleToCss(styliStyle, breakpoints)

    /**
     * if cssFragment has been inserted into dom, ignore next same cssFragment
     */
    if (!setStyliTagContent.cache[className]) {
      setStyliTagContent(className, styliTag, cssFragment)
    }

    cssFragmentList.forEach((mediaCSSFragment, idx) => {
      const breakpoint = breakpoints[idx]
      const tag = getMediaStyliTag('' + idx, breakpoint)
      const content = getMediaTagContent(tag)
      const cacheKey = breakpoint + mediaCSSFragment
      if (!setMediaTagContent.cache[cacheKey]) {
        setMediaTagContent(cacheKey, tag, breakpoint, `${content} ${mediaCSSFragment}`)
      }
    })<commit_msg>fix: toCss, canUseDom should before generate tag<commit_after>import { PlainObject, StyliStyle } from '../types'
import { kebab, canUseDom, hash, memorize } from '../utils'

function generateStyliTag(name: string) {
  const tag = document.createElement('style')
  tag.dataset.styli = name
  document.querySelector('head')?.append(tag)
  return tag
}

const getMediaStyliTag = memorize((value: number) => {
  const tag = generateStyliTag(`media-styli-${value}`)
  tag.innerHTML = `@media (min-width: ${value}px}) {}`
  return tag
})

const generateClassName = memorize((key: string) => `styli-${hash(key)}`)

function styliStyleToCss(style: StyliStyle, breakpoints: number[]) {
  let className = generateClassName(JSON.stringify(style))
  let cssFragment = ''
  let cssFragmentList: string[] = []

  for (let [key, value] of Object.entries(style)) {
    const cssKeyName = kebab(key)
    // media queries
    if (Array.isArray(value)) {
      if (!breakpoints.length) {
        throw new Error('breakpoints is not provide, array value is not support')
      }
      for (let i = 0; i < breakpoints.length; i++) {
        cssFragmentList[i] = `${cssFragmentList[i] || ''} ${cssKeyName}: ${value[i]};`
      }
    } else {
      cssFragment = `${cssFragment} ${cssKeyName}: ${value};`
    }
  }

  cssFragmentList = cssFragmentList.map((fragment) => `.${className} { ${fragment} }`)
  cssFragment = `.${className} { ${cssFragment} }`

  return { cssFragment, className, cssFragmentList }
}

function getMediaTagContent(tag: any) {
  const content = tag.innerHTML
  const [, match = ''] = content.match(/{(.*)}/) || []
  return match
}

const setMediaTagContent = memorize((tag: any, breakpoint: number, content: string) => {
  tag.innerHTML = `@media (min-width: ${breakpoint}px) {${content}}`
  return true
})

const setStyliTagContent = memorize((tag: any, content: string) => {
  tag.innerHTML = tag.innerHTML + content
  return true
})

interface ToCSSConfig {
  breakpoints?: number[]
}

export function toCss(config?: ToCSSConfig) {
  if (!canUseDom) {
    throw new Error('current environment is not support this plugin')
  }

  const breakpoints = config?.breakpoints || [640, 768, 1024, 1280]
  const styliTag = generateStyliTag('styli')

  return function (finalProps: PlainObject, styliStyle: StyliStyle, props: PlainObject) {
    const { cssFragment, className, cssFragmentList } = styliStyleToCss(styliStyle, breakpoints)

    /**
     * if cssFragment has been inserted into dom, ignore next same cssFragment
     */
    if (!setStyliTagContent.cache[className]) {
      setStyliTagContent(className, styliTag, cssFragment)
    }

    cssFragmentList.forEach((mediaCSSFragment, idx) => {
      const breakpoint = breakpoints[idx]
      const tag = getMediaStyliTag('' + idx, breakpoint)
      const content = getMediaTagContent(tag)
      const cacheKey = breakpoint + mediaCSSFragment
      if (!setMediaTagContent.cache[cacheKey]) {
        setMediaTagContent(cacheKey, tag, breakpoint, `${content} ${mediaCSSFragment}`)
      }
    })
{'fraction_non_alphanumeric': 0.14208010548870942, 'fraction_numerical': 0.004944783253667381, 'mean_word_length': 4.069340016708438, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '801170', 'n_tokens_mistral': 1913, 'n_tokens_neox': 1848, 'n_words': 460}