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

import { computed, ref } from 'vue';

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

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

import {
  applyBatchDrugNumberApi,
  getAllDrugNumbersForBatchApi,
  previewBatchDrugNumberApi,
} from '../api';
import {
  isDrugNumberChanged,
  parseDrugNumberExcelBuffer,
} from '../utils/parseDrugNumberExcel';

type MatchedRow = {
  id: number;
  drug_name: string;
  drug_number_old: string | null;
  drug_number_new: string;
};

type MatchedRowView = MatchedRow & { _changed: boolean };

type AllDrugItem = {
  id: number;
  drug_name: string;
  drug_number: null | string;
  pinyin_simple?: string | null;
};
type CardStatusFilter = 'all' | 'changed' | 'unchanged';

const gridApi = ref();
const fileList = ref<UploadFile[]>([]);
const selectedFile = ref<File | null>(null);
const matched = ref<MatchedRow[]>([]);
const notFound = ref<{ drug_name: string; drug_number: string }[]>([]);
const invalidRows = ref<{ row: number; drug_name: string; drug_number: string }[]>(
  [],
);
const previewLoading = ref(false);
const applyLoading = ref(false);
const parseHint = ref('');

const allDrugs = ref<AllDrugItem[]>([]);
/** 打开弹窗时服务端原始编号，用于判断是否有变更 */
const initialNumbers = ref<Record<number, string>>({});
/** 当前编辑中的编号（卡片可改；导入解析会写入） */
const localNumbers = ref<Record<number, string>>({});
const loadingAll = ref(false);
const cardFilter = ref('');
const cardStatusFilter = ref<CardStatusFilter>('all');

const matchedView = computed<MatchedRowView[]>(() =>
  matched.value.map((row) => {
    const newNum =
      localNumbers.value[row.id] ?? String(row.drug_number_new ?? '');
    return {
      ...row,
      drug_number_new: newNum,
      _changed: isDrugNumberChanged(initialNumbers.value[row.id], newNum),
    };
  }),
);

const changedCount = computed(
  () => matchedView.value.filter((r) => r._changed).length,
);
const cardStatusCounts = computed(() => {
  const all = allDrugs.value.length;
  const changed = allDrugs.value.filter((d) => cardIsChanged(d)).length;
  return {
    all,
    changed,
    unchanged: all - changed,
  };
});

/** 含卡片直接编辑：任意与初始值不同的条数 */
const totalPendingChanges = computed(() =>
  allDrugs.value.filter((d) =>
    isDrugNumberChanged(
      initialNumbers.value[d.id],
      localNumbers.value[d.id],
    ),
  ).length,
);

function cardIsChanged(d: AllDrugItem) {
  return isDrugNumberChanged(
    initialNumbers.value[d.id],
    localNumbers.value[d.id],
  );
}

function setLocalNumber(id: number, v: string) {
  localNumbers.value = { ...localNumbers.value, [id]: v ?? '' };
}

const filteredCards = computed(() => {
  const q = cardFilter.value.trim().toLowerCase();
  const keywordFiltered = allDrugs.value.filter((d) => {
    const nameOk = d.drug_name.toLowerCase().includes(q);
    const py = (d.pinyin_simple ?? '').toLowerCase();
    const pyOk = py.includes(q);
    return q ? nameOk || pyOk : true;
  });
  if (cardStatusFilter.value === 'changed') {
    return keywordFiltered.filter((d) => cardIsChanged(d));
  }
  if (cardStatusFilter.value === 'unchanged') {
    return keywordFiltered.filter((d) => !cardIsChanged(d));
  }
  return keywordFiltered;
});

const columns = [
  { title: 'ID', dataIndex: 'id', key: 'id', width: 72 },
  { title: '药品名称', dataIndex: 'drug_name', key: 'drug_name', ellipsis: true },
  {
    title: '当前编号',
    dataIndex: 'drug_number_old',
    key: 'drug_number_old',
    width: 120,
  },
  {
    title: '新编号',
    dataIndex: 'drug_number_new',
    key: 'drug_number_new',
    width: 120,
  },
  {
    title: '状态',
    dataIndex: '_changed',
    key: 'status',
    width: 88,
  },
];

function resetState() {
  selectedFile.value = null;
  fileList.value = [];
  matched.value = [];
  notFound.value = [];
  invalidRows.value = [];
  parseHint.value = '';
  cardFilter.value = '';
  cardStatusFilter.value = 'all';
  allDrugs.value = [];
  initialNumbers.value = {};
  localNumbers.value = {};
}

const [ModalComp, modalApi] = useVbenModal({
  fullscreenButton: false,
  draggable: true,
  showConfirmButton: false,
  class: '!w-[min(960px,92vw)]',
  onCancel() {
    resetState();
    modalApi.close();
  },
  onOpenChange(isOpen: boolean) {
    gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
    if (isOpen) {
      loadAllDrugs();
    } else {
      resetState();
    }
  },
});

