<template>
  <div class="page" :class="{ 'shipment-page--desktop': isDesktop }" @click="closeShipmentContextMenu">
    <div v-if="study.currentStudy" class="page-inner">
      <!-- ==================== 表格卡片（含筛选栏） ==================== -->
      <div class="table-card">
        <div class="table-card-toolbar">
          <div class="toolbar-filters">
            <div class="filter-item">
              <span class="filter-label">{{ TEXT.common.fields.site }}</span>
              <el-select v-model="filters.center_id" clearable :placeholder="TEXT.common.placeholders.select" class="filter-select">
                <el-option
                  v-for="site in sites"
                  :key="site.id"
                  :label="site.name || TEXT.common.fallback"
                  :value="site.id"
                />
              </el-select>
            </div>
            <div class="filter-item">
              <span class="filter-label">{{ TEXT.common.fields.direction }}</span>
              <el-select v-model="filters.direction" clearable :placeholder="TEXT.common.placeholders.select" class="filter-select">
                <el-option :label="TEXT.enums.shipmentDirection.SEND" value="SEND" />
                <el-option :label="TEXT.enums.shipmentDirection.RETURN" value="RETURN" />
              </el-select>
            </div>
            <div class="filter-item">
              <span class="filter-label">{{ TEXT.common.fields.status }}</span>
              <el-select v-model="filters.status" clearable :placeholder="TEXT.common.placeholders.select" class="filter-select">
                <el-option :label="TEXT.enums.shipmentStatus.PENDING" value="PENDING" />
                <el-option :label="TEXT.enums.shipmentStatus.IN_TRANSIT" value="IN_TRANSIT" />
                <el-option :label="TEXT.enums.shipmentStatus.SIGNED" value="SIGNED" />
                <el-option :label="TEXT.enums.shipmentStatus.EXCEPTION" value="EXCEPTION" />
              </el-select>
            </div>
            <div class="filter-actions">
              <el-button type="primary" size="small" @click="handleSearch" class="filter-btn">{{ TEXT.common.actions.search }}</el-button>
              <el-button size="small" @click="resetFilters" class="filter-btn">{{ TEXT.common.actions.reset }}</el-button>
            </div>
          </div>
          <div class="toolbar-right">
            <div v-if="items.length" class="filter-summary">
              {{ items.length }} 条记录
            </div>
            <el-button v-if="canCreate" type="primary" @click="openCreate" class="create-btn">
              <el-icon class="el-icon--left"><Plus /></el-icon>
              {{ TEXT.modules.drugShipments.newTitle }}
            </el-button>
          </div>
        </div>
        <div class="shipment-workbench" :class="{ 'is-desktop': isDesktop }">
          <div
            ref="shipmentTablePaneRef"
            class="shipment-table-pane"
            :tabindex="isDesktop ? 0 : undefined"
            @keydown.enter.prevent="openSelectedShipment"
          >
            <el-table
              v-loading="loading"
              :data="sortedItems"
              class="shipment-table"
              style="width: 100%"
              table-layout="fixed"
              highlight-current-row
              :row-class-name="shipmentRowClass"
              @row-click="handleShipmentRowClick"
              @row-dblclick="openShipmentDetail"
              @row-contextmenu="openShipmentContextMenu"
            >
              <el-table-column prop="site_name" :label="TEXT.common.fields.site" show-overflow-tooltip>
                <template #default="{ row }">
                  <span class="cell-primary cell-nowrap">{{ row.site_name || TEXT.common.fallback }}</span>
                </template>
              </el-table-column>
              <el-table-column prop="direction" :label="TEXT.common.fields.direction">
                <template #default="{ row }">
                  <span :class="['dir-tag', row.direction === 'SEND' ? 'dir-tag--send' : 'dir-tag--return']">
                    {{ displayEnum(TEXT.enums.shipmentDirection, row.direction) }}
                  </span>
                </template>
              </el-table-column>
              <el-table-column prop="ship_date" :label="TEXT.common.fields.shipDate">
                <template #default="{ row }"><span class="cell-nowrap">{{ displayDate(row.ship_date) }}</span></template>
              </el-table-column>
              <el-table-column prop="receive_date" :label="TEXT.common.fields.receiveDate">
                <template #default="{ row }"><span class="cell-nowrap">{{ displayDate(row.receive_date) }}</span></template>
              </el-table-column>
              <el-table-column prop="quantity" :label="TEXT.common.fields.quantity">
                <template #default="{ row }">
                  <span class="cell-mono cell-nowrap">{{ typeof row.quantity === "number" ? row.quantity : TEXT.common.fallback }}</span>
                </template>
              </el-table-column>
              <el-table-column prop="batch_no" :label="TEXT.common.fields.batchNo" show-overflow-tooltip>
                <template #default="{ row }">
                  <span class="cell-mono cell-nowrap">{{ row.batch_no || TEXT.common.fallback }}</span>
                </template>
              </el-table-column>
              <el-table-column prop="status" :label="TEXT.common.fields.status">
                <template #default="{ row }">
                  <span :class="['status-pill', `status-pill--${statusType(row.status)}`]">
                    {{ displayEnum(TEXT.enums.shipmentStatus, row.status) }}
                  </span>
                </template>
              </el-table-column>
              <el-table-column prop="remark" :label="TEXT.common.fields.remark" show-overflow-tooltip>
                <template #default="{ row }">
                  <span class="cell-muted cell-nowrap">{{ row.remark || TEXT.common.fallback }}</span>
                </template>
              </el-table-column>
              <el-table-column v-if="!isDesktop && (canUpdate || canDelete)" :label="TEXT.common.labels.actions" fixed="right">
                <template #default="{ row }">
                  <div class="cell-actions">
                    <el-button v-if="canUpdate" link type="primary" size="small" @click.stop="openEdit(row)">{{ TEXT.common.actions.edit }}</el-button>
                    <el-button
                      v-if="canDelete"
                      link
                      type="danger"
                      size="small"
                      :disabled="isInactiveSite(row.center_id)"
                      @click.stop="remove(row)"
                    >
                      {{ TEXT.common.actions.delete }}
                    </el-button>
                  </div>
                </template>
              </el-table-column>
              <template #empty>
                <div v-if="!loading" class="table-empty">
                  <div class="empty-icon">
                    <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
                  </div>
                  <span>{{ TEXT.modules.drugShipments.empty }}</span>
                </div>
              </template>
            </el-table>
          </div>

          <aside v-if="isDesktop" class="shipment-preview-pane">
            <template v-if="selectedShipment">
              <div class="preview-head">
                <div>
                  <div class="preview-kicker">当前发运</div>
                  <div class="preview-title">{{ selectedShipment.site_name || TEXT.common.fallback }}</div>
                </div>
                <el-button size="small" type="primary" @click="openSelectedShipment">打开详情</el-button>
              </div>
              <dl class="preview-list">
                <dt>{{ TEXT.common.fields.direction }}</dt>
                <dd>{{ displayEnum(TEXT.enums.shipmentDirection, selectedShipment.direction) }}</dd>
                <dt>{{ TEXT.common.fields.status }}</dt>
                <dd>{{ displayEnum(TEXT.enums.shipmentStatus, selectedShipment.status) }}</dd>
                <dt>{{ TEXT.common.fields.shipDate }}</dt>
                <dd>{{ displayDate(selectedShipment.ship_date) }}</dd>
                <dt>{{ TEXT.common.fields.receiveDate }}</dt>
                <dd>{{ displayDate(selectedShipment.receive_date) }}</dd>
                <dt>{{ TEXT.common.fields.quantity }}</dt>
                <dd>{{ typeof selectedShipment.quantity === "number" ? selectedShipment.quantity : TEXT.common.fallback }}</dd>
                <dt>{{ TEXT.common.fields.batchNo }}</dt>
                <dd>{{ selectedShipment.batch_no || TEXT.common.fallback }}</dd>
                <dt>{{ TEXT.common.fields.carrier }}</dt>
                <dd>{{ selectedShipment.carrier || TEXT.common.fallback }}</dd>
                <dt>{{ TEXT.common.fields.trackingNo }}</dt>
                <dd>{{ selectedShipment.tracking_no || TEXT.common.fallback }}</dd>
                <dt>{{ TEXT.common.fields.remark }}</dt>
                <dd>{{ selectedShipment.remark || TEXT.common.fallback }}</dd>
              </dl>
              <div class="preview-actions">
                <el-button
                  v-if="canUpdate"
                  size="small"
                  :disabled="isInactiveSite(selectedShipment.center_id)"
                  @click="openSelectedShipmentEditor"
                >
                  {{ TEXT.common.actions.edit }}
                </el-button>
                <el-button
                  v-if="canDelete"
                  size="small"
                  type="danger"
                  plain
                  :disabled="isInactiveSite(selectedShipment.center_id)"
                  @click="removeSelectedShipment"
                >
                  {{ TEXT.common.actions.delete }}
                </el-button>
              </div>
            </template>
            <div v-else class="preview-empty">
              <span>选择一行查看发运摘要</span>
            </div>
          </aside>
        </div>
      </div>
    </div>
    <StateEmpty v-else :description="TEXT.common.empty.selectProject" />

    <div
      v-if="isDesktop && shipmentContextMenu.visible && selectedShipment"
      class="shipment-context-menu"
      :style="{ left: `${shipmentContextMenu.x}px`, top: `${shipmentContextMenu.y}px` }"
      @click.stop
    >
      <button type="button" @click="openSelectedShipment">打开详情</button>
      <button
        v-if="canUpdate"
        type="button"
        :disabled="isInactiveSite(selectedShipment.center_id)"
        @click="openSelectedShipmentEditor"
      >
        {{ TEXT.common.actions.edit }}
      </button>
      <button
        v-if="canDelete"
        type="button"
        class="danger"
        :disabled="isInactiveSite(selectedShipment.center_id)"
        @click="removeSelectedShipment"
      >
        {{ TEXT.common.actions.delete }}
      </button>
    </div>

    <!-- ==================== 编辑抽屉 ==================== -->
    <el-drawer
      v-if="drawerVisible"
      v-model="drawerVisible"
      direction="rtl"
      size="620px"
      :close-on-click-modal="true"
      :before-close="drawerDirtyGuard.beforeClose"
      :show-close="false"
      class="shipment-editor-drawer"
    >
      <template #header>
        <div class="editor-header">
          <div class="editor-title">{{ editingId ? TEXT.modules.drugShipments.editTitle : TEXT.modules.drugShipments.newTitle }}</div>
        </div>
      </template>

      <el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="shipment-form">
        <div class="form-section">
          <div class="form-section-title">
            <span class="dot dot--blue" />
            {{ TEXT.common.labels.basicInfo }}
          </div>
          <div class="field-grid field-grid--2">
            <el-form-item :label="TEXT.common.fields.site" prop="center_id">
              <el-select v-model="form.center_id" :placeholder="TEXT.common.placeholders.select" class="full-width" :disabled="isFormReadOnly">
                <el-option
                  v-for="site in sites"
                  :key="site.id"
                  :label="site.name || TEXT.common.fallback"
                  :value="site.id"
                  :disabled="!site.is_active"
                />
              </el-select>
            </el-form-item>
            <el-form-item :label="TEXT.common.fields.direction" prop="direction">
              <el-select v-model="form.direction" :placeholder="TEXT.common.placeholders.select" class="full-width" :disabled="isFormReadOnly">
                <el-option :label="TEXT.enums.shipmentDirection.SEND" value="SEND" />
                <el-option :label="TEXT.enums.shipmentDirection.RETURN" value="RETURN" />
              </el-select>
            </el-form-item>
          </div>
          <el-form-item :label="TEXT.common.fields.status" prop="status">
            <el-select v-model="form.status" :placeholder="TEXT.common.placeholders.select" class="full-width" :disabled="isFormReadOnly">
              <el-option :label="TEXT.enums.shipmentStatus.PENDING" value="PENDING" />
              <el-option :label="TEXT.enums.shipmentStatus.IN_TRANSIT" value="IN_TRANSIT" />
              <el-option :label="TEXT.enums.shipmentStatus.SIGNED" value="SIGNED" />
              <el-option :label="TEXT.enums.shipmentStatus.EXCEPTION" value="EXCEPTION" />
            </el-select>
          </el-form-item>
          <div v-if="selectedSiteInactive" class="inactive-hint">
            <span class="hint-icon">!</span>
            <span>中心已停用，当前记录不可编辑</span>
          </div>
        </div>

        <div class="form-section">
          <div class="form-section-title">
            <span class="dot dot--amber" />
            {{ TEXT.modules.drugShipments.recordLabel }}
          </div>
          <div class="field-grid field-grid--2">
            <el-form-item :label="TEXT.common.fields.shipDate" prop="ship_date" :required="requiresShipmentDetails(form.status)">
              <el-date-picker
                v-model="form.ship_date"
                type="date"
                value-format="YYYY-MM-DD"
                :placeholder="TEXT.common.placeholders.select"
                class="full-width"
                :disabled="isFormReadOnly"
              />
            </el-form-item>
            <el-form-item :label="TEXT.common.fields.receiveDate" prop="receive_date" :required="requiresReceiveDate(form.status)">
              <el-date-picker
                v-model="form.receive_date"
                type="date"
                value-format="YYYY-MM-DD"
                :placeholder="TEXT.common.placeholders.select"
                class="full-width"
                :disabled="isFormReadOnly"
              />
            </el-form-item>
          </div>
          <div class="field-grid field-grid--2">
            <el-form-item :label="TEXT.common.fields.quantity" prop="quantity" :required="requiresShipmentDetails(form.status)">
              <el-input-number v-model="form.quantity" :min="0" :controls="false" class="full-width" :disabled="isFormReadOnly" />
            </el-form-item>
            <el-form-item :label="TEXT.common.fields.batchNo" prop="batch_no" :required="requiresShipmentDetails(form.status)">
              <el-input v-model="form.batch_no" :placeholder="TEXT.common.placeholders.input + TEXT.common.fields.batchNo" :disabled="isFormReadOnly" />
            </el-form-item>
          </div>
          <div class="field-grid field-grid--2">
            <el-form-item :label="TEXT.common.fields.carrier" prop="carrier" :required="requiresShipmentDetails(form.status)">
              <el-input v-model="form.carrier" :placeholder="TEXT.common.placeholders.input + TEXT.common.fields.carrier" :disabled="isFormReadOnly" />
            </el-form-item>
            <el-form-item :label="TEXT.common.fields.trackingNo" prop="tracking_no" :required="requiresShipmentDetails(form.status)">
              <el-input v-model="form.tracking_no" :placeholder="TEXT.common.placeholders.input + TEXT.common.fields.trackingNo" :disabled="isFormReadOnly" />
            </el-form-item>
          </div>
        </div>

        <div class="form-section">
          <div class="form-section-title">
            <span class="dot dot--green" />
            {{ TEXT.common.fields.remark }}
          </div>
          <el-form-item prop="remark" :required="requiresRemark(form.status)">
            <el-input
              v-model="form.remark"
              type="textarea"
              :rows="4"
              :placeholder="TEXT.common.placeholders.input + TEXT.common.fields.remark"
              :disabled="isFormReadOnly"
            />
          </el-form-item>
        </div>

        <div class="form-section">
          <div class="form-section-title">
            <span class="dot dot--purple" />
            {{ TEXT.common.labels.attachments }}
          </div>
          <AttachmentList
            ref="attachmentPanelRef"
            :study-id="study.currentStudy?.id || ''"
            entity-type="drug_shipment"
            :entity-id="editingId"
            :mode="'upload'"
            :readonly="isFormReadOnly"
          />
        </div>
      </el-form>

      <template #footer>
        <div class="drawer-footer">
          <el-button @click="drawerVisible = false" class="footer-btn">{{ TEXT.common.actions.cancel }}</el-button>
          <el-button type="primary" :loading="saving" :disabled="isFormReadOnly" @click="saveForm" class="footer-btn footer-btn--primary">{{ TEXT.common.actions.save }}</el-button>
        </div>
      </template>
    </el-drawer>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from "vue";
