<script lang="ts" setup>
/**
 * 门店仓库批量改价预览：ExcelJS 本地解析 → 分类对比 → 确认落库
 * 仅改零售价；Excel 含单位列时忽略并提示；配送仓锁定行不可提交
 */
import type { UploadFile } from 'ant-design-vue';

import { computed, ref, watch } from 'vue';

import { useVbenModal } from '@vben/common-ui';

import { Button, message, Spin, Tag, UploadDragger } from 'ant-design-vue';

import { downloadByData } from '#/util/tool';

import {
  applyStoreBatchPriceApi,
  getStoreAllForBatchPriceApi,
  getStoreExportDataApi,
  type StoreBatchPriceItem,
  type StoreCentralAvailableItem,
} from '../api';
import {
  exportStoreListExcel,
  type StoreExportRow,
} from '../utils/exportStorePriceExcel';
import { parseStorePriceExcelBuffer } from '../utils/parseStorePriceExcel';
import {
  getPriceDelta,
  isFieldChanged,
  parsePriceValue,
  reconcileExcelPrice,
  roundPrice4,
} from '../../admin/utils/priceCompare';
import PriceCompareCardGrid, {
  type PriceCompareFilterTab,
  type PriceCompareRow,
  type UnmatchedRow,
} from '../../shared/components/PriceCompareCardGrid.vue';

const gridApi = ref();
const productType = ref(1);
const fileList = ref<UploadFile[]>([]);
const selectedFile = ref<File | null>(null);
const loadingAll = ref(false);
const parseLoading = ref(false);
const applyLoading = ref(false);
const parseHint = ref('');
const parsedReady = ref(false);
const filterTab = ref<PriceCompareFilterTab>('all');

const storeItems = ref<StoreBatchPriceItem[]>([]);
const centralAvailable = ref<StoreCentralAvailableItem[]>([]);
/** relation.id → 原始零售价 */
const initialPriceMap = ref<Record<number, number>>({});
/** relation.id → 当前编辑零售价 */
const localPriceMap = ref<Record<number, number>>({});
const newRows = ref<
  Array<{
    tempId: number;
    drug_id: number;
    drug_name: string;
    drug_number: string;
    unit_name: string;
    price: number;
  }>
>([]);
const unmatchedRows = ref<UnmatchedRow[]>([]);
const excelTouchedIds = ref<Set<number>>(new Set());
let tempIdCounter = -1;
let loadAllPromise: Promise<void> | null = null;

function resetState() {
  selectedFile.value = null;
  fileList.value = [];
  storeItems.value = [];
  centralAvailable.value = [];
  initialPriceMap.value = {};
  localPriceMap.value = {};
  newRows.value = [];
  unmatchedRows.value = [];
  excelTouchedIds.value = new Set();
  parseHint.value = '';
  parsedReady.value = false;
  filterTab.value = 'all';
  tempIdCounter = -1;
}

function clearParseResult() {
  newRows.value = [];
  unmatchedRows.value = [];
  excelTouchedIds.value = new Set();
  parseHint.value = '';
  parsedReady.value = false;
  const next: Record<number, number> = {};
  for (const [id, price] of Object.entries(initialPriceMap.value)) {
    next[Number(id)] = price;
  }
  localPriceMap.value = next;
}

function buildSnapshot(items: StoreBatchPriceItem[]) {
  const init: Record<number, number> = {};
  const loc: Record<number, number> = {};
  for (const item of items) {
    // 与 Excel 解析同一套 4 位小数，避免 format_price 把浮点误差打成「降一分」
    const price = roundPrice4(item.price);
    init[item.id] = price;
    loc[item.id] = price;
  }
  initialPriceMap.value = init;
  localPriceMap.value = loc;
}

/**
 * 组装对比行：门店仅零售价；配送仓锁定标记 _priceLocked
 */
