<script lang="ts" setup>
import type { UploadFile } from 'ant-design-vue';

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

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

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

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

import {
  applyBatchPriceApi,
  getAllForBatchPriceApi,
  type WarehouseBatchPriceItem,
} from '../api';
import { exportWarehousePriceExcel } from '../utils/exportWarehousePriceExcel';
import { parseWarehousePriceExcelBuffer } from '../utils/parseWarehousePriceExcel';
import {
  getPriceDelta,
  isFieldChanged,
  isMarketGtPrice,
  isPriceChanged,
  normalizePricePair,
  parsePriceValue,
  type PricePair,
} from '../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 savingRowId = ref<number | null>(null);
const parseHint = ref('');
const parsedReady = ref(false);
const filterTab = ref<PriceCompareFilterTab>('all');

const warehouseItems = ref<WarehouseBatchPriceItem[]>([]);
const initialSnapshot = ref<Record<number, PricePair>>({});
const localPrices = ref<Record<number, PricePair>>({});
const newRows = ref<
  Array<{
    tempId: number;
    drug_name: string;
    drug_number: string;
    market_price: number;
    price: number;
  }>
>([]);
const unmatchedRows = ref<UnmatchedRow[]>([]);
const excelTouchedIds = ref<Set<number>>(new Set());
let tempIdCounter = -1;
let loadAllPromise: Promise<void> | null = null;

function buildSnapshot(items: WarehouseBatchPriceItem[]) {
  const init: Record<number, PricePair> = {};
  const loc: Record<number, PricePair> = {};
  for (const item of items) {
    const pair = normalizePricePair({
      market_price: parsePriceValue(item.market_price),
      price: parsePriceValue(item.price),
    });
    init[item.id] = { ...pair };
    loc[item.id] = { ...pair };
  }
  initialSnapshot.value = init;
  localPrices.value = loc;
}

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

function buildCompareRow(
  item: {
    id: number;
    drug_id?: number;
    drug_name: string;
    drug_number: string;
    pinyin_simple?: string;
  },
  current: PricePair,
  orig: PricePair,
  isNew: boolean,
): PriceCompareRow {
  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: orig.market_price,
    orig_price: orig.price,
    market_price: current.market_price,
    price: current.price,
    _marketDelta: getPriceDelta(orig.market_price, current.market_price),
    _priceDelta: getPriceDelta(orig.price, current.price),
    _marketChanged: isFieldChanged(orig.market_price, current.market_price),
    _priceChanged: isFieldChanged(orig.price, current.price),
    _changed: isNew || isPriceChanged(orig, current),
    _marketGtPrice: isMarketGtPrice(current),
    _isNew: isNew,
    _fromExcel: isNew ? true : undefined,
  };
}

const compareRows = computed<PriceCompareRow[]>(() => {
  const existingRows: PriceCompareRow[] = warehouseItems.value
    .filter((item) => excelTouchedIds.value.has(item.id))
    .map((item) => {
      const current = localPrices.value[item.id] ?? normalizePricePair({
        market_price: parsePriceValue(item.market_price),
        price: parsePriceValue(item.price),
      });
      const orig = initialSnapshot.value[item.id] ?? current;
      return buildCompareRow(item, current, orig, false);
    });

  const pendingNewRows: PriceCompareRow[] = newRows.value.map((row) =>
    buildCompareRow(
      {
        id: row.tempId,
        drug_name: row.drug_name,
        drug_number: row.drug_number,
      },
      { market_price: row.market_price, price: row.price },
      { market_price: 0, price: 0 },
      true,
    ),
  );

  return [...existingRows, ...pendingNewRows];
});

const changedCount = computed(
  () => compareRows.value.filter((r) => r._changed).length,
);
const newCount = computed(() => newRows.value.length);

function restoreLocalPricesFromSnapshot() {
  const loc: Record<number, PricePair> = {};
  for (const [id, pair] of Object.entries(initialSnapshot.value)) {
    loc[Number(id)] = { ...pair };
  }
  localPrices.value = loc;
}

function clearParseResult() {
  newRows.value = [];
  unmatchedRows.value = [];
  excelTouchedIds.value = new Set();
  parseHint.value = '';
  parsedReady.value = false;
  restoreLocalPricesFromSnapshot();
}

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 getAllForBatchPriceApi({ type: productType.value });
      warehouseItems.value = res?.items ?? [];
      buildSnapshot(warehouseItems.value);
    } catch {
      message.error('加载仓库药品失败');
      warehouseItems.value = [];
      initialSnapshot.value = {};
      localPrices.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();
  }
}

