import type { JSX } from 'hono/jsx'
import { type TagDef, UnitlessProps, NonStyleKeys } from './types'

export const styles: Record<string, Record<string, string>> = {}
export const Styles = () => <style dangerouslySetInnerHTML={{ __html: stylesToCSS(styles) }} />

// turns style object into string CSS definition
export function stylesToCSS(styles: Record<string, Record<string, string>>): string {
  let out: string[] = []

  for (const [selector, style] of Object.entries(styles)) {
    if (Object.keys(style).length === 0) continue

    out.push(`.${selector} {`)
    for (const [name, value] of Object.entries(style).sort(([a], [b]) => a.localeCompare(b)))
      out.push(`  ${name}: ${value};`)
    out.push(`}\n`)
  }

  return out.join('\n')
}

// creates a CSS class name
function makeClassName(baseName: string, partName?: string, variantName?: string, variantKey?: string): string {
  const cls = partName ? `${baseName}_${partName}` : baseName

  if (variantName && variantKey) {
    return cls + `.${variantName}-${variantKey}`
  } else {
    return cls
  }
}

// 'fontSize' => 'font-size'
function camelToDash(name: string): string {
  let out = ''

  for (const letter of name.split(''))
    out += letter.toUpperCase() === letter ? `-${letter.toLowerCase()}` : letter

  return out
}

// turns a TagDef into a JSX style object
function makeStyle(def: TagDef) {
  const style: Record<string, string> = {}

  for (const [name, value] of Object.entries(def)) {
    if (NonStyleKeys.has(name)) continue
    style[camelToDash(name)] = `${typeof value === 'number' && !UnitlessProps.has(name) ? `${value}px` : value}`
  }

  return style
}

// turns a TagDef into a JSX component
function makeComponent(baseName: string, rootDef: TagDef, rootProps: Record<string, any>, partName?: string) {
  const def = partName ? rootDef.parts?.[partName]! : rootDef
  const base = def.base ?? 'div'
  const Tag = (base) as keyof JSX.IntrinsicElements

  return ({ children, ...props }: { children: any, [key: string]: any }) => {
    const classNames = [makeClassName(baseName, partName)]

    const allProps = { ...rootProps, ...props }

    for (const [key, value] of Object.entries(allProps)) {
      const variantConfig = rootDef.variants?.[key]
      if (!variantConfig) continue

      // Remove variant prop from being passed to HTML element
      delete props[key]

      const variantName = key
      const variantKey = value

      let variantDef: TagDef | undefined
      // Distinguish boolean variants from keyed variants:
      // - Boolean variants: component({ variant: true }) → variantConfig is a TagDef
      // - Keyed variants: component({ variant: 'key' }) → variantConfig[key] is a TagDef
      if (value === true) {
        variantDef = variantConfig as TagDef
      } else if (typeof value === 'string') {
        variantDef = (variantConfig as Record<string, TagDef>)[value]
      }
      if (!variantDef) continue

      classNames.push(variantKey === true ? variantName : `${variantName}-${variantKey}`)
    }

    return <Tag class={classNames.join(' ')} {...props}>{children}</Tag>
  }
}

// adds CSS styles for tag definition
function registerStyles(name: string, def: TagDef) {
  const rootClassName = makeClassName(name)
  styles[rootClassName] ??= makeStyle(def)

  for (const [state, style] of Object.entries(def.states ?? {}))
    styles[`${rootClassName}${state}`] = makeStyle(style)

  for (const [partName, partDef] of Object.entries(def.parts ?? {})) {
    const partClassName = makeClassName(name, partName)
    styles[partClassName] ??= makeStyle(partDef)
    for (const [state, style] of Object.entries(partDef.states ?? {}))
      styles[`${partClassName}${state}`] = makeStyle(style)

  }

  for (const [variantName, variantConfig] of Object.entries(def.variants ?? {})) {
    // Detect boolean vs keyed variants by checking if config has structural keys or looks like a TagDef
    const isBooleanVariant = 'parts' in variantConfig || 'styles' in variantConfig || 'states' in variantConfig ||
      // If first key is camelCase or contains CSS-like properties, treat as boolean variant
      Object.keys(variantConfig).some(k => k !== k.toLowerCase() || typeof (variantConfig as any)[k] !== 'object')

    if (isBooleanVariant) {
      // Boolean variant - variantConfig is a TagDef
      const variantDef = variantConfig as TagDef
      const baseClassName = makeClassName(name)
      const className = `${baseClassName}.${variantName}`
      styles[className] ??= makeStyle(variantDef)
      for (const [state, style] of Object.entries(variantDef.states ?? {}))
        styles[`${className}${state}`] = makeStyle(style)

      for (const [partName, partDef] of Object.entries(variantDef.parts ?? {})) {
        const basePartClassName = makeClassName(name, partName)
        const partClassName = `${basePartClassName}.${variantName}`
        styles[partClassName] ??= makeStyle(partDef)
        for (const [state, style] of Object.entries(partDef.states ?? {}))
          styles[`${partClassName}${state}`] = makeStyle(style)
      }
    } else {
      // Keyed variant - iterate over the keys
      for (const [variantKey, variantDef] of Object.entries(variantConfig as Record<string, TagDef>)) {
        const className = makeClassName(name, undefined, variantName, variantKey)
        styles[className] ??= makeStyle(variantDef)
        for (const [state, style] of Object.entries(variantDef.states ?? {}))
          styles[`${className}${state}`] = makeStyle(style)

        for (const [partName, partDef] of Object.entries(variantDef.parts ?? {})) {
          const partClassName = makeClassName(name, partName, variantName, variantKey)
          styles[partClassName] ??= makeStyle(partDef)
          for (const [state, style] of Object.entries(partDef.states ?? {}))
            styles[`${partClassName}${state}`] = makeStyle(style)
        }
      }
    }
  }
}

let anonComponents = 1

// the main event
export function define(nameOrDef: string | TagDef, defIfNamed?: TagDef) {
  const name = defIfNamed ? (nameOrDef as string) : `Def${anonComponents++}`
  const def = defIfNamed ?? nameOrDef as TagDef

  registerStyles(name, def)

  return (props: Record<string, any>) => {
    const parts: Record<string, Function> = {}

    for (const [part] of Object.entries(def.parts ?? {}))
      parts[part] = makeComponent(name, def, props, part)

    parts.Root = makeComponent(name, def, props)
    return def.render?.({ props, parts }) ?? <parts.Root {...props}>{props.children}</parts.Root>
  }
}

define.Styles = Styles