<script lang="ts" setup>
/**
 * 中药药名气泡选择器
 * - 对齐诊断/医嘱 EntryKeywordBubble：Input 聚焦弹出 Teleport 浮层
 * - 远程搜索药品（getProductListDoctorReception），支持药名/别名/首拼/全拼/数字
 * - 列表展示拼音列；命中别名时展示匹配别名及其拼音
 * - 选中后回传完整商品行，供接诊/复诊/特色方/常用方复用现有落库逻辑
 */
import {
  computed,
  nextTick,
  onMounted,
  onUnmounted,
  ref,
  watch,
} from 'vue';

import { LoadingOutlined, SearchOutlined } from '@ant-design/icons-vue';
import { useDebounceFn } from '@vueuse/core';
import { Empty, Input, Spin } from 'ant-design-vue';

import {
  rankByMatchScore,
  splitHighlight,
  type HighlightPart,
} from '#/utils/dictSearchRank';
import { getProductListDoctorReception } from '#/views/doctor/doctor-reception/api';

export type ChineseDrugBubbleItem = {
  id: number;
  drug_id: number;
  price: number;
  buy_price?: number;
  drug: Record<string, any>;
  [key: string]: any;
};

const props = withDefaults(
  defineProps<{
    /** 已选药名（展示回填；打开搜索时仍可编辑关键词） */
    displayName?: string;
    /** 药品类型：1 中药，颗粒等由调用方传入 */
    type?: number;
    storeId?: number;
    registerId?: number;
    placeholder?: string;
    disabled?: boolean;
    /** 卡片内紧凑宽度 */
    compact?: boolean;
    /** 额外 class，便于焦点跳转 querySelector */
    inputClass?: string;
  }>(),
  {
    displayName: '',
    type: 1,
    storeId: 0,
    registerId: 0,
    placeholder: '药名',
    disabled: false,
    compact: false,
    inputClass: '',
  },
);

const emit = defineEmits<{
  select: [item: ChineseDrugBubbleItem];
}>();

const ROW_HEIGHT = 48;
const HINT_HEIGHT = 34;
const HEAD_HEIGHT = 32;
const PANEL_PAD = 8;
const PREFERRED_PANEL = 360;

const containerRef = ref<HTMLElement | null>(null);
const panelRef = ref<HTMLElement | null>(null);
const listRef = ref<HTMLElement | null>(null);
const inputRef = ref<{ focus?: () => void } | null>(null);
const keyword = ref('');
const loading = ref(false);
const open = ref(false);
/** 是否处于「正在搜」：true 时不把 displayName 强写回 keyword */
const searching = ref(false);
const highlightIndex = ref(0);
const options = ref<ChineseDrugBubbleItem[]>([]);
const scrollTop = ref(0);
const listViewportH = ref(200);
const panelStyle = ref<Record<string, string>>({});

const totalHeight = computed(() => options.value.length * ROW_HEIGHT);
const startIndex = computed(() =>
  Math.max(0, Math.floor(scrollTop.value / ROW_HEIGHT) - 2),
);
const endIndex = computed(() => {
  const visible = Math.ceil(listViewportH.value / ROW_HEIGHT) + 4;
  return Math.min(options.value.length - 1, startIndex.value + visible);
});
const visibleRows = computed(() => {
  const list = options.value;
  const start = startIndex.value;
  const end = endIndex.value;
  if (list.length === 0 || end < start) return [];
  return list.slice(start, end + 1).map((item, i) => ({
    item,
    index: start + i,
  }));
});
const offsetY = computed(() => startIndex.value * ROW_HEIGHT);

/**
 * 同步展示名：未打开搜索时用 displayName 回填输入框
 */
watch(
  () => props.displayName,
  (name) => {
    if (!searching.value && !open.value) {
      keyword.value = String(name || '');
    }
  },
  { immediate: true },
);

function partsOf(text: string): HighlightPart[] {
  return splitHighlight(text, keyword.value.trim());
}

function drugNameOf(item: ChineseDrugBubbleItem): string {
  return String(item?.drug?.drug_name || item?.drug_name || '').trim();
}

/** 关键词命中的别名（后端 matched_alias） */
function matchedAliasOf(item: ChineseDrugBubbleItem): string {
  return String(
    item?.matched_alias || item?.drug?.matched_alias || '',
  ).trim();
}

/**
 * 拼音展示：命中别名时用别名拼音，否则用药名首拼(全拼)
 */
