import type { ChildrenList, PartialElement, RenderOptions } from '@furystack/shades'
import { Shade, createComponent } from '@furystack/shades'
import { buildTransition, cssVariableTheme } from '../services/css-variable-theme.js'
import { paletteMainColors } from '../services/palette-css-vars.js'
import type { Palette } from '../services/theme-provider-service.js'
import { clipboard } from './icons/icon-definitions.js'
import { Icon } from './icons/icon.js'

/**
 * Typography variant determines semantic HTML tag and default styles.
 */
export type TypographyVariant =
  | 'h1'
  | 'h2'
  | 'h3'
  | 'h4'
  | 'h5'
  | 'h6'
  | 'subtitle1'
  | 'subtitle2'
  | 'body1'
  | 'body2'
  | 'caption'
  | 'overline'

/**
 * Color options for the Typography component.
 * Supports palette colors and text-level semantic colors.
 */
export type TypographyColor = keyof Palette | 'textPrimary' | 'textSecondary' | 'textDisabled'

export type TypographyProps = PartialElement<HTMLElement> & {
  /** The typographic variant to use. Determines tag and style. Defaults to 'body1'. */
  variant?: TypographyVariant
  /** Text color. Defaults to 'textPrimary'. */
  color?: TypographyColor
  /** Truncate text with ellipsis. `true` for single-line, a number for max line count. */
  ellipsis?: boolean | number
  /** Show a copy button that copies text content to clipboard. */
  copyable?: boolean
  /** Add bottom margin for spacing. */
  gutterBottom?: boolean
  /** Text alignment. */
  align?: 'left' | 'center' | 'right' | 'justify'
}

type VariantDef = {
  fontSize: string
  fontWeight: string
  lineHeight: string
  letterSpacing: string
  textTransform?: string
  marginTop?: string
  marginBottom?: string
}