function buildCompareRow(
  item: {
    id: number;
    drug_id?: number;
    drug_name: string;
    drug_number: string;
    pinyin_simple?: string;
    unit_name?: string;
    has_delivery_warehouse?: number;
  },
  currentPrice: number,
  origPrice: number,
  isNew: boolean,
): PriceCompareRow {
  const priceLocked = !isNew && Number(item.has_delivery_warehouse) === 1;
  const priceChanged = isFieldChanged(origPrice, currentPrice);
  // 锁定行即使 Excel 改了价也不算「有修改」，避免误提交
  const changed = isNew || (!priceLocked && priceChanged);
  return {
    id: item.id,
    drug_id: item.drug_id,
    drug_name: item.drug_name,
    drug_number: item.drug_number,
    pinyin_simple: item.pinyin_simple ?? '',
    orig_market_price: 0,
    orig_price: origPrice,
    orig_unit_name: item.unit_name ?? '',
    market_price: 0,
    price: currentPrice,
    unit_name: item.unit_name ?? '',
    _changed: changed,
    _marketGtPrice: false,
    _unitChanged: false,
    _isNew: isNew,
    _fromExcel: excelTouchedIds.value.has(item.id) || isNew,
    _priceLocked: priceLocked,
    _marketDelta: 'same',
    _priceDelta: getPriceDelta(origPrice, currentPrice),
    _marketChanged: false,
    _priceChanged: priceChanged,
  };
}

const compareRows = computed<PriceCompareRow[]>(() => {
  const existing = storeItems.value.map((item) => {
    const orig = initialPriceMap.value[item.id] ?? parsePriceValue(item.price);
    const current = localPriceMap.value[item.id] ?? orig;
    return buildCompareRow(item, current, orig, false);
  });
  const news = newRows.value.map((row) =>
    buildCompareRow(
      {
        id: row.tempId,
        drug_id: row.drug_id,
        drug_name: row.drug_name,
        drug_number: row.drug_number,
        unit_name: row.unit_name,
      },
      row.price,
      0,
      true,
    ),
  );
  return [...existing, ...news];
});

const changedCount = computed(
  () =>
    compareRows.value.filter((r) => r._changed && !r._isNew && !r._priceLocked)
      .length,
);
const newCount = computed(
  () => compareRows.value.filter((r) => r._isNew).length,
);

const [ModalComp, modalApi] = useVbenModal({
  fullscreen: true,
  fullscreenButton: true,
  draggable: true,
  confirmText: '批量确认更新',
  cancelText: '关闭',
  showConfirmButton: true,
  onConfirm: runBatchApply,
  onCancel() {
    resetState();
    modalApi.close();
  },
  onOpenChange(isOpen: boolean) {
    gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
    productType.value = isOpen ? modalApi.getData()?.productType ?? 1 : 1;
    if (isOpen) {
      modalApi.setState({ fullscreen: true });
      syncModalFooterState();
      loadAllDrugs();
    } else {
      resetState();
    }
  },
});

function syncModalFooterState() {
  modalApi.setState({
    confirmDisabled:
      !parsedReady.value ||
      (changedCount.value === 0 && newCount.value === 0),
    confirmLoading: applyLoading.value,
  });
}

watch(
  [changedCount, newCount, applyLoading, parseLoading, parsedReady],
  syncModalFooterState,
);

async function loadAllDrugs() {
  loadingAll.value = true;
  const task = (async () => {
    try {
      const res = await getStoreAllForBatchPriceApi({ type: productType.value });
      storeItems.value = res?.items ?? [];
      centralAvailable.value = res?.central_available ?? [];
      buildSnapshot(storeItems.value);
    } catch {
      message.error('加载门店药品失败');
      storeItems.value = [];
      centralAvailable.value = [];
      initialPriceMap.value = {};
      localPriceMap.value = {};
    } finally {
      loadingAll.value = false;
      loadAllPromise = null;
    }
  })();
  loadAllPromise = task;
  await task;
}

async function handleFileChange(info: { file: UploadFile }) {
  const { file } = info;
  if (file.status === 'removed') {
    selectedFile.value = null;
    clearParseResult();
    filterTab.value = 'all';
    return;
  }
  const raw = file.originFileObj ?? file;
  if (raw instanceof File) {
    selectedFile.value = raw;
    await runParse();
  }
}

/**
 * 下载标准模板：与列表页「导出」同一套 export-data + 列结构
 */
async function downloadTemplate() {
  try {
    const res = await getStoreExportDataApi({ type: productType.value });
    const rows = (res?.rows ?? []) as StoreExportRow[];
    if (rows.length === 0) {
      message.warning('暂无药品数据');
      return;
    }
    const buffer = await exportStoreListExcel(rows, '门店仓库');
    downloadByData(
      buffer,
      '萧康云医-门店仓库商品导出.xlsx',
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    );
    message.success(`模板已下载（${res?.total ?? rows.length} 条，与导出一致）`);
  } catch {
    message.error('模板下载失败');
  }
}