function pinyinLabelOf(item: ChineseDrugBubbleItem): string {
  const aliasPy = String(
    item?.matched_alias_pinyin || item?.drug?.matched_alias_pinyin || '',
  ).trim();
  if (matchedAliasOf(item) && aliasPy) return aliasPy;
  const display = String(item?.drug?.pinyin_display || '').trim();
  if (display) return display;
  const simple = String(item?.drug?.pinyin_simple || '').trim();
  const full = String(item?.drug?.pinyin_full || '').trim();
  if (simple && full && simple.toLowerCase() !== full.toLowerCase()) {
    return `${simple}(${full})`;
  }
  return simple || full || '-';
}

function unitLabelOf(item: ChineseDrugBubbleItem): string {
  const unit = item?.drug?.unit || item?.unit;
  if (typeof unit === 'object' && unit?.name) return String(unit.name);
  return String(item?.drug?.specification || item?.specification || '').trim() || '-';
}

function priceLabelOf(item: ChineseDrugBubbleItem): string {
  const p = Number(item?.price ?? 0);
  return Number.isFinite(p) ? `¥${p.toFixed(2)}` : '-';
}

/**
 * 面板 Teleport + fixed，避免接诊/Modal overflow 裁切
 */
function updatePanelPlacement() {
  const el = containerRef.value;
  if (!el) return;
  const rect = el.getBoundingClientRect();
  const vw = window.innerWidth;
  const vh = window.innerHeight;
  const gap = 4;
  const spaceBelow = vh - rect.bottom - PANEL_PAD;
  const spaceAbove = rect.top - PANEL_PAD;
  const placeTop = spaceBelow < 180 && spaceAbove > spaceBelow;
  const avail = Math.max(140, placeTop ? spaceAbove : spaceBelow);
  const panelMax = Math.min(PREFERRED_PANEL, avail);
  listViewportH.value = Math.max(80, panelMax - HINT_HEIGHT - HEAD_HEIGHT);

  // 含拼音列后加宽，避免别名/全拼被裁切
  const PANEL_MIN_WIDTH = 560;
  const width = Math.min(
    Math.max(rect.width, PANEL_MIN_WIDTH),
    vw - PANEL_PAD * 2,
  );
  let left = rect.left;
  if (left + width > vw - PANEL_PAD) {
    left = vw - PANEL_PAD - width;
  }
  if (left < PANEL_PAD) left = PANEL_PAD;

  const base: Record<string, string> = {
    position: 'fixed',
    zIndex: '4000',
    width: `${width}px`,
    maxHeight: `${panelMax}px`,
    left: `${left}px`,
    right: 'auto',
  };
  if (placeTop) {
    panelStyle.value = {
      ...base,
      top: 'auto',
      bottom: `${Math.max(PANEL_PAD, vh - rect.top + gap)}px`,
    };
  } else {
    panelStyle.value = {
      ...base,
      top: `${rect.bottom + gap}px`,
      bottom: 'auto',
    };
  }
}

/**
 * 远程搜药；空关键词不请求（与原中药 Select 一致）
 */
async function fetchDrugs(kw: string) {
  const name = String(kw || '').trim();
  if (!name) {
    options.value = [];
    loading.value = false;
    return;
  }
  loading.value = true;
  try {
    const res = await getProductListDoctorReception({
      name,
      type: props.type,
      store_id: props.storeId || 0,
      ...(props.registerId > 0 ? { register_id: props.registerId } : {}),
    });
    const list = Array.isArray(res) ? res : [];
    const mapped: ChineseDrugBubbleItem[] = list.map((item: any) => {
      const drug = item.drug || {};
      return {
        ...item,
        id: Number(item.id || 0),
        drug_id: Number(item.drug_id || drug.id || 0),
        price: Number(item.price ?? 0),
        buy_price: item.buy_price,
        drug: {
          ...drug,
          drug_name: drug.drug_name || item.drug_name || '',
        },
      };
    });
    options.value = rankByMatchScore(
      mapped,
      name,
      (it) => drugNameOf(it),
      (it) =>
        [
          it.drug?.pinyin_simple,
          it.drug?.pinyin_full,
          it.drug?.pinyin_display,
          it.drug?.drug_alias,
          it.matched_alias || it.drug?.matched_alias,
          it.matched_alias_pinyin || it.drug?.matched_alias_pinyin,
          it.drug?.search_digits,
          it.drug?.specification,
        ]
          .map((v) => String(v || ''))
          .join(' '),
    );
    highlightIndex.value = options.value.length > 0 ? 0 : -1;
    scrollTop.value = 0;
    if (listRef.value) listRef.value.scrollTop = 0;
  } catch (e) {
    console.error('中药药名搜索失败:', e);
    options.value = [];
  } finally {
    loading.value = false;
    nextTick(() => updatePanelPlacement());
  }
}

