"use client";

import { useEffect, useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

import "@/components/trade-in/trade-in.css";
import { Topbar, type ActiveTab } from "@/components/trade-in/Topbar";
import { apiFetch } from "@/lib/api";

// ── Shared Types ───────────────────────────────────────────────────────────

export interface ScrapeRunRow {
  run_id: number;
  source: string;
  status: string;
  params: Record<string, unknown> | null;
  counters: {
    anchors_total?: number;
    anchors_done?: number;
    lots_fetched?: number;
    lots_inserted?: number;
    lots_updated?: number;
    unique_houses?: number;
    houses_enriched?: number;
    houses_failed?: number;
    detail_attempted?: number;
    detail_enriched?: number;
    detail_failed?: number;
    errors_count?: number;
  } | null;
  error: string | null;
  started_at: string | null;
  finished_at: string | null;
  heartbeat_at: string | null;
}

export interface ScheduleConfig {
  id: number;
  source: string;
  enabled: boolean;
  window_start_hour: number;
  window_end_hour: number;
  default_params: {
    pages_per_anchor?: number;
    detail_top_n?: number;
    request_delay_sec?: number;
    enrich_houses?: boolean;
    enrich_address?: boolean;
    radius_m?: number;
  };
  last_run_id: number | null;
  last_run_at: string | null;
  next_run_at: string | null;
  updated_at: string | null;
}

export interface ScraperSetting {
  source: string;
  request_delay_sec: number;
  description: string | null;
  updated_at: string | null;
}

export interface CitySweepStartResp {
  run_id: number;
  status: string;
  pages_per_anchor: number;
  detail_top_n: number;
}

// ── Shared sweep param types ───────────────────────────────────────────────

export interface SweepParamConfig {
  /** has detail_top_n + enrich_houses params */
  hasDetailParams: boolean;
  /** has enrich_address param (yandex) */
  hasEnrichAddress: boolean;
  defaults: {
    pages_per_anchor: number;
    radius_m: number;
    request_delay_sec: number;
    detail_top_n?: number;
    enrich_houses?: boolean;
    enrich_address?: boolean;
  };
}

// ── Shared API source name type ────────────────────────────────────────────

export type ScraperSource = "avito" | "cian" | "yandex";
export type { ActiveTab };
export type ScheduleSourceKey =
  | "avito_city_sweep"
  | "cian_city_sweep"
  | "yandex_city_sweep";

// ── Shared Hooks ───────────────────────────────────────────────────────────

export function useStartCitySweep(source: ScraperSource) {
  const qc = useQueryClient();
  return useMutation<CitySweepStartResp, Error, Record<string, unknown>>({
    mutationFn: (input) =>
      apiFetch<CitySweepStartResp>(
        `/api/v1/admin/scrape/${source}-city-sweep`,
        {
          method: "POST",
          body: JSON.stringify(input),
        },
      ),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["city-sweep-runs", source] });
    },
  });
}

export function useCitySweepRuns(source: ScraperSource) {
  return useQuery<ScrapeRunRow[]>({
    queryKey: ["city-sweep-runs", source],
    queryFn: () =>
      apiFetch<ScrapeRunRow[]>(
        `/api/v1/admin/scrape/${source}-city-sweep/runs?limit=10`,
      ),
    refetchInterval: 5_000,
    staleTime: 0,
  });
}

export function useCancelCitySweep(source: ScraperSource) {
  const qc = useQueryClient();
  return useMutation<
    { ok: boolean; run_id: number; cancelled: boolean },
    Error,
    number
  >({
    mutationFn: (run_id) =>
      apiFetch(
        `/api/v1/admin/scrape/${source}-city-sweep/${run_id}/cancel`,
        { method: "POST" },
      ),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["city-sweep-runs", source] });
    },
  });
}

export function useSchedules() {
  return useQuery<ScheduleConfig[]>({
    queryKey: ["scrape-schedules"],
    queryFn: () =>
      apiFetch<ScheduleConfig[]>("/api/v1/admin/scrape/schedules"),
    refetchInterval: 30_000,
  });
}