function findStoreByDrugId(drugId: number) {
  return storeItems.value.find((d) => d.drug_id === drugId);
}

function findCentralByDrugId(drugId: number) {
  return centralAvailable.value.find((d) => d.drug_id === drugId);
}

async function runParse() {
  if (!selectedFile.value) {
    return;
  }
  if (loadingAll.value && loadAllPromise) {
    await loadAllPromise;
  }
  parseLoading.value = true;
  syncModalFooterState();
  try {
    const buf = await selectedFile.value.arrayBuffer();
    const { items, invalidCount, rowCount, hasUnitColumn } =
      await parseStorePriceExcelBuffer(buf);

    if (hasUnitColumn) {
      message.info('门店导入已忽略单位列（单位仅平台总仓可改）');
    }

    if (items.length === 0) {
      message.warning(
        invalidCount > 0
          ? `未解析到有效行（${invalidCount} 行缺少合法药品ID）`
          : '未解析到有效的价格行',
      );
      return;
    }

    const hints: string[] = [
      rowCount > 5000
        ? `共 ${rowCount} 行，已按上限处理前 5000 行。`
        : `共解析 ${items.length} 条有效行。`,
    ];
    if (hasUnitColumn) {
      hints.push('已忽略单位列。');
    }
    if (invalidCount > 0) {
      hints.push(`${invalidCount} 行因药品ID无效已跳过。`);
    }
    parseHint.value = hints.join(' ');

    const nextUnmatched: UnmatchedRow[] = [];
    const nextNew: typeof newRows.value = [];
    const nextLocal = { ...localPriceMap.value };
    const nextTouched = new Set<number>();

    for (const row of items) {
      const matched = findStoreByDrugId(row.drug_id);
      if (matched) {
        nextTouched.add(matched.id);
        const orig = initialPriceMap.value[matched.id] ?? parsePriceValue(matched.price);
        // 与快照对账，消化 Excel 一分钱浮点误差
        nextLocal[matched.id] = reconcileExcelPrice(row.price, orig);
        continue;
      }
      const central = findCentralByDrugId(row.drug_id);
      if (central) {
        nextNew.push({
          tempId: tempIdCounter--,
          drug_id: central.drug_id,
          drug_name: central.drug_name || row.drug_name,
          drug_number: central.drug_number || row.drug_number,
          unit_name: central.unit_name ?? '',
          price: row.price,
        });
        continue;
      }
      nextUnmatched.push({
        drug_name: row.drug_name || `ID:${row.drug_id}`,
        drug_number: row.drug_number || String(row.drug_id),
        market_price: 0,
        price: row.price,
        unit_name: '',
      });
    }

    localPriceMap.value = nextLocal;
    excelTouchedIds.value = nextTouched;
    newRows.value = nextNew;
    unmatchedRows.value = nextUnmatched;
    parsedReady.value = true;

    const changedN = storeItems.value.filter((item) => {
      if (!nextTouched.has(item.id)) {
        return false;
      }
      const orig = initialPriceMap.value[item.id];
      const cur = nextLocal[item.id];
      return (
        Number(item.has_delivery_warehouse) !== 1 &&
        isFieldChanged(orig, cur)
      );
    }).length;

    if (changedN > 0) {
      filterTab.value = 'changed';
    } else if (nextNew.length > 0) {
      filterTab.value = 'new';
    } else {
      filterTab.value = 'unchanged';
      message.info('未检测到零售价变更');
    }
  } catch {
    message.error('解析失败，请检查 Excel 格式');
  } finally {
    parseLoading.value = false;
    syncModalFooterState();
  }
}

function syncRowFromGrid(row: PriceCompareRow) {
  if (row._isNew) {
    newRows.value = newRows.value.map((item) =>
      item.tempId === row.id ? { ...item, price: row.price } : item,
    );
    return;
  }
  localPriceMap.value = {
    ...localPriceMap.value,
    [row.id]: row.price,
  };
}

