import { createRoot } from "react-dom/client";
import { useEffect, useMemo, useState } from "react";

type ProcessType = "lead_analysis" | "category_analysis";
type SortDirection = "ASC" | "DESC";

type Run = {
  processType: ProcessType;
  runId: number;
  timestamp: string;
  status: string;
  jobType: string;
  source: string | null;
  output: string | null;
  totalProducts: number;
  fbaCount: number;
  fbmCount: number;
  skipCount: number;
};

type RunsResponse = {
  items: Run[];
  page: number;
  pageSize: number;
  total: number;
  totalPages: number;
};

type RunDetail = {
  processType: ProcessType;
  runId: number;
  timestamp: string;
  status: string;
  jobType: string;
  source: string | null;
  output: string | null;
  totalProducts: number;
  fbaCount: number;
  fbmCount: number;
  skipCount: number;
  summary: {
    totalProducts: number;
    fbaCount: number;
    fbmCount: number;
    skipCount: number;
  };
  errorMessage?: string;
  availableAsins?: number;
};

type ResultItem = {
  id?: number;
  run_id: number;
  asin: string;
  product_name: string | null;
  brand: string | null;
  category: string | null;
  current_price: number | null;
  avg_price_90d: number | null;
  sales_rank: number | null;
  seller_count: number | null;
  monthly_sold: number | null;
  verdict: "FBA" | "FBM" | "SKIP";
  amazon_is_seller: number | null;
  amazon_buybox_share_pct_90d: number | null;
  confidence: number | null;
  sellability_status: string | null;
  reasoning: string | null;
  fetched_at: string;
};

type ResultsResponse = {
  items: ResultItem[];
  page: number;
  pageSize: number;
  total: number;
  totalPages: number;
};

type VerdictFilter = "" | "FBA" | "FBM" | "SKIP";
type AmazonSellerFilter = "" | "yes" | "no";

type ProductListItem = {
  processType: ProcessType;
  runId: number;
  asin: string;
  product_name: string | null;
  brand: string | null;
  category: string | null;
  verdict: "FBA" | "FBM" | "SKIP";
  confidence: number | null;
  sellability_status: string | null;
  monthly_sold: number | null;
  seller_count: number | null;
  amazon_is_seller: number | null;
  amazon_buybox_share_pct_90d: number | null;
  sales_rank: number | null;
  current_price: number | null;
  avg_price_90d: number | null;
  reasoning: string | null;
  fetched_at: string;
};

type ProductListResponse = {
  items: ProductListItem[];
  page: number;
  pageSize: number;
  total: number;
  totalPages: number;
};

type StalkerResultItem = {
  runId: number;
  started_at: string;
  status: string;
  input_file: string;
  seller_id: string;
  seller_name: string | null;
  rating: number | null;
  rating_count: number | null;
  storefront_asin_total: number | null;
  persisted_inventory_sample_count: number | null;
  discovered_from_count: number;
  first_seen_at: string;
  last_seen_at: string;
  persisted_inventory_asin_count: number;
  inventory_sample_asins: string | null;
};

type StalkerResultsResponse = {
  items: StalkerResultItem[];
  summary: {
    runs: number;
    sellers: number;
    persistedInventoryAsins: number;
  };
  page: number;
  pageSize: number;
  total: number;
  totalPages: number;
};

type StalkerProductItem = {
  runId: number;
  started_at: string;
  seller_id: string;
  seller_name: string | null;
  rating: number | null;
  rating_count: number | null;
  asin: string;
  can_sell: number;
  sellability_status: string;
  sellability_reason: string | null;
  product_title: string | null;
  brand: string | null;
  category_tree: string | null;
  current_price: number | null;
  avg_price_90d: number | null;
  sales_rank: number | null;
  monthly_sold: number | null;
  seller_count: number | null;
  amazon_is_seller: number | null;
  verdict: "FBA" | "FBM" | "SKIP" | null;
  confidence: number | null;
  reasoning: string | null;
  last_seen_at: string;
};

type StalkerProductsResponse = {
  items: StalkerProductItem[];
  summary: {
    runs: number;
    sellers: number;
    products: number;
  };
  page: number;
  pageSize: number;
  total: number;
  totalPages: number;
};

type SortState = {
  field: string;
  direction: SortDirection;
};

function formatDate(input: string): string {
  const d = new Date(input);
  if (Number.isNaN(d.getTime())) return input;
  return d.toLocaleString();
}

function formatNumber(value: number | null | undefined): string {
  if (value === null || value === undefined) return "-";
  return new Intl.NumberFormat().format(value);
}

function formatCurrency(value: number | null | undefined): string {
  if (value === null || value === undefined) return "-";
  return new Intl.NumberFormat(undefined, {
    style: "currency",
    currency: "USD",
    maximumFractionDigits: 2,
  }).format(value);
}

function formatAmazonSeller(value: number | null | undefined): string {
  if (value === null || value === undefined) return "-";
  return value === 1 ? "Yes" : "No";
}

function formatBoolean(value: number | null | undefined): string {
  if (value === null || value === undefined) return "-";
  return value === 1 ? "Yes" : "No";
}

function parseStringArrayJson(value: string | null | undefined): string[] {
  if (!value) return [];
  try {
    const parsed = JSON.parse(value);
    return Array.isArray(parsed)
      ? parsed.filter((item): item is string => typeof item === "string")
      : [];
  } catch {
    return [];
  }
}

function buildSortValue(sort: SortState): string {
  return `${sort.field}:${sort.direction}`;
}

function nextSort(current: SortState, field: string): SortState {
  if (current.field !== field) {
    return { field, direction: "ASC" };
  }
  return {
    field,
    direction: current.direction === "ASC" ? "DESC" : "ASC",
  };
}

function statusBadgeClass(status: string): string {
  if (status === "ok" || status === "completed") return "badge badge-ok";
  if (status === "failed") return "badge badge-failed";
  return "badge badge-empty";
}

function verdictBadgeClass(verdict: string): string {
  if (verdict === "FBA") return "badge badge-fba";
  if (verdict === "FBM") return "badge badge-fbm";
  return "badge badge-skip";
}

function TinyBar({ fba, fbm, skip }: { fba: number; fbm: number; skip: number }) {
  const total = Math.max(1, fba + fbm + skip);
  const fbaPct = (fba / total) * 100;
  const fbmPct = (fbm / total) * 100;
  const skipPct = (skip / total) * 100;

  return (
    <div className="tiny-bar" title={`FBA ${fba}, FBM ${fbm}, SKIP ${skip}`}>
      <span className="tiny-fba" style={{ width: `${fbaPct}%` }} />
      <span className="tiny-fbm" style={{ width: `${fbmPct}%` }} />
      <span className="tiny-skip" style={{ width: `${skipPct}%` }} />
    </div>
  );
}

