<script lang="ts" setup>
/**
 * 处方订单卡片看板（C 端紧凑排版 + Apple 毛玻璃）：与列表视图展示同一批字段，仅排版不同
 *
 * 与商品订单卡片同一套设计语言：多列自适应网格、毛玻璃 14px 圆角 hairline 卡片、
 * 状态彩点、tinted 小胶囊、人员信息内联行、底部 hairline 操作栏。
 * 数据自取：props.formValues 变化时重置到第 1 页，触底自动加载下一页。
 */
import type { ActionItem } from '#/components/table-action';

import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';

import { Empty, message, Spin } from 'ant-design-vue';

import { TableAction } from '#/components/table-action';
import { pillStyle, tagColorHex } from '#/utils/tagColor';

import { getPrescriptionListApi } from '../api';
import {
  PRESCRIPTION_IS_ONLINE_FALLBACK,
  PRESCRIPTION_IS_ONLINE_MAP,
  PRESCRIPTION_ORDER_TYPE_MAP,
  PRESCRIPTION_STATUS_MAP,
} from '../config/constants';

const props = defineProps<{
  /** 顶部统一搜索条件（含 status Tab / patient_keyword / doctor_keyword 等） */
  formValues?: Record<string, any>;
}>();

const emit = defineEmits<{
  /** 查看处方详情（复用父级弹窗） */
  openDetail: [prescriptionId: number];
  /** 处方溯源（复用父级弹窗） */
  openSource: [prescriptionId: number];
  /** 点诊所名打开诊所卡片 */
  openStore: [storeId: number];
}>();

const items = ref<any[]>([]);
const page = ref(1);
const pageCount = ref(1);
const loading = ref(false);
const loadingMore = ref(false);
/** 请求序号：只应用最后一次响应，避免快速改筛选时旧结果覆盖新列表 */
let requestSeq = 0;

const scrollEl = ref<HTMLElement | null>(null);
const sentinelEl = ref<HTMLElement | null>(null);
let observer: IntersectionObserver | null = null;

const hasMore = computed(() => page.value < pageCount.value);

/** 处方类型元数据 */
function typeMeta(row: Record<string, any>) {
  return PRESCRIPTION_ORDER_TYPE_MAP[Number(row?.prescription_type)] ?? null;
}

/** 审核状态元数据 */
function statusMeta(row: Record<string, any>) {
  return PRESCRIPTION_STATUS_MAP[Number(row?.status)] ?? null;
}

/** 处方来源元数据（未知兜底线下） */
function isOnlineMeta(row: Record<string, any>) {
  return PRESCRIPTION_IS_ONLINE_MAP[Number(row?.is_online)] ?? PRESCRIPTION_IS_ONLINE_FALLBACK;
}

/** 行操作：与列表 #action 槽一致（查看处方/处方溯源），通过 emit 复用父级弹窗 */
function buildActions(row: Record<string, any>): ActionItem[] {
  return [
    {
      label: '查看处方',
      type: 'link',
      icon: 'marketeq:eye',
      onClick: () => emit('openDetail', Number(row.id)),
    },
    {
      label: '处方溯源',
      type: 'link',
      icon: 'mdi:timeline-text',
      onClick: () => emit('openSource', Number(row.id)),
    },
  ];
}

/**
 * 拉取一页数据；reset 时清空列表并回到第 1 页
 * 用 requestSeq 丢弃过期响应，防止筛选连点时旧条件结果覆盖新条件
 */
async function fetchPage(targetPage: number, reset = false) {
  const seq = ++requestSeq;
  if (reset) {
    loading.value = true;
  } else {
    loadingMore.value = true;
  }
  try {
    const res = await getPrescriptionListApi({
      page: targetPage,
      pageSize: 12,
      ...(props.formValues || {}),
    });
    if (seq !== requestSeq) return;
    items.value = reset ? (res?.items ?? []) : [...items.value, ...(res?.items ?? [])];
    page.value = res?.page ?? targetPage;
    pageCount.value = res?.page_count ?? 1;
  } catch (error) {
    if (seq !== requestSeq) return;
    console.error(error);
    message.error('加载处方列表失败');
  } finally {
    if (seq === requestSeq) {
      loading.value = false;
      loadingMore.value = false;
    }
  }
}

/** 筛选条件变化：重置回第 1 页 */
async function reload() {
  page.value = 1;
  items.value = [];
  await fetchPage(1, true);
}

