﻿<template>
  <div class="page-shell module-page">
    <BrandTopBar active="query-asin" />

    <div class="main-content">
      <aside class="left-panel">
        <div class="section-title">店铺输入</div>
        <div class="input-zone">
          <div class="hint">
            左侧负责录入店铺并加入备选区，确认命中后推送到 Python 队列。查询ASIN按店铺串行执行：上一个任务完成后会自动开始下一个，失败也会继续下一个。
          </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="run-row">
          <button
            type="button"
            class="btn-run"
            :disabled="matching"
            @click="runMatch"
          >
            {{ matching ? "匹配中..." : "匹配店铺" }}
          </button>
          <button
            type="button"
            class="btn-run btn-queue"
            :disabled="isQueueBusy || !matchedRunnableItems.length"
            @click="pushToPythonQueue"
          >
            {{ isQueueBusy ? "串行执行中..." : "推送到 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="`${item.resultId}-${item.taskId}`"
                    class="task-item"
                  >
                    <div class="left split-result-main">
                      <span class="id" :title="item.shopName || ''">
                        {{ item.shopName || "-" }}
                      </span>
                      <div class="files">任务 ID: {{ item.taskId ?? "-" }}</div>
                      <div v-if="item.resultId" class="files">
                        结果 ID: {{ item.resultId }}
                      </div>
                      <div v-if="item.shopId" class="files">
                        店铺 ID: {{ item.shopId }}
                      </div>
                      <div v-if="item.platform" class="files">
                        平台: {{ item.platform }}
                      </div>
                      <div class="files">
                        创建时间: {{ formatDateTime(item.createdAt) }}
                      </div>
                      <div class="files">
                        ASIN结构: {{ formatTemplateSummary(item) }}
                      </div>
                      <div v-if="item.error" class="files">
                        错误: {{ item.error }}
                      </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="`${item.resultId}-${item.taskId}`"
                    class="task-item"
                  >
                    <div class="left split-result-main">
                      <span class="id" :title="item.shopName || ''">
                        {{ item.shopName || "-" }}
                      </span>
                      <div class="files">任务 ID: {{ item.taskId ?? "-" }}</div>
                      <div v-if="item.resultId" class="files">
                        结果 ID: {{ item.resultId }}
                      </div>
                      <div v-if="item.shopId" class="files">
                        店铺 ID: {{ item.shopId }}
                      </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">
                        ASIN结构: {{ formatTemplateSummary(item) }}
                      </div>
                      <div v-if="item.error" class="files">
                        错误: {{ item.error }}
                      </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 {
  addQueryAsinCandidate,
  createQueryAsinTask,
  deleteQueryAsinCandidate,
  deleteQueryAsinHistory,
  deleteQueryAsinTask,
  getQueryAsinDashboard,
  getQueryAsinHistory,
  getQueryAsinTaskProgressBatch,
  getQueryAsinResultDownloadUrl,
  listQueryAsinCandidates,
  matchQueryAsinShops,
  submitQueryAsinTaskResult,
  type QueryAsinCandidateVo,
  type QueryAsinCountryAsins,
  type QueryAsinDashboardVo,
  type QueryAsinHistoryItem,
  type QueryAsinShopQueueItem,
  type QueryAsinTaskItem,
} 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";

const MAX_TRANSIENT_ERRORS = 30;

const shopInput = ref("");
const candidates = ref<QueryAsinCandidateVo[]>([]);
const selectedCandidates = ref<QueryAsinCandidateVo[]>([]);
const matchedItems = ref<QueryAsinShopQueueItem[]>([]);
const historyItems = ref<QueryAsinHistoryItem[]>([]);
const dashboard = ref<QueryAsinDashboardVo>({
  candidateCount: 0,
  processedTaskCount: 0,
  successTaskCount: 0,
  failedTaskCount: 0,
});
const adding = ref(false);
const matching = ref(false);
const pushing = ref(false);
const queuePushResult = ref("");
const queuePayloadText = ref("");
const pendingQueue = ref<QueryAsinShopQueueItem[]>([]);
const activeTaskId = ref<number | null>(null);
const activeQueueItem = ref<QueryAsinShopQueueItem | null>(null);
const queueWorkerRunning = ref(false);
const autoQueueEnabled = ref(false);
let historyPollTimer: number | null = null;
const timers = createCategorizedTimers("query-asin");

const matchedRunnableItems = computed(() =>
  matchedItems.value.filter((item) => item.matched && (item.queryAsins || []).some((row) => row.asins?.length)),
);
const currentSectionItems = computed(() =>
  historyItems.value.filter((item) => !isTaskTerminal(item.taskStatus)),
);
const historySectionItems = computed(() =>
  historyItems.value.filter((item) => isTaskTerminal(item.taskStatus)),
);
const hasQueuedTaskWork = computed(
  () =>
    !!activeTaskId.value ||
    pendingQueue.value.length > 0,
);
const hasQueueWork = computed(() => queueWorkerRunning.value || hasQueuedTaskWork.value);
const isQueueBusy = computed(() => queueWorkerRunning.value && hasQueuedTaskWork.value);

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

function matchedStorageKey() {
  return `query-asin:matched:${uidForStorage()}`;
}

function queueStateStorageKey() {
  return `query-asin:queue-state:${uidForStorage()}`;
}

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

function historyItemKey(item: QueryAsinHistoryItem) {
  return `${item.taskId ?? 0}:${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 `query-asin:${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: QueryAsinShopQueueItem) {
  const message = (row.matchMessage || "").trim();
  if (message) return message;
  if (row.matched && row.matchStatus === "MATCHED") {
    return "索引已命中，可推送到 Python 队列";
  }
  if (row.matched) {
    return "已命中索引，请结合状态列确认";
  }
  return "未命中或未就绪，请检查店铺名";
}

function flattenQueryAsinRows(record: Pick<QueryAsinHistoryItem, "shopName" | "queryAsins">) {
  const sections = readQueryAsins(record);
  const rowCount = Math.max(...sections.map((section) => section.asins?.length || 0), 1);
  return Array.from({ length: rowCount }, (_, rowIndex) => ({
    shopName: rowIndex === 0 ? record.shopName : "",
    countries: sections.map((section) => ({
      country: section.country,
      asin: section.asins?.[rowIndex] || "",
    })),
  }));
}

function formatTemplateSummary(record: Pick<QueryAsinHistoryItem, "shopName" | "queryAsins">) {
  const sections = readQueryAsins(record);
  const total = sections.reduce((sum, section) => sum + (section.asins?.length || 0), 0);
  const countries = sections.filter((section) => section.asins?.length).map((section) => section.country).join("、");
  return `${countries || "无国家"}，共 ${total} 个 ASIN`;
}

function copyQueryAsins(items?: QueryAsinCountryAsins[]) {
  return (items || [])
    .map((item) => ({
      country: (item.country || "").trim().toUpperCase(),
      asins: Array.from(new Set((item.asins || []).map((asin) => (asin || "").trim().toUpperCase()).filter(Boolean))),
    }))
    .filter((item) => item.country && item.asins.length);
}

function readQueryAsins(record?: { queryAsins?: QueryAsinCountryAsins[]; query_asins?: QueryAsinCountryAsins[] } | null) {
  return copyQueryAsins(record?.queryAsins?.length ? record.queryAsins : record?.query_asins);
}

function hasQueryAsins(item?: { queryAsins?: QueryAsinCountryAsins[]; query_asins?: QueryAsinCountryAsins[] } | null) {
  return readQueryAsins(item).some((section) => section.asins.length);
}

function fallbackQueryAsinsForShop(shopName?: string) {
  const normalizedShopName = (shopName || "").trim();
  const candidates = [
    activeQueueItem.value,
    ...pendingQueue.value,
    ...matchedItems.value,
  ];
  const exact = candidates.find((item) => (item?.shopName || "").trim() === normalizedShopName && hasQueryAsins(item));
  const any = candidates.find((item) => hasQueryAsins(item));
  return readQueryAsins(exact || any);
}

function withQueryAsinFallback<T extends QueryAsinHistoryItem | QueryAsinShopQueueItem>(
  item: T,
  fallback?: QueryAsinShopQueueItem | QueryAsinHistoryItem | null,
) {
  const queryAsins = hasQueryAsins(item)
    ? readQueryAsins(item)
    : readQueryAsins(fallback || undefined);
  return {
    ...item,
    queryAsins: queryAsins.length ? queryAsins : fallbackQueryAsinsForShop(item.shopName),
  };
}

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: QueryAsinHistoryItem) {
  return Boolean(item.resultId && (item.fileReady || item.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 = {
    pendingQueue: pendingQueue.value,
    activeTaskId: activeTaskId.value,
    activeQueueItem: activeQueueItem.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 {
      pendingQueue?: QueryAsinShopQueueItem[];
      activeTaskId?: number | null;
      activeQueueItem?: QueryAsinShopQueueItem | null;
    };
    pendingQueue.value = parsed.pendingQueue || [];
    activeTaskId.value = parsed.activeTaskId ?? null;
    activeQueueItem.value = parsed.activeQueueItem ?? null;
  } catch {
    pendingQueue.value = [];
    activeTaskId.value = null;
    activeQueueItem.value = null;
  }
}

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

function resetQueueWorkerIfIdle() {
  if (hasQueuedTaskWork.value) {
    return;
  }
  queueWorkerRunning.value = false;
  pushing.value = false;
  autoQueueEnabled.value = false;
  stopHistoryPolling();
  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: QueryAsinShopQueueItem) {
  matchedItems.value = matchedItems.value.filter(
    (item) => rowKeyForMatch(item) !== rowKeyForMatch(row),
  );
  saveMatchedItems();
}

function removeHistoryItemLocally(item: QueryAsinHistoryItem) {
  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;
  });
}

function mergeQueueItems(
  base: QueryAsinShopQueueItem[],
  incoming: QueryAsinShopQueueItem[],
) {
  const map = new Map<string, QueryAsinShopQueueItem>();
  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 loadCandidates() {
  candidates.value = await listQueryAsinCandidates();
}

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

async function loadHistory() {
  const data = await getQueryAsinHistory();
  historyItems.value = (data.items || []).map((item) => withQueryAsinFallback(item));
}

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

function reconcileActiveQueueTaskWithHistory() {
  if (!activeTaskId.value) {
    resetQueueWorkerIfIdle();
    return;
  }
  const exists = historyItems.value.some((item) => item.taskId === activeTaskId.value);
  if (exists) {
    return;
  }
  const missingTaskId = activeTaskId.value;
  clearActiveQueueTask();
  queuePushResult.value = pendingQueue.value.length
    ? `任务 ${missingTaskId} 已不存在，已跳过并继续剩余队列`
    : "当前任务已不存在，已释放本地队列状态";
  resetQueueWorkerIfIdle();
}

function mergeHistoryProgressItems(incoming: QueryAsinHistoryItem[]) {
  if (!incoming.length) {
    return;
  }
  const incomingMap = new Map(
    incoming.map((item) => [historyItemKey(item), withQueryAsinFallback(item)] as const),
  );
  const merged = historyItems.value.map((item) => {
    const next = incomingMap.get(historyItemKey(item));
    if (!next) {
      return withQueryAsinFallback(item);
    }
    const queryAsins = hasQueryAsins(next)
      ? readQueryAsins(next)
      : readQueryAsins(item).length
        ? readQueryAsins(item)
        : fallbackQueryAsinsForShop(next.shopName || item.shopName);
    return {
      ...item,
      ...next,
      queryAsins,
    };
  });
  for (const item of incoming) {
    const key = historyItemKey(item);
    if (!merged.some((current) => historyItemKey(current) === key)) {
      merged.push(withQueryAsinFallback(item));
    }
  }
  historyItems.value = merged;
}

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 getQueryAsinTaskProgressBatch(ids);
  mergeHistoryProgressItems(batch.items || []);
  if ((batch.missingTaskIds || []).length) {
    await loadHistory();
    reconcileActiveQueueTaskWithHistory();
  }
}

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: QueryAsinCandidateVo[]) {
  selectedCandidates.value = rows;
}

async function confirmAdd() {
  const name = shopInput.value.trim();
  if (!name) {
    ElMessage.warning("请输入店铺名");
    return;
  }
  adding.value = true;
  try {
    await addQueryAsinCandidate(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 removeCandidate(id: number) {
  try {
    await deleteQueryAsinCandidate(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: QueryAsinShopQueueItem[],
  incoming: QueryAsinShopQueueItem[],
) {
  const map = new Map<string, QueryAsinShopQueueItem>();
  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 matchQueryAsinShops(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: QueryAsinShopQueueItem) {
  if (hasQueueWork.value) {
    ElMessage.warning("队列执行中，请等待当前串行任务结束后再调整");
    return;
  }
  matchedItems.value = matchedItems.value.filter(
    (item) => rowKeyForMatch(item) !== rowKeyForMatch(row),
  );
  saveMatchedItems();
}

function buildTaskItem(item: QueryAsinShopQueueItem): QueryAsinTaskItem {
  return {
    shopName: item.shopName,
    matched: item.matched,
    shopId: item.shopId,
    platform: item.platform,
    companyName: item.companyName,
    matchStatus: item.matchStatus,
    matchMessage: item.matchMessage,
    queryAsins: copyQueryAsins(item.queryAsins),
  };
}

function buildQueuePayload(taskId: number, item: QueryAsinHistoryItem) {
  const submissionId = createSubmissionId(taskId, item.shopName);
  return {
    type: "query-asin-run",
    ts: Date.now(),
    data: {
      taskId,
      user_id: Number(uidForStorage()) || 0,
      source: "frontend-vue-query-asin",
      submissionId,
      items: [
        {
          shopName: item.shopName,
          submissionId,
          shopId: item.shopId,
          platform: item.platform,
          companyName: item.companyName,
          matched: item.matched,
          matchStatus: item.matchStatus,
          matchMessage: item.matchMessage,
          queryAsins: copyQueryAsins(item.queryAsins),
        },
      ],
      query_asins: copyQueryAsins(item.queryAsins),
      query_asin_rows: flattenQueryAsinRows(item),
    },
  };
}

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";
    try {
      await refreshActiveTaskProgress([taskId]);
      if (activeTaskId.value !== taskId) {
        return "FAILED";
      }
      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");
    }

    while (!disposed && (activeTaskId.value || pendingQueue.value.length)) {
      if (activeTaskId.value) {
        const finalStatus = await waitForTaskTerminal(activeTaskId.value);
        queuePushResult.value =
          pendingQueue.value.length > 0
            ? `任务 ${activeTaskId.value} ${finalStatus === "SUCCESS" ? "已完成" : "执行失败"}，自动继续下一个（剩余 ${pendingQueue.value.length} 条）`
            : `任务 ${activeTaskId.value} ${finalStatus === "SUCCESS" ? "已完成" : "执行失败"}`;
        clearActiveQueueTask();
        continue;
      }

      const nextItem = pendingQueue.value.shift();
      saveQueueState();
      if (!nextItem) break;

      const created = await withTransientRetry(
        () => createQueryAsinTask([buildTaskItem(nextItem)]),
        (attempt, maxAttempts) => {
          queuePushResult.value = `后端服务暂时不可用，正在重试创建任务（${attempt}/${maxAttempts}）...`;
        },
      );
      const createdItem = created.items?.[0]
        ? withQueryAsinFallback(created.items[0], nextItem)
        : undefined;
      if (!createdItem?.taskId || !createdItem.resultId) {
        throw new Error("后端未返回有效任务标识");
      }

      activeTaskId.value = created.taskId;
      activeQueueItem.value = withQueryAsinFallback(nextItem, createdItem);
      saveQueueState();
      await refreshTaskViews();

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

      const pushResult = await api.enqueue_json(payload);
      if (!pushResult?.success) {
        await submitQueryAsinTaskResult(created.taskId, {
          shops: [
            {
              shopName: createdItem.shopName || nextItem.shopName || "",
              error: pushResult?.error || `任务 ${created.taskId} 推送失败`,
              shopDone: true,
              submissionId: createSubmissionId(
                created.taskId,
                createdItem.shopName || nextItem.shopName,
              ),
            },
          ],
        });
        await refreshTaskViews();
        queuePushResult.value = `任务 ${created.taskId} 推送失败，已自动继续下一个任务`;
        clearActiveQueueTask();
        continue;
      }

      queuePushResult.value =
        pendingQueue.value.length > 0
          ? `任务 ${created.taskId} 已入队，等待完成后自动继续下一个（剩余 ${pendingQueue.value.length} 条）`
          : `任务 ${created.taskId} 已入队，等待执行完成`;
    }

    queuePushResult.value = "串行队列已执行完成";
    await refreshTaskViews();
    ElMessage.success("查询ASIN队列已按顺序执行完成");
  } 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() {
  autoQueueEnabled.value = true;
  resetQueueWorkerIfIdle();
  const runnable = matchedRunnableItems.value;
  if (!runnable.length) {
    ElMessage.warning("请先匹配可用店铺");
    return;
  }
  pendingQueue.value = mergeQueueItems(pendingQueue.value, runnable);
  saveQueueState();
  queuePushResult.value = `已加入 ${runnable.length} 条店铺，开始串行执行`;
  await processQueue();
}

async function downloadResult(item: QueryAsinHistoryItem) {
  if (!item.resultId) return;
  const api = getPywebviewApi();
  if (!api?.save_file_from_url_new) {
    ElMessage.error("当前客户端未提供下载能力");
    return;
  }
  const url = getQueryAsinResultDownloadUrl(item.resultId);
  const filename = item.outputFilename || `${item.shopName || "result"}.xlsx`;
  const result = await api.save_file_from_url_new(url, filename);
  if (result.success) {
    ElMessage.success(`已保存: ${result.path || filename}`);
  } else if (result.error && result.error !== "用户取消") {
    ElMessage.error(result.error);
  }
}

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

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

  if (activeTaskId.value || pendingQueue.value.length) {
    autoQueueEnabled.value = true;
    queuePushResult.value =
      activeTaskId.value != null
        ? `检测到未完成队列，继续等待任务 ${activeTaskId.value} 完成并自动接续后续店铺`
        : `检测到未完成队列，继续执行剩余 ${pendingQueue.value.length} 条店铺`;
    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; }
.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; }
.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>

