<template>
  <section
    ref="feedbackRootRef"
    class="operation-feedback-inline"
    :class="{ 'is-submitted': submitted }"
    :aria-labelledby="feedbackTitleId"
  >
    <header class="operation-feedback-inline-head">
      <strong :id="feedbackTitleId">这次处理符合预期吗？</strong>
      <button
        v-if="!submitted"
        type="button"
        class="operation-feedback-link"
        :disabled="busy"
        @click="emit('dismiss')"
      >
        稍后
      </button>
    </header>

    <div
      class="operation-feedback-stars"
      role="radiogroup"
      :aria-labelledby="feedbackTitleId"
      :aria-describedby="feedbackDescriptionId"
      @mouseleave="hoverRating = 0"
    >
      <button
        v-for="option in ratingOptions"
        :key="option.value"
        type="button"
        class="operation-feedback-star"
        :class="{ active: option.value === effectiveRating, preview: option.value <= displayRating }"
        :disabled="busy || submitted"
        :aria-checked="effectiveRating === option.value ? 'true' : 'false'"
        :aria-label="`${option.value}星，${option.label}`"
        :data-score="option.value"
        role="radio"
        :tabindex="resolveRatingTabIndex(option.value)"
        @click="selectRating(option.value)"
        @mouseenter="hoverRating = option.value"
        @focus="hoverRating = option.value"
        @blur="hoverRating = 0"
        @keydown="handleRatingKeydown($event, option.value)"
      >
        <i
          aria-hidden="true"
          :class="option.value <= displayRating ? 'mdi mdi-star' : 'mdi mdi-star-outline'"
        ></i>
      </button>
    </div>
    <p :id="feedbackDescriptionId" class="operation-feedback-status" aria-live="polite">
      {{ selectedFeedbackText }}
    </p>
    <p v-if="submitted" class="operation-feedback-thanks" aria-live="polite">
      <i class="mdi mdi-check-circle-outline" aria-hidden="true"></i>
      <span>感谢您的反馈。谢谢</span>
    </p>

    <Transition name="feedback-reason-slide">
      <div v-if="showReasonInput" class="operation-feedback-low-rating">
        <label class="operation-feedback-reason">
          <span>哪里不符合预期？</span>
          <textarea
            v-model="reason"
            maxlength="500"
            rows="2"
            :disabled="busy"
            placeholder="例如：意图识别不准、信息提取遗漏..."
          ></textarea>
          <small>{{ reason.length }}/500</small>
        </label>

        <button
          type="button"
          class="operation-feedback-primary"
          :disabled="busy || !rating"
          @click="submit"
        >
          <i v-if="busy" class="mdi mdi-loading mdi-spin"></i>
          <span>{{ busy ? '提交中...' : '提交' }}</span>
        </button>
      </div>
    </Transition>

    <p v-if="errorMessage" class="operation-feedback-error">{{ errorMessage }}</p>
  </section>
</template>

<script setup>
import { computed, nextTick, ref, watch } from 'vue'

const props = defineProps({
  busy: { type: Boolean, default: false },
  errorMessage: { type: String, default: '' },
  resetKey: { type: String, default: '' },
  submitted: { type: Boolean, default: false },
  submittedRating: { type: Number, default: 0 }
})