async function runBatchApply() {
  const changedExisting = compareRows.value.filter(
    (r) => r._changed && !r._isNew && !r._priceLocked,
  );
  const creates = newRows.value.map((row) => ({
    drug_id: row.drug_id,
    price: row.price,
  }));

  if (changedExisting.length === 0 && creates.length === 0) {
    message.info('没有需要保存的变更');
    return;
  }

  applyLoading.value = true;
  modalApi.setState({ loading: true, confirmLoading: true });
  try {
    const res = await applyStoreBatchPriceApi({
      type: productType.value,
      updates: changedExisting.map((row) => ({
        id: row.id,
        price: parsePriceValue(row.price),
      })),
      creates,
    });
    const updateN = res?.update_count ?? 0;
    const newN = res?.new_count ?? 0;
    message.success(`已更新 ${updateN} 条，新增 ${newN} 条`);
    gridApi.value?.reload?.();
    resetState();
    modalApi.close();
  } catch {
    message.error('批量更新失败');
  } finally {
    applyLoading.value = false;
    modalApi.setState({ loading: false, confirmLoading: false });
    syncModalFooterState();
  }
}
</script>

<template>
  <ModalComp title="批量改价预览（门店仓库）">
    <Transition name="fade-slide" mode="out-in">
      <div
        v-if="!parsedReady"
        class="flex h-[calc(100vh-220px)] w-full flex-col items-center justify-center px-4 sm:px-12 md:px-24"
      >
        <div class="relative w-full max-w-[85vw] xl:max-w-screen-xl">
          <div
            v-if="loadingAll || parseLoading"
            class="absolute inset-0 z-50 flex items-center justify-center rounded-3xl bg-white/70 backdrop-blur-[2px] dark:bg-gray-900/70"
          >
            <Spin size="large" tip="解析数据中..." />
          </div>
          <div class="flex w-full flex-col items-center gap-8">
            <div class="text-center">
              <h2 class="mb-3 text-3xl font-medium text-gray-800 dark:text-gray-100">
                导入门店零售价
              </h2>
              <p class="text-lg text-gray-500">
                上传 Excel 预览有变动 / 无变动 / 待新增后再确认；单位列会被忽略
              </p>
            </div>
            <div class="batch-price-upload batch-price-upload--hero w-full transition-transform duration-300 hover:scale-[1.01]">
              <UploadDragger
                v-model:file-list="fileList"
                :before-upload="() => false"
                :max-count="1"
                :show-upload-list="false"
                accept=".xlsx,.xls"
                name="file"
                @change="handleFileChange"
              >
                <div class="flex min-h-[420px] w-full flex-col items-center justify-center py-16">
                  <div class="mb-8 rounded-full bg-blue-50 p-6 text-blue-500 ring-8 ring-blue-50/50 dark:bg-blue-900/30 dark:ring-blue-900/20">
                    <svg class="h-14 w-14" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
                    </svg>
                  </div>
                  <p class="text-2xl font-medium text-gray-700 dark:text-gray-200">
                    点击或将 Excel 文件拖拽至此
                  </p>
                  <p class="mt-4 text-lg text-gray-400">
                    纯前端本地解析，确认后才提交服务器
                  </p>
                </div>
              </UploadDragger>
            </div>
            <div class="flex min-h-[28px] items-center gap-2">
              <p
                v-if="selectedFile"
                class="text-lg font-medium text-blue-600 dark:text-blue-400"
              >
                已选文件：{{ selectedFile.name }}
              </p>
            </div>
            <Button
              :disabled="loadingAll || storeItems.length === 0"
              size="large"
              type="dashed"
              class="!rounded-full px-10 py-2 shadow-sm"
              @click="downloadTemplate"
            >
              下载标准模板
            </Button>
          </div>
        </div>
      </div>

      <div
        v-else
        class="flex w-full flex-col gap-4"
        style="height: calc(100vh - 200px)"
      >
        <div
          class="flex shrink-0 flex-col gap-4 rounded-xl border border-gray-100 bg-gray-50/50 p-4 shadow-sm backdrop-blur-sm dark:border-gray-800 dark:bg-gray-900/30 sm:flex-row sm:items-center sm:justify-between"
        >
          <div class="flex items-center gap-4">
            <div class="batch-price-upload batch-price-upload--mini">
              <UploadDragger
                v-model:file-list="fileList"
                :before-upload="() => false"
                :max-count="1"
                :show-upload-list="false"
                accept=".xlsx,.xls"
                name="file"
                class="!rounded-lg"
                @change="handleFileChange"
              >
                <div class="flex items-center gap-2 px-4 py-1.5 text-sm font-medium text-gray-600 dark:text-gray-300">
                  重新上传
                </div>
              </UploadDragger>
            </div>
            <div v-if="selectedFile" class="text-sm text-gray-500">
              当前:
              <span class="font-medium text-gray-700 dark:text-gray-200">
                {{ selectedFile.name }}
              </span>
            </div>
          </div>
          <div class="flex items-center gap-3">
            <Button size="small" type="link" @click="downloadTemplate">
              下载模板
            </Button>
            <div class="h-4 w-px bg-gray-300 dark:bg-gray-700"></div>
            <Button
              v-if="selectedFile"
              :disabled="parseLoading"
              size="small"
              type="link"
              @click="runParse"
            >
              重新解析
            </Button>
          </div>
        </div>

        <div
          class="relative flex min-h-0 flex-1 flex-col overflow-hidden rounded-xl bg-white shadow-sm ring-1 ring-gray-100 dark:bg-gray-900 dark:ring-gray-800"
        >
          <div
            v-if="loadingAll || parseLoading"
            class="absolute inset-0 z-50 flex items-center justify-center bg-white/70 backdrop-blur-[2px] dark:bg-gray-900/70"
          >
            <Spin size="large" tip="数据处理中..." />
          </div>
          <div class="custom-scrollbar relative w-full flex-1 overflow-y-auto p-4">
            <PriceCompareCardGrid
              v-model:filter-tab="filterTab"
              :rows="compareRows"
              :unmatched-rows="unmatchedRows"
              :show-unit="false"
              :show-market-price="false"
              :show-row-save="false"
              :show-price-locked-tab="true"
              price-label="零售价"
              @cell-change="syncRowFromGrid"
            />
          </div>
        </div>
      </div>
    </Transition>

    <template v-if="parsedReady" #prepend-footer>
      <div class="flex items-center gap-2">
        <Tag
          :color="changedCount > 0 ? 'warning' : 'default'"
          class="!rounded-md shadow-sm"
        >
          待保存修改 {{ changedCount }}
        </Tag>
        <Tag
          :color="newCount > 0 ? 'processing' : 'default'"
          class="!rounded-md shadow-sm"
        >
          待新增数据 {{ newCount }}
        </Tag>
        <span v-if="parseHint" class="ml-2 text-xs text-gray-400">
          {{ parseHint }}
        </span>
      </div>
    </template>
  </ModalComp>