function detectAnomaly(item: ResultItem): string {
  const confidence = item.confidence ?? 0;
  if ((item.sellability_status === "restricted" || item.sellability_status === "not_available") && item.verdict !== "SKIP") {
    return "restricted-vs-verdict";
  }
  if (item.verdict === "FBA" && confidence < 60) {
    return "low-confidence-fba";
  }
  if ((item.sales_rank ?? Number.MAX_SAFE_INTEGER) > 300000 && item.verdict === "FBA") {
    return "high-rank-fba";
  }
  return "";
}

function Dashboard({
  onOpenRun,
  onOpenProducts,
  onOpenStalker,
  onOpenStalkerProducts,
}: {
  onOpenRun: (run: Run) => void;
  onOpenProducts: (verdict: VerdictFilter) => void;
  onOpenStalker: () => void;
  onOpenStalkerProducts: () => void;
}) {
  const [runs, setRuns] = useState<RunsResponse | null>(null);
  const [loading, setLoading] = useState(false);
  const [search, setSearch] = useState("");
  const [processType, setProcessType] = useState("");
  const [status, setStatus] = useState("");
  const [startDate, setStartDate] = useState("");
  const [endDate, setEndDate] = useState("");
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(25);
  const [sort, setSort] = useState<SortState>({ field: "timestamp", direction: "DESC" });
  const [refreshTick, setRefreshTick] = useState(0);
  const [deletingKey, setDeletingKey] = useState<string | null>(null);

  const summary = useMemo(() => {
    if (!runs) return { total: 0, fba: 0, fbm: 0, skip: 0 };
    return runs.items.reduce(
      (acc, run) => {
        acc.total += run.totalProducts;
        acc.fba += run.fbaCount;
        acc.fbm += run.fbmCount;
        acc.skip += run.skipCount;
        return acc;
      },
      { total: 0, fba: 0, fbm: 0, skip: 0 },
    );
  }, [runs]);

  const timeline = useMemo(() => {
    if (!runs) return [] as Run[];
    return [...runs.items]
      .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime())
      .slice(-12);
  }, [runs]);

  useEffect(() => {
    let cancelled = false;
    async function load() {
      setLoading(true);
      const params = new URLSearchParams({
        page: String(page),
        pageSize: String(pageSize),
        sort: buildSortValue(sort),
      });
      if (search) params.set("q", search);
      if (processType) params.set("processType", processType);
      if (status) params.set("status", status);
      if (startDate) params.set("startDate", `${startDate}T00:00:00.000Z`);
      if (endDate) params.set("endDate", `${endDate}T23:59:59.999Z`);

      const resp = await fetch(`/api/runs?${params.toString()}`);
      const data = (await resp.json()) as RunsResponse;
      if (!cancelled) {
        setRuns(data);
        setLoading(false);
      }
    }

    load();
    return () => {
      cancelled = true;
    };
  }, [search, processType, status, startDate, endDate, page, pageSize, sort, refreshTick]);

  async function deleteRun(run: Run) {
    const key = `${run.processType}-${run.runId}`;
    const confirmed = window.confirm(`Delete run ${run.runId} (${run.processType}) and all associated results?`);
    if (!confirmed) return;

    setDeletingKey(key);
    try {
      const response = await fetch(`/api/runs/${run.processType}/${run.runId}`, { method: "DELETE" });
      if (!response.ok) {
        const errorPayload = await response.json().catch(() => null) as { error?: string } | null;
        const message = errorPayload?.error ?? "Failed to delete run";
        window.alert(message);
        return;
      }
      setPage(1);
      setRefreshTick((tick) => tick + 1);
    } finally {
      setDeletingKey(null);
    }
  }

  return (
    <div className="page">
      <div className="card">
        <div className="section-header">
          <h2>Runs Dashboard</h2>
          <div className="button-row">
            <button onClick={onOpenStalker}>Stalker sellers</button>
            <button onClick={onOpenStalkerProducts}>Sellable products</button>
          </div>
        </div>
      </div>

      <div className="metrics">
        <div className="metric" role="button" tabIndex={0} onClick={() => onOpenProducts("")} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") onOpenProducts(""); }}>
          <div className="label">Total products</div>
          <div className="value">{formatNumber(summary.total)}</div>
        </div>
        <div className="metric" role="button" tabIndex={0} onClick={() => onOpenProducts("FBA")} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") onOpenProducts("FBA"); }}>
          <div className="label">FBA</div>
          <div className="value">{formatNumber(summary.fba)}</div>
        </div>
        <div className="metric" role="button" tabIndex={0} onClick={() => onOpenProducts("FBM")} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") onOpenProducts("FBM"); }}>
          <div className="label">FBM</div>
          <div className="value">{formatNumber(summary.fbm)}</div>
        </div>
        <div className="metric" role="button" tabIndex={0} onClick={() => onOpenProducts("SKIP")} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") onOpenProducts("SKIP"); }}>
          <div className="label">SKIP</div>
          <div className="value">{formatNumber(summary.skip)}</div>
        </div>
      </div>

      <div className="card">
        <div className="toolbar">
          <input value={search} onChange={(e) => { setPage(1); setSearch(e.target.value); }} placeholder="Search run/job/source" />
          <select value={processType} onChange={(e) => { setPage(1); setProcessType(e.target.value); }}>
            <option value="">All processes</option>
            <option value="lead_analysis">lead_analysis</option>
            <option value="category_analysis">category_analysis</option>
          </select>
          <select value={status} onChange={(e) => { setPage(1); setStatus(e.target.value); }}>
            <option value="">All statuses</option>
            <option value="completed">completed</option>
            <option value="ok">ok</option>
            <option value="empty">empty</option>
            <option value="failed">failed</option>
          </select>
          <input type="date" value={startDate} onChange={(e) => { setPage(1); setStartDate(e.target.value); }} />
          <input type="date" value={endDate} onChange={(e) => { setPage(1); setEndDate(e.target.value); }} />
          <select value={String(pageSize)} onChange={(e) => { setPage(1); setPageSize(Number(e.target.value)); }}>
            <option value="25">25 / page</option>
            <option value="50">50 / page</option>
            <option value="100">100 / page</option>
          </select>
        </div>
      </div>

      <div className="card">
        <div className="table-wrap">
          <table>
            <thead>
              <tr>
                <th><button onClick={() => setSort(nextSort(sort, "runId"))}>Run ID</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "processType"))}>Process</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "jobType"))}>Job Type</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "timestamp"))}>Timestamp</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "status"))}>Status</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "totalProducts"))}>Total</button></th>
                <th>FBA</th>
                <th>FBM</th>
                <th>SKIP</th>
                <th>Mix</th>
                <th>Source</th>
                <th>Open</th>
                <th>Delete</th>
              </tr>
            </thead>
            <tbody>
              {loading ? (
                <tr><td colSpan={13}>Loading...</td></tr>
              ) : runs?.items.length ? (
                runs.items.map((run) => (
                  <tr key={`${run.processType}-${run.runId}`}>
                    <td>{run.runId}</td>
                    <td>{run.processType}</td>
                    <td>{run.jobType}</td>
                    <td>{formatDate(run.timestamp)}</td>
                    <td><span className={statusBadgeClass(run.status)}>{run.status}</span></td>
                    <td>{formatNumber(run.totalProducts)}</td>
                    <td>{formatNumber(run.fbaCount)}</td>
                    <td>{formatNumber(run.fbmCount)}</td>
                    <td>{formatNumber(run.skipCount)}</td>
                    <td><TinyBar fba={run.fbaCount} fbm={run.fbmCount} skip={run.skipCount} /></td>
                    <td>{run.source || "-"}</td>
                    <td><button onClick={() => onOpenRun(run)}>View</button></td>
                    <td>
                      <button disabled={deletingKey === `${run.processType}-${run.runId}`} onClick={() => deleteRun(run)}>
                        {deletingKey === `${run.processType}-${run.runId}` ? "Deleting..." : "Delete"}
                      </button>
                    </td>
                  </tr>
                ))
              ) : (
                <tr><td colSpan={13}>No runs found</td></tr>
              )}
            </tbody>
          </table>
        </div>
        <div className="pager">
          <div>Showing {runs?.items.length ?? 0} of {runs?.total ?? 0}</div>
          <div>
            <button disabled={page <= 1} onClick={() => setPage((p) => Math.max(1, p - 1))}>Previous</button>
            <span style={{ padding: "0 8px" }}>Page {runs?.page ?? page} / {runs?.totalPages ?? 1}</span>
            <button disabled={Boolean(runs && page >= runs.totalPages)} onClick={() => setPage((p) => p + 1)}>Next</button>
          </div>
        </div>
      </div>

      <div className="card">
        <h3>Recent trend (last 12 runs in current view)</h3>
        <div className="spark-grid" style={{ marginTop: 10 }}>
          {timeline.length === 0 ? (
            <div className="meta">No trend data</div>
          ) : (
            timeline.map((run) => (
              <div key={`trend-${run.processType}-${run.runId}`} className="spark-item" title={`${run.runId} • ${formatDate(run.timestamp)}`}>
                <div className="spark-label">#{run.runId}</div>
                <TinyBar fba={run.fbaCount} fbm={run.fbmCount} skip={run.skipCount} />
              </div>
            ))
          )}
        </div>
      </div>
    </div>
  );
}