const emit = defineEmits(['dismiss', 'submit'])
const feedbackIdSuffix = Math.random().toString(36).slice(2, 10)
const feedbackTitleId = `operation-feedback-title-${feedbackIdSuffix}`
const feedbackDescriptionId = `operation-feedback-note-${feedbackIdSuffix}`
const ratingOptions = [
  { value: 1, label: '很差' },
  { value: 2, label: '不满意' },
  { value: 3, label: '一般' },
  { value: 4, label: '满意' },
  { value: 5, label: '很好' }
]
const rating = ref(0)
const hoverRating = ref(0)
const reason = ref('')
const feedbackRootRef = ref(null)
const normalizedSubmittedRating = computed(() => {
  const score = Number(props.submittedRating || 0)
  return Number.isInteger(score) && score >= 1 && score <= 5 ? score : 0
})
const effectiveRating = computed(() => (props.submitted ? normalizedSubmittedRating.value || rating.value : rating.value))
const displayRating = computed(() => (props.submitted ? effectiveRating.value : hoverRating.value || rating.value))
const selectedOption = computed(() => ratingOptions.find((option) => option.value === effectiveRating.value) || null)
const selectedFeedbackText = computed(() => (
  props.submitted
    ? '感谢您的反馈。谢谢'
    : selectedOption.value
    ? rating.value <= 3
      ? `已选择 ${selectedOption.value.value} 星，可补充原因后提交。`
      : `已选择 ${selectedOption.value.value} 星，按 Enter 确认。`
    : '请选择一个评分。'
))
const showReasonInput = computed(() => !props.submitted && rating.value > 0 && rating.value <= 3)

function focusRatingButton(score) {
  nextTick(() => {
    feedbackRootRef.value
      ?.querySelector(`[data-score="${score}"]`)
      ?.focus()
  })
}

function selectRating(score, options = {}) {
  if (props.busy || props.submitted) {
    return
  }
  const shouldSubmitHighRating = options.submitHighRating !== false
  rating.value = score
  if (score > 3 && shouldSubmitHighRating) {
    reason.value = ''
    emit('submit', {
      rating: score,
      reason: ''
    })
  }
}

function resolveRatingTabIndex(score) {
  if (props.submitted) {
    return -1
  }
  return rating.value
    ? rating.value === score ? 0 : -1
    : score === 1 ? 0 : -1
}

function handleRatingKeydown(event, score) {
  const key = event.key
  const currentIndex = ratingOptions.findIndex((option) => option.value === score)
  if (currentIndex < 0) {
    return
  }

  const lastIndex = ratingOptions.length - 1
  let nextIndex = currentIndex
  if (['ArrowRight', 'ArrowDown'].includes(key)) {
    nextIndex = currentIndex === lastIndex ? 0 : currentIndex + 1
  } else if (['ArrowLeft', 'ArrowUp'].includes(key)) {
    nextIndex = currentIndex === 0 ? lastIndex : currentIndex - 1
  } else if (key === 'Home') {
    nextIndex = 0
  } else if (key === 'End') {
    nextIndex = lastIndex
  } else {
    return
  }

  event.preventDefault()
  const nextScore = ratingOptions[nextIndex].value
  selectRating(nextScore, { submitHighRating: false })
  focusRatingButton(nextScore)
}

function resetForm() {
  rating.value = 0
  hoverRating.value = 0
  reason.value = ''
}

function submit() {
  if (props.submitted) {
    return
  }
  emit('submit', {
    rating: rating.value,
    reason: reason.value
  })
}

watch(
  () => props.resetKey,
  () => resetForm()
)

watch(
  () => props.submittedRating,
  (nextRating) => {
    const score = Number(nextRating || 0)
    if (props.submitted && Number.isInteger(score) && score >= 1 && score <= 5) {
      rating.value = score
      hoverRating.value = 0
    }
  }
)
</script>

<style scoped>
.operation-feedback-inline {
  width: fit-content;
  max-width: min(100%, 360px);
  display: inline-grid;
  grid-template-columns: minmax(0, auto);
  gap: 8px;
  padding: 9px 11px;
  border: 1px solid #d8e2ee;
  border-radius: 14px;
  background: #ffffff;
  color: #24324a;
  box-shadow: 0 8px 18px rgb(148 163 184 / 12%);
}

.operation-feedback-inline.is-submitted {
  border-color: #d0d5dd;
  background: #f8fafc;
  color: #475467;
  box-shadow: none;
}

.operation-feedback-inline-head {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  align-items: center;
}

.operation-feedback-inline strong {
  color: #172033;
  font-size: 12px;
  font-weight: 760;
  line-height: 1.35;
}

