<template>
  <div class="page-shell module-page">
    <BrandTopBar active="patrol-delete" />

    <div class="main-content">
      <aside class="left-panel">
        <div class="section-title">店铺输入</div>
        <div class="input-zone">
          <div class="hint">
            左侧负责录入店铺并加入备选区，点击“匹配店铺”只会生成匹配结果；确认后需手动点击“推送到 Python 队列”才会创建并执行任务。
          </div>
          <div class="input-row">
            <el-input
              v-model="shopInput"
              clearable
              placeholder="请输入店铺名后回车或点击添加"
              @keyup.enter="confirmAdd"
            />
            <button
              type="button"
              class="opt-btn"
              :disabled="adding"
              @click="confirmAdd"
            >
              {{ adding ? "添加中..." : "添加" }}
            </button>
          </div>
        </div>

        <div class="section-title">备选区</div>
        <div v-if="!candidates.length" class="empty-candidates">
          暂无备选店铺，请先输入并添加
        </div>
        <div v-else class="candidate-table-scroll">
          <el-table
            :data="candidates"
            row-key="id"
            height="260"
            class="candidate-table"
            @selection-change="onSelectionChange"
          >
            <el-table-column type="selection" width="42" />
            <el-table-column
              prop="shop_name"
              label="店铺名"
              min-width="140"
              show-overflow-tooltip
            />
            <el-table-column label="操作" width="72" align="center">
              <template #default="{ row }">
                <button
                  type="button"
                  class="link-danger"
                  @click="removeCandidate(row.id)"
                >
                  删除
                </button>
              </template>
            </el-table-column>
          </el-table>
        </div>

        <div class="section-title condition-title">
          <span>删除条件</span>
          <span class="condition-title-hint">
            注意删除条件的描述需要和页面描述一致
          </span>
        </div>
        <div class="condition-panel">
          <div class="condition-input-row">
            <el-input
              v-model="conditionInput"
              clearable
              placeholder="请输入删除条件"
              @keyup.enter="confirmAddCondition"
            />
            <button
              type="button"
              class="opt-btn"
              :disabled="addingCondition"
              @click="confirmAddCondition"
            >
              {{ addingCondition ? "保存中..." : "保存" }}
            </button>
          </div>
          <div v-if="!deleteConditions.length" class="empty-conditions">
            暂无删除条件，保存后可在推送任务时多选携带。
          </div>
          <ul v-else class="condition-list">
            <li
              v-for="condition in deleteConditions"
              :key="condition.id"
              class="condition-item"
            >
              <label class="condition-check">
                <input
                  v-model="selectedConditionIds"
                  type="checkbox"
                  :value="condition.id"
                />
                <span :title="condition.conditionText">
                  {{ condition.conditionText }}
                </span>
              </label>
              <button
                type="button"
                class="link-danger"
                @click="removeCondition(condition.id)"
              >
                删除
              </button>
            </li>
          </ul>
        </div>

        <div class="run-row">
          <button
            type="button"
            class="btn-run"
            :disabled="matching || hasQueueWork"
            @click="runMatch"
          >
            {{ matching ? "匹配中..." : "匹配店铺" }}
          </button>
          <button
            type="button"
            class="btn-run btn-queue"
            :disabled="pushing || !matchedRunnableItems.length"
            @click="pushToPythonQueue"
          >
            {{ pushing ? "任务执行中..." : "推送到 Python 队列" }}
          </button>
        </div>

        <p class="loading-msg">
          当前页面会把匹配结果合并为一个巡店删除任务推送；多个店铺会在同一个任务和同一个结果文件中汇总。
        </p>

        <div v-if="queuePushResult" class="queue-debug-card">
          <div class="section-title queue-debug-title">队列状态</div>
          <div class="queue-debug-line">{{ queuePushResult }}</div>
          <pre v-if="queuePayloadText" class="queue-debug-payload">{{
            queuePayloadText
          }}</pre>
        </div>
      </aside>

      <section class="right-panel">
        <div class="panel-header">匹配与任务</div>
        <div class="task-list-wrap">
          <div class="clean-result-summary">
            <div class="summary-card">
              <span class="summary-label">备选店铺</span>
              <strong>{{ dashboard.candidateCount }}</strong>
            </div>
            <div class="summary-card">
              <span class="summary-label">已处理任务</span>
              <strong>{{ dashboard.processedTaskCount }}</strong>
            </div>
            <div class="summary-card">
              <span class="summary-label">成功任务</span>
              <strong>{{ dashboard.successTaskCount }}</strong>
            </div>
            <div class="summary-card">
              <span class="summary-label">失败任务</span>
              <strong>{{ dashboard.failedTaskCount }}</strong>
            </div>
          </div>

          <div class="subsection-title">匹配结果</div>
          <div v-if="!matchedItems.length" class="empty-tasks narrow">
            完成“匹配店铺”后，这里会展示匹配结果。
          </div>
          <el-table
            v-else
            :data="matchedItems"
            :row-key="rowKeyForMatch"
            :highlight-current-row="false"
            class="result-table match-table"
          >
            <el-table-column prop="shopName" label="店铺名" min-width="120" />
            <el-table-column label="匹配" width="72" align="center">
              <template #default="{ row }">
                <span :class="row.matched ? 'ok' : 'fail'">
                  {{ row.matched ? "是" : "否" }}
                </span>
              </template>
            </el-table-column>
            <el-table-column
              prop="shopId"
              label="店铺 ID"
              min-width="120"
              show-overflow-tooltip
            />
            <el-table-column
              prop="platform"
              label="平台"
              width="88"
              show-overflow-tooltip
            />
            <el-table-column
              prop="companyName"
              label="公司"
              min-width="120"
              show-overflow-tooltip
            />
            <el-table-column label="状态" width="110" show-overflow-tooltip>
              <template #default="{ row }">
                {{ formatMatchStatus(row.matchStatus) }}
              </template>
            </el-table-column>
            <el-table-column label="说明" min-width="180" show-overflow-tooltip>
              <template #default="{ row }">
                {{ formatMatchRemark(row) }}
              </template>
            </el-table-column>
            <el-table-column label="操作" width="72" align="center">
              <template #default="{ row }">
                <button
                  type="button"
                  class="link-danger"
                  @click="removeMatchedRow(row)"
                >
                  删除
                </button>
              </template>
            </el-table-column>
          </el-table>

          <div class="result-list-wrap">
            <div class="result-list-header">
              <span>任务记录</span>
            </div>

            <div
              v-if="!currentSectionItems.length && !historySectionItems.length"
              class="empty-tasks"
            >
              暂无任务记录。推送到 Python 队列后，这里会展示当前任务和历史任务。
            </div>

            <template v-else>
              <div v-if="currentSectionItems.length" class="result-subsection">
                <div class="result-subsection-title">当前任务</div>
                <ul class="task-list">
                  <li
                    v-for="item in currentSectionItems"
                    :key="taskGroupKey(item)"
                    class="task-item"
                  >
                    <div class="left split-result-main">
                      <span class="id" :title="formatTaskGroupShopNames(item)">
                        {{ formatTaskGroupShopNames(item) }}
                      </span>
                      <div class="files">任务 ID: {{ item.taskId ?? "-" }}</div>
                      <div v-if="formatTaskGroupResultIds(item)" class="files">
                        结果 ID: {{ formatTaskGroupResultIds(item) }}
                      </div>
                      <div v-if="formatTaskGroupShopIds(item)" class="files">
                        店铺 ID: {{ formatTaskGroupShopIds(item) }}
                      </div>
                      <div v-if="item.platform" class="files">
                        平台: {{ item.platform }}
                      </div>
                      <div class="files">
                        创建时间: {{ formatDateTime(item.createdAt) }}
                      </div>
                      <div class="files">
                        模板: {{ formatTemplateSummary(item) }}
                      </div>
                      <div v-if="formatTaskGroupErrors(item)" class="files">
                        错误: {{ formatTaskGroupErrors(item) }}
                      </div>
                    </div>
                    <div class="task-right">
                      <span
                        class="status"
                        :class="statusClass(item.taskStatus)"
                      >
                        {{ statusText(item.taskStatus) }}
                      </span>
                      <button
                        type="button"
                        class="btn-delete"
                        @click="deleteTaskRecord(item)"
                      >
                        删除
                      </button>
                    </div>
                  </li>
                </ul>
              </div>

              <div v-if="historySectionItems.length" class="result-subsection">
                <div class="result-subsection-title">历史任务</div>
                <ul class="task-list">
                  <li
                    v-for="item in historySectionItems"
                    :key="taskGroupKey(item)"
                    class="task-item"
                  >
                    <div class="left split-result-main">
                      <span class="id" :title="formatTaskGroupShopNames(item)">
                        {{ formatTaskGroupShopNames(item) }}
                      </span>
                      <div class="files">任务 ID: {{ item.taskId ?? "-" }}</div>
                      <div v-if="formatTaskGroupResultIds(item)" class="files">
                        结果 ID: {{ formatTaskGroupResultIds(item) }}
                      </div>
                      <div v-if="formatTaskGroupShopIds(item)" class="files">
                        店铺 ID: {{ formatTaskGroupShopIds(item) }}
                      </div>
                      <div v-if="item.platform" class="files">
                        平台: {{ item.platform }}
                      </div>
                      <div class="files">
                        创建时间: {{ formatDateTime(item.createdAt) }}
                      </div>
                      <div v-if="item.finishedAt" class="files">
                        完成时间: {{ formatDateTime(item.finishedAt) }}
                      </div>
                      <div class="files">
                        模板: {{ formatTemplateSummary(item) }}
                      </div>
                      <div v-if="formatTaskGroupErrors(item)" class="files">
                        错误: {{ formatTaskGroupErrors(item) }}
                      </div>
                    </div>
                    <div class="task-right">
                      <span
                        class="status"
                        :class="statusClass(item.taskStatus)"
                      >
                        {{ statusText(item.taskStatus) }}
                      </span>
                      <button
                        v-if="canDownload(item)"
                        type="button"
                        class="archive"
                        @click="downloadResult(item)"
                      >
                        下载结果
                      </button>
                      <button
                        type="button"
                        class="btn-delete"
                        @click="deleteTaskRecord(item)"
                      >
                        删除
                      </button>
                    </div>
                  </li>
                </ul>
              </div>
            </template>
          </div>
        </div>
      </section>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from "vue";