function RunDetails({
  processType,
  runId,
  onBack,
}: {
  processType: ProcessType;
  runId: number;
  onBack: () => void;
}) {
  const [run, setRun] = useState<RunDetail | null>(null);
  const [results, setResults] = useState<ResultsResponse | null>(null);
  const [loading, setLoading] = useState(false);
  const [search, setSearch] = useState("");
  const [verdict, setVerdict] = useState("");
  const [sellabilityStatus, setSellabilityStatus] = useState("");
  const [amazonSellerFilter, setAmazonSellerFilter] =
    useState<AmazonSellerFilter>("");
  const [minConfidence, setMinConfidence] = useState("");
  const [maxConfidence, setMaxConfidence] = useState("");
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(25);
  const [sort, setSort] = useState<SortState>({ field: "monthly_sold", direction: "DESC" });
  const [refreshTick, setRefreshTick] = useState(0);
  const [reanalyzing, setReanalyzing] = useState<Record<string, boolean>>({});

  const anomalies = useMemo(() => {
    if (!results) return [] as ResultItem[];
    return results.items.filter((item) => detectAnomaly(item) !== "");
  }, [results]);

  useEffect(() => {
    let cancelled = false;
    async function loadRun() {
      const res = await fetch(`/api/runs/${processType}/${runId}`);
      const payload = (await res.json()) as RunDetail;
      if (!cancelled) {
        setRun(payload);
      }
    }
    loadRun();
    return () => {
      cancelled = true;
    };
  }, [processType, runId, refreshTick]);

  useEffect(() => {
    let cancelled = false;
    async function loadResults() {
      setLoading(true);
      const params = new URLSearchParams({
        page: String(page),
        pageSize: String(pageSize),
        sort: buildSortValue(sort),
      });
      if (search) params.set("q", search);
      if (verdict) params.set("verdict", verdict);
      if (sellabilityStatus) params.set("sellabilityStatus", sellabilityStatus);
      if (amazonSellerFilter) params.set("amazonIsSeller", amazonSellerFilter);
      if (minConfidence) params.set("minConfidence", minConfidence);
      if (maxConfidence) params.set("maxConfidence", maxConfidence);

      const res = await fetch(`/api/runs/${processType}/${runId}/results?${params.toString()}`);
      const payload = (await res.json()) as ResultsResponse;
      if (!cancelled) {
        setResults(payload);
        setLoading(false);
      }
    }

    loadResults();
    return () => {
      cancelled = true;
    };
  }, [processType, runId, search, verdict, sellabilityStatus, amazonSellerFilter, minConfidence, maxConfidence, page, pageSize, sort, refreshTick]);

  useEffect(() => {
    const interval = window.setInterval(() => {
      setRefreshTick((tick) => tick + 1);
    }, 4000);
    return () => {
      window.clearInterval(interval);
    };
  }, [processType, runId]);

  async function reanalyzeAsin(asin: string) {
    if (reanalyzing[asin]) return;
    setReanalyzing((prev) => ({ ...prev, [asin]: true }));
    try {
      const response = await fetch(
        `/api/runs/${processType}/${runId}/asins/${encodeURIComponent(asin)}/reanalyze`,
        { method: "POST" },
      );
      if (!response.ok) {
        const payload = (await response.json().catch(() => null)) as { error?: string } | null;
        window.alert(payload?.error ?? "Failed to re-analyze ASIN");
        return;
      }
      setRefreshTick((tick) => tick + 1);
    } finally {
      setReanalyzing((prev) => {
        const next = { ...prev };
        delete next[asin];
        return next;
      });
    }
  }

  return (
    <div className="page">
      <button className="back" onClick={onBack}>Back</button>

      <div className="card">
        <h2>Run Detail</h2>
        <div className="meta-grid" style={{ marginTop: 12 }}>
          <div className="meta"><strong>Process:</strong> {processType}</div>
          <div className="meta"><strong>Run ID:</strong> {runId}</div>
          <div className="meta"><strong>Status:</strong> {run ? <span className={statusBadgeClass(run.status)}>{run.status}</span> : "-"}</div>
          <div className="meta"><strong>Timestamp:</strong> {run ? formatDate(run.timestamp) : "-"}</div>
          <div className="meta"><strong>Job:</strong> {run?.jobType ?? "-"}</div>
          <div className="meta"><strong>Source:</strong> {run?.source ?? "-"}</div>
          <div className="meta"><strong>Total:</strong> {formatNumber(run?.summary.totalProducts)}</div>
          <div className="meta"><strong>FBA/FBM/SKIP:</strong> {formatNumber(run?.summary.fbaCount)}/{formatNumber(run?.summary.fbmCount)}/{formatNumber(run?.summary.skipCount)}</div>
        </div>
        <div style={{ marginTop: 10 }}>
          <TinyBar fba={run?.summary.fbaCount ?? 0} fbm={run?.summary.fbmCount ?? 0} skip={run?.summary.skipCount ?? 0} />
        </div>
      </div>

      <div className="card">
        <div className="toolbar">
          <input value={search} onChange={(e) => { setPage(1); setSearch(e.target.value); }} placeholder="Search ASIN/name/brand/category/reason" />
          <select value={verdict} onChange={(e) => { setPage(1); setVerdict(e.target.value); }}>
            <option value="">All verdicts</option>
            <option value="FBA">FBA</option>
            <option value="FBM">FBM</option>
            <option value="SKIP">SKIP</option>
          </select>
          <select value={sellabilityStatus} onChange={(e) => { setPage(1); setSellabilityStatus(e.target.value); }}>
            <option value="">All sellability</option>
            <option value="available">available</option>
            <option value="restricted">restricted</option>
            <option value="not_available">not_available</option>
            <option value="unknown">unknown</option>
          </select>
          <select
            value={amazonSellerFilter}
            onChange={(e) => {
              setPage(1);
              setAmazonSellerFilter(e.target.value as AmazonSellerFilter);
            }}
          >
            <option value="">Amazon seller: all</option>
            <option value="yes">Amazon seller: yes</option>
            <option value="no">Amazon seller: no</option>
          </select>
          <input value={minConfidence} onChange={(e) => { setPage(1); setMinConfidence(e.target.value); }} placeholder="Min confidence" />
          <input value={maxConfidence} onChange={(e) => { setPage(1); setMaxConfidence(e.target.value); }} placeholder="Max confidence" />
          <select value={String(pageSize)} onChange={(e) => { setPage(1); setPageSize(Number(e.target.value)); }}>
            <option value="25">25 / page</option>
            <option value="50">50 / page</option>
            <option value="100">100 / page</option>
          </select>
        </div>
        <div style={{ marginTop: 10 }}>
          <a
            href={`/api/runs/${processType}/${runId}/export.csv?q=${encodeURIComponent(search)}&verdict=${encodeURIComponent(verdict)}&sellabilityStatus=${encodeURIComponent(sellabilityStatus)}&amazonIsSeller=${encodeURIComponent(amazonSellerFilter)}&minConfidence=${encodeURIComponent(minConfidence)}&maxConfidence=${encodeURIComponent(maxConfidence)}&sort=${encodeURIComponent(buildSortValue(sort))}`}
          >
            <button>Export filtered CSV</button>
          </a>
        </div>
      </div>

      <div className="card">
        <h3>Anomalies in current page</h3>
        {anomalies.length === 0 ? (
          <div className="meta" style={{ marginTop: 8 }}>No anomalies detected with current heuristic.</div>
        ) : (
          <div className="anomaly-list" style={{ marginTop: 8 }}>
            {anomalies.slice(0, 8).map((item) => (
              <div key={`anom-${item.asin}-${item.fetched_at}`} className="anomaly-item">
                <a href={`http://amazon.com/dp/${item.asin}`} target="_blank" rel="noreferrer">{item.asin}</a>
                <span className={verdictBadgeClass(item.verdict)}>{item.verdict}</span>
                <span>{detectAnomaly(item)}</span>
              </div>
            ))}
          </div>
        )}
      </div>

      <div className="card">
        <div className="table-wrap">
          <table>
            <thead>
              <tr>
                <th><button onClick={() => setSort(nextSort(sort, "asin"))}>ASIN</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "verdict"))}>Verdict</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "monthly_sold"))}>Monthly Sold</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "seller_count"))}>Sellers</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "amazon_is_seller"))}>Amazon Seller</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "amazon_buybox_share_pct_90d"))}>Amazon Buy Box 90d %</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "sales_rank"))}>Sales Rank</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "current_price"))}>Current Price</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "product_name"))}>Product</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "brand"))}>Brand</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "category"))}>Category</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "avg_price_90d"))}>Avg 90d</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "confidence"))}>Confidence</button></th>
                <th className="reason-col"><button onClick={() => setSort(nextSort(sort, "reasoning"))}>Reasoning</button></th>
                <th>Action</th>
              </tr>
            </thead>
            <tbody>
              {loading ? (
                <tr><td colSpan={15}>Loading...</td></tr>
              ) : results?.items.length ? (
                results.items.map((item) => (
                  <tr key={`${item.asin}-${item.fetched_at}`}>
                    <td><a href={`http://amazon.com/dp/${item.asin}`} target="_blank" rel="noreferrer">{item.asin}</a></td>
                    <td><span className={verdictBadgeClass(item.verdict)}>{item.verdict}</span></td>
                    <td>{formatNumber(item.monthly_sold)}</td>
                    <td>{formatNumber(item.seller_count)}</td>
                    <td>{formatAmazonSeller(item.amazon_is_seller)}</td>
                    <td>{formatNumber(item.amazon_buybox_share_pct_90d)}</td>
                    <td>{formatNumber(item.sales_rank)}</td>
                    <td>{formatCurrency(item.current_price)}</td>
                    <td title={item.reasoning || undefined}>{item.product_name || "-"}</td>
                    <td>{item.brand || "-"}</td>
                    <td>{item.category || "-"}</td>
                    <td>{formatCurrency(item.avg_price_90d)}</td>
                    <td>{formatNumber(item.confidence)}</td>
                    <td className="reason-col" title={item.reasoning || undefined}>{item.reasoning || "-"}</td>
                    <td>
                      <button
                        onClick={() => reanalyzeAsin(item.asin)}
                        disabled={Boolean(reanalyzing[item.asin])}
                      >
                        {reanalyzing[item.asin] ? "Re-analyzing..." : "Re-analyze"}
                      </button>
                    </td>
                  </tr>
                ))
              ) : (
                <tr><td colSpan={15}>No results found</td></tr>
              )}
            </tbody>
          </table>
        </div>
        <div className="pager">
          <div>Showing {results?.items.length ?? 0} of {results?.total ?? 0}</div>
          <div>
            <button disabled={page <= 1} onClick={() => setPage((p) => Math.max(1, p - 1))}>Previous</button>
            <span style={{ padding: "0 8px" }}>Page {results?.page ?? page} / {results?.totalPages ?? 1}</span>
            <button disabled={Boolean(results && page >= results.totalPages)} onClick={() => setPage((p) => p + 1)}>Next</button>
          </div>
        </div>
      </div>
    </div>
  );
}