import { useRouter } from "vue-router";
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
import { useAuthStore } from "../../store/auth";
import { useStudyStore } from "../../store/study";
import {
  createDrugShipment,
  deleteDrugShipment,
  listDrugShipments,
  updateDrugShipment,
} from "../../api/drugShipments";
import { fetchSites } from "../../api/sites";
import AttachmentList from "../../components/attachments/AttachmentList.vue";
import StateEmpty from "../../components/StateEmpty.vue";
import { TEXT } from "../../locales";
import { displayDate, displayEnum } from "../../utils/display";
import { isApiPermissionAllowed } from "../../utils/apiPermissionValue";
import { isSystemAdmin } from "../../utils/roles";
import { useDrawerDirtyGuard } from "../../utils/drawerDirtyGuard";
import { isTauriRuntime } from "../../runtime";

type ShipmentDirection = "SEND" | "RETURN";
type ShipmentStatus = "PENDING" | "IN_TRANSIT" | "SIGNED" | "EXCEPTION";

interface ShipmentRow {
  id: string;
  center_id: string;
  site_name: string;
  direction: ShipmentDirection;
  ship_date: string;
  receive_date: string;
  quantity: number | null;
  batch_no: string;
  carrier: string;
  tracking_no: string;
  status: ShipmentStatus;
  remark: string;
}