import { ElMessage } from "element-plus";
import BrandTopBar from "@/pages/brand/components/BrandTopBar.vue";
import {
  addPatrolDeleteCandidate,
  addPatrolDeleteCondition,
  createPatrolDeleteTask,
  deletePatrolDeleteCandidate,
  deletePatrolDeleteCondition,
  deletePatrolDeleteHistory,
  deletePatrolDeleteTask,
  getPatrolDeleteDashboard,
  getPatrolDeleteHistory,
  getPatrolDeleteTaskProgressBatch,
  getPatrolDeleteResultDownloadUrl,
  listPatrolDeleteConditions,
  listPatrolDeleteCandidates,
  matchPatrolDeleteShops,
  submitPatrolDeleteTaskResult,
  type PatrolDeleteCandidateVo,
  type PatrolDeleteConditionVo,
  type PatrolDeleteCartRatio,
  type PatrolDeleteCountrySection,
  type PatrolDeleteDashboardVo,
  type PatrolDeleteHistoryItem,
  type PatrolDeleteShopQueueItem,
  type PatrolDeleteTaskItem,
} from "@/shared/api/java-modules";
import { getPywebviewApi } from "@/shared/bridges/pywebview";
import { getTaskPollIntervalMs } from "@/shared/task-progress-config";
import { createCategorizedTimers } from "@/shared/utils/categorized-timers";
import { saveUrlWithProgress } from "@/shared/utils/download-progress";