const debouncedFetch = useDebounceFn((kw: string) => {
  void fetchDrugs(kw);
}, 280);

function openPanel() {
  if (props.disabled) return;
  open.value = true;
  searching.value = true;
  nextTick(() => {
    updatePanelPlacement();
    if (keyword.value.trim()) {
      debouncedFetch(keyword.value);
    } else {
      options.value = [];
    }
  });
}

function closePanel(restoreDisplay = true) {
  open.value = false;
  searching.value = false;
  options.value = [];
  highlightIndex.value = 0;
  if (restoreDisplay) {
    keyword.value = String(props.displayName || '');
  }
}

function handleFocus() {
  if (props.disabled) return;
  // 聚焦时清空为搜索态，便于直接输入新药名
  if (props.displayName && keyword.value === props.displayName) {
    keyword.value = '';
  }
  openPanel();
}

function handleInput() {
  searching.value = true;
  if (!open.value) openPanel();
  else updatePanelPlacement();
  debouncedFetch(keyword.value);
}

function selectItem(item: ChineseDrugBubbleItem) {
  const name = drugNameOf(item);
  keyword.value = name;
  searching.value = false;
  open.value = false;
  options.value = [];
  emit('select', item);
}

/**
 * 键盘高亮循环移动（与诊断 EntryKeywordBubble 一致：第一项再上键 → 最后一项）
 */
function moveHighlight(delta: number) {
  if (options.value.length === 0) return;
  const len = options.value.length;
  let next = highlightIndex.value;
  if (next < 0) next = 0;
  else next = (next + delta + len) % len;
  highlightIndex.value = next;
  ensureRowVisible(highlightIndex.value);
}

function handleKeydown(e: KeyboardEvent) {
  if (!open.value) {
    if (e.key === 'ArrowDown' || e.key === 'Enter') {
      e.preventDefault();
      openPanel();
    }
    return;
  }
  if (e.key === 'Escape') {
    e.preventDefault();
    closePanel(true);
    return;
  }
  if (options.value.length === 0) return;
  if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
    e.preventDefault();
    moveHighlight(1);
  } else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
    e.preventDefault();
    moveHighlight(-1);
  } else if (e.key === 'Enter') {
    e.preventDefault();
    const hit = options.value[highlightIndex.value];
    if (hit) selectItem(hit);
  }
}

function ensureRowVisible(index: number) {
  const el = listRef.value;
  if (!el || index < 0) return;
  const top = index * ROW_HEIGHT;
  const bottom = top + ROW_HEIGHT;
  if (top < el.scrollTop) el.scrollTop = top;
  else if (bottom > el.scrollTop + el.clientHeight) {
    el.scrollTop = bottom - el.clientHeight;
  }
  // 同步虚拟列表切片（循环跳到首/尾时必须刷新可见区）
  scrollTop.value = el.scrollTop;
}

function onListScroll() {
  scrollTop.value = listRef.value?.scrollTop || 0;
}

function onDocPointerDown(e: MouseEvent) {
  if (!open.value) return;
  const t = e.target as Node;
  if (containerRef.value?.contains(t)) return;
  if (panelRef.value?.contains(t)) return;
  closePanel(true);
}

function onWinChange() {
  if (open.value) updatePanelPlacement();
}

onMounted(() => {
  document.addEventListener('mousedown', onDocPointerDown, true);
  window.addEventListener('resize', onWinChange);
  window.addEventListener('scroll', onWinChange, true);
});

onUnmounted(() => {
  document.removeEventListener('mousedown', onDocPointerDown, true);
  window.removeEventListener('resize', onWinChange);
  window.removeEventListener('scroll', onWinChange, true);
});

defineExpose({
  focus() {
    nextTick(() => {
      const el = inputRef.value as any;
      el?.focus?.();
      const native = containerRef.value?.querySelector(
        'input',
      ) as HTMLInputElement | null;
      native?.focus();
    });
  },
  close() {
    closePanel(true);
  },
});
</script>