function ProductList({ verdict, onBack }: { verdict: VerdictFilter; onBack: () => void }) {
  const [items, setItems] = useState<ProductListResponse | null>(null);
  const [loading, setLoading] = useState(false);
  const [search, setSearch] = useState("");
  const [activeVerdict, setActiveVerdict] = useState<VerdictFilter>(verdict);
  const [amazonSellerFilter, setAmazonSellerFilter] =
    useState<AmazonSellerFilter>("");
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(25);
  const [sort, setSort] = useState<SortState>({ field: "monthly_sold", direction: "DESC" });
  const [reanalyzing, setReanalyzing] = useState<Record<string, boolean>>({});

  useEffect(() => {
    setActiveVerdict(verdict);
    setPage(1);
  }, [verdict]);

  useEffect(() => {
    let cancelled = false;
    async function load() {
      setLoading(true);
      const params = new URLSearchParams({ page: String(page), pageSize: String(pageSize) });
      params.set("sort", buildSortValue(sort));
      if (search) params.set("q", search);
      if (activeVerdict) params.set("verdict", activeVerdict);
      if (amazonSellerFilter) params.set("amazonIsSeller", amazonSellerFilter);
      const res = await fetch(`/api/products?${params.toString()}`);
      const payload = (await res.json()) as ProductListResponse;
      if (!cancelled) {
        setItems(payload);
        setLoading(false);
      }
    }

    load();
    return () => {
      cancelled = true;
    };
  }, [search, activeVerdict, amazonSellerFilter, page, pageSize, sort]);

  async function reanalyzeAsin(item: ProductListItem) {
    const key = `${item.processType}:${item.runId}:${item.asin}`;
    if (reanalyzing[key]) return;
    setReanalyzing((prev) => ({ ...prev, [key]: true }));
    try {
      const response = await fetch(
        `/api/runs/${item.processType}/${item.runId}/asins/${encodeURIComponent(item.asin)}/reanalyze`,
        { method: "POST" },
      );
      if (!response.ok) {
        const payload = (await response.json().catch(() => null)) as { error?: string } | null;
        window.alert(payload?.error ?? "Failed to re-analyze ASIN");
        return;
      }
      const params = new URLSearchParams({ page: String(page), pageSize: String(pageSize) });
      params.set("sort", buildSortValue(sort));
      if (search) params.set("q", search);
      if (activeVerdict) params.set("verdict", activeVerdict);
      if (amazonSellerFilter) params.set("amazonIsSeller", amazonSellerFilter);
      const res = await fetch(`/api/products?${params.toString()}`);
      const payload = (await res.json()) as ProductListResponse;
      setItems(payload);
    } finally {
      setReanalyzing((prev) => {
        const next = { ...prev };
        delete next[key];
        return next;
      });
    }
  }

  return (
    <div className="page">
      <button className="back" onClick={onBack}>Back</button>
      <div className="card">
        <h2>Products</h2>
        <div className="toolbar" style={{ marginTop: 10 }}>
          <input value={search} onChange={(e) => { setPage(1); setSearch(e.target.value); }} placeholder="Search ASIN/name/brand/category" />
          <select value={activeVerdict} onChange={(e) => { setPage(1); setActiveVerdict(e.target.value as VerdictFilter); }}>
            <option value="">All verdicts</option>
            <option value="FBA">FBA</option>
            <option value="FBM">FBM</option>
            <option value="SKIP">SKIP</option>
          </select>
          <select
            value={amazonSellerFilter}
            onChange={(e) => {
              setPage(1);
              setAmazonSellerFilter(e.target.value as AmazonSellerFilter);
            }}
          >
            <option value="">Amazon seller: all</option>
            <option value="yes">Amazon seller: yes</option>
            <option value="no">Amazon seller: no</option>
          </select>
          <select value={String(pageSize)} onChange={(e) => { setPage(1); setPageSize(Number(e.target.value)); }}>
            <option value="25">25 / page</option>
            <option value="50">50 / page</option>
            <option value="100">100 / page</option>
          </select>
        </div>
      </div>
      <div className="card">
        <div className="table-wrap">
          <table>
            <thead>
              <tr>
                <th><button onClick={() => setSort(nextSort(sort, "asin"))}>ASIN</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "verdict"))}>Verdict</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "monthly_sold"))}>Monthly Sold</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "seller_count"))}>Sellers</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "amazon_is_seller"))}>Amazon Seller</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "amazon_buybox_share_pct_90d"))}>Amazon Buy Box 90d %</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "sales_rank"))}>Sales Rank</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "current_price"))}>Current Price</button></th>
                <th className="product-col"><button onClick={() => setSort(nextSort(sort, "product_name"))}>Product</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "brand"))}>Brand</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "category"))}>Category</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "avg_price_90d"))}>Avg 90d</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "confidence"))}>Confidence</button></th>
                <th className="reason-col"><button onClick={() => setSort(nextSort(sort, "reasoning"))}>Reasoning</button></th>
                <th>Action</th>
              </tr>
            </thead>
            <tbody>
              {loading ? (
                <tr><td colSpan={15}>Loading...</td></tr>
              ) : items?.items.length ? (
                items.items.map((item) => (
                  <tr key={`${item.processType}-${item.runId}-${item.asin}-${item.fetched_at}`}>
                    <td><a href={`http://amazon.com/dp/${item.asin}`} target="_blank" rel="noreferrer">{item.asin}</a></td>
                    <td><span className={verdictBadgeClass(item.verdict)}>{item.verdict}</span></td>
                    <td>{formatNumber(item.monthly_sold)}</td>
                    <td>{formatNumber(item.seller_count)}</td>
                    <td>{formatAmazonSeller(item.amazon_is_seller)}</td>
                    <td>{formatNumber(item.amazon_buybox_share_pct_90d)}</td>
                    <td>{formatNumber(item.sales_rank)}</td>
                    <td>{formatCurrency(item.current_price)}</td>
                    <td className="product-col" title={item.reasoning || undefined}>{item.product_name || "-"}</td>
                    <td>{item.brand || "-"}</td>
                    <td>{item.category || "-"}</td>
                    <td>{formatCurrency(item.avg_price_90d)}</td>
                    <td>{formatNumber(item.confidence)}</td>
                    <td className="reason-col" title={item.reasoning || undefined}>{item.reasoning || "-"}</td>
                    <td>
                      <button
                        onClick={() => reanalyzeAsin(item)}
                        disabled={Boolean(reanalyzing[`${item.processType}:${item.runId}:${item.asin}`])}
                      >
                        {reanalyzing[`${item.processType}:${item.runId}:${item.asin}`] ? "Re-analyzing..." : "Re-analyze"}
                      </button>
                    </td>
                  </tr>
                ))
              ) : (
                <tr><td colSpan={15}>No products found</td></tr>
              )}
            </tbody>
          </table>
        </div>
        <div className="pager">
          <div>Showing {items?.items.length ?? 0} of {items?.total ?? 0}</div>
          <div>
            <button disabled={page <= 1} onClick={() => setPage((p) => Math.max(1, p - 1))}>Previous</button>
            <span style={{ padding: "0 8px" }}>Page {items?.page ?? page} / {items?.totalPages ?? 1}</span>
            <button disabled={Boolean(items && page >= items.totalPages)} onClick={() => setPage((p) => p + 1)}>Next</button>
          </div>
        </div>
      </div>
    </div>
  );
}