export function useUpdateSchedule() {
  const qc = useQueryClient();
  return useMutation<
    ScheduleConfig,
    Error,
    {
      source: ScheduleSourceKey;
      payload: {
        enabled: boolean;
        window_start_hour: number;
        window_end_hour: number;
        default_params: Record<string, unknown>;
      };
    }
  >({
    mutationFn: ({ source, payload }) =>
      apiFetch<ScheduleConfig>(`/api/v1/admin/scrape/schedules/${source}`, {
        method: "PUT",
        body: JSON.stringify(payload),
      }),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["scrape-schedules"] });
    },
  });
}

export function useScraperSettings() {
  return useQuery<ScraperSetting[]>({
    queryKey: ["scraper-settings"],
    queryFn: () =>
      apiFetch<{ settings: ScraperSetting[] }>(
        "/api/v1/admin/scraper-settings",
      ).then((r) =>
        (r.settings ?? []).map((s) => ({
          ...s,
          request_delay_sec: Number(s.request_delay_sec),
        })),
      ),
    staleTime: 30_000,
    refetchInterval: 60_000,
  });
}

export function useUpdateScraperSetting() {
  const qc = useQueryClient();
  return useMutation<
    ScraperSetting,
    Error,
    { source: string; request_delay_sec: number }
  >({
    mutationFn: ({ source, request_delay_sec }) =>
      apiFetch<ScraperSetting>(`/api/v1/admin/scraper-settings/${source}`, {
        method: "PUT",
        body: JSON.stringify({ source, request_delay_sec }),
      }),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ["scraper-settings"] });
    },
  });
}

// ── Shared Helpers ─────────────────────────────────────────────────────────

export function translateStatus(s: string): string {
  switch (s) {
    case "running":
      return "выполняется";
    case "done":
      return "готово";
    case "failed":
      return "ошибка";
    case "cancelled":
      return "отменено";
    case "zombie":
      return "зомби";
    case "skipped":
      return "пропущено";
    case "banned":
      return "блок";
    default:
      return s;
  }
}

export function formatTime(iso: string | null): string {
  if (!iso) return "—";
  try {
    return new Date(iso).toLocaleString("ru-RU", {
      day: "2-digit",
      month: "2-digit",
      hour: "2-digit",
      minute: "2-digit",
      second: "2-digit",
    });
  } catch {
    return iso;
  }
}

export function utcToMsk(hour: number): number {
  return (hour + 3) % 24;
}

export function mskToUtc(hour: number): number {
  return (hour - 3 + 24) % 24;
}

function formatMskRange(utcStart: number, utcEnd: number): string {
  const ms = utcToMsk(utcStart);
  const me = utcToMsk(utcEnd);
  return `${String(ms).padStart(2, "0")}:00–${String(me).padStart(2, "0")}:00 МСК`;
}

// ── ResultPanel (reusable) ─────────────────────────────────────────────────

export interface ResultPanelProps<TData> {
  mut: {
    data?: TData;
    error: Error | null;
    isPending: boolean;
    isSuccess: boolean;
  };
}

export function ResultPanel<TData>({ mut }: ResultPanelProps<TData>) {
  if (mut.error) {
    return (
      <div className="scraper-result scraper-result--error">
        <strong>Ошибка:</strong> {mut.error.message}
      </div>
    );
  }
  if (mut.isSuccess && mut.data) {
    return (
      <div className="scraper-result">
        <pre>{JSON.stringify(mut.data, null, 2)}</pre>
      </div>
    );
  }
  return null;
}

// ── Runs Log Section ───────────────────────────────────────────────────────

interface RunsLogSectionProps {
  source: ScraperSource;
}