type FormModel = Omit<ShipmentRow, "id" | "site_name">;

const study = useStudyStore();
const auth = useAuthStore();
const router = useRouter();
const loading = ref(false);
const saving = ref(false);
const items = ref<ShipmentRow[]>([]);
const sites = ref<any[]>([]);
const drawerVisible = ref(false);
const editingId = ref("");
const formRef = ref<FormInstance>();
const attachmentPanelRef = ref<InstanceType<typeof AttachmentList> | null>(null);
const shipmentTablePaneRef = ref<HTMLElement | null>(null);
const isDesktop = isTauriRuntime();
const selectedShipmentId = ref("");
const shipmentContextMenu = ref({ visible: false, x: 0, y: 0 });
const filters = reactive({
  center_id: study.currentSite?.id || "",
  direction: "" as "" | ShipmentDirection,
  status: "" as "" | ShipmentStatus,
});

const defaultForm: FormModel = {
  center_id: "",
  direction: "SEND",
  ship_date: "",
  receive_date: "",
  quantity: null,
  batch_no: "",
  carrier: "",
  tracking_no: "",
  status: "PENDING",
  remark: "",
};

const form = reactive<FormModel>({ ...defaultForm });
const drawerDirtyGuard = useDrawerDirtyGuard(() => ({
  form,
  attachments: attachmentPanelRef.value?.pendingSnapshot() || [],
}));