const variantDefs: Record<TypographyVariant, VariantDef> = {
  h1: {
    fontSize: cssVariableTheme.typography.fontSize.xxxxl,
    fontWeight: cssVariableTheme.typography.fontWeight.bold,
    lineHeight: cssVariableTheme.typography.lineHeight.tight,
    letterSpacing: cssVariableTheme.typography.letterSpacing.tight,
    marginBottom: '0.3em',
  },
  h2: {
    fontSize: cssVariableTheme.typography.fontSize.xxxl,
    fontWeight: cssVariableTheme.typography.fontWeight.bold,
    lineHeight: cssVariableTheme.typography.lineHeight.tight,
    letterSpacing: cssVariableTheme.typography.letterSpacing.dense,
    marginTop: '1.5em',
    marginBottom: '0.3em',
  },
  h3: {
    fontSize: cssVariableTheme.typography.fontSize.xxl,
    fontWeight: cssVariableTheme.typography.fontWeight.semibold,
    lineHeight: cssVariableTheme.typography.lineHeight.tight,
    letterSpacing: cssVariableTheme.typography.letterSpacing.normal,
    marginTop: '1.25em',
    marginBottom: '0.25em',
  },
  h4: {
    fontSize: cssVariableTheme.typography.fontSize.xl,
    fontWeight: cssVariableTheme.typography.fontWeight.semibold,
    lineHeight: cssVariableTheme.typography.lineHeight.tight,
    letterSpacing: cssVariableTheme.typography.letterSpacing.wide,
    marginTop: '1em',
    marginBottom: '0.25em',
  },
  h5: {
    fontSize: cssVariableTheme.typography.fontSize.lg,
    fontWeight: cssVariableTheme.typography.fontWeight.medium,
    lineHeight: cssVariableTheme.typography.lineHeight.normal,
    letterSpacing: cssVariableTheme.typography.letterSpacing.normal,
    marginTop: '0.75em',
    marginBottom: '0.35em',
  },
  h6: {
    fontSize: cssVariableTheme.typography.fontSize.md,
    fontWeight: cssVariableTheme.typography.fontWeight.medium,
    lineHeight: cssVariableTheme.typography.lineHeight.normal,
    letterSpacing: cssVariableTheme.typography.letterSpacing.wide,
    marginTop: '0.5em',
    marginBottom: '0.2em',
  },
  subtitle1: {
    fontSize: cssVariableTheme.typography.fontSize.md,
    fontWeight: cssVariableTheme.typography.fontWeight.medium,
    lineHeight: cssVariableTheme.typography.lineHeight.normal,
    letterSpacing: cssVariableTheme.typography.letterSpacing.wide,
    marginBottom: '0.35em',
  },
  subtitle2: {
    fontSize: cssVariableTheme.typography.fontSize.sm,
    fontWeight: cssVariableTheme.typography.fontWeight.medium,
    lineHeight: cssVariableTheme.typography.lineHeight.normal,
    letterSpacing: '0.1px',
    marginBottom: '0.25em',
  },
  body1: {
    fontSize: cssVariableTheme.typography.fontSize.md,
    fontWeight: cssVariableTheme.typography.fontWeight.normal,
    lineHeight: cssVariableTheme.typography.lineHeight.relaxed,
    letterSpacing: cssVariableTheme.typography.letterSpacing.wide,
    marginBottom: '0.75em',
  },
  body2: {
    fontSize: cssVariableTheme.typography.fontSize.sm,
    fontWeight: cssVariableTheme.typography.fontWeight.normal,
    lineHeight: cssVariableTheme.typography.lineHeight.relaxed,
    letterSpacing: cssVariableTheme.typography.letterSpacing.wide,
    marginBottom: '0.5em',
  },
  caption: {
    fontSize: cssVariableTheme.typography.fontSize.xs,
    fontWeight: cssVariableTheme.typography.fontWeight.normal,
    lineHeight: cssVariableTheme.typography.lineHeight.normal,
    letterSpacing: '0.4px',
  },
  overline: {
    fontSize: cssVariableTheme.typography.fontSize.xs,
    fontWeight: cssVariableTheme.typography.fontWeight.medium,
    lineHeight: cssVariableTheme.typography.lineHeight.normal,
    letterSpacing: cssVariableTheme.typography.letterSpacing.widest,
    textTransform: 'uppercase',
    marginBottom: '0.5em',
  },
}

const buildVariantCssRules = (): Record<string, Record<string, string>> => {
  const rules: Record<string, Record<string, string>> = {}
  for (const [variant, def] of Object.entries(variantDefs)) {
    const rule: Record<string, string> = {
      fontSize: def.fontSize,
      fontWeight: def.fontWeight,
      lineHeight: def.lineHeight,
      letterSpacing: def.letterSpacing,
    }
    if (def.textTransform) {
      rule.textTransform = def.textTransform
    }
    if (def.marginTop) {
      rule.marginTop = def.marginTop
    }
    if (def.marginBottom) {
      rule.marginBottom = def.marginBottom
    }
    rules[`&[data-variant="${variant}"]`] = rule
  }
  return rules
}

const colorToVar = (color: TypographyColor): string => {
  if (color === 'textPrimary') return cssVariableTheme.text.primary
  if (color === 'textSecondary') return cssVariableTheme.text.secondary
  if (color === 'textDisabled') return cssVariableTheme.text.disabled
  return paletteMainColors[color].main
}

type TypographyTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span'

const variantToTag = (variant: TypographyVariant): TypographyTag => {
  if (variant.startsWith('h')) return variant as TypographyTag
  if (variant === 'subtitle1' || variant === 'subtitle2') return 'h6'
  if (variant === 'body1' || variant === 'body2') return 'p'
  return 'span'
}