const COUNTRY_TEMPLATE = ["德国", "英国", "法国", "意大利", "西班牙"] as const;
const MAX_TRANSIENT_ERRORS = 30;

const shopInput = ref("");
const conditionInput = ref("");
const candidates = ref<PatrolDeleteCandidateVo[]>([]);
const selectedCandidates = ref<PatrolDeleteCandidateVo[]>([]);
const deleteConditions = ref<PatrolDeleteConditionVo[]>([]);
const selectedConditionIds = ref<number[]>([]);
const matchedItems = ref<PatrolDeleteShopQueueItem[]>([]);
const historyItems = ref<PatrolDeleteHistoryItem[]>([]);
const dashboard = ref<PatrolDeleteDashboardVo>({
  candidateCount: 0,
  processedTaskCount: 0,
  successTaskCount: 0,
  failedTaskCount: 0,
});
const adding = ref(false);
const addingCondition = ref(false);
const matching = ref(false);
const pushing = ref(false);
const queuePushResult = ref("");
const queuePayloadText = ref("");
const activeTaskId = ref<number | null>(null);
const queueWorkerRunning = ref(false);
let historyPollTimer: number | null = null;
const timers = createCategorizedTimers("patrol-delete");

const matchedRunnableItems = computed(() =>
  matchedItems.value.filter((item) => item.matched),
);
const taskRecordItems = computed(() => groupHistoryItemsByTask(historyItems.value));
const currentSectionItems = computed(() =>
  taskRecordItems.value.filter((item) => !isTaskTerminal(item.taskStatus)),
);
const historySectionItems = computed(() =>
  taskRecordItems.value.filter((item) => isTaskTerminal(item.taskStatus)),
);
const hasQueueWork = computed(
  () =>
    queueWorkerRunning.value ||
    !!activeTaskId.value,
);

function uidForStorage() {
  return typeof window !== "undefined"
    ? window.localStorage.getItem("uid") || "0"
    : "0";
}

function matchedStorageKey() {
  return `patrol-delete:matched:${uidForStorage()}`;
}

function queueStateStorageKey() {
  return `patrol-delete:queue-state:${uidForStorage()}`;
}

function rowKeyForMatch(row: PatrolDeleteShopQueueItem) {
  return `${(row.shopName || "").trim()}::${row.shopId || ""}`;
}

function historyItemKey(item: PatrolDeleteHistoryItem) {
  return `${item.taskId ?? 0}:${item.resultId ?? 0}`;
}

function taskGroupKey(item: PatrolDeleteHistoryItem) {
  return `task:${item.taskId ?? item.resultId ?? 0}`;
}

let disposed = false;

function sleep(ms: number) {
  if (disposed) return Promise.resolve();
  return timers.sleep("queue-wait", ms);
}

function clearSleepTimers() {
  timers.clearCategory("queue-wait");
}

function isTransientBackendError(error: unknown) {
  const message =
    error instanceof Error ? error.message.toLowerCase() : String(error || "").toLowerCase();
  return (
    message.includes("network error") ||
    message.includes("failed to fetch") ||
    message.includes("timeout") ||
    message.includes("503") ||
    message.includes("502") ||
    message.includes("504") ||
    message.includes("500") ||
    message.includes("load failed")
  );
}

async function withTransientRetry<T>(
  action: () => Promise<T>,
  onRetry?: (attempt: number, maxAttempts: number) => void,
  maxAttempts = 20,
) {
  let attempt = 0;
  while (true) {
    if (disposed) throw new Error("component disposed");
    try {
      return await action();
    } catch (error) {
      if (!isTransientBackendError(error) || attempt >= maxAttempts - 1) {
        throw error;
      }
      attempt += 1;
      onRetry?.(attempt, maxAttempts);
      await sleep(getTaskPollIntervalMs());
    }
  }
}

function createSubmissionId(taskId: number, shopName?: string) {
  return `patrol-delete:${taskId}:${shopName || "shop"}:${Date.now()}`;
}

function formatDateTime(value?: string) {
  if (!value) return "-";
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return value;
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  const hours = String(date.getHours()).padStart(2, "0");
  const minutes = String(date.getMinutes()).padStart(2, "0");
  const seconds = String(date.getSeconds()).padStart(2, "0");
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}

function formatMatchStatus(status?: string) {
  const value = (status || "").trim();
  const map: Record<string, string> = {
    MATCHED: "已匹配",
    PENDING: "待匹配",
    CONFLICT: "需人工确认",
    INDEX_STALE: "索引过期",
  };
  return map[value] || value || "-";
}

function formatMatchRemark(row: PatrolDeleteShopQueueItem) {
  const message = (row.matchMessage || "").trim();
  if (message) return message;
  if (row.matched && row.matchStatus === "MATCHED") {
    return "索引已命中，可推送到 Python 队列";
  }
  if (row.matched) {
    return "已命中索引，请结合状态列确认";
  }
  return "未命中或未就绪，请检查店铺名";
}

function buildTemplateCountrySections(): PatrolDeleteCountrySection[] {
  return COUNTRY_TEMPLATE.map((country) => ({
    country,
    rows: [
      {
        status: "全部",
        quantity: "",
        deleteQuantity: "",
        processStatus: "",
      },
    ],
  }));
}

function buildTemplateCartRatios(): PatrolDeleteCartRatio[] {
  return COUNTRY_TEMPLATE.map((country) => ({
    country,
    ratio: "",
  }));
}