.operation-feedback-link {
  flex: 0 0 auto;
  height: 22px;
  padding: 0 6px;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: #475467;
  font-size: 11px;
  font-weight: 700;
  cursor: pointer;
  transition: border-color 0.18s ease, color 0.18s ease, background 0.18s ease;
}

.operation-feedback-link:hover,
.operation-feedback-link:focus-visible {
  border-color: #c8d5e6;
  background: #f5f8fc;
  color: #1d4ed8;
  outline: none;
}

.operation-feedback-stars {
  display: flex;
  align-items: center;
  gap: 3px;
}

.operation-feedback-star {
  width: 24px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: #98a2b3;
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  transition: border-color 0.16s ease, color 0.16s ease, background 0.16s ease;
}

.operation-feedback-star:hover,
.operation-feedback-star:focus-visible {
  border-color: #d69a2d;
  background: #fffaf0;
  color: #b7791f;
  outline: none;
}

.operation-feedback-star.preview {
  color: #b7791f;
}

.operation-feedback-star.active {
  border-color: #c78315;
  background: #fff7e6;
  color: #8a4f00;
}

.operation-feedback-star:disabled {
  cursor: not-allowed;
  opacity: 0.7;
}

.operation-feedback-inline.is-submitted .operation-feedback-star.preview {
  color: #667085;
}

.operation-feedback-inline.is-submitted .operation-feedback-star.active {
  border-color: #d0d5dd;
  background: #eef2f6;
  color: #475467;
}

.operation-feedback-status {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.operation-feedback-low-rating {
  display: grid;
  gap: 7px;
  min-width: min(320px, calc(100vw - 96px));
}

.operation-feedback-reason {
  display: grid;
  gap: 6px;
}

.operation-feedback-reason span {
  color: #344054;
  font-size: 11px;
  font-weight: 760;
}

.operation-feedback-reason textarea {
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  min-height: 54px;
  padding: 7px 9px;
  border: 1px solid #d0d5dd;
  border-radius: 4px;
  background: #ffffff;
  color: #172033;
  font: inherit;
  font-size: 12px;
  line-height: 1.5;
  outline: none;
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}

.operation-feedback-reason textarea:focus {
  border-color: #2563eb;
  box-shadow: 0 0 0 3px rgb(37 99 235 / 12%);
}

.operation-feedback-reason small {
  justify-self: end;
  color: #98a2b3;
  font-size: 11px;
}

.operation-feedback-error {
  margin: 0;
  color: #b42318;
  font-size: 12px;
}

.operation-feedback-thanks {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin: 0;
  color: #475467;
  font-size: 12px;
  font-weight: 760;
  line-height: 1.45;
}

.operation-feedback-thanks i {
  color: #667085;
  font-size: 14px;
}

.operation-feedback-primary {
  justify-self: end;
  min-width: 58px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 0 10px;
  border: 1px solid #2563eb;
  border-radius: 4px;
  font-size: 11px;
  font-weight: 760;
  cursor: pointer;
  transition: border-color 0.18s ease, background 0.18s ease, opacity 0.18s ease;
}

.operation-feedback-primary {
  background: #2563eb;
  color: #ffffff;
}

.operation-feedback-primary:hover:not(:disabled),
.operation-feedback-primary:focus-visible {
  border-color: #1d4ed8;
  background: #1d4ed8;
  outline: none;
}

.operation-feedback-link:disabled,
.operation-feedback-primary:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

.feedback-reason-slide-enter-active,
.feedback-reason-slide-leave-active {
  transition: opacity 0.18s ease, transform 0.18s ease;
}

.feedback-reason-slide-enter-from,
.feedback-reason-slide-leave-to {
  opacity: 0;
  transform: translateY(-6px);
}

@media (max-width: 540px) {
  .operation-feedback-inline {
    max-width: 100%;
  }

  .operation-feedback-low-rating {
    min-width: min(280px, calc(100vw - 80px));
  }
}
</style>