/** 操作后原地刷新：按已加载条数一次性重拉，保持滚动位置 */
async function refreshKeepPage() {
  const loaded = Math.max(items.value.length, 12);
  const seq = ++requestSeq;
  loading.value = true;
  try {
    const res = await getPrescriptionListApi({
      page: 1,
      pageSize: loaded,
      ...(props.formValues || {}),
    });
    if (seq !== requestSeq) return;
    items.value = res?.items ?? [];
    page.value = Math.ceil((res?.items?.length ?? 0) / 12) || 1;
    pageCount.value = Math.ceil((res?.total ?? 0) / 12) || 1;
  } catch (error) {
    if (seq === requestSeq) console.error(error);
  } finally {
    if (seq === requestSeq) loading.value = false;
  }
}

defineExpose({ reload, refreshKeepPage });

async function loadMore() {
  if (!hasMore.value || loading.value || loadingMore.value) return;
  await fetchPage(page.value + 1, false);
}

/** 触底哨兵：滚动到底部附近自动加载下一页 */
function setupObserver() {
  observer?.disconnect();
  if (!sentinelEl.value) return;
  observer = new IntersectionObserver(
    (entries) => {
      if (entries.some((e) => e.isIntersecting)) {
        void loadMore();
      }
    },
    { root: scrollEl.value, rootMargin: '120px' },
  );
  observer.observe(sentinelEl.value);
}

watch(
  () => props.formValues,
  () => {
    void reload();
  },
  { deep: true },
);

onMounted(async () => {
  await reload();
  await nextTick();
  setupObserver();
});

onBeforeUnmount(() => {
  observer?.disconnect();
  observer = null;
});
</script>

<template>
  <div ref="scrollEl" class="rx-card-scroll">
    <Spin :spinning="loading">
      <div v-if="items.length" class="rx-card-grid">
        <div v-for="row in items" :key="row.id" class="rx-card">
          <!-- 角落径向荧光（与提现管理统计岛同款质感装饰） -->
          <div class="rx-card__glow" />
          <!-- 头部：诊所名（可点）+ 状态彩点 -->
          <div class="rx-card__head">
            <button
              v-if="row.store?.id || row.store_id"
              type="button"
              class="rx-card__store"
              @click="emit('openStore', Number(row.store?.id || row.store_id))"
            >
              <span class="rx-card__store-name">{{ row.store?.name || '—' }}</span>
              <span class="rx-card__store-arrow">›</span>
            </button>
            <span v-else class="rx-card__store-name">{{ row.store?.name || '—' }}</span>
            <span
              v-if="statusMeta(row)"
              class="rx-card__status"
              :style="{ color: tagColorHex(statusMeta(row)!.color) }"
            >
              <i
                class="rx-card__status-dot"
                :style="{ background: tagColorHex(statusMeta(row)!.color) }"
              ></i>
              {{ statusMeta(row)!.label }}
            </span>
          </div>
          <!-- 处方号 / 创建时间 -->
          <div class="rx-card__sub">
            <span class="truncate">{{ row.prescription_no || '—' }}</span>
            <span class="rx-card__dot">·</span>
            <span class="shrink-0">{{ row.created_at || '—' }}</span>
          </div>
          <!-- 语义胶囊：处方类型 / 来源 -->
          <div class="rx-card__pills">
            <span v-if="typeMeta(row)" class="rx-pill" :style="pillStyle(typeMeta(row)!.color)">
              {{ typeMeta(row)!.label }}
            </span>
            <span class="rx-pill" :style="pillStyle(isOnlineMeta(row).color)">
              {{ isOnlineMeta(row).label }}
            </span>
          </div>
          <!-- 医生 / 就诊人：内联小字行（省高度） -->
          <div class="rx-card__people">
            <span class="rx-kv">
              <i>开方医生</i>
              <em>{{ row.doctor_info?.name || '—' }}</em>
            </span>
            <span class="rx-kv">
              <i>就诊人</i>
              <em>{{ row.user_patient?.name || '—' }}</em>
            </span>
          </div>
          <!-- 拒绝原因（仅已拒绝时显示） -->
          <div v-if="Number(row.status) === 2 && row.reject_reason" class="rx-card__reject">
            拒绝原因：{{ row.reject_reason }}
          </div>
          <!-- 弹性占位：高度富余时把 footer 推到底（同行等高卡对齐），紧凑时保底 8px 间距 -->
          <div class="rx-card__spacer"></div>
          <!-- 底部操作：与列表 #action 槽同语义 -->
          <div class="rx-card__footer">
            <TableAction :actions="buildActions(row)" :drop-down-actions="[]" />
          </div>
        </div>
      </div>
      <Empty v-else-if="!loading" description="暂无处方" />
    </Spin>
    <div ref="sentinelEl" class="h-8"></div>
    <div v-if="loadingMore" class="py-3 text-center text-xs text-[hsl(var(--muted-foreground))]">
      加载中…
    </div>
    <div
      v-else-if="items.length && !hasMore"
      class="py-3 text-center text-xs text-[hsl(var(--muted-foreground))]"
    >
      没有更多了
    </div>
  </div>