const projectRole = computed(() => study.currentStudyRole || (study.currentStudy as any)?.role_in_study || "");
const isAdmin = computed(() => isSystemAdmin(auth.user));
const canCreate = computed(() => isAdmin.value || isApiPermissionAllowed(study.currentPermissions?.[projectRole.value]?.["drug_shipments:create"]));
const canUpdate = computed(() => isAdmin.value || isApiPermissionAllowed(study.currentPermissions?.[projectRole.value]?.["drug_shipments:update"]));
const canDelete = computed(() => isAdmin.value || isApiPermissionAllowed(study.currentPermissions?.[projectRole.value]?.["drug_shipments:delete"]));
const siteActiveMap = computed(() =>
  sites.value.reduce((acc: Record<string, boolean>, site: any) => {
    if (site?.id) acc[site.id] = !!site.is_active;
    return acc;
  }, {})
);
const selectedSiteInactive = computed(() => !!editingId.value && !!form.center_id && siteActiveMap.value[form.center_id] === false);
const isFormReadOnly = computed(() => (editingId.value ? !canUpdate.value : !canCreate.value) || selectedSiteInactive.value);
const sortedItems = computed(() =>
  [...items.value].sort((a, b) => Number(isInactiveSite(a?.center_id)) - Number(isInactiveSite(b?.center_id)))
);
const selectedShipment = computed(() =>
  sortedItems.value.find((item) => item.id === selectedShipmentId.value) || null
);
const requiresShipmentDetails = (status: ShipmentStatus) => status !== "PENDING";
const requiresReceiveDate = (status: ShipmentStatus) => status === "SIGNED";
const requiresRemark = (status: ShipmentStatus) => status === "EXCEPTION";

const validateShipmentDetail = (_rule: unknown, value: string | number | null, callback: (error?: Error) => void) => {
  if (requiresShipmentDetails(form.status) && (value === null || value === undefined || value === "")) {
    callback(new Error(TEXT.common.messages.required));
    return;
  }
  callback();
};

const validateShipmentTextDetail = (_rule: unknown, value: string, callback: (error?: Error) => void) => {
  if (requiresShipmentDetails(form.status) && !value?.trim()) {
    callback(new Error(TEXT.common.messages.required));
    return;
  }
  callback();
};