async function loadAllDrugs() {
  loadingAll.value = true;
  try {
    const res = await getAllDrugNumbersForBatchApi();
    const items = res?.items ?? [];
    allDrugs.value = items;
    const init: Record<number, string> = {};
    const loc: Record<number, string> = {};
    for (const d of items) {
      const s = d.drug_number ?? '';
      init[d.id] = s;
      loc[d.id] = s;
    }
    initialNumbers.value = init;
    localNumbers.value = loc;
  } catch {
    message.error('加载中药列表失败');
    allDrugs.value = [];
    initialNumbers.value = {};
    localNumbers.value = {};
  } finally {
    loadingAll.value = false;
  }
}

const handleFileChange = (info: { file: UploadFile }) => {
  const { file } = info;
  if (file.status === 'removed') {
    selectedFile.value = null;
    matched.value = [];
    notFound.value = [];
    invalidRows.value = [];
    parseHint.value = '';
    return;
  }
  const raw = file.originFileObj ?? file;
  if (raw instanceof File) {
    selectedFile.value = raw;
  }
};

function rowClassName(record: MatchedRowView) {
  return record._changed ? 'drug-batch-row-changed' : 'drug-batch-row-unchanged';
}

async function runPreview() {
  if (!selectedFile.value) {
    message.warning('请先选择 Excel 文件');
    return;
  }
  previewLoading.value = true;
  try {
    const buf = await selectedFile.value.arrayBuffer();
    const { items, invalidCount, rowCount } = parseDrugNumberExcelBuffer(buf);
    if (items.length === 0) {
      message.warning('未解析到有效的「药品编号」与「药品名称」行');
      matched.value = [];
      notFound.value = [];
      invalidRows.value = [];
      parseHint.value = '';
      return;
    }

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

    const res = await previewBatchDrugNumberApi({ items });
    matched.value = (res.matched || []) as MatchedRow[];
    notFound.value = res.not_found || [];
    invalidRows.value = res.invalid || [];
    for (const m of matched.value) {
      const v = String(m.drug_number_new ?? '');
      localNumbers.value = { ...localNumbers.value, [m.id]: v };
    }
    if (invalidCount > 0) {
      message.info(`Excel 中有 ${invalidCount} 行因缺少编号或名称被跳过`);
    }
    if ((notFound.value?.length || 0) > 0) {
      Modal.warning({
        title: '部分药品名称在系统中未找到',
        content: `未匹配 ${notFound.value.length} 条，请核对药名是否与后台完全一致。`,
      });
    }
  } catch {
    message.error('预览失败');
  } finally {
    previewLoading.value = false;
  }
}

async function runApply() {
  const items = allDrugs.value
    .filter((d) =>
      isDrugNumberChanged(
        initialNumbers.value[d.id],
        localNumbers.value[d.id],
      ),
    )
    .map((d) => ({
      id: d.id,
      drug_number: String(localNumbers.value[d.id] ?? ''),
    }));
  if (items.length === 0) {
    message.info('没有需要更新的编号（与打开弹窗时一致）');
    return;
  }

  applyLoading.value = true;
  modalApi.setState({ loading: true });
  try {
    const res = await applyBatchDrugNumberApi({ items });
    const n = res?.updated_count ?? res?.updatedCount ?? 0;
    message.success(`已更新 ${n} 条记录`);
    gridApi.value?.reload();
    resetState();
    modalApi.close();
  } catch {
    message.error('更新失败');
  } finally {
    applyLoading.value = false;
    modalApi.setState({ loading: false });
  }
}
</script>