function flattenTemplateRows(
  record: Pick<
    PatrolDeleteHistoryItem,
    "shopName" | "countrySections" | "cartRatios"
  >,
) {
  const sections = record.countrySections || [];
  const ratios = record.cartRatios || [];
  const rowCount = Math.max(...sections.map((section) => section.rows.length), 1);
  return Array.from({ length: rowCount }, (_, rowIndex) => ({
    shopName: rowIndex === 0 ? record.shopName : "",
    countries: sections.map((section) => {
      const row = section.rows[rowIndex];
      return {
        country: section.country,
        status: row?.status || "",
        quantity: row?.quantity || "",
        deleteQuantity: row?.deleteQuantity || "",
        processStatus: row?.processStatus || "",
      };
    }),
    cartRatios:
      rowIndex === 0
        ? ratios.map((item) => ({
            country: item.country,
            ratio: item.ratio,
          }))
        : [],
  }));
}

function formatTemplateSummary(
  record: Pick<
    PatrolDeleteHistoryItem,
    "shopName" | "countrySections" | "cartRatios"
  >,
) {
  const countries = new Set([
    ...(record.countrySections || []).map((item) => item.country).filter(Boolean),
    ...(record.cartRatios || []).map((item) => item.country).filter(Boolean),
  ]);
  return `${countries.size || 0} 个国家，含状态数据和购物车比例`;
}

function uniqueJoined(values: Array<string | number | undefined | null>) {
  return Array.from(
    new Set(
      values
        .map((value) => (value == null ? "" : String(value).trim()))
        .filter(Boolean),
    ),
  ).join("、");
}

function groupHistoryItemsByTask(items: PatrolDeleteHistoryItem[]) {
  const groups = new Map<string, PatrolDeleteHistoryItem[]>();
  for (const item of items || []) {
    const key = String(item.taskId ?? `result:${item.resultId ?? Math.random()}`);
    const group = groups.get(key) || [];
    group.push(item);
    groups.set(key, group);
  }
  return Array.from(groups.values()).map((group) => {
    const base =
      group.find((item) => canDownload(item)) ||
      group.find((item) => item.taskStatus) ||
      group[0];
    const errors = uniqueJoined(group.map((item) => item.error));
    return {
      ...base,
      shopName: uniqueJoined(group.map((item) => item.shopName)) || base.shopName,
      shopId: uniqueJoined(group.map((item) => item.shopId)) || base.shopId,
      error: errors || base.error,
      __groupItems: group,
    } as PatrolDeleteHistoryItem & { __groupItems?: PatrolDeleteHistoryItem[] };
  });
}

function taskGroupItems(item: PatrolDeleteHistoryItem) {
  return ((item as PatrolDeleteHistoryItem & { __groupItems?: PatrolDeleteHistoryItem[] }).__groupItems || [item]);
}

function formatTaskGroupShopNames(item: PatrolDeleteHistoryItem) {
  return uniqueJoined(taskGroupItems(item).map((row) => row.shopName)) || "-";
}

function formatTaskGroupResultIds(item: PatrolDeleteHistoryItem) {
  return uniqueJoined(taskGroupItems(item).map((row) => row.resultId));
}

function formatTaskGroupShopIds(item: PatrolDeleteHistoryItem) {
  return uniqueJoined(taskGroupItems(item).map((row) => row.shopId));
}

function formatTaskGroupErrors(item: PatrolDeleteHistoryItem) {
  return uniqueJoined(taskGroupItems(item).map((row) => row.error));
}

function isTaskTerminal(status?: string) {
  return status === "SUCCESS" || status === "FAILED" || status === "COMPLETED";
}

function statusText(status?: string) {
  if (status === "SUCCESS" || status === "COMPLETED") return "已完成";
  if (status === "FAILED") return "失败";
  return "执行中";
}

function statusClass(status?: string) {
  return status === "SUCCESS" || status === "COMPLETED"
    ? "success"
    : status === "FAILED"
      ? "failed"
      : "running";
}

function canDownload(item: PatrolDeleteHistoryItem) {
  return taskGroupItems(item).some((row) =>
    Boolean(row.resultId && (row.fileReady || row.downloadUrl)),
  );
}

function saveMatchedItems() {
  if (typeof window === "undefined") return;
  window.localStorage.setItem(
    matchedStorageKey(),
    JSON.stringify(matchedItems.value),
  );
}

function loadMatchedItems() {
  try {
    const raw =
      typeof window !== "undefined"
        ? window.localStorage.getItem(matchedStorageKey())
        : null;
    matchedItems.value = raw ? JSON.parse(raw) : [];
  } catch {
    matchedItems.value = [];
  }
}

function saveQueueState() {
  if (typeof window === "undefined") return;
  const payload = {
    activeTaskId: activeTaskId.value,
  };
  window.localStorage.setItem(queueStateStorageKey(), JSON.stringify(payload));
}

function loadQueueState() {
  try {
    const raw =
      typeof window !== "undefined"
        ? window.localStorage.getItem(queueStateStorageKey())
        : null;
    if (!raw) return;
    const parsed = JSON.parse(raw) as {
      activeTaskId?: number | null;
    };
    activeTaskId.value = parsed.activeTaskId ?? null;
  } catch {
    activeTaskId.value = null;
  }
}

function clearActiveQueueTask() {
  activeTaskId.value = null;
  saveQueueState();
}

function isRecordMissingError(error: unknown) {
  const message = error instanceof Error ? error.message : String(error || "");
  return /记录不存在|任务不存在|不存在|已删除|not\s*found|404/i.test(message);
}

function removeMatchedRowLocally(row: PatrolDeleteShopQueueItem) {
  const key = rowKeyForMatch(row);
  matchedItems.value = matchedItems.value.filter((item) => rowKeyForMatch(item) !== key);
  saveMatchedItems();
}