const validateReceiveDate = (_rule: unknown, value: string, callback: (error?: Error) => void) => {
  if (requiresReceiveDate(form.status) && !value) {
    callback(new Error(TEXT.common.messages.required));
    return;
  }
  callback();
};

const validateRemark = (_rule: unknown, value: string, callback: (error?: Error) => void) => {
  if (requiresRemark(form.status) && !value?.trim()) {
    callback(new Error(TEXT.common.messages.required));
    return;
  }
  callback();
};

const rules: FormRules<FormModel> = {
  center_id: [{ required: true, message: TEXT.common.messages.required, trigger: "change" }],
  direction: [{ required: true, message: TEXT.common.messages.required, trigger: "change" }],
  ship_date: [{ validator: validateShipmentDetail, trigger: "change" }],
  receive_date: [{ validator: validateReceiveDate, trigger: "change" }],
  quantity: [{ validator: validateShipmentDetail, trigger: "change" }],
  batch_no: [{ validator: validateShipmentTextDetail, trigger: "blur" }],
  carrier: [{ validator: validateShipmentTextDetail, trigger: "blur" }],
  tracking_no: [{ validator: validateShipmentTextDetail, trigger: "blur" }],
  status: [{ required: true, message: TEXT.common.messages.required, trigger: "change" }],
  remark: [{ validator: validateRemark, trigger: "blur" }],
};

const normalizeShipment = (item: any): ShipmentRow => ({
  id: item.id,
  center_id: item.center_id || "",
  site_name: item.site_name || "",
  direction: item.direction || "SEND",
  ship_date: item.ship_date || "",
  receive_date: item.receive_date || "",
  quantity: item.quantity ?? null,
  batch_no: item.batch_no || "",
  carrier: item.carrier || "",
  tracking_no: item.tracking_no || "",
  status: item.status || "PENDING",
  remark: item.remark || "",
});

const loadSites = async () => {
  const studyId = study.currentStudy?.id;
  if (!studyId) {
    sites.value = [];
    return;
  }
  try {
    const { data } = await fetchSites(studyId, { limit: 500 });
    sites.value = Array.isArray(data) ? data : data.items || [];
  } catch {
    sites.value = [];
  }
};

const load = async () => {
  const studyId = study.currentStudy?.id;
  if (!studyId) {
    items.value = [];
    return;
  }
  loading.value = true;
  try {
    const { data } = await listDrugShipments(studyId, {
      center_id: filters.center_id || undefined,
      direction: filters.direction || undefined,
      status: filters.status || undefined,
      limit: 500,
    });
    const list = Array.isArray(data) ? data : data?.items || [];
    items.value = list.map(normalizeShipment);
  } catch (e: any) {
    items.value = [];
    ElMessage.error(e?.response?.data?.detail || e?.response?.data?.message || TEXT.common.messages.loadFailed);
  } finally {
    loading.value = false;
  }
};

const resetForm = () => {
  Object.assign(form, defaultForm);
  formRef.value?.clearValidate();
};

const openCreate = () => {
  if (!canCreate.value) {
    ElMessage.warning("权限不足");
    return;
  }
  editingId.value = "";
  resetForm();
  if (study.currentSite?.id && siteActiveMap.value[study.currentSite.id] !== false) {
    form.center_id = study.currentSite.id;
  }
  drawerDirtyGuard.syncBaseline();
  drawerVisible.value = true;
};

const openEdit = (row: ShipmentRow) => {
  if (!canUpdate.value) {
    ElMessage.warning("权限不足");
    return;
  }
  editingId.value = row.id;
  Object.assign(form, {
    center_id: row.center_id,
    direction: row.direction,
    ship_date: row.ship_date,
    receive_date: row.receive_date,
    quantity: row.quantity,
    batch_no: row.batch_no,
    carrier: row.carrier,
    tracking_no: row.tracking_no,
    status: row.status,
    remark: row.remark,
  });
  formRef.value?.clearValidate();
  drawerDirtyGuard.syncBaseline();
  drawerVisible.value = true;
};

const saveForm = async () => {
  const studyId = study.currentStudy?.id;
  if (!studyId) return;
  if (isFormReadOnly.value) {
    ElMessage.warning(selectedSiteInactive.value ? "中心已停用" : "权限不足");
    return;
  }
  const valid = await formRef.value?.validate().catch(() => false);
  if (!valid) return;
  const payload = {
    center_id: form.center_id,
    direction: form.direction,
    ship_date: form.ship_date || null,
    receive_date: form.receive_date || null,
    quantity: form.quantity,
    batch_no: form.batch_no.trim() || null,
    carrier: form.carrier.trim() || null,
    tracking_no: form.tracking_no.trim() || null,
    status: form.status,
    remark: form.remark.trim() || null,
  };
  saving.value = true;
  try {
    let shipmentId = editingId.value;
    if (editingId.value) {
      await updateDrugShipment(studyId, editingId.value, payload);
    } else {
      const { data } = (await createDrugShipment(studyId, payload)) as any;
      shipmentId = data?.id || "";
      if (shipmentId) editingId.value = shipmentId;
    }
    await attachmentPanelRef.value?.uploadPending(shipmentId);
    await load();
    drawerDirtyGuard.syncBaseline();
    drawerVisible.value = false;
    ElMessage.success(TEXT.common.messages.saveSuccess);
  } catch (e: any) {
    ElMessage.error(e?.response?.data?.detail || e?.response?.data?.message || e?.message || TEXT.common.messages.saveFailed);
  } finally {
    saving.value = false;
  }
};

