/* eslint-disable @typescript-eslint/ban-ts-comment */
import { transform } from '@xstyled/core'
import { createX, StyleGenerator } from '@xstyled/styled-components'
import { BoxElements } from '@xstyled/styled-components/dist/BoxElements'
import { flattenStrings } from '@xstyled/util'
import scStyled, {
  createGlobalStyle as scCreateGlobalStyle,
  css as scCss,
  DefaultTheme,
  FlattenSimpleInterpolation,
  StyledConfig,
  ThemedCssFunction,
  ThemedStyledFunction,
  ThemedStyledInterface,
} from 'styled-components'

import { system, SystemProps } from '../system'

/** Custom 'css' function with theme support. */
export type CSSFunction = ThemedCssFunction<DefaultTheme>

export type CreateGlobalStyle = typeof scCreateGlobalStyle

/** Type of the custom 'styled' function. */
export type VStyled = ThemedStyledInterface<DefaultTheme> & {
  [Key in keyof BoxElements]: ThemedStyledFunction<BoxElements[Key], DefaultTheme, SystemProps>
}

/** Creates enhanced 'styled' function with optional style generators and prop-forwarding safeguard. */
function createBaseStyled(css: CSSFunction, generator?: StyleGenerator) {
  const config = generator ? forwardPropConfig(generator) : {}

  return ((component: Parameters<typeof scStyled>[0]) => {
    const baseStyled = config ? scStyled(component).withConfig(config) : scStyled(component)

    return getCreateStyle(baseStyled, css, generator)
  }) as VStyled
}

/** Enhances createGlobalStyle function with custom 'css' helper. */
function createCreateGlobalStyle(): CreateGlobalStyle {
  const css = createCssFunction()
  return ((...args: Parameters<CreateGlobalStyle>): ReturnType<CreateGlobalStyle> =>
    scCreateGlobalStyle([
      // @ts-ignore
      css(...args),
    ])) as CreateGlobalStyle
}

/** Enhances styled-components' 'css' function with automatic theming. */
function createCssFunction() {
  return ((...args: Parameters<CSSFunction>) => {
    const scCssArgs = scCss(...args)
    const flattenedArgs = flattenStrings(scCssArgs as any[])
    return flattenedArgs.map(transform) as FlattenSimpleInterpolation
  }) as CSSFunction
}

/**
 * Creates full 'styled' function with properties for components.
 * Includes base version and system version using 'Box' postfix.
 */
function createStyled(generator: StyleGenerator): VStyled {
  const css = createCssFunction()
  const styled = createBaseStyled(css)
  const vstyled = createBaseStyled(css, generator)

  Object.keys(scStyled).forEach(key => {
    // @ts-ignore
    styled[key] = styled(key)
    // @ts-ignore
    styled[`${key}Box`] = vstyled(key)
  })

  return styled
}

/** Provides configuration to the 'styled' function, which filters out xstyled system props. */
function forwardPropConfig(generator: StyleGenerator) {
  return {
    shouldForwardProp: (prop: any, defaultValidatorFn: (prop: any) => boolean) => {
      if (typeof prop === 'string' && generator.meta.props.includes(prop)) return false
      return defaultValidatorFn(prop)
    },
  }
}

/** Extends styled component with a given styles generator (system). */
function getCreateStyle(baseCreateStyle: ThemedStyledFunction<any, any>, css: CSSFunction, generator?: StyleGenerator) {
  const createStyle = (...args: Parameters<typeof css>) => baseCreateStyle`${css(...args)}${generator}`

  createStyle.attrs = (attrs: Parameters<typeof baseCreateStyle.attrs>[0]) =>
    getCreateStyle(baseCreateStyle.attrs(attrs), css, generator)

  createStyle.withConfig = (config: StyledConfig<any>) =>
    getCreateStyle(baseCreateStyle.withConfig(config), css, generator)

  return createStyle
}

export const createGlobalStyle = createCreateGlobalStyle()

export const css = createCssFunction()

export const v = createX<SystemProps>(system)

export const styled = createStyled(system)

export default styled