<template>
  <div
    ref="containerRef"
    class="chinese-drug-name-bubble"
    :class="[{ 'chinese-drug-name-bubble--compact': compact }, inputClass]"
  >
    <Input
      ref="inputRef"
      v-model:value="keyword"
      :placeholder="placeholder"
      :disabled="disabled"
      allow-clear
      size="small"
      @input="handleInput"
      @focus="handleFocus"
      @keydown="handleKeydown"
    >
      <template #prefix>
        <LoadingOutlined v-if="loading" class="text-muted-foreground" />
        <SearchOutlined v-else class="text-muted-foreground" />
      </template>
    </Input>
    <Teleport to="body">
      <div
        v-if="open"
        ref="panelRef"
        class="chinese-drug-name-bubble__panel"
        :style="panelStyle"
      >
        <div class="chinese-drug-name-bubble__hint">
          <span>↑↓←→ 循环切换 · Enter 选用 · Esc 关闭</span>
          <span class="chinese-drug-name-bubble__count">
            <template v-if="keyword.trim()">搜到 {{ options.length }} 条</template>
            <template v-else>输入药名搜索</template>
          </span>
        </div>
        <Spin :spinning="loading">
          <Empty
            v-if="!loading && options.length === 0"
            :image="Empty.PRESENTED_IMAGE_SIMPLE"
            :description="
              keyword.trim()
                ? '未找到匹配药材'
                : '请输入药名/别名/拼音/数字搜索'
            "
            class="py-4"
          />
          <template v-else>
            <div
              class="chinese-drug-name-bubble__head"
              :class="{ 'has-score': !!keyword.trim() }"
            >
              <span class="col-name">药名</span>
              <span class="col-py">拼音</span>
              <span class="col-spec">规格/单位</span>
              <span class="col-price">价格</span>
              <span v-if="keyword.trim()" class="col-score">匹配度</span>
            </div>
            <div
              ref="listRef"
              class="chinese-drug-name-bubble__list"
              :style="{ height: `${listViewportH}px` }"
              @scroll="onListScroll"
            >
              <div
                class="chinese-drug-name-bubble__phantom"
                :style="{ height: `${totalHeight}px` }"
              />
              <div
                class="chinese-drug-name-bubble__virtual"
                :style="{ transform: `translateY(${offsetY}px)` }"
              >
                <div
                  v-for="{ item, index } in visibleRows"
                  :key="`${item.drug_id}_${index}`"
                  class="chinese-drug-name-bubble__row"
                  :class="{
                    'is-active': index === highlightIndex,
                    'has-score': !!keyword.trim(),
                  }"
                  :style="{ height: `${ROW_HEIGHT}px` }"
                  @mousedown.prevent="selectItem(item)"
                  @mouseenter="highlightIndex = index"
                >
                  <span
                    class="col-name"
                    :title="
                      matchedAliasOf(item)
                        ? `${drugNameOf(item)}（别名：${matchedAliasOf(item)}）`
                        : drugNameOf(item)
                    "
                  >
                    <span class="col-name__main">
                      <template
                        v-for="(p, pi) in partsOf(drugNameOf(item))"
                        :key="`n_${pi}`"
                      >
                        <em v-if="p.hit" class="hit">{{ p.text }}</em>
                        <template v-else>{{ p.text }}</template>
                      </template>
                    </span>
                    <span
                      v-if="matchedAliasOf(item)"
                      class="col-name__alias"
                    >
                      别名：
                      <template
                        v-for="(p, pi) in partsOf(matchedAliasOf(item))"
                        :key="`a_${pi}`"
                      >
                        <em v-if="p.hit" class="hit">{{ p.text }}</em>
                        <template v-else>{{ p.text }}</template>
                      </template>
                    </span>
                  </span>
                  <span class="col-py" :title="pinyinLabelOf(item)">
                    <template v-if="pinyinLabelOf(item) === '-'">-</template>
                    <template v-else>
                      <template
                        v-for="(p, pi) in partsOf(pinyinLabelOf(item))"
                        :key="`py_${pi}`"
                      >
                        <em v-if="p.hit" class="hit">{{ p.text }}</em>
                        <template v-else>{{ p.text }}</template>
                      </template>
                    </template>
                  </span>
                  <span class="col-spec" :title="unitLabelOf(item)">
                    {{ unitLabelOf(item) }}
                  </span>
                  <span class="col-price">{{ priceLabelOf(item) }}</span>
                  <span v-if="keyword.trim()" class="col-score">
                    {{ Number(item.match_score || 0) }}%
                  </span>
                </div>
              </div>
            </div>
          </template>
        </Spin>
      </div>
    </Teleport>
  </div>