</template>

<style scoped>
.fade-slide-enter-active,
.fade-slide-leave-active {
  transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.fade-slide-enter-from {
  opacity: 0;
  transform: translateY(20px);
}
.fade-slide-leave-to {
  opacity: 0;
  transform: translateY(-20px);
}
.custom-scrollbar::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
  background-color: rgba(156, 163, 175, 0.5);
  border-radius: 9999px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
  background-color: rgba(107, 114, 128, 0.8);
}
.custom-scrollbar::-webkit-scrollbar-track {
  background: transparent;
}
.batch-price-upload {
  width: 100%;
  display: block;
}
.batch-price-upload :deep(.ant-upload-wrapper),
.batch-price-upload :deep(.ant-upload-list),
.batch-price-upload :deep(.ant-upload-drag) {
  width: 100% !important;
  display: block;
}
.batch-price-upload--hero :deep(.ant-upload-drag) {
  background: rgba(249, 250, 251, 0.5);
  border: 2px dashed #d1d5db;
  border-radius: 1.5rem;
  transition: all 0.3s ease;
}
.batch-price-upload--hero :deep(.ant-upload-drag:hover) {
  border-color: #3b82f6;
  background: rgba(239, 246, 255, 0.5);
}
.batch-price-upload--mini :deep(.ant-upload-drag) {
  border: 1px dashed #d1d5db;
  border-radius: 0.5rem;
  background: transparent;
  transition: all 0.2s;
}
.batch-price-upload--mini :deep(.ant-upload-drag:hover) {
  border-color: #3b82f6;
  background: rgba(239, 246, 255, 0.5);
}
</style>