function StalkerExplorer({
  onBack,
  onOpenProducts,
}: {
  onBack: () => void;
  onOpenProducts: () => void;
}) {
  const [results, setResults] = useState<StalkerResultsResponse | null>(null);
  const [loading, setLoading] = useState(false);
  const [purging, setPurging] = useState(false);
  const [search, setSearch] = useState("");
  const [sellerId, setSellerId] = useState("");
  const [runId, setRunId] = useState("");
  const [minRatingCount, setMinRatingCount] = useState("1");
  const [maxRatingCount, setMaxRatingCount] = useState("30");
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(25);
  const [sort, setSort] = useState<SortState>({ field: "persisted_inventory_asin_count", direction: "DESC" });
  const [refreshTick, setRefreshTick] = useState(0);

  useEffect(() => {
    let cancelled = false;
    async function load() {
      setLoading(true);
      const params = new URLSearchParams({
        page: String(page),
        pageSize: String(pageSize),
        sort: buildSortValue(sort),
      });
      if (search) params.set("q", search);
      if (sellerId) params.set("sellerId", sellerId);
      if (runId) params.set("runId", runId);
      if (minRatingCount) params.set("minRatingCount", minRatingCount);
      if (maxRatingCount) params.set("maxRatingCount", maxRatingCount);

      const res = await fetch(`/api/stalker/results?${params.toString()}`);
      const payload = (await res.json()) as StalkerResultsResponse;
      if (!cancelled) {
        setResults(payload);
        setLoading(false);
      }
    }

    load();
    return () => {
      cancelled = true;
    };
  }, [search, sellerId, runId, minRatingCount, maxRatingCount, page, pageSize, sort, refreshTick]);

  async function purgeStalkerData() {
    const confirmed = window.confirm("Permanently delete all Stalker runs, sellers, and sellable products from the database?");
    if (!confirmed) return;

    setPurging(true);
    try {
      const res = await fetch("/api/stalker/purge", { method: "DELETE" });
      if (!res.ok) {
        const payload = await res.json().catch(() => null) as { error?: string } | null;
        window.alert(payload?.error ?? "Failed to purge Stalker data");
        return;
      }
      setPage(1);
      setRefreshTick((tick) => tick + 1);
    } finally {
      setPurging(false);
    }
  }

  return (
    <div className="page">
      <button className="back" onClick={onBack}>Back</button>

      <div className="card">
        <div className="section-header">
          <h2>Seller Storefronts</h2>
          <div className="button-row">
            <button onClick={onOpenProducts}>Sellable products</button>
            <button className="danger" disabled={purging} onClick={purgeStalkerData}>
              {purging ? "Purging..." : "Purge Stalker data"}
            </button>
          </div>
        </div>
      </div>

      <div className="metrics">
        <div className="metric">
          <div className="label">Runs</div>
          <div className="value">{formatNumber(results?.summary.runs)}</div>
        </div>
        <div className="metric">
          <div className="label">Matched sellers</div>
          <div className="value">{formatNumber(results?.summary.sellers)}</div>
        </div>
        <div className="metric">
          <div className="label">Sellable inventory ASINs</div>
          <div className="value">{formatNumber(results?.summary.persistedInventoryAsins)}</div>
        </div>
      </div>

      <div className="card">
        <div className="toolbar">
          <input value={search} onChange={(e) => { setPage(1); setSearch(e.target.value); }} placeholder="Search seller or sellable ASIN" />
          <input value={sellerId} onChange={(e) => { setPage(1); setSellerId(e.target.value.toUpperCase()); }} placeholder="Seller ID" />
          <input value={runId} onChange={(e) => { setPage(1); setRunId(e.target.value); }} placeholder="Run ID" />
          <input value={minRatingCount} onChange={(e) => { setPage(1); setMinRatingCount(e.target.value); }} placeholder="Min rating count" />
          <input value={maxRatingCount} onChange={(e) => { setPage(1); setMaxRatingCount(e.target.value); }} placeholder="Max rating count" />
          <select value={String(pageSize)} onChange={(e) => { setPage(1); setPageSize(Number(e.target.value)); }}>
            <option value="25">25 / page</option>
            <option value="50">50 / page</option>
            <option value="100">100 / page</option>
          </select>
        </div>
      </div>

      <div className="card">
        <div className="table-wrap">
          <table className="stalker-table">
            <thead>
              <tr>
                <th><button onClick={() => setSort(nextSort(sort, "runId"))}>Run</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "seller_id"))}>Seller ID</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "seller_name"))}>Seller</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "rating"))}>Rating</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "rating_count"))}>Rating Count</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "discovered_from_count"))}>Hits</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "storefront_asin_total"))}>Storefront total</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "persisted_inventory_asin_count"))}>Sellable sample</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "last_seen_at"))}>Last Seen</button></th>
                <th className="inventory-col">Sellable inventory ASIN sample</th>
              </tr>
            </thead>
            <tbody>
              {loading ? (
                <tr><td colSpan={10}>Loading...</td></tr>
              ) : results?.items.length ? (
                results.items.map((item) => {
                  const inventorySample = (item.inventory_sample_asins ?? "")
                    .split(",")
                    .filter(Boolean)
                    .slice(0, 20);
                  return (
                    <tr key={`${item.runId}-${item.seller_id}`}>
                      <td>{item.runId}</td>
                      <td>{item.seller_id}</td>
                      <td>{item.seller_name || "-"}</td>
                      <td>{formatNumber(item.rating)}</td>
                      <td>{formatNumber(item.rating_count)}</td>
                      <td>{formatNumber(item.discovered_from_count)}</td>
                      <td>{formatNumber(item.storefront_asin_total)}</td>
                      <td>{formatNumber(item.persisted_inventory_asin_count)}</td>
                      <td>{formatDate(item.last_seen_at)}</td>
                      <td className="inventory-col">
                        {inventorySample.length === 0 ? "-" : inventorySample.map((asin) => (
                          <a key={asin} href={`http://amazon.com/dp/${asin}`} target="_blank" rel="noreferrer">{asin}</a>
                        ))}
                      </td>
                    </tr>
                  );
                })
              ) : (
                <tr><td colSpan={10}>No seller storefronts found</td></tr>
              )}
            </tbody>
          </table>
        </div>
        <div className="pager">
          <div>Showing {results?.items.length ?? 0} of {results?.total ?? 0}</div>
          <div>
            <button disabled={page <= 1} onClick={() => setPage((p) => Math.max(1, p - 1))}>Previous</button>
            <span style={{ padding: "0 8px" }}>Page {results?.page ?? page} / {results?.totalPages ?? 1}</span>
            <button disabled={Boolean(results && page >= results.totalPages)} onClick={() => setPage((p) => p + 1)}>Next</button>
          </div>
        </div>
      </div>
    </div>
  );
}