</template>

<style scoped>
.chinese-drug-name-bubble {
  position: relative;
  display: inline-block;
  vertical-align: middle;
  box-sizing: border-box;
  /* 锁死宽度：避免接诊/复诊/特色方/常用方 flex 布局把药名框撑满整行 */
  width: 100px !important;
  min-width: 88px !important;
  max-width: 110px !important;
  flex: 0 0 100px !important;
  overflow: hidden;
}
.chinese-drug-name-bubble--compact {
  width: 100px !important;
  min-width: 88px !important;
  max-width: 110px !important;
  flex: 0 0 100px !important;
}
.chinese-drug-name-bubble :deep(.ant-input-affix-wrapper) {
  width: 100% !important;
  max-width: 100% !important;
  min-width: 0 !important;
  padding-inline: 6px;
  box-sizing: border-box;
}
.chinese-drug-name-bubble :deep(.ant-input-prefix) {
  margin-inline-end: 2px;
}
.chinese-drug-name-bubble :deep(.ant-input) {
  width: 100% !important;
  min-width: 0 !important;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chinese-drug-name-bubble__panel {
  overflow: hidden;
  display: flex;
  flex-direction: column;
  border-radius: 8px;
  border: 1px solid hsl(var(--border));
  background: hsl(var(--popover, var(--background)));
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.chinese-drug-name-bubble__hint {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 12px;
  font-size: 12px;
  color: hsl(var(--muted-foreground));
  border-bottom: 1px solid hsl(var(--border));
  background: hsl(var(--muted) / 0.35);
}
.chinese-drug-name-bubble__count {
  flex-shrink: 0;
  font-weight: 600;
  color: hsl(var(--foreground));
}
.chinese-drug-name-bubble__list {
  position: relative;
  overflow-y: auto;
  overflow-x: hidden;
}
.chinese-drug-name-bubble__phantom {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: -1;
  pointer-events: none;
}
.chinese-drug-name-bubble__virtual {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
}
.chinese-drug-name-bubble__head,
.chinese-drug-name-bubble__row {
  display: grid;
  grid-template-columns: minmax(90px, 1.1fr) minmax(110px, 1.3fr) 90px 64px;
  gap: 8px;
  padding: 6px 12px;
  font-size: 13px;
  align-items: center;
  box-sizing: border-box;
}
.chinese-drug-name-bubble__head.has-score,
.chinese-drug-name-bubble__row.has-score {
  grid-template-columns: minmax(90px, 1.1fr) minmax(100px, 1.2fr) 80px 56px 48px;
}
.chinese-drug-name-bubble__head {
  flex-shrink: 0;
  font-weight: 600;
  color: hsl(var(--muted-foreground));
  background: hsl(var(--popover, var(--background)));
  border-bottom: 1px solid hsl(var(--border));
}
.chinese-drug-name-bubble__row {
  cursor: pointer;
  border-bottom: 1px solid hsl(var(--border) / 0.6);
}
.chinese-drug-name-bubble__row:hover,
.chinese-drug-name-bubble__row.is-active {
  background: hsl(var(--accent));
  color: hsl(var(--accent-foreground));
}
.col-name,
.col-py,
.col-spec {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.col-name {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  font-weight: 500;
  white-space: normal;
  line-height: 1.25;
}
.col-name__main {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.col-name__alias {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 11px;
  font-weight: 400;
  color: hsl(var(--muted-foreground));
}
.col-py {
  font-size: 12px;
  color: hsl(var(--muted-foreground));
}
.col-spec {
  font-size: 12px;
  color: hsl(var(--muted-foreground));
}
.col-price {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.col-score {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: #d4380d;
  white-space: nowrap;
}
.chinese-drug-name-bubble__row.is-active .col-spec,
.chinese-drug-name-bubble__row.is-active .col-py,
.chinese-drug-name-bubble__row.is-active .col-name__alias {
  color: inherit;
  opacity: 0.85;
}
.chinese-drug-name-bubble__row.is-active .col-score {
  color: inherit;
}
.hit {
  font-style: normal;
  color: #cf1322;
  background: rgba(255, 214, 102, 0.55);
  border-radius: 2px;
  padding: 0 1px;
}
</style>
