<template>
  <div ref="containerRef" class="keyboard-root relative w-full h-full overflow-visible">
    <div
      :style="styleMap?.mainStyle"
      class="keyboard-main w-full h-full box-border flex flex-col overflow-hidden relative"
    >
      <ImageBg
        v-if="styleMap?.mainStyle?.imageSrc"
        :src="styleMap?.mainStyle?.imageSrc"
        :imageStyle="styleMap?.mainStyle?.imageStyle"
      />
      <div
        v-for="(row, rowIndex) in layoutRows"
        :key="`${currentMode}-${rowIndex}`"
        class="flex-1 w-full flex items-center"
        :style="{ columnGap: styleMap?.mainStyle?.columnGap }"
      >
        <button
          v-for="key in row"
          :key="key.id"
          type="button"
          class="keyboard-key h-full flex items-center justify-center overflow-hidden relative border-none p-0 cursor-default"
          :class="{
            'keyboard-key--action': isActionKey(key),
            'keyboard-key--space': key.action === 'space'
          }"
          :style="getKeyStyle(key, row)"
          @pointerdown="handlePointerDown(key, $event)"
          @pointerup="clearPopup"
          @pointercancel="clearPopup"
          @pointerleave="handlePointerLeave(key)"
        >
          <ImageBg
            v-if="styleMap?.itemsStyle?.imageSrc"
            :src="styleMap?.itemsStyle?.imageSrc"
            :imageStyle="styleMap?.itemsStyle?.imageStyle"
          />
          <i
            v-if="isSymbolKey(key)"
            class="keyboard-key__icon lvgl-icon not-italic z-1"
            v-html="getSymbol(key.symbol || '')"
          ></i>
          <span v-else class="keyboard-key__label z-1">{{ key.label }}</span>
        </button>
      </div>
    </div>

    <div v-if="popup" class="keyboard-popup absolute z-30 pointer-events-none" :style="popup.style">
      <div class="keyboard-popup__inner" :style="popupBubbleStyle">
        <span class="z-1">{{ popup.label }}</span>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, ref, watch, type CSSProperties } from 'vue'
import { useProjectStore } from '@/store/modules/project'
import { getSymbol } from '@/utils'
import { useWidgetStyle } from '../hooks/useWidgetStyle'
import ImageBg from '../ImageBg.vue'

import { KEYBOARD_LAYOUTS, type KeyboardKey, type KeyboardMode } from './data'

const props = defineProps<{
  id: string
  width: number
  height: number
  styles: any
  state?: string
  part?: string
  mode?: KeyboardMode
  showPopovers?: boolean
  textareaId?: string
}>()

const projectStore = useProjectStore()
const containerRef = ref<HTMLDivElement>()
const pressedKeyId = ref('')
const currentMode = ref<KeyboardMode>('lower')
const cursorIndex = ref(0)
const popup = ref<{
  label: string
  style: CSSProperties
} | null>(null)
let lastTextareaId = ''

const styleMap = useWidgetStyle({
  widget: 'lv_keyboard',
  props
})

watch(
  () => props.mode,
  (value) => {
    currentMode.value = value || 'lower'
  },
  {
    immediate: true
  }
)

const layoutRows = computed(() => {
  return KEYBOARD_LAYOUTS[currentMode.value] || KEYBOARD_LAYOUTS.lower
})

const targetTextarea = computed(() => {
  if (!props.textareaId) return
  const widget = projectStore.getWidgetById(props.textareaId)
  return widget?.type === 'lv_textarea' ? widget : undefined
})

const targetText = computed(() => String(targetTextarea.value?.props?.text ?? ''))

watch(
  () => [props.textareaId, targetText.value],
  ([textareaId, text]) => {
    if (textareaId !== lastTextareaId) {
      cursorIndex.value = text?.length || 0
      lastTextareaId = textareaId || ''
      return
    }
    cursorIndex.value = clamp(cursorIndex.value, 0, text?.length || 0)
  },
  {
    immediate: true
  }
)