function removeHistoryItemLocally(item: PatrolDeleteHistoryItem) {
  historyItems.value = historyItems.value.filter((row) => {
    if (item.resultId != null && row.resultId === item.resultId) return false;
    if (item.taskId != null && row.taskId === item.taskId) return false;
    return true;
  });
}

async function loadCandidates() {
  candidates.value = await listPatrolDeleteCandidates();
}

async function loadConditions() {
  deleteConditions.value = await listPatrolDeleteConditions();
  const existingIds = new Set(deleteConditions.value.map((item) => item.id));
  selectedConditionIds.value = selectedConditionIds.value.filter((id) =>
    existingIds.has(id),
  );
}

async function loadDashboard() {
  dashboard.value = await getPatrolDeleteDashboard();
}

async function loadHistory() {
  const data = await getPatrolDeleteHistory();
  historyItems.value = data.items || [];
}

async function refreshTaskViews() {
  await Promise.all([loadDashboard(), loadHistory()]);
}

function mergeHistoryProgressItems(incoming: PatrolDeleteHistoryItem[]) {
  if (!incoming.length) {
    return;
  }
  const incomingMap = new Map(
    incoming.map((item) => [historyItemKey(item), item] as const),
  );
  const merged = historyItems.value.map((item) => {
    const next = incomingMap.get(historyItemKey(item));
    if (!next) {
      return item;
    }
    return {
      ...item,
      ...next,
      countrySections: next.countrySections || item.countrySections || [],
      cartRatios: next.cartRatios || item.cartRatios || [],
    };
  });
  for (const item of incoming) {
    const key = historyItemKey(item);
    if (!merged.some((current) => historyItemKey(current) === key)) {
      merged.push(item);
    }
  }
  historyItems.value = merged;
}

function upsertHistoryItems(incoming: PatrolDeleteHistoryItem[]) {
  mergeHistoryProgressItems(incoming);
}

async function refreshActiveTaskProgress(taskIds?: number[]) {
  const ids = Array.from(
    new Set(
      (taskIds && taskIds.length
        ? taskIds
        : currentSectionItems.value.map((item) => item.taskId ?? 0)
      ).filter((taskId) => Number.isFinite(taskId) && taskId > 0),
    ),
  );
  if (!ids.length) {
    return [];
  }
  const batch = await getPatrolDeleteTaskProgressBatch(ids);
  mergeHistoryProgressItems(batch.items || []);
  if ((batch.missingTaskIds || []).length) {
    await loadHistory();
  }
  return batch.missingTaskIds || [];
}

function startHistoryPolling() {
  stopHistoryPolling();
  if (disposed || !hasQueueWork.value) return;
  const run = async () => {
    historyPollTimer = null;
    if (disposed || !hasQueueWork.value) return;
    await refreshActiveTaskProgress();
    if (!disposed && hasQueueWork.value) {
      historyPollTimer = timers.setTimeout("history-poll", run, getTaskPollIntervalMs() * 2);
    }
  };
  historyPollTimer = timers.setTimeout("history-poll", run, getTaskPollIntervalMs() * 2);
}

function stopHistoryPolling() {
  if (historyPollTimer) {
    timers.clearTimer("history-poll", historyPollTimer);
    historyPollTimer = null;
  }
}

function onSelectionChange(rows: PatrolDeleteCandidateVo[]) {
  selectedCandidates.value = rows;
}

async function confirmAdd() {
  const name = shopInput.value.trim();
  if (!name) {
    ElMessage.warning("请输入店铺名");
    return;
  }
  adding.value = true;
  try {
    await addPatrolDeleteCandidate(name);
    shopInput.value = "";
    await Promise.all([loadCandidates(), loadDashboard()]);
    ElMessage.success("已加入备选区");
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : "添加失败");
  } finally {
    adding.value = false;
  }
}

async function confirmAddCondition() {
  const text = conditionInput.value.trim();
  if (!text) {
    ElMessage.warning("请输入删除条件");
    return;
  }
  addingCondition.value = true;
  try {
    const saved = await addPatrolDeleteCondition(text);
    conditionInput.value = "";
    await loadConditions();
    if (saved?.id && !selectedConditionIds.value.includes(saved.id)) {
      selectedConditionIds.value = [...selectedConditionIds.value, saved.id];
    }
    ElMessage.success("删除条件已保存");
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : "保存失败");
  } finally {
    addingCondition.value = false;
  }
}

async function removeCondition(id: number) {
  try {
    await deletePatrolDeleteCondition(id);
    selectedConditionIds.value = selectedConditionIds.value.filter(
      (item) => item !== id,
    );
    await loadConditions();
    ElMessage.success("删除条件已删除");
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : "删除失败");
  }
}

async function removeCandidate(id: number) {
  try {
    await deletePatrolDeleteCandidate(id);
    selectedCandidates.value = selectedCandidates.value.filter(
      (item) => item.id !== id,
    );
    await Promise.all([loadCandidates(), loadDashboard()]);
    ElMessage.success("已删除");
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : "删除失败");
  }
}

function mergeMatchedItems(
  base: PatrolDeleteShopQueueItem[],
  incoming: PatrolDeleteShopQueueItem[],
) {
  const map = new Map<string, PatrolDeleteShopQueueItem>();
  for (const item of base) map.set(rowKeyForMatch(item), item);
  for (const item of incoming) map.set(rowKeyForMatch(item), item);
  return Array.from(map.values());
}

async function runMatch() {
  const names = selectedCandidates.value
    .map((item) => item.shop_name)
    .filter(Boolean);
  if (!names.length) {
    ElMessage.warning("请先勾选备选区店铺");
    return;
  }
  matching.value = true;
  try {
    const data = await matchPatrolDeleteShops(names);
    matchedItems.value = mergeMatchedItems(matchedItems.value, data.items || []);
    saveMatchedItems();
    ElMessage.success(`本次返回 ${data.items?.length || 0} 条匹配结果`);
  } catch (error) {
    ElMessage.error(error instanceof Error ? error.message : "匹配失败");
  } finally {
    matching.value = false;
  }
}