function StalkerProductsExplorer({
  onBack,
  onOpenSellers,
}: {
  onBack: () => void;
  onOpenSellers: () => void;
}) {
  const [results, setResults] = useState<StalkerProductsResponse | null>(null);
  const [loading, setLoading] = useState(false);
  const [search, setSearch] = useState("");
  const [sellerId, setSellerId] = useState("");
  const [runId, setRunId] = useState("");
  const [verdict, setVerdict] = useState("");
  const [amazonIsSeller, setAmazonIsSeller] = useState("");
  const [minPrice, setMinPrice] = useState("");
  const [maxPrice, setMaxPrice] = useState("");
  const [minMonthlySold, setMinMonthlySold] = useState("");
  const [maxMonthlySold, setMaxMonthlySold] = useState("");
  const [minSalesRank, setMinSalesRank] = useState("");
  const [maxSalesRank, setMaxSalesRank] = useState("");
  const [minSellerCount, setMinSellerCount] = useState("");
  const [maxSellerCount, setMaxSellerCount] = useState("");
  const [minRatingCount, setMinRatingCount] = useState("");
  const [maxRatingCount, setMaxRatingCount] = useState("");
  const [minConfidence, setMinConfidence] = useState("");
  const [maxConfidence, setMaxConfidence] = useState("");
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(50);
  const [sort, setSort] = useState<SortState>({ field: "monthly_sold", direction: "DESC" });

  function buildStalkerProductParams(includePaging: boolean): URLSearchParams {
    const params = new URLSearchParams({
      sort: buildSortValue(sort),
    });
    if (includePaging) {
      params.set("page", String(page));
      params.set("pageSize", String(pageSize));
    }
    if (search) params.set("q", search);
    if (sellerId) params.set("sellerId", sellerId);
    if (runId) params.set("runId", runId);
    if (verdict) params.set("verdict", verdict);
    if (amazonIsSeller) params.set("amazonIsSeller", amazonIsSeller);
    if (minPrice) params.set("minPrice", minPrice);
    if (maxPrice) params.set("maxPrice", maxPrice);
    if (minMonthlySold) params.set("minMonthlySold", minMonthlySold);
    if (maxMonthlySold) params.set("maxMonthlySold", maxMonthlySold);
    if (minSalesRank) params.set("minSalesRank", minSalesRank);
    if (maxSalesRank) params.set("maxSalesRank", maxSalesRank);
    if (minSellerCount) params.set("minSellerCount", minSellerCount);
    if (maxSellerCount) params.set("maxSellerCount", maxSellerCount);
    if (minRatingCount) params.set("minRatingCount", minRatingCount);
    if (maxRatingCount) params.set("maxRatingCount", maxRatingCount);
    if (minConfidence) params.set("minConfidence", minConfidence);
    if (maxConfidence) params.set("maxConfidence", maxConfidence);
    return params;
  }

  useEffect(() => {
    let cancelled = false;
    async function load() {
      setLoading(true);
      const params = buildStalkerProductParams(true);

      const res = await fetch(`/api/stalker/products?${params.toString()}`);
      const payload = (await res.json()) as StalkerProductsResponse;
      if (!cancelled) {
        setResults(payload);
        setLoading(false);
      }
    }

    load();
    return () => {
      cancelled = true;
    };
  }, [
    search,
    sellerId,
    runId,
    verdict,
    amazonIsSeller,
    minPrice,
    maxPrice,
    minMonthlySold,
    maxMonthlySold,
    minSalesRank,
    maxSalesRank,
    minSellerCount,
    maxSellerCount,
    minRatingCount,
    maxRatingCount,
    minConfidence,
    maxConfidence,
    page,
    pageSize,
    sort,
  ]);

  function resetFilters() {
    setSearch("");
    setSellerId("");
    setRunId("");
    setVerdict("");
    setAmazonIsSeller("");
    setMinPrice("");
    setMaxPrice("");
    setMinMonthlySold("");
    setMaxMonthlySold("");
    setMinSalesRank("");
    setMaxSalesRank("");
    setMinSellerCount("");
    setMaxSellerCount("");
    setMinRatingCount("");
    setMaxRatingCount("");
    setMinConfidence("");
    setMaxConfidence("");
    setPage(1);
  }

  const exportHref = `/api/stalker/products/export.xlsx?${buildStalkerProductParams(false).toString()}`;

  return (
    <div className="page">
      <button className="back" onClick={onBack}>Back</button>

      <div className="card">
        <div className="section-header">
          <h2>Sellable Stalker Products</h2>
          <button onClick={onOpenSellers}>Seller storefronts</button>
        </div>
      </div>

      <div className="metrics">
        <div className="metric">
          <div className="label">Runs</div>
          <div className="value">{formatNumber(results?.summary.runs)}</div>
        </div>
        <div className="metric">
          <div className="label">Sellers</div>
          <div className="value">{formatNumber(results?.summary.sellers)}</div>
        </div>
        <div className="metric">
          <div className="label">Sellable products</div>
          <div className="value">{formatNumber(results?.summary.products)}</div>
        </div>
      </div>

      <div className="card">
        <div className="toolbar">
          <input value={search} onChange={(e) => { setPage(1); setSearch(e.target.value); }} placeholder="Search ASIN, product, brand, category, or seller" />
          <input value={sellerId} onChange={(e) => { setPage(1); setSellerId(e.target.value.toUpperCase()); }} placeholder="Seller ID" />
          <input value={runId} onChange={(e) => { setPage(1); setRunId(e.target.value); }} placeholder="Run ID" />
          <select value={verdict} onChange={(e) => { setPage(1); setVerdict(e.target.value); }}>
            <option value="">All verdicts</option>
            <option value="FBA">FBA</option>
            <option value="FBM">FBM</option>
            <option value="SKIP">SKIP</option>
            <option value="UNANALYZED">Unanalyzed</option>
          </select>
          <select value={amazonIsSeller} onChange={(e) => { setPage(1); setAmazonIsSeller(e.target.value); }}>
            <option value="">Amazon seller: all</option>
            <option value="yes">Amazon seller: yes</option>
            <option value="no">Amazon seller: no</option>
            <option value="unknown">Amazon seller: unknown</option>
          </select>
          <input value={minPrice} onChange={(e) => { setPage(1); setMinPrice(e.target.value); }} placeholder="Min price" />
          <input value={maxPrice} onChange={(e) => { setPage(1); setMaxPrice(e.target.value); }} placeholder="Max price" />
          <input value={minMonthlySold} onChange={(e) => { setPage(1); setMinMonthlySold(e.target.value); }} placeholder="Min monthly sold" />
          <input value={maxMonthlySold} onChange={(e) => { setPage(1); setMaxMonthlySold(e.target.value); }} placeholder="Max monthly sold" />
          <input value={minSalesRank} onChange={(e) => { setPage(1); setMinSalesRank(e.target.value); }} placeholder="Min rank" />
          <input value={maxSalesRank} onChange={(e) => { setPage(1); setMaxSalesRank(e.target.value); }} placeholder="Max rank" />
          <input value={minSellerCount} onChange={(e) => { setPage(1); setMinSellerCount(e.target.value); }} placeholder="Min sellers" />
          <input value={maxSellerCount} onChange={(e) => { setPage(1); setMaxSellerCount(e.target.value); }} placeholder="Max sellers" />
          <input value={minRatingCount} onChange={(e) => { setPage(1); setMinRatingCount(e.target.value); }} placeholder="Min seller rating count" />
          <input value={maxRatingCount} onChange={(e) => { setPage(1); setMaxRatingCount(e.target.value); }} placeholder="Max seller rating count" />
          <input value={minConfidence} onChange={(e) => { setPage(1); setMinConfidence(e.target.value); }} placeholder="Min confidence" />
          <input value={maxConfidence} onChange={(e) => { setPage(1); setMaxConfidence(e.target.value); }} placeholder="Max confidence" />
          <select value={String(pageSize)} onChange={(e) => { setPage(1); setPageSize(Number(e.target.value)); }}>
            <option value="25">25 / page</option>
            <option value="50">50 / page</option>
            <option value="100">100 / page</option>
          </select>
          <button onClick={resetFilters}>Reset filters</button>
          <a className="button-link" href={exportHref}>Export XLSX</a>
        </div>
      </div>

      <div className="card">
        <div className="table-wrap">
          <table className="stalker-table">
            <thead>
              <tr>
                <th><button onClick={() => setSort(nextSort(sort, "asin"))}>ASIN</button></th>
                <th className="product-col"><button onClick={() => setSort(nextSort(sort, "product_title"))}>Product</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "brand"))}>Brand</button></th>
                <th>Category</th>
                <th><button onClick={() => setSort(nextSort(sort, "monthly_sold"))}>Monthly Sold</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "seller_count"))}>Sellers</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "amazon_is_seller"))}>Amazon Seller</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "sales_rank"))}>Sales Rank</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "current_price"))}>Current Price</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "avg_price_90d"))}>Avg 90d</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "verdict"))}>Verdict</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "confidence"))}>Confidence</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "seller_id"))}>Seller ID</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "seller_name"))}>Seller</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "rating_count"))}>Rating Count</button></th>
                <th>Status</th>
                <th><button onClick={() => setSort(nextSort(sort, "runId"))}>Run</button></th>
                <th><button onClick={() => setSort(nextSort(sort, "last_seen_at"))}>Last Seen</button></th>
              </tr>
            </thead>
            <tbody>
              {loading ? (
                <tr><td colSpan={18}>Loading...</td></tr>
              ) : results?.items.length ? (
                results.items.map((item) => {
                  const categories = parseStringArrayJson(item.category_tree);
                  return (
                    <tr key={`${item.runId}-${item.seller_id}-${item.asin}`}>
                      <td><a href={`http://amazon.com/dp/${item.asin}`} target="_blank" rel="noreferrer">{item.asin}</a></td>
                      <td className="product-col" title={item.product_title || undefined}>{item.product_title || "-"}</td>
                      <td>{item.brand || "-"}</td>
                      <td>{categories.at(-1) || "-"}</td>
                      <td>{formatNumber(item.monthly_sold)}</td>
                      <td>{formatNumber(item.seller_count)}</td>
                      <td>{formatAmazonSeller(item.amazon_is_seller)}</td>
                      <td>{formatNumber(item.sales_rank)}</td>
                      <td>{formatCurrency(item.current_price)}</td>
                      <td>{formatCurrency(item.avg_price_90d)}</td>
                      <td>{item.verdict ? <span className={verdictBadgeClass(item.verdict)}>{item.verdict}</span> : "-"}</td>
                      <td title={item.reasoning || undefined}>{formatNumber(item.confidence)}</td>
                      <td>{item.seller_id}</td>
                      <td>{item.seller_name || "-"}</td>
                      <td>{formatNumber(item.rating_count)}</td>
                      <td><span className="badge badge-ok">{item.sellability_status}</span></td>
                      <td>{item.runId}</td>
                      <td>{formatDate(item.last_seen_at)}</td>
                    </tr>
                  );
                })
              ) : (
                <tr><td colSpan={18}>No sellable Stalker products found</td></tr>
              )}
            </tbody>
          </table>
        </div>
        <div className="pager">
          <div>Showing {results?.items.length ?? 0} of {results?.total ?? 0}</div>
          <div>
            <button disabled={page <= 1} onClick={() => setPage((p) => Math.max(1, p - 1))}>Previous</button>
            <span style={{ padding: "0 8px" }}>Page {results?.page ?? page} / {results?.totalPages ?? 1}</span>
            <button disabled={Boolean(results && page >= results.totalPages)} onClick={() => setPage((p) => p + 1)}>Next</button>
          </div>
        </div>
      </div>
    </div>
  );
}