const popupBubbleStyle = computed((): CSSProperties => {
  const itemsStyle = styleMap.value?.itemsStyle || {}
  const backgroundColor =
    typeof itemsStyle.backgroundColor === 'string' ? itemsStyle.backgroundColor : '#ffffffff'

  return {
    ...itemsStyle,
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    width: '100%',
    height: '100%',
    filter: 'brightness(1.02)',
    boxShadow: itemsStyle.boxShadow || '0 8px 20px rgba(0, 0, 0, 0.12)',
    position: 'relative',
    zIndex: 1,
    ['--popup-tail-color' as string]: backgroundColor
  }
})

function clamp(value: number, min: number, max: number) {
  return Math.min(max, Math.max(min, value))
}

const getKeyFlex = (keyWidth: number, row: KeyboardKey[]) => {
  const total = row.reduce((sum, item) => sum + item.width, 0)
  const basis = `${(keyWidth / total) * 100}%`
  return {
    flexBasis: basis,
    flexGrow: 1,
    flexShrink: 1
  }
}

const isActionKey = (key: KeyboardKey) =>
  typeof key.actionStyle === 'boolean'
    ? key.actionStyle
    : key.action !== 'input' && key.action !== 'space'
const isSymbolKey = (key: KeyboardKey) => !!key.symbol

const getKeyStyle = (key: KeyboardKey, row: KeyboardKey[]) => {
  const itemsStyle = styleMap.value?.itemsStyle || {}

  const actionStyle = isActionKey(key)
    ? {
        backgroundColor: '#e0e0e0',
        fontSize: '10px'
      }
    : {}

  return {
    ...itemsStyle,
    ...getKeyFlex(key.width, row),
    ...actionStyle,
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    userSelect: 'none',
    backgroundClip: 'padding-box'
  } as CSSProperties
}

const getPopupLabel = (key: KeyboardKey) => {
  if (key.action === 'space') return key.popupLabel || 'Space'
  return key.popupLabel || key.value || key.label
}

const shouldShowPopup = (key: KeyboardKey) => {
  return !!props.showPopovers && (key.action === 'input' || key.action === 'space')
}

const showPopup = (key: KeyboardKey, element: HTMLElement) => {
  if (!containerRef.value || !shouldShowPopup(key)) return

  const containerRect = containerRef.value.getBoundingClientRect()
  const rect = element.getBoundingClientRect()
  popup.value = {
    label: getPopupLabel(key),
    style: {
      left: `${rect.left - containerRect.left}px`,
      top: `${Math.max(rect.top - containerRect.top - rect.height * 0.82, 0)}px`,
      width: `${rect.width}px`,
      height: `${Math.max(rect.height * 1.18, rect.height + 10)}px`
    }
  }
}

const clearPopup = () => {
  pressedKeyId.value = ''
  popup.value = null
}

const handlePointerDown = (key: KeyboardKey, event: PointerEvent) => {
  pressedKeyId.value = key.id
  const element = event.currentTarget as HTMLElement | null
  if (element) {
    showPopup(key, element)
  }
}

const handlePointerLeave = (key: KeyboardKey) => {
  if (pressedKeyId.value === key.id) {
    clearPopup()
  }
}
</script>

<style lang="less" scoped>
.keyboard-key {
  appearance: none;
  transition:
    transform 0.12s ease,
    filter 0.12s ease;
}

.keyboard-key--action {
  background: #dedede;
}

.keyboard-key__label {
  padding: 0 6px;
}

.keyboard-key__icon {
  font-size: 1.1em;
  line-height: 1;
}

.keyboard-key--space .keyboard-key__label {
  opacity: 0;
}

.keyboard-popup {
  transform: translateY(-6px);
}

.keyboard-popup__inner {
  overflow: visible;
}

.keyboard-popup__inner::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: -8px;
  transform: translateX(-50%);
  border-left: 8px solid transparent;
  border-right: 8px solid transparent;
  border-top: 8px solid var(--popup-tail-color, #ffffff);
}
</style>