function removeMatchedRow(row: PatrolDeleteShopQueueItem) {
  removeMatchedRowLocally(row);
  ElMessage.success("已从匹配结果移除");
}

function buildTaskItem(item: PatrolDeleteShopQueueItem): PatrolDeleteTaskItem {
  return {
    shopName: item.shopName,
    matched: item.matched,
    shopId: item.shopId,
    platform: item.platform,
    companyName: item.companyName,
    matchStatus: item.matchStatus,
    matchMessage: item.matchMessage,
    countrySections: buildTemplateCountrySections(),
    cartRatios: buildTemplateCartRatios(),
  };
}

function selectedDeleteConditions() {
  const selected = new Set(selectedConditionIds.value);
  return deleteConditions.value
    .filter((condition) => selected.has(condition.id))
    .map((condition) => ({
      id: condition.id,
      conditionText: condition.conditionText,
    }));
}

function buildQueuePayload(taskId: number, items: PatrolDeleteHistoryItem[]) {
  const firstItem = items[0];
  const deleteConditionsForTask = selectedDeleteConditions();
  return {
    type: "patrol-delete-run",
    ts: Date.now(),
    data: {
      taskId,
      user_id: Number(uidForStorage()) || 0,
      source: "frontend-vue-patrol-delete",
      delete_conditions: deleteConditionsForTask,
      deleteConditions: deleteConditionsForTask,
      items: items.map((item) => ({
          shopName: item.shopName,
          shopId: item.shopId,
          platform: item.platform,
          companyName: item.companyName,
          matched: item.matched,
          matchStatus: item.matchStatus,
          matchMessage: item.matchMessage,
          countrySections: item.countrySections || [],
          cartRatios: item.cartRatios || [],
      })),
      template_rows: firstItem ? flattenTemplateRows(firstItem) : [],
      shop_template_rows: items.map((item) => ({
        shopName: item.shopName,
        shopId: item.shopId,
        templateRows: flattenTemplateRows(item),
      })),
      country_sections: firstItem?.countrySections || [],
      cart_ratios: firstItem?.cartRatios || [],
    },
  };
}

function findHistoryItemByTaskId(taskId: number) {
  return historyItems.value.find((item) => item.taskId === taskId);
}

async function waitForTaskTerminal(taskId: number) {
  let transientErrorCount = 0;
  while (true) {
    if (disposed) return "STOPPED";
    if (activeTaskId.value !== taskId) return "DELETED";
    try {
      const missingTaskIds = await refreshActiveTaskProgress([taskId]);
      if (missingTaskIds.includes(taskId)) {
        clearActiveQueueTask();
        return "DELETED";
      }
      if (activeTaskId.value !== taskId) return "DELETED";
      if (transientErrorCount > 0) {
        queuePushResult.value = `任务 ${taskId} 后端已恢复，继续等待执行结果...`;
      }
      transientErrorCount = 0;
      const item = findHistoryItemByTaskId(taskId);
      const status = item?.taskStatus || "";
      if (status === "SUCCESS" || status === "FAILED" || status === "COMPLETED") {
        return status;
      }
    } catch (error) {
      if (!isTransientBackendError(error)) {
        throw error;
      }
      transientErrorCount += 1;
      if (transientErrorCount >= MAX_TRANSIENT_ERRORS) {
        throw new Error(
          `任务 ${taskId} 等待后端恢复超时，请稍后刷新查看状态`,
        );
      }
      queuePushResult.value = `任务 ${taskId} 执行中，正在等待后端服务恢复（${transientErrorCount}/${MAX_TRANSIENT_ERRORS}）...`;
    }
    await sleep(getTaskPollIntervalMs());
  }
}

async function processQueue() {
  if (disposed || queueWorkerRunning.value) return;
  queueWorkerRunning.value = true;
  pushing.value = true;
  startHistoryPolling();

  try {
    const api = getPywebviewApi();
    if (!api?.enqueue_json) {
      throw new Error("当前客户端未提供 enqueue_json");
    }

    if (activeTaskId.value) {
      const finalStatus = await waitForTaskTerminal(activeTaskId.value);
      queuePushResult.value =
        finalStatus === "DELETED"
          ? "任务已删除"
          : `任务 ${activeTaskId.value} ${finalStatus === "SUCCESS" ? "已完成" : "执行失败"}`;
      clearActiveQueueTask();
      return;
    }

      const runnable = matchedRunnableItems.value;
      if (!runnable.length) {
        ElMessage.warning("请先匹配可用店铺");
        return;
      }
      const created = await withTransientRetry(
        () => createPatrolDeleteTask(runnable.map(buildTaskItem)),
        (attempt, maxAttempts) => {
          queuePushResult.value = `后端服务暂时不可用，正在重试创建任务（${attempt}/${maxAttempts}）...`;
        },
      );
      const createdItems = created.items || [];
      if (!created.taskId || !createdItems.length || createdItems.some((item) => !item.taskId || !item.resultId)) {
        throw new Error("后端未返回有效任务标识");
      }

      activeTaskId.value = created.taskId;
      saveQueueState();
      upsertHistoryItems(createdItems);
      void loadDashboard();

      const payload = buildQueuePayload(created.taskId, createdItems);
      queuePayloadText.value = JSON.stringify(payload, null, 2);

      const pushResult = await api.enqueue_json(payload);
      if (!pushResult?.success) {
        await submitPatrolDeleteTaskResult(created.taskId, {
          shops: createdItems.map((createdItem) => ({
              shopName: createdItem.shopName || "",
              error: pushResult?.error || `任务 ${created.taskId} 推送失败`,
              countrySections: createdItem.countrySections || [],
              cartRatios: createdItem.cartRatios || [],
              shopDone: true,
              submissionId: createSubmissionId(
                created.taskId,
                createdItem.shopName,
              ),
              chunkIndex: 1,
              chunkTotal: 1,
          })),
        });
        for (const item of runnable) removeMatchedRowLocally(item);
        await refreshTaskViews();
        queuePushResult.value = `任务 ${created.taskId} 推送失败`;
        clearActiveQueueTask();
        return;
      }

      for (const item of runnable) removeMatchedRowLocally(item);
      queuePushResult.value = `任务 ${created.taskId} 已入队，共 ${createdItems.length} 个店铺，等待执行完成`;

    const finalStatus = await waitForTaskTerminal(created.taskId);
    queuePushResult.value = `任务 ${created.taskId} ${finalStatus === "SUCCESS" ? "已完成" : finalStatus === "DELETED" ? "已删除" : "执行失败"}`;
    clearActiveQueueTask();
    await refreshTaskViews();
    ElMessage.success("巡店删除任务已完成");
  } catch (error) {
    if (disposed) return;
    const message = error instanceof Error ? error.message : "队列执行失败";
    queuePushResult.value = message;
    ElMessage.error(message);
    await refreshTaskViews();
  } finally {
    queueWorkerRunning.value = false;
    pushing.value = false;
    stopHistoryPolling();
    saveQueueState();
  }
}