const resetFilters = () => {
  filters.center_id = "";
  filters.direction = "";
  filters.status = "";
  study.setCurrentSite(null);
  load();
};

const handleSearch = () => {
  load();
};

const isInactiveSite = (siteId?: string) => !!siteId && siteActiveMap.value[siteId] === false;
const shipmentRowClass = ({ row }: { row: ShipmentRow }) =>
  [
    row?.id ? "clickable-row" : "",
    isInactiveSite(row?.center_id) ? "row-inactive" : "",
    isDesktop && row?.id === selectedShipmentId.value ? "row-selected" : "",
  ]
    .filter(Boolean)
    .join(" ");

const selectShipment = (row: ShipmentRow) => {
  if (!row?.id) return;
  selectedShipmentId.value = row.id;
  shipmentTablePaneRef.value?.focus({ preventScroll: true });
};

const handleShipmentRowClick = (row: ShipmentRow) => {
  if (!row?.id) return;
  if (isDesktop) {
    selectShipment(row);
    return;
  }
  router.push(`/drug/shipments/${row.id}`);
};

const openShipmentDetail = (row?: ShipmentRow | null) => {
  const target = row?.id ? row : selectedShipment.value;
  if (target?.id) router.push(`/drug/shipments/${target.id}`);
};

const openSelectedShipment = () => {
  closeShipmentContextMenu();
  openShipmentDetail(selectedShipment.value);
};

const openSelectedShipmentEditor = () => {
  const target = selectedShipment.value;
  closeShipmentContextMenu();
  if (target) openEdit(target);
};

const removeSelectedShipment = () => {
  const target = selectedShipment.value;
  closeShipmentContextMenu();
  if (target) {
    void remove(target);
  }
};

const closeShipmentContextMenu = () => {
  if (!shipmentContextMenu.value.visible) return;
  shipmentContextMenu.value = { visible: false, x: 0, y: 0 };
};

const openShipmentContextMenu = (row: ShipmentRow, _column: unknown, event: MouseEvent) => {
  if (!isDesktop || !row?.id) return;
  event.preventDefault();
  selectShipment(row);
  shipmentContextMenu.value = {
    visible: true,
    x: Math.max(8, Math.min(event.clientX, window.innerWidth - 184)),
    y: Math.max(8, Math.min(event.clientY, window.innerHeight - 132)),
  };
};

const statusType = (status: string) => {
  switch (status) {
    case "PENDING": return "info";
    case "IN_TRANSIT": return "primary";
    case "SIGNED": return "success";
    case "EXCEPTION": return "danger";
    default: return "info";
  }
};

const remove = async (row: ShipmentRow) => {
  const studyId = study.currentStudy?.id;
  if (!studyId) return;
  if (!canDelete.value) {
    ElMessage.warning("权限不足");
    return;
  }
  if (isInactiveSite(row?.center_id)) {
    ElMessage.warning("中心已停用");
    return;
  }
  const ok = await ElMessageBox.confirm(TEXT.modules.drugShipments.confirmDelete, TEXT.common.labels.tips, { type: "warning" }).catch(() => null);
  if (!ok) return;
  try {
    await deleteDrugShipment(studyId, row.id);
    await load();
    ElMessage.success(TEXT.common.messages.deleteSuccess);
  } catch (e: any) {
    ElMessage.error(e?.response?.data?.detail || e?.response?.data?.message || TEXT.common.messages.deleteFailed);
  }
};

watch(
  () => filters.center_id,
  (val: string) => {
    if (val) {
      const matched = sites.value.find((site) => site.id === val);
      if (matched) study.setCurrentSite(matched);
    } else {
      study.setCurrentSite(null);
    }
  }
);

watch(
  () => study.currentSite,
  (newSite: any) => {
    filters.center_id = newSite?.id || "";
  }
);

watch(
  () => study.currentStudy?.id,
  async () => {
    filters.center_id = study.currentSite?.id || "";
    filters.direction = "";
    filters.status = "";
    drawerVisible.value = false;
    await loadSites();
    await load();
  }
);

watch(
  () => sortedItems.value.map((item) => item.id).join("|"),
  () => {
    if (!isDesktop) return;
    const rows = sortedItems.value;
    if (!rows.length) {
      selectedShipmentId.value = "";
      return;
    }
    if (!rows.some((item) => item.id === selectedShipmentId.value)) {
      selectedShipmentId.value = rows[0].id;
    }
  },
  { immediate: true }
);

onMounted(async () => {
  await loadSites();
  await load();
});
</script>

<style scoped>
/* ==================== Base ==================== */
.page {
  display: flex;
  flex-direction: column;
  padding: 4px 0;
  background: transparent;
}

/* 桌面端：撑满父容器高度 */
.shipment-page--desktop {
  height: 100%;
  min-height: 0;
  padding: 0;
}

.page-inner {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* 桌面端：page-inner 撑满 .page */
.shipment-page--desktop > .page-inner {
  flex: 1;
  min-height: 0;
  gap: 0;
}

/* ==================== Page Header ==================== */
.create-btn {
  height: 34px;
  border-radius: 8px;
  padding: 0 16px;
  font-weight: 500;
}

/* ==================== Table Toolbar ==================== */
.table-card-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 14px 20px;
  border-bottom: 1px solid #f0f0f0;
  gap: 12px;
}