<template>
  <ModalComp title="批量更新药品编码">
    <div class="mb-3 text-sm text-gray-600 dark:text-gray-300">
      下方卡片可直接修改编号；亦可使用 Excel：首列表头为「药品编号/药品编码」、第二列「药品名称」，或与之一致的列顺序。仅更新编号与更新时间。
    </div>

    <Spin :spinning="loadingAll">
      <div class="mb-1 text-sm font-medium text-gray-800 dark:text-gray-100">
        当前中药参考
        <Tag>{{ filteredCards.length }}</Tag>
        条
      </div>
      <Input
        v-model:value="cardFilter"
        allow-clear
        class="mb-2"
        placeholder="按药品名称或拼音首拼筛选"
      />
      <Segmented
        v-model:value="cardStatusFilter"
        class="mb-2"
        :options="[
          { label: `全部 ${cardStatusCounts.all}`, value: 'all' },
          { label: `有变动 ${cardStatusCounts.changed}`, value: 'changed' },
          {
            label: `无变动 ${cardStatusCounts.unchanged}`,
            value: 'unchanged',
          },
        ]"
      />
      <div
        class="mb-4 grid max-h-56 grid-cols-2 gap-2 overflow-y-auto sm:grid-cols-3 md:grid-cols-4"
      >
        <Card
          v-for="d in filteredCards"
          :key="d.id"
          :class="[
            '!mb-0 shadow-sm transition-colors',
            cardIsChanged(d)
              ? '!border-amber-400 bg-amber-50 dark:!border-amber-500/70 dark:bg-amber-950/45'
              : '',
          ]"
          size="small"
        >
          <div
            class="truncate text-sm font-medium text-gray-900 dark:text-gray-100"
            :title="d.drug_name"
          >
            {{ d.drug_name }}
          </div>
          <Input
            class="mt-1"
            placeholder="药品编号"
            size="small"
            :value="localNumbers[d.id] ?? ''"
            @update:value="(v: string) => setLocalNumber(d.id, v)"
          />
        </Card>
      </div>
    </Spin>

    <UploadDragger
      v-model:file-list="fileList"
      :before-upload="() => false"
      :max-count="1"
      accept=".xlsx,.xls"
      name="file"
      @change="handleFileChange"
    >
      <p class="ant-upload-text">点击或拖拽选择 Excel</p>
      <p class="ant-upload-hint">选择后点击「导入并解析」从服务器比对当前编号</p>
    </UploadDragger>

    <div class="mt-3 flex flex-wrap gap-2">
      <Button
        :loading="previewLoading"
        type="primary"
        @click="runPreview"
      >
        导入并解析
      </Button>
      <Button
        :disabled="totalPendingChanges === 0"
        :loading="applyLoading"
        danger
        type="primary"
        @click="runApply"
      >
        确认更新到数据库
      </Button>
    </div>

    <p v-if="parseHint" class="mt-2 text-xs text-gray-500 dark:text-gray-400">
      {{ parseHint }}
    </p>

    <div class="mt-2 text-sm text-gray-600 dark:text-gray-300">
      待保存变更：
      <Tag :color="totalPendingChanges > 0 ? 'orange' : 'default'">{{
        totalPendingChanges
      }}</Tag>
      条（含卡片修改与导入）
    </div>

    <div v-if="matched.length > 0" class="mt-4">
      <div class="mb-2 text-sm font-medium text-gray-900 dark:text-gray-100">
        本次 Excel 匹配
        <Tag color="blue">{{ matched.length }}</Tag>
        条，其中
        <Tag v-if="changedCount > 0" color="orange">{{ changedCount }}</Tag>
        <Tag v-else color="default">0</Tag>
        条与初始编号不同
      </div>
      <Table
        :columns="columns"
        :data-source="matchedView"
        :pagination="{ pageSize: 8 }"
        :row-class-name="(record: MatchedRowView) => rowClassName(record)"
        :row-key="(row: MatchedRowView, index: number) => `${row.id}-${index}`"
        :scroll="{ y: 280 }"
        size="small"
      >
        <template #bodyCell="{ column, record }">
          <template v-if="column.key === 'status'">
            <Tag v-if="record._changed" color="orange">已变更</Tag>
            <Tag v-else color="default">无变化</Tag>
          </template>
        </template>
      </Table>
    </div>

    <div v-if="notFound.length > 0" class="mt-4">
      <div class="mb-1 text-sm font-medium text-orange-600 dark:text-orange-400">
        未找到对应中药（{{ notFound.length }}）
      </div>
      <div
        class="max-h-32 overflow-auto rounded border border-orange-100 bg-orange-50 p-2 text-xs text-gray-800 dark:border-orange-900/60 dark:bg-orange-950/40 dark:text-gray-200"
      >
        <div v-for="(row, i) in notFound.slice(0, 50)" :key="i">
          {{ row.drug_name }} — {{ row.drug_number }}
        </div>
        <div v-if="notFound.length > 50">… 其余 {{ notFound.length - 50 }} 条略</div>
      </div>
    </div>

    <div
      v-if="invalidRows.length > 0"
      class="mt-2 text-xs text-gray-500 dark:text-gray-400"
    >
      服务端标记无效行：{{ invalidRows.length }} 条
    </div>
  </ModalComp>
</template>

<style scoped>
:deep(.drug-batch-row-changed) > td {
  background-color: rgb(254 252 232) !important;
}

:deep(.dark .drug-batch-row-changed) > td {
  background-color: rgb(69 26 3 / 0.45) !important;
}

:deep(.drug-batch-row-unchanged) > td {
  opacity: 0.85;
}

:deep(.dark .drug-batch-row-unchanged) > td {
  opacity: 0.92;
}
</style>