async function pushToPythonQueue() {
  const runnable = matchedRunnableItems.value;
  if (!runnable.length) {
    ElMessage.warning("请先匹配可用店铺");
    return;
  }
  queuePushResult.value = `开始创建任务，共 ${runnable.length} 个店铺`;
  await processQueue();
}

async function downloadResult(item: PatrolDeleteHistoryItem) {
  const downloadable = taskGroupItems(item).find((row) =>
    Boolean(row.resultId && (row.fileReady || row.downloadUrl)),
  );
  if (!downloadable?.resultId) return;
  const url = getPatrolDeleteResultDownloadUrl(downloadable.resultId);
  const filename =
    downloadable.outputFilename || `${item.shopName || "patrol-delete-result"}.xlsx`;
  const result = await saveUrlWithProgress(url, filename, `patrol-delete:${downloadable.resultId}`);
  if (result.success) {
    ElMessage.success(`已保存: ${result.path || filename}`);
  } else if (result.error && result.error !== "用户取消") {
    ElMessage.error(result.error);
  }
}

async function deleteTaskRecord(item: PatrolDeleteHistoryItem) {
  try {
    if (item.taskId) {
      await deletePatrolDeleteTask(item.taskId);
      if (activeTaskId.value === item.taskId) {
        clearActiveQueueTask();
      }
    } else if (item.resultId) {
      await deletePatrolDeleteHistory(item.resultId);
    } else {
      throw new Error("缺少可删除的任务标识");
    }
    removeHistoryItemLocally(item);
    await Promise.allSettled([loadCandidates(), refreshTaskViews()]);
    ElMessage.success("已删除");
  } catch (error) {
    if (isRecordMissingError(error)) {
      if (item.taskId && activeTaskId.value === item.taskId) {
        clearActiveQueueTask();
      }
      removeHistoryItemLocally(item);
      await loadCandidates();
      ElMessage.success("后端记录已不存在，已同步移除本地记录");
      return;
    }
    ElMessage.error(error instanceof Error ? error.message : "删除失败");
  }
}

onMounted(async () => {
  loadMatchedItems();
  loadQueueState();
  await Promise.all([loadCandidates(), loadConditions(), loadDashboard(), loadHistory()]);

  if (activeTaskId.value) {
    queuePushResult.value =
      activeTaskId.value != null
        ? `检测到未完成任务，继续等待任务 ${activeTaskId.value} 完成`
        : "";
    void processQueue();
  }
});

onUnmounted(() => {
  disposed = true;
  stopHistoryPolling();
  clearSleepTimers();
  timers.clearScope();
});
</script>