.toolbar-filters {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  flex-wrap: wrap;
}

.toolbar-right {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-left: auto;
  flex-shrink: 0;
}

.filter-item {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.filter-label {
  font-size: 11px;
  font-weight: 600;
  color: #8a8a8a;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.filter-select {
  width: 140px;
}

.filter-actions {
  display: flex;
  gap: 8px;
  align-items: flex-end;
  padding-bottom: 1px;
}

.filter-btn {
  border-radius: 6px;
}

.filter-summary {
  font-size: 12px;
  font-weight: 500;
  color: #a3a3a3;
  white-space: nowrap;
}

/* ==================== Table Card ==================== */
.table-card {
  background: #ffffff;
  border-radius: 8px;
  overflow: hidden;
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.04),
    0 2px 8px rgba(0, 0, 0, 0.02);
}

/* 桌面端：table-card 填满、无圆角 */
.shipment-page--desktop .table-card {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-radius: 0;
  box-shadow: none;
}

.shipment-workbench {
  min-width: 0;
  min-height: 0;
}

.shipment-workbench.is-desktop {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 316px;
  /* 桌面端：不限定固定高度，由父容器 flex 撑满 */
  flex: 1;
  min-height: 0;
}

.shipment-table-pane {
  min-width: 0;
  outline: none;
}

/* 禁用表格横向滚动：阻断滚动行为 + 隐藏滚动条元素 */
.shipment-table-pane :deep(.el-scrollbar__wrap) {
  overflow-x: hidden;
}
.shipment-table-pane :deep(.el-scrollbar__bar.is-horizontal) {
  display: none;
}

.shipment-workbench.is-desktop .shipment-table-pane {
  border-right: 1px solid #e3e9f1;
}

.shipment-preview-pane {
  display: flex;
  min-width: 0;
  min-height: 0;
  flex-direction: column;
  gap: 16px;
  padding: 16px;
  background: #f8fafc;
}

.preview-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.preview-kicker {
  margin-bottom: 5px;
  color: #6b7e95;
  font-size: 11px;
  font-weight: 800;
}

.preview-title {
  display: -webkit-box;
  overflow: hidden;
  color: #142033;
  font-size: 17px;
  font-weight: 800;
  line-height: 1.35;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
}

.preview-list {
  display: grid;
  grid-template-columns: 78px minmax(0, 1fr);
  gap: 10px 12px;
  margin: 0;
}

.preview-list dt {
  color: #7b8da3;
  font-size: 12px;
  font-weight: 700;
}

.preview-list dd {
  min-width: 0;
  margin: 0;
  overflow-wrap: anywhere;
  color: #223349;
  font-size: 13px;
  font-weight: 650;
}

.preview-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: auto;
}

.preview-empty {
  display: flex;
  min-height: 180px;
  align-items: center;
  justify-content: center;
  color: #7b8da3;
  font-size: 13px;
  font-weight: 700;
  text-align: center;
}

.shipment-context-menu {
  position: fixed;
  z-index: 2300;
  display: flex;
  width: 172px;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  border: 1px solid #cbd7e5;
  border-radius: 8px;
  background: #ffffff;
  box-shadow: 0 14px 38px rgba(15, 23, 42, 0.16);
}

.shipment-context-menu button {
  appearance: none;
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 30px;
  padding: 0 9px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: #223349;
  cursor: pointer;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  text-align: left;
}

.shipment-context-menu button:hover:not(:disabled) {
  background: #eef4fb;
  color: #183756;
}

.shipment-context-menu button.danger {
  color: #b42318;
}

.shipment-context-menu button:disabled {
  cursor: not-allowed;
  color: #9aaabd;
}

.shipment-table {
  --el-table-border-color: transparent;
  --el-table-row-hover-bg-color: #f8f9fb;
}

.shipment-table :deep(.el-table__inner-wrapper::before) {
  display: none;
}

.shipment-table :deep(th.el-table__cell) {
  background: #f4f5f7;
  color: #2a2a2a;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 14px 14px;
  border-bottom: 2px solid #e0e0e0 !important;
}

.shipment-table :deep(th.el-table__cell .cell) {
  display: flex;
  align-items: center;
  gap: 4px;
}

.shipment-table :deep(td.el-table__cell) {
  padding: 12px 14px;
  color: #0a0a0a;
  font-size: 14px;
  font-weight: 500;
}

.shipment-table :deep(.el-table__body tr) {
  cursor: pointer;
  transition: background 0.1s ease;
}

.shipment-table :deep(.el-table__body tr.row-selected > td) {
  background: #eaf1f8 !important;
}

.cell-nowrap {
  white-space: nowrap;
}

.cell-primary {
  font-weight: 600;
  color: #0a0a0a;
}

.cell-mono {
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-size: 13px;
  color: #0a0a0a;
}

.cell-muted {
  color: #737373;
  font-size: 14px;
}

.cell-actions {
  display: flex;
  gap: 8px;
  white-space: nowrap;
}

.cell-actions :deep(.el-button) {
  height: auto;
  padding: 0;
  font-size: 13px;
}

.cell-actions :deep(.el-button + .el-button) {
  margin-left: 0;
}

/* ==================== Direction Tags ==================== */
.dir-tag {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  font-size: 12px;
  font-weight: 700;
  border-radius: 4px;
  line-height: 1.6;
}