function RunsLogSection({ source }: RunsLogSectionProps) {
  const runsQ = useCitySweepRuns(source);
  const cancelMut = useCancelCitySweep(source);

  return (
    <section className="scraper-section scraper-section--highlight">
      <h2>Очередь / Лог прогонов</h2>
      <p className="scraper-hint">
        Автообновление каждые 5 секунд. Показаны последние 10 прогонов.
      </p>
      <div className="city-sweep-runs">
        <h3>Recent runs (auto-refresh 5s)</h3>
        {runsQ.isPending && <p className="scraper-hint">Загрузка…</p>}
        {runsQ.error && (
          <p className="scraper-result scraper-result--error">
            Ошибка загрузки runs: {runsQ.error.message}
          </p>
        )}
        {runsQ.data && runsQ.data.length === 0 && (
          <p className="scraper-hint">Нет запусков пока.</p>
        )}
        {runsQ.data && runsQ.data.length > 0 && (
          <table className="runs-table">
            <thead>
              <tr>
                <th scope="col">#</th>
                <th scope="col">Статус</th>
                <th scope="col">Старт</th>
                <th scope="col">Heartbeat</th>
                <th scope="col">Anchors</th>
                <th scope="col">Lots</th>
                <th scope="col">Houses</th>
                <th scope="col">Detail</th>
                <th scope="col">Errors</th>
                <th scope="col">Действие</th>
              </tr>
            </thead>
            <tbody>
              {runsQ.data.map((r) => (
                <tr key={r.run_id} className={`run-row run-row--${r.status}`}>
                  <td>{r.run_id}</td>
                  <td>
                    <span className={`run-badge run-badge--${r.status}`}>
                      {translateStatus(r.status)}
                    </span>
                  </td>
                  <td>{formatTime(r.started_at)}</td>
                  <td>{formatTime(r.heartbeat_at)}</td>
                  <td>
                    {r.counters?.anchors_done ?? 0}/
                    {r.counters?.anchors_total ?? 0}
                  </td>
                  <td>
                    {r.counters?.lots_fetched ?? 0}{" "}
                    <span className="run-muted">
                      (+{r.counters?.lots_inserted ?? 0})
                    </span>
                  </td>
                  <td>
                    {r.counters?.houses_enriched ?? 0}/
                    {r.counters?.unique_houses ?? 0}
                  </td>
                  <td>
                    {r.counters?.detail_enriched ?? 0}/
                    {r.counters?.detail_attempted ?? 0}
                  </td>
                  <td>{r.counters?.errors_count ?? 0}</td>
                  <td>
                    {r.status === "running" && (
                      <button
                        type="button"
                        className="cancel-btn"
                        disabled={
                          cancelMut.isPending &&
                          cancelMut.variables === r.run_id
                        }
                        onClick={() => cancelMut.mutate(r.run_id)}
                      >
                        Отменить
                      </button>
                    )}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        )}
      </div>
    </section>
  );
}

// ── Cron Schedule Section ──────────────────────────────────────────────────

interface CronScheduleSectionProps {
  scheduleSource: ScheduleSourceKey;
  paramConfig: SweepParamConfig;
}

function CronScheduleSection({
  scheduleSource,
  paramConfig,
}: CronScheduleSectionProps) {
  const schedulesQ = useSchedules();
  const updateScheduleMut = useUpdateSchedule();

  const [schEnabled, setSchEnabled] = useState(true);
  const [schMskStart, setSchMskStart] = useState(5);
  const [schMskEnd, setSchMskEnd] = useState(8);
  const [schPages, setSchPages] = useState(
    paramConfig.defaults.pages_per_anchor,
  );
  const [schTopN, setSchTopN] = useState(paramConfig.defaults.detail_top_n ?? 0);
  const [schDelay, setSchDelay] = useState(
    paramConfig.defaults.request_delay_sec,
  );
  const [schRadius, setSchRadius] = useState(paramConfig.defaults.radius_m);
  const [schEnrichHouses, setSchEnrichHouses] = useState(
    paramConfig.defaults.enrich_houses ?? false,
  );
  const [schEnrichAddress, setSchEnrichAddress] = useState(
    paramConfig.defaults.enrich_address ?? false,
  );

  useEffect(() => {
    const sweep = schedulesQ.data?.find((s) => s.source === scheduleSource);
    if (!sweep) return;
    setSchEnabled(sweep.enabled);
    setSchMskStart(utcToMsk(sweep.window_start_hour));
    setSchMskEnd(utcToMsk(sweep.window_end_hour));
    const p = sweep.default_params ?? {};
    if (typeof p.pages_per_anchor === "number")
      setSchPages(p.pages_per_anchor);
    if (typeof p.detail_top_n === "number") setSchTopN(p.detail_top_n);
    if (typeof p.request_delay_sec === "number")
      setSchDelay(p.request_delay_sec);
    if (typeof p.radius_m === "number") setSchRadius(p.radius_m);
    if (typeof p.enrich_houses === "boolean") setSchEnrichHouses(p.enrich_houses);
    if (typeof p.enrich_address === "boolean")
      setSchEnrichAddress(p.enrich_address);
  }, [schedulesQ.data, scheduleSource]);

  const currentSweep = schedulesQ.data?.find(
    (s) => s.source === scheduleSource,
  );

  return (
    <section className="scraper-section scraper-section--schedule">
      <h2>Cron-расписание автозапуска (in-app scheduler)</h2>
      <p className="scraper-hint">
        Backend периодически (каждую минуту) проверяет это расписание и
        запускает city sweep автоматически в случайное время в указанном окне.
        Без SSH, без crontab — всё в БД и UI.
      </p>

      {schedulesQ.isPending && <p className="scraper-hint">Загрузка…</p>}
      {schedulesQ.error && (
        <p className="scraper-result scraper-result--error">
          Ошибка загрузки: {schedulesQ.error.message}
        </p>
      )}

      {currentSweep && (
        <div className="schedule-status">
          <div className="schedule-status__row">
            <span className="schedule-status__label">Статус:</span>
            <span
              className={`run-badge run-badge--${currentSweep.enabled ? "running" : "cancelled"}`}
            >
              {currentSweep.enabled ? "включено" : "отключено"}
            </span>
          </div>
          <div className="schedule-status__row">
            <span className="schedule-status__label">Окно (МСК):</span>
            <span>
              {formatMskRange(
                currentSweep.window_start_hour,
                currentSweep.window_end_hour,
              )}
            </span>
            <span className="run-muted">
              ({String(currentSweep.window_start_hour).padStart(2, "0")}:00–
              {String(currentSweep.window_end_hour).padStart(2, "0")}:00 UTC)
            </span>
          </div>
          <div className="schedule-status__row">
            <span className="schedule-status__label">Следующий запуск:</span>
            <span>{formatTime(currentSweep.next_run_at)}</span>
          </div>
          <div className="schedule-status__row">
            <span className="schedule-status__label">Последний запуск:</span>
            <span>
              {currentSweep.last_run_at
                ? formatTime(currentSweep.last_run_at)
                : "—"}
              {currentSweep.last_run_id &&
                ` (run #${currentSweep.last_run_id})`}
            </span>
          </div>
        </div>
      )}

      <form
        className="scraper-form"
        onSubmit={(e) => {
          e.preventDefault();
          const defaultParams: Record<string, unknown> = {
            pages_per_anchor: schPages,
            request_delay_sec: schDelay,
            radius_m: schRadius,
          };
          if (paramConfig.hasDetailParams) {
            defaultParams.detail_top_n = schTopN;
            defaultParams.enrich_houses = schEnrichHouses;
          }
          if (paramConfig.hasEnrichAddress) {
            defaultParams.enrich_address = schEnrichAddress;
          }
          updateScheduleMut.mutate({
            source: scheduleSource,
            payload: {
              enabled: schEnabled,
              window_start_hour: mskToUtc(schMskStart),
              window_end_hour: mskToUtc(schMskEnd),
              default_params: defaultParams,
            },
          });
        }}
      >
        <label className="checkbox">
          <input
            type="checkbox"
            checked={schEnabled}
            onChange={(e) => setSchEnabled(e.target.checked)}
          />
          Включено
        </label>
        <label>
          Окно начала (МСК)
          <input
            type="number"
            min={0}
            max={23}
            value={schMskStart}
            onChange={(e) => setSchMskStart(parseInt(e.target.value, 10))}
          />
        </label>
        <label>
          Окно окончания (МСК)
          <input
            type="number"
            min={0}
            max={23}
            value={schMskEnd}
            onChange={(e) => setSchMskEnd(parseInt(e.target.value, 10))}
          />
        </label>
        <label>
          Pages/anchor
          <input
            type="number"
            min={1}
            max={10}
            value={schPages}
            onChange={(e) => setSchPages(parseInt(e.target.value, 10))}
          />
        </label>
        {paramConfig.hasDetailParams && (
          <>
            <label>
              Detail top-N
              <input
                type="number"
                min={0}
                max={30}
                value={schTopN}
                onChange={(e) => setSchTopN(parseInt(e.target.value, 10))}
              />
            </label>
          </>
        )}
        <label>
          Delay (сек)
          <input
            type="number"
            step={0.5}
            min={3}
            max={15}
            value={schDelay}
            onChange={(e) => setSchDelay(parseFloat(e.target.value))}
          />
        </label>
        <label>
          Радиус (м)
          <input
            type="number"
            min={500}
            max={5000}
            step={100}
            value={schRadius}
            onChange={(e) => setSchRadius(parseInt(e.target.value, 10))}
          />
        </label>
        {paramConfig.hasDetailParams && (
          <label className="checkbox">
            <input
              type="checkbox"
              checked={schEnrichHouses}
              onChange={(e) => setSchEnrichHouses(e.target.checked)}
            />
            Enrich houses
          </label>
        )}
        {paramConfig.hasEnrichAddress && (
          <label className="checkbox">
            <input
              type="checkbox"
              checked={schEnrichAddress}
              onChange={(e) => setSchEnrichAddress(e.target.checked)}
            />
            Enrich address
          </label>
        )}
        <button type="submit" disabled={updateScheduleMut.isPending}>
          {updateScheduleMut.isPending
            ? "Сохраняем…"
            : "Сохранить расписание"}
        </button>
      </form>

      {updateScheduleMut.error && (
        <div className="scraper-result scraper-result--error">
          Ошибка: {updateScheduleMut.error.message}
        </div>
      )}
      {updateScheduleMut.isSuccess && updateScheduleMut.data && (
        <div className="scraper-result">
          Расписание обновлено. Next run:{" "}
          {formatTime(updateScheduleMut.data.next_run_at)}
        </div>
      )}
    </section>
  );
}

// ── Per-source Delay Section ───────────────────────────────────────────────

interface PerSourceDelaySectionProps {
  source: ScraperSource;
  delayMin?: number;
  delayMax?: number;
}

function PerSourceDelaySection({
  source,
  delayMin = 1,
  delayMax = 60,
}: PerSourceDelaySectionProps) {
  const settingsQ = useScraperSettings();
  const updateMut = useUpdateScraperSetting();

  const currentSetting = settingsQ.data?.find((s) => s.source === source);
  const [delayInput, setDelayInput] = useState<string>("5.0");
  const [initialized, setInitialized] = useState(false);

  useEffect(() => {
    if (currentSetting && !initialized) {
      setDelayInput(String(currentSetting.request_delay_sec));
      setInitialized(true);
    }
  }, [currentSetting, initialized]);

  return (
    <section className="scraper-section">
      <h2>Задержка запросов (этот скраппер)</h2>
      <p className="scraper-hint">
        Задержка применяется только к скрапперу <strong>{source}</strong>.
        Изменение вступает в силу немедленно без рестарта backend. Глобальный
        нижний порог настраивается на{" "}
        <a href="/scrapers">странице хаба скрапперов</a>.
      </p>

      {settingsQ.isPending && (
        <p className="scraper-hint">Загрузка настроек…</p>
      )}
      {settingsQ.error && (
        <p className="scraper-result scraper-result--error">
          Ошибка загрузки: {settingsQ.error.message}
        </p>
      )}

      {currentSetting && (
        <div className="schedule-status" style={{ marginBottom: "12px" }}>
          <div className="schedule-status__row">
            <span className="schedule-status__label">Текущая задержка:</span>
            <span>
              <strong>{currentSetting.request_delay_sec} сек</strong>
            </span>
          </div>
          {currentSetting.updated_at && (
            <div className="schedule-status__row">
              <span className="schedule-status__label">Обновлено:</span>
              <span>{formatTime(currentSetting.updated_at)}</span>
            </div>
          )}
          {currentSetting.description && (
            <div className="schedule-status__row">
              <span className="schedule-status__label">Описание:</span>
              <span className="run-muted">{currentSetting.description}</span>
            </div>
          )}
        </div>
      )}

      <form
        className="scraper-form"
        onSubmit={(e) => {
          e.preventDefault();
          updateMut.mutate({
            source,
            request_delay_sec: parseFloat(delayInput),
          });
        }}
      >
        <label>
          Задержка (сек, {delayMin}–{delayMax})
          <input
            type="number"
            min={delayMin}
            max={delayMax}
            step={0.5}
            value={delayInput}
            onChange={(e) => setDelayInput(e.target.value)}
            required
          />
        </label>
        <button type="submit" disabled={updateMut.isPending}>
          {updateMut.isPending ? "Сохраняем…" : "Сохранить задержку"}
        </button>
      </form>

      {updateMut.error && (
        <div className="scraper-result scraper-result--error">
          <strong>Ошибка:</strong> {updateMut.error.message}
        </div>
      )}
      {updateMut.isSuccess && updateMut.data && (
        <div className="scraper-result">
          Сохранено. Задержка:{" "}
          <strong>
            {Number(updateMut.data.request_delay_sec).toFixed(1)} сек
          </strong>
        </div>
      )}
    </section>
  );
}

// ── City Sweep Section (shared trigger) ───────────────────────────────────

interface CitySweepSectionProps {
  source: ScraperSource;
  paramConfig: SweepParamConfig;
}

function CitySweepSection({ source, paramConfig }: CitySweepSectionProps) {
  const sweepMut = useStartCitySweep(source);

  const [sweepPages, setSweepPages] = useState(
    String(paramConfig.defaults.pages_per_anchor),
  );
  const [sweepTopN, setSweepTopN] = useState(
    String(paramConfig.defaults.detail_top_n ?? 0),
  );
  const [sweepDelay, setSweepDelay] = useState(
    String(paramConfig.defaults.request_delay_sec),
  );
  const [sweepRadius, setSweepRadius] = useState(
    String(paramConfig.defaults.radius_m),
  );
  const [sweepEnrichHouses, setSweepEnrichHouses] = useState(
    paramConfig.defaults.enrich_houses ?? false,
  );
  const [sweepEnrichAddress, setSweepEnrichAddress] = useState(
    paramConfig.defaults.enrich_address ?? false,
  );

  return (
    <section className="scraper-section">
      <h2>Полный обход ЕКБ (city sweep)</h2>
      <p className="scraper-hint">
        Iterate anchors × N pages → save listings → enrich. Запускается в
        background. Можно отменить в логе прогонов ниже.
      </p>
      <form
        className="scraper-form"
        onSubmit={(e) => {
          e.preventDefault();
          const body: Record<string, unknown> = {
            pages_per_anchor: parseInt(sweepPages, 10),
            request_delay_sec: parseFloat(sweepDelay),
            radius_m: parseInt(sweepRadius, 10),
          };
          if (paramConfig.hasDetailParams) {
            body.detail_top_n = parseInt(sweepTopN, 10);
            body.enrich_houses = sweepEnrichHouses;
          }
          if (paramConfig.hasEnrichAddress) {
            body.enrich_address = sweepEnrichAddress;
          }
          sweepMut.mutate(body);
        }}
      >
        <label>
          Страниц на anchor
          <input
            type="number"
            min={1}
            max={10}
            value={sweepPages}
            onChange={(e) => setSweepPages(e.target.value)}
          />
        </label>
        {paramConfig.hasDetailParams && (
          <label>
            Detail top-N
            <input
              type="number"
              min={0}
              max={30}
              value={sweepTopN}
              onChange={(e) => setSweepTopN(e.target.value)}
            />
          </label>
        )}
        <label>
          Delay (сек)
          <input
            type="number"
            step={0.5}
            min={3}
            max={15}
            value={sweepDelay}
            onChange={(e) => setSweepDelay(e.target.value)}
          />
        </label>
        <label>
          Радиус (м)
          <input
            type="number"
            min={500}
            max={5000}
            step={100}
            value={sweepRadius}
            onChange={(e) => setSweepRadius(e.target.value)}
          />
        </label>
        {paramConfig.hasDetailParams && (
          <label className="checkbox">
            <input
              type="checkbox"
              checked={sweepEnrichHouses}
              onChange={(e) => setSweepEnrichHouses(e.target.checked)}
            />
            Enrich houses
          </label>
        )}
        {paramConfig.hasEnrichAddress && (
          <label className="checkbox">
            <input
              type="checkbox"
              checked={sweepEnrichAddress}
              onChange={(e) => setSweepEnrichAddress(e.target.checked)}
            />
            Enrich address
          </label>
        )}
        <button type="submit" disabled={sweepMut.isPending}>
          {sweepMut.isPending ? "Запускаем…" : "Запустить sweep"}
        </button>
      </form>

      {sweepMut.error && (
        <div className="scraper-result scraper-result--error">
          <strong>Ошибка:</strong> {sweepMut.error.message}
        </div>
      )}
      {sweepMut.isSuccess && sweepMut.data && (
        <div className="scraper-result">
          <pre>{JSON.stringify(sweepMut.data, null, 2)}</pre>
        </div>
      )}
    </section>
  );
}

// ── ScraperPage config ─────────────────────────────────────────────────────

export interface ScraperPageConfig {
  source: ScraperSource;
  activeTab: ActiveTab;
  displayName: string;
  subtitle: string;
  scheduleSource: ScheduleSourceKey;
  paramConfig: SweepParamConfig;
  delayMin?: number;
  delayMax?: number;
  /** Extra trigger forms rendered inside the "Запуск" section */
  extraTriggers?: React.ReactNode;
  /** Extra tabs/sections rendered after the standard 4 sections */
  extraSections?: React.ReactNode;
}

// ── ScraperPage (main export) ──────────────────────────────────────────────

export function ScraperPage({ config }: { config: ScraperPageConfig }) {
  const {
    source,
    activeTab,
    displayName,
    subtitle,
    scheduleSource,
    paramConfig,
    delayMin,
    delayMax,
    extraTriggers,
    extraSections,
  } = config;

  return (
    <>
      <Topbar active={activeTab} />
      <main className="page scraper-page">
        <h1 className="scraper-h1">{displayName} — admin триггер</h1>
        <p className="scraper-subtitle">{subtitle}</p>

        {/* Section 1: Запуск — city sweep + extra provider triggers */}
        <section className="scraper-section">
          <h2>Запуск</h2>
          {extraTriggers}
          <CitySweepSection source={source} paramConfig={paramConfig} />
        </section>

        {/* Section 2: Runs log */}
        <RunsLogSection source={source} />

        {/* Section 3: Cron schedule */}
        <CronScheduleSection
          scheduleSource={scheduleSource}
          paramConfig={paramConfig}
        />

        {/* Section 4: Per-source delay */}
        <PerSourceDelaySection
          source={source}
          delayMin={delayMin}
          delayMax={delayMax}
        />

        {/* Extra sections (e.g. cookies tab for cian) */}
        {extraSections}
      </main>
    </>
  );
}