<style scoped>
.module-page { min-height: 100vh; background: #1a1a1a; }
.main-content { display: flex; min-height: calc(100vh - 56px); height: calc(100vh - 56px); }
.left-panel { width: 400px; background: #1e1e1e; padding: 20px; overflow-y: auto; border-right: 1px solid #2a2a2a; }
.right-panel { flex: 1; display: flex; flex-direction: column; min-width: 0; background: #1a1a1a; }
.section-title { font-size: 13px; color: #bbb; margin-bottom: 10px; }
.condition-title { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.condition-title-hint { flex: 1; min-width: 0; color: #d6a95a; font-size: 12px; text-align: right; line-height: 1.35; }
.input-zone { border: 1px dashed #3a3a3a; border-radius: 10px; padding: 16px; background: #252525; margin-bottom: 20px; }
.hint { color: #888; font-size: 12px; margin-bottom: 12px; line-height: 1.5; text-align: left; }
.input-row { display: flex; gap: 10px; align-items: center; }
.input-row :deep(.el-input) { flex: 1; }
.opt-btn { padding: 8px 12px; font-size: 12px; color: #ccc; background: #2a2a2a; border: 1px solid #3a3a3a; border-radius: 6px; cursor: pointer; white-space: nowrap; }
.opt-btn:hover:not(:disabled) { color: #3498db; border-color: #3498db; }
.opt-btn:disabled { opacity: 0.6; cursor: not-allowed; }
.empty-candidates { color: #666; font-size: 13px; padding: 16px; border: 1px dashed #333; border-radius: 8px; margin-bottom: 16px; }
.candidate-table-scroll { max-height: 320px; overflow: auto; margin-bottom: 18px; border-radius: 8px; border: 1px solid #2a2a2a; }
.candidate-table { --el-table-bg-color: #252525; --el-table-tr-bg-color: #252525; --el-table-header-bg-color: #2a2a2a; --el-table-text-color: #ccc; --el-table-border-color: #333; }
.condition-panel { margin-bottom: 18px; border: 1px solid #2a2a2a; border-radius: 8px; background: #222; padding: 12px; }
.condition-input-row { display: flex; gap: 10px; align-items: center; margin-bottom: 10px; }
.condition-input-row :deep(.el-input) { flex: 1; }
.empty-conditions { color: #666; font-size: 12px; padding: 10px 4px; }
.condition-list { list-style: none; margin: 0; padding: 0; max-height: 150px; overflow: auto; display: flex; flex-direction: column; gap: 8px; }
.condition-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 6px; border-bottom: 1px solid #303030; }
.condition-item:last-child { border-bottom: none; }
.condition-check { display: flex; align-items: center; gap: 8px; min-width: 0; color: #cfd6df; font-size: 12px; cursor: pointer; }
.condition-check input { width: 16px; height: 16px; margin: 0; flex: 0 0 auto; }
.condition-check span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.link-danger { background: none; border: none; color: #e74c3c; cursor: pointer; font-size: 12px; padding: 0; }
.link-danger:hover { text-decoration: underline; }
.run-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 8px; }
.btn-run { padding: 10px 18px; font-size: 14px; font-weight: 600; color: #fff; background: #3498db; border: none; border-radius: 8px; cursor: pointer; }
.btn-run:hover:not(:disabled) { background: #2980b9; }
.btn-run:disabled { opacity: 0.55; cursor: not-allowed; }
.btn-queue { background: #27ae60; }
.btn-queue:hover:not(:disabled) { background: #219a52; }
.loading-msg { margin-top: 12px; font-size: 12px; color: #777; line-height: 1.5; }
.queue-debug-card { margin-top: 18px; padding: 14px; border: 1px solid #2a2a2a; border-radius: 10px; background: #202020; }
.queue-debug-title { margin-bottom: 8px; }
.queue-debug-line { color: #b8c1cc; font-size: 12px; line-height: 1.6; margin-bottom: 8px; }
.queue-debug-payload { margin: 0; max-height: 220px; overflow: auto; padding: 10px; border-radius: 8px; background: #141414; color: #8fd3ff; font-size: 11px; line-height: 1.5; white-space: pre-wrap; word-break: break-all; }
.panel-header { padding: 16px 20px; font-size: 15px; font-weight: 600; color: #e0e0e0; border-bottom: 1px solid #2a2a2a; }
.task-list-wrap { flex: 1; padding: 16px 20px; overflow: auto; }
.clean-result-summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin-bottom: 16px; }
.summary-card { padding: 14px 16px; border: 1px solid #2a2a2a; border-radius: 10px; background: #1e1e1e; }
.summary-card strong { display: block; margin-top: 8px; font-size: 22px; color: #eaf4ff; }
.summary-label { font-size: 12px; color: #8d8d8d; }
.subsection-title { font-size: 13px; color: #999; margin: 8px 0 10px; }
.empty-tasks { color: #666; font-size: 13px; padding: 16px; text-align: center; }
.empty-tasks.narrow { padding: 12px 8px; }
.match-table { margin-bottom: 18px; }
.match-table :deep(.el-table__body tr:hover > td.el-table__cell), .match-table :deep(.el-table__body tr.hover-row > td.el-table__cell), .match-table :deep(.el-table__body tr.current-row > td.el-table__cell) { background-color: var(--el-table-tr-bg-color, #222) !important; }
.result-table { --el-table-bg-color: #222; --el-table-tr-bg-color: #222; --el-table-header-bg-color: #2a2a2a; --el-table-text-color: #ccc; --el-table-border-color: #333; }
.ok { color: #27ae60; }
.fail { color: #e67e22; }
.result-list-wrap { border: 1px solid #2a2a2a; border-radius: 10px; background: #1e1e1e; min-height: 220px; margin-top: 8px; }
.result-list-header { padding: 12px 16px; border-bottom: 1px solid #2a2a2a; font-size: 14px; color: #ddd; }
.result-subsection { padding: 12px 12px 4px; }
.result-subsection-title { font-size: 12px; color: #888; margin-bottom: 8px; }
.task-list { list-style: none; margin: 0; padding: 0; }
.task-item { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 12px 14px; border: 1px solid #2a2a2a; border-radius: 8px; margin-bottom: 8px; background: #222; }
.left { flex: 1; min-width: 0; }
.split-result-main { display: flex; flex-direction: column; gap: 4px; }
.id { font-weight: 600; color: #e0e0e0; font-size: 13px; }
.files { font-size: 12px; color: #888; }
.task-right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.status { padding: 4px 10px; border-radius: 6px; font-size: 12px; white-space: nowrap; }
.status.success { background: rgba(46, 204, 113, 0.18); color: #2ecc71; }
.status.failed { background: rgba(231, 76, 60, 0.18); color: #ff6b6b; }
.status.running { background: rgba(52, 152, 219, 0.18); color: #3498db; }
.archive { padding: 6px 10px; border-radius: 6px; font-size: 12px; background: rgba(52, 152, 219, 0.18); color: #69b6ff; border: none; cursor: pointer; }
.archive:hover { background: rgba(52, 152, 219, 0.28); }
.btn-delete { padding: 6px 10px; border-radius: 6px; font-size: 12px; background: rgba(231, 76, 60, 0.12); color: #ff8f8f; border: none; cursor: pointer; }
.btn-delete:hover { background: rgba(231, 76, 60, 0.22); }
@media (max-width: 1100px) {
  .main-content { flex-direction: column; height: auto; }
  .left-panel { width: 100%; border-right: none; border-bottom: 1px solid #2a2a2a; }
  .clean-result-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
</style>