type AppRoute =
  | { kind: "dashboard" }
  | { kind: "run"; processType: ProcessType; runId: number }
  | { kind: "products"; verdict: VerdictFilter }
  | { kind: "stalker" }
  | { kind: "stalker-products" };

function parseRoute(pathname: string, search: string): AppRoute {
  const runMatch = pathname.match(/^\/runs\/(lead_analysis|category_analysis)\/(\d+)$/);
  if (runMatch) {
    return { kind: "run", processType: runMatch[1] as ProcessType, runId: Number(runMatch[2]) };
  }

  if (pathname === "/products") {
    const params = new URLSearchParams(search);
    const verdictParam = params.get("verdict");
    const verdict = verdictParam === "FBA" || verdictParam === "FBM" || verdictParam === "SKIP" ? verdictParam : "";
    return { kind: "products", verdict };
  }

  if (pathname === "/stalker") {
    return { kind: "stalker" };
  }

  if (pathname === "/stalker/products") {
    return { kind: "stalker-products" };
  }

  return { kind: "dashboard" };
}

function App() {
  const [route, setRoute] = useState<AppRoute>(() => parseRoute(window.location.pathname, window.location.search));

  useEffect(() => {
    const onPopState = () => setRoute(parseRoute(window.location.pathname, window.location.search));
    window.addEventListener("popstate", onPopState);
    return () => window.removeEventListener("popstate", onPopState);
  }, []);

  function openRun(run: Run) {
    const path = `/runs/${run.processType}/${run.runId}`;
    history.pushState({}, "", path);
    setRoute({ kind: "run", processType: run.processType, runId: run.runId });
  }

  function openProducts(verdict: VerdictFilter) {
    const path = verdict ? `/products?verdict=${encodeURIComponent(verdict)}` : "/products";
    history.pushState({}, "", path);
    setRoute({ kind: "products", verdict });
  }

  function openStalker() {
    history.pushState({}, "", "/stalker");
    setRoute({ kind: "stalker" });
  }

  function openStalkerProducts() {
    history.pushState({}, "", "/stalker/products");
    setRoute({ kind: "stalker-products" });
  }

  function backToDashboard() {
    history.pushState({}, "", "/");
    setRoute({ kind: "dashboard" });
  }

  if (route.kind === "run") {
    return <RunDetails processType={route.processType} runId={route.runId} onBack={backToDashboard} />;
  }

  if (route.kind === "products") {
    return <ProductList verdict={route.verdict} onBack={backToDashboard} />;
  }

  if (route.kind === "stalker") {
    return <StalkerExplorer onBack={backToDashboard} onOpenProducts={openStalkerProducts} />;
  }

  if (route.kind === "stalker-products") {
    return <StalkerProductsExplorer onBack={backToDashboard} onOpenSellers={openStalker} />;
  }

  return (
    <Dashboard
      onOpenRun={openRun}
      onOpenProducts={openProducts}
      onOpenStalker={openStalker}
      onOpenStalkerProducts={openStalkerProducts}
    />
  );
}

const root = document.getElementById("root");
if (!root) {
  throw new Error("Root element not found");
}

createRoot(root).render(<App />);