.dir-tag--send {
  background: #fff7ed;
  color: #ea580c;
}

.dir-tag--return {
  background: #f0fdf4;
  color: #16a34a;
}

/* ==================== Status Pills ==================== */
.status-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 12px;
  font-size: 12px;
  font-weight: 700;
  border-radius: 20px;
  line-height: 1.6;
}

.status-pill--info {
  background: #f5f5f5;
  color: #737373;
}

.status-pill--primary {
  background: #eff4ff;
  color: #2563eb;
}

.status-pill--success {
  background: #dcfce7;
  color: #16a34a;
}

.status-pill--danger {
  background: #fef2f2;
  color: #dc2626;
}

/* ==================== Empty State ==================== */
.table-empty {
  min-height: 200px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.empty-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: #f5f5f5;
  color: #c4c4c4;
}

.table-empty span {
  font-size: 13px;
  font-weight: 500;
  color: #a3a3a3;
}

/* ==================== Drawer Editor ==================== */
:deep(.shipment-editor-drawer > .el-drawer__header) {
  margin-bottom: 0;
  padding: 20px 24px 8px;
}

:deep(.shipment-editor-drawer > .el-drawer__body) {
  padding: 0 24px 4px;
}

.editor-header {
  display: flex;
  flex-direction: column;
}

.editor-title {
  font-size: 18px;
  font-weight: 700;
  color: #0a0a0a;
  line-height: 1.2;
  letter-spacing: -0.01em;
}

.shipment-form {
  padding: 0;
}

/* ==================== Form Sections ==================== */
.form-section {
  padding-bottom: 6px;
}

.form-section + .form-section {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid #f0f0f0;
}

.form-section-title {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  font-weight: 600;
  color: #0a0a0a;
  margin-bottom: 16px;
}

.dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}

.dot--blue { background: #3b82f6; }
.dot--amber { background: #f59e0b; }
.dot--green { background: #22c55e; }
.dot--purple { background: #8b5cf6; }

.field-grid {
  display: grid;
  gap: 0 16px;
}

.field-grid--2 {
  grid-template-columns: 1fr 1fr;
}

.full-width {
  width: 100%;
}

.inactive-hint {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  background: #fffbeb;
  border: 1px solid #fde68a;
  border-radius: 8px;
  font-size: 13px;
  color: #92400e;
  margin-top: 4px;
}

.hint-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #f59e0b;
  color: #ffffff;
  font-size: 12px;
  line-height: 1;
}

.drawer-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

.footer-btn {
  height: 36px;
  border-radius: 8px;
  padding: 0 20px;
  font-size: 13px;
  font-weight: 500;
}

.footer-btn--primary {
  padding: 0 24px;
}

.shipment-form :deep(.el-form-item) {
  margin-bottom: 14px;
}

.shipment-form :deep(.el-form-item__label) {
  font-size: 12px;
  font-weight: 600;
  color: #525252;
  padding-bottom: 4px;
}

.shipment-form :deep(.el-form-item__error) {
  font-size: 11px;
}

/* ==================== Responsive ==================== */
@media (max-width: 960px) {
  .field-grid--2 {
    grid-template-columns: 1fr;
  }
  .toolbar-filters {
    flex-direction: column;
    align-items: stretch;
  }
  .filter-select {
    width: 100%;
  }
  .table-card-toolbar {
    flex-direction: column;
    gap: 12px;
    align-items: stretch;
  }
  .shipment-workbench.is-desktop {
    grid-template-columns: minmax(0, 1fr);
  }
  .shipment-workbench.is-desktop .shipment-table-pane {
    border-right: 0;
  }
  .shipment-preview-pane {
    display: none;
  }
}

@media (max-width: 640px) {
  .create-btn {
    width: 100%;
  }
}

:global([data-ctms-theme="dark"] .shipment-page--desktop .table-card) {
  background: #172033;
}

:global([data-ctms-theme="dark"] .shipment-page--desktop .shipment-preview-pane) {
  border-color: #26364a;
  background: #111827;
}

:global([data-ctms-theme="dark"] .shipment-page--desktop .preview-title),
:global([data-ctms-theme="dark"] .shipment-page--desktop .preview-list dd) {
  color: #f8fafc;
}

:global([data-ctms-theme="dark"] .shipment-page--desktop .preview-kicker),
:global([data-ctms-theme="dark"] .shipment-page--desktop .preview-list dt),
:global([data-ctms-theme="dark"] .shipment-page--desktop .preview-empty) {
  color: #94a3b8;
}

:global([data-ctms-theme="dark"] .shipment-page--desktop .shipment-context-menu) {
  border-color: #334155;
  background: #172033;
}

:global([data-ctms-theme="dark"] .shipment-page--desktop .shipment-context-menu button) {
  color: #dbe5f1;
}

:global([data-ctms-theme="dark"] .shipment-page--desktop .shipment-context-menu button:hover:not(:disabled)) {
  background: #243247;
  color: #bfdbfe;
}

:global([data-ctms-theme="dark"] .shipment-page--desktop .shipment-table .el-table__body tr.row-selected > td) {
  background: #243247 !important;
}
</style>

<style>
.row-inactive td {
  color: var(--ctms-text-secondary);
  background-color: #fff7d6 !important;
}

.row-inactive .status-pill,
.row-inactive .dir-tag {
  opacity: 0.6;
}
</style>