async function downloadTemplate() {
  if (warehouseItems.value.length === 0) {
    message.warning('暂无药品数据');
    return;
  }
  try {
    const buffer = await exportWarehousePriceExcel(warehouseItems.value);
    downloadByData(
      buffer,
      '萧康云医-中药仓库改价模板.xlsx',
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    );
    message.success('模板已下载');
  } catch {
    message.error('模板下载失败');
  }
}

function findWarehouseByNumber(number: string) {
  const n = number.trim();
  if (!n) {
    return undefined;
  }
  return warehouseItems.value.find((d) => d.drug_number === n);
}

function findWarehouseByName(name: string) {
  const n = name.trim();
  if (!n) {
    return undefined;
  }
  return warehouseItems.value.find((d) => d.drug_name === n);
}

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 } =
      await parseWarehousePriceExcelBuffer(buf);

    if (items.length === 0) {
      message.warning('未解析到有效的价格行');
      return;
    }

    parseHint.value =
      rowCount > 5000
        ? `共 ${rowCount} 行，已按上限处理前 5000 行。`
        : `共解析 ${items.length} 条有效行。`;

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

    for (const row of items) {
      let matched = findWarehouseByNumber(row.drug_number);
      if (!matched) {
        matched = findWarehouseByName(row.drug_name);
      }

      if (matched) {
        nextTouched.add(matched.id);
        const orig = initialSnapshot.value[matched.id];
        nextLocal[matched.id] = {
          market_price: row.market_price,
          price: row.price,
        };
        if (
          orig &&
          isPriceChanged(orig, {
            market_price: row.market_price,
            price: row.price,
          })
        ) {
          changedExistingCount += 1;
        }
        continue;
      }

      if (row.drug_name || row.drug_number) {
        tempIdCounter -= 1;
        nextNew.push({
          tempId: tempIdCounter,
          drug_name: row.drug_name,
          drug_number: row.drug_number,
          market_price: row.market_price,
          price: row.price,
        });
      } else {
        nextUnmatched.push(row);
      }
    }

    localPrices.value = nextLocal;
    newRows.value = nextNew;
    unmatchedRows.value = nextUnmatched;
    excelTouchedIds.value = nextTouched;

    if (changedExistingCount > 0) {
      filterTab.value = 'changed';
    } else if (nextNew.length > 0) {
      filterTab.value = 'new';
    } else {
      filterTab.value = 'all';
    }

    if (invalidCount > 0) {
      message.info(`Excel 中有 ${invalidCount} 行因缺少名称或编码被跳过`);
    }
    if (nextNew.length > 0) {
      message.info(`有 ${nextNew.length} 条药品将自动新增入库`);
    }

    parsedReady.value = true;
  } catch {
    message.error('解析失败');
  } finally {
    parseLoading.value = false;
    syncModalFooterState();
  }
}

function syncRowFromGrid(row: PriceCompareRow) {
  const normalized = normalizePricePair({
    market_price: parsePriceValue(row.market_price),
    price: parsePriceValue(row.price),
  });
  if (row._isNew) {
    newRows.value = newRows.value.map((item) =>
      item.tempId === row.id
        ? {
          ...item,
          market_price: normalized.market_price,
          price: normalized.price,
        }
        : item,
    );
    return;
  }
  localPrices.value = {
    ...localPrices.value,
    [row.id]: normalized,
  };
}

function confirmIfMarketGtPrice(rows: PriceCompareRow[]): Promise<boolean> {
  const hasWarning = rows.some((r) => r._marketGtPrice);
  if (!hasWarning) {
    return Promise.resolve(true);
  }
  return new Promise((resolve) => {
    Modal.confirm({
      title: '供货价高于售价',
      content: '存在供货价高于售价的药品，确认继续保存吗？',
      onOk: () => resolve(true),
      onCancel: () => resolve(false),
    });
  });
}

async function handleRowSave(row: PriceCompareRow) {
  syncRowFromGrid(row);
  const ok = await confirmIfMarketGtPrice([row]);
  if (!ok) {
    return;
  }

  const saved = normalizePricePair({
    market_price: parsePriceValue(row.market_price),
    price: parsePriceValue(row.price),
  });

  savingRowId.value = row.id;
  try {
    if (row._isNew) {
      await applyBatchPriceApi({
        type: productType.value,
        updates: [],
        creates: [
          {
            drug_name: row.drug_name,
            drug_number: row.drug_number,
            market_price: saved.market_price,
            price: saved.price,
          },
        ],
        notify: false,
      });
      newRows.value = newRows.value.filter((item) => item.tempId !== row.id);
    } else {
      await applyBatchPriceApi({
        type: productType.value,
        updates: [
          {
            id: row.id,
            market_price: saved.market_price,
            price: saved.price,
          },
        ],
        creates: [],
        notify: false,
      });
      initialSnapshot.value = {
        ...initialSnapshot.value,
        [row.id]: { ...saved },
      };
      localPrices.value = {
        ...localPrices.value,
        [row.id]: { ...saved },
      };
      warehouseItems.value = warehouseItems.value.map((item) =>
        item.id === row.id
          ? { ...item, market_price: saved.market_price, price: saved.price }
          : item,
      );
    }
    message.success('保存成功，订阅门店价格正在后台同步');
    syncModalFooterState();
    gridApi.value?.reload?.();
  } catch {
    message.error('保存失败');
  } finally {
    savingRowId.value = null;
  }
}