const typographyCss = {
  display: 'block',
  margin: '0',
  padding: '0',
  fontFamily: cssVariableTheme.typography.fontFamily,
  color: 'var(--typo-color)',
  textShadow: cssVariableTheme.typography.textShadow || 'none',

  ...buildVariantCssRules(),

  '&[data-gutter-bottom]': {
    marginBottom: '0.35em',
  },

  '&[data-align="left"]': { textAlign: 'left' },
  '&[data-align="center"]': { textAlign: 'center' },
  '&[data-align="right"]': { textAlign: 'right' },
  '&[data-align="justify"]': { textAlign: 'justify' },

  '&[data-ellipsis="true"]': {
    overflow: 'hidden',
    textOverflow: 'ellipsis',
    whiteSpace: 'nowrap',
  },

  '&[data-ellipsis="multiline"]': {
    overflow: 'hidden',
    display: '-webkit-box',
  },

  '& .typo-copy-btn': {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    background: 'transparent',
    border: 'none',
    color: cssVariableTheme.text.secondary,
    cursor: 'pointer',
    padding: `0 ${cssVariableTheme.spacing.xs}`,
    fontSize: '0.85em',
    lineHeight: '1',
    verticalAlign: 'middle',
    borderRadius: cssVariableTheme.shape.borderRadius.sm,
    transition: buildTransition(
      ['color', cssVariableTheme.transitions.duration.fast, cssVariableTheme.transitions.easing.default],
      ['background', cssVariableTheme.transitions.duration.fast, cssVariableTheme.transitions.easing.default],
    ),
  },
  '& .typo-copy-btn:hover': {
    color: cssVariableTheme.text.primary,
    background: cssVariableTheme.action.hoverBackground,
  },
}

const typographyRender = ({ props, children, useHostProps }: RenderOptions<TypographyProps>): JSX.Element | null => {
  const { variant = 'body1', color = 'textPrimary', ellipsis, copyable, gutterBottom, align, style } = props

  const hostStyle: Record<string, string> = {
    '--typo-color': colorToVar(color),
  }
  if (typeof ellipsis === 'number') {
    hostStyle.webkitLineClamp = String(ellipsis)
    hostStyle.webkitBoxOrient = 'vertical'
  }
  if (style) {
    Object.assign(hostStyle, style)
  }
  useHostProps({
    'data-gutter-bottom': gutterBottom ? '' : undefined,
    'data-align': align || undefined,
    'data-ellipsis': ellipsis === true ? 'true' : typeof ellipsis === 'number' ? 'multiline' : undefined,
    'data-variant': variant,
    style: hostStyle,
  })

  if (!copyable) {
    return <>{children}</>
  }

  const handleCopy = (e: Event) => {
    const btn = e.currentTarget as HTMLElement
    const host = btn.parentElement
    if (!host) return
    const clone = host.cloneNode(true) as HTMLElement
    clone.querySelectorAll('.typo-copy-btn').forEach((el) => el.remove())
    navigator.clipboard.writeText(clone.textContent ?? '').catch(() => {})
  }

  return (
    <>
      {children}
      <button type="button" className="typo-copy-btn" onclick={handleCopy} title="Copy to clipboard">
        <Icon icon={clipboard} size={14} />
      </button>
    </>
  )
}

const tagConfigs: Array<[TypographyTag, new () => HTMLElement]> = [
  ['h1', HTMLHeadingElement],
  ['h2', HTMLHeadingElement],
  ['h3', HTMLHeadingElement],
  ['h4', HTMLHeadingElement],
  ['h5', HTMLHeadingElement],
  ['h6', HTMLHeadingElement],
  ['p', HTMLParagraphElement],
  ['span', HTMLSpanElement],
]

const shadesByTag = {} as Record<TypographyTag, (props: TypographyProps, children?: ChildrenList) => JSX.Element>

for (const [tag, elementBase] of tagConfigs) {
  shadesByTag[tag] = Shade<TypographyProps>({
    customElementName: `shade-typography-${tag}`,
    elementBase,
    elementBaseName: tag,
    css: typographyCss,
    render: typographyRender,
  })
}

/**
 * Typography component for consistent text styling.
 * Maps variants to semantic HTML tags and uses theme typography tokens.
 */
export const Typography = (props: TypographyProps, children?: ChildrenList): JSX.Element => {
  const tag = variantToTag(props.variant ?? 'body1')
  return shadesByTag[tag](props, children)
}