</template>

<style scoped>
.rx-card-scroll {
  height: 100%;
  /* 左右留 6px：卡片 hover 阴影不被容器裁出切边 */
  padding: 4px 6px 20px;
  overflow: auto;
}

/* 多列自适应网格：单卡最窄 340px，宽屏自动 2-4 列（C 端紧凑卡片密度）；
   min(340px, 100%) 防止窄容器（分屏）时轨道小于容器导致横向滚动 */
.rx-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr));
  gap: 10px;
}

/* 玻璃质感紧凑卡片（与提现管理统计岛同款）：主色渐变玻璃底 + 12px 圆角 + 1px 边框（禁止左侧竖色条） */
.rx-card {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: linear-gradient(
    135deg,
    hsl(var(--primary) / 4%),
    hsl(var(--card, var(--background)))
  );
  backdrop-filter: blur(8px);
  border: 1px solid hsl(var(--border));
  border-radius: 12px;
  transition:
    transform 0.2s ease,
    box-shadow 0.2s ease,
    border-color 0.2s ease;
}

.rx-card:hover {
  border-color: hsl(var(--primary) / 40%);
  box-shadow:
    0 8px 20px hsl(var(--primary) / 14%),
    0 2px 6px rgb(0 0 0 / 6%);
  transform: translateY(-3px);
}

/* 角落径向荧光：右上角淡主色光斑，hover 增强 */
.rx-card__glow {
  position: absolute;
  top: -40px;
  right: -40px;
  width: 110px;
  height: 110px;
  pointer-events: none;
  background: radial-gradient(circle, hsl(var(--primary) / 18%) 0%, transparent 70%);
  opacity: 0.4;
  transition: opacity 0.2s ease;
}

.rx-card:hover .rx-card__glow {
  opacity: 1;
}

.rx-card__head {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 10px 12px 0;
}

.rx-card__store {
  display: inline-flex;
  min-width: 0;
  align-items: center;
  gap: 2px;
  padding: 0;
  cursor: pointer;
  background: none;
  border: none;
}

.rx-card__store-name {
  overflow: hidden;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.2px;
  color: hsl(var(--foreground));
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rx-card__store:hover .rx-card__store-name {
  color: hsl(var(--primary));
}

.rx-card__store-arrow {
  flex-shrink: 0;
  font-size: 14px;
  color: hsl(var(--muted-foreground));
}

.rx-card__status {
  display: inline-flex;
  flex-shrink: 0;
  gap: 4px;
  align-items: center;
  margin-left: auto;
  font-size: 12px;
  font-weight: 600;
}

.rx-card__status-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
}

.rx-card__sub {
  display: flex;
  gap: 4px;
  align-items: center;
  padding: 1px 12px 0;
  font-size: 11px;
  color: hsl(var(--muted-foreground));
}

.rx-card__dot {
  flex-shrink: 0;
}

.rx-card__pills {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 6px 12px 0;
}

.rx-pill {
  display: inline-flex;
  align-items: center;
  padding: 1px 7px;
  font-size: 10px;
  font-weight: 500;
  line-height: 16px;
  border: none;
  border-radius: 999px;
}

/* 医生/就诊人内联行 */
.rx-card__people {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 14px;
  padding: 7px 12px 0;
  font-size: 12px;
  line-height: 1.6;
}

.rx-kv {
  display: inline-flex;
  gap: 4px;
  align-items: baseline;
  min-width: 0;
}

.rx-kv > i {
  flex-shrink: 0;
  font-size: 11px;
  font-style: normal;
  color: hsl(var(--muted-foreground) / 75%);
}

.rx-kv > em {
  overflow: hidden;
  font-style: normal;
  font-weight: 500;
  color: hsl(var(--foreground));
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 拒绝原因警示条（warning tinted，无左色条） */
.rx-card__reject {
  padding: 4px 8px;
  margin: 6px 12px 0;
  font-size: 11px;
  line-height: 1.6;
  color: hsl(var(--warning, 38 92% 50%));
  background: hsl(var(--warning, 38 92% 50%) / 10%);
  border-radius: 8px;
}

/* 弹性占位：吃掉剩余高度让 footer 吸底（同行等高卡操作栏对齐） */
.rx-card__spacer {
  flex: 1 1 auto;
  min-height: 8px;
}

.rx-card__footer {
  display: flex;
  justify-content: flex-end;
  padding: 4px 6px;
  border-top: 1px solid hsl(var(--border) / 50%);
}
</style>