async function runBatchApply() {
  const changedExisting = compareRows.value.filter(
    (r) => r._changed && !r._isNew,
  );
  const creates = newRows.value.map((row) => {
    const prices = normalizePricePair({
      market_price: row.market_price,
      price: row.price,
    });
    return {
      drug_name: row.drug_name,
      drug_number: row.drug_number,
      market_price: prices.market_price,
      price: prices.price,
    };
  });

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

  const ok = await confirmIfMarketGtPrice([
    ...changedExisting,
    ...creates.map((c) =>
      buildCompareRow(
        {
          id: 0,
          drug_name: c.drug_name,
          drug_number: c.drug_number,
        },
        { market_price: c.market_price, price: c.price },
        { market_price: 0, price: 0 },
        true,
      ),
    ),
  ]);
  if (!ok) {
    return;
  }

  applyLoading.value = true;
  modalApi.setState({ loading: true, confirmLoading: true });
  try {
    const res = await applyBatchPriceApi({
      type: productType.value,
      updates: changedExisting.map((row) => {
        const prices = normalizePricePair({
          market_price: parsePriceValue(row.market_price),
          price: parsePriceValue(row.price),
        });
        return {
          id: row.id,
          market_price: prices.market_price,
          price: prices.price,
        };
      }),
      creates,
      notify: true,
    });
    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="批量改价预览（中药总仓库）">
    <!-- Wrap main content in transition for smooth View swapping -->
    <Transition name="fade-slide" mode="out-in">

      <!-- State A: Hero Upload View -->
      <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="w-full max-w-[85vw] xl:max-w-screen-xl relative">
          <!-- 核心隔离：State A 也采用遮罩加载，彻底抛弃包裹式用法 -->
          <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 rounded-3xl"
          >
            <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 || warehouseItems.length === 0"
              size="large"
              type="dashed"
              class="!rounded-full px-10 py-2 shadow-sm"
              @click="downloadTemplate"
            >
              下载标准模板
            </Button>
          </div>
        </div>
      </div>

      <!-- State B: Result Grid View -->
      <div v-else class="flex flex-col gap-4 w-full" style="height: calc(100vh - 200px);">

        <!-- Header Info Bar -->
        <div class="flex 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 shrink-0">
          <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">
                  <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
                  </svg>
                  重新上传
                </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>

        <!-- Main Compare Grid Container (原生 Flex，无第三方组件污染) -->
        <div class="relative flex 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 min-h-0">

          <!-- 核心隔离：绝对定位的 Loading 遮罩层，完美规避 Spin 破坏 Flex 布局高度的深坑 -->
          <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="flex-1 w-full overflow-y-auto p-4 custom-scrollbar relative">
            <PriceCompareCardGrid
              v-model:filter-tab="filterTab"
              :rows="compareRows"
              :saving-row-id="savingRowId"
              :unmatched-rows="unmatchedRows"
              @cell-change="syncRowFromGrid"
              @row-save="handleRowSave"
            />
          </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>
/* View Transition */
.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;
}


/* Upload Dragger overrides for modern aesthetic & FORCE FULL WIDTH */
.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;
}

/* Hero Dragger Specific Styles */
.batch-price-upload--hero :deep(.ant-upload-drag) {
  background: rgba(249, 250, 251, 0.5); /* subtle gray */
  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); /* subtle blue */
}
:deep(.dark .batch-price-upload--hero .ant-upload-drag) {
  background: rgba(31, 41, 55, 0.3);
  border-color: #4b5563;
}
:deep(.dark .batch-price-upload--hero .ant-upload-drag:hover) {
  border-color: #3b82f6;
  background: rgba(29, 78, 216, 0.1);
}

/* Mini Dragger (Header state) */
.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);
}
:deep(.dark .batch-price-upload--mini .ant-upload-drag) {
  border-color: #4b5563;
}
:deep(.dark .batch-price-upload--mini .ant-upload-drag:hover) {
  border-color: #3b82f6;
  background: rgba(29, 78, 216, 0.1);
}
</style>
