"use client";

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

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

// ── Types ──────────────────────────────────────────────────────────────────

interface ScrapeAroundInput {
  lat: number;
  lon: number;
  radius_m: number;
}

interface ScrapeAroundResp {
  total_fetched: number;
  total_inserted: number;
  total_updated: number;
  by_source: Array<{
    source: string;
    fetched: number;
    inserted: number;
    updated: number;
  }>;
}

interface HouseTriggerResp {
  ok: boolean;
  house_url: string;
  counters: Record<string, number | string>;
}

interface DetailTriggerResp {
  ok: boolean;
  item_id: string;
  updated: boolean;
}

interface ImvInput {
  address: string;
  rooms: number;
  area_m2: number;
  floor: number;
  floor_at_home: number;
  house_type: string;
  renovation_type: string;
  has_balcony: boolean;
  has_loggia: boolean;
}

interface ImvResp {
  ok: boolean;
  cache_key: string;
  recommended_price: number;
  range: [number, number];
  market_count: number | null;
  evaluation_id: number;
  history_saved: number;
}

// ── Mutations ──────────────────────────────────────────────────────────────

function useScrapeAround() {
  return useMutation<ScrapeAroundResp, Error, ScrapeAroundInput>({
    mutationFn: (input) =>
      apiFetch<ScrapeAroundResp>("/api/v1/admin/scrape", {
        method: "POST",
        body: JSON.stringify({ ...input, sources: ["avito"] }),
      }),
  });
}

function useScrapeHouse() {
  return useMutation<HouseTriggerResp, Error, { house_url: string }>({
    mutationFn: ({ house_url }) =>
      apiFetch<HouseTriggerResp>(
        `/api/v1/admin/scrape/avito-house?house_url=${encodeURIComponent(house_url)}`,
        { method: "POST" },
      ),
  });
}

function useScrapeDetail() {
  return useMutation<DetailTriggerResp, Error, { item_url: string }>({
    mutationFn: ({ item_url }) =>
      apiFetch<DetailTriggerResp>(
        `/api/v1/admin/scrape/avito-detail?item_url=${encodeURIComponent(item_url)}`,
        { method: "POST" },
      ),
  });
}

function useScrapeImv() {
  return useMutation<ImvResp, Error, ImvInput>({
    mutationFn: (input) => {
      const qs = new URLSearchParams({
        address: input.address,
        rooms: String(input.rooms),
        area_m2: String(input.area_m2),
        floor: String(input.floor),
        floor_at_home: String(input.floor_at_home),
        house_type: input.house_type,
        renovation_type: input.renovation_type,
        has_balcony: String(input.has_balcony),
        has_loggia: String(input.has_loggia),
      });
      return apiFetch<ImvResp>(
        `/api/v1/admin/scrape/avito-imv?${qs.toString()}`,
        { method: "POST" },
      );
    },
  });
}

// ── City Sweep types + hooks ───────────────────────────────────────────────

interface CitySweepInput {
  pages_per_anchor: number;
  detail_top_n: number;
  request_delay_sec: number;
  enrich_houses: boolean;
  radius_m: number;
}

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

interface CitySweepCounters {
  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;
}

interface ScrapeRunRow {
  run_id: number;
  source: string;
  status: string;
  params: Record<string, unknown> | null;
  counters: CitySweepCounters | null;
  error: string | null;
  started_at: string | null;
  finished_at: string | null;
  heartbeat_at: string | null;
}

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

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

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

// ── Schedule types + hooks ─────────────────────────────────────────────────

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;
    radius_m?: number;
  };
  last_run_id: number | null;
  last_run_at: string | null;
  next_run_at: string | null;
  updated_at: string | null;
}

interface ScheduleConfigUpdate {
  enabled: boolean;
  window_start_hour: number;
  window_end_hour: number;
  default_params: Record<string, unknown>;
}

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

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

// ── Result panel ───────────────────────────────────────────────────────────

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

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;
}

// ── Page ───────────────────────────────────────────────────────────────────

export default function AvitoScraperPage() {
  // 1. Around-point
  const [lat, setLat] = useState("56.8400");
  const [lon, setLon] = useState("60.6050");
  const [radius, setRadius] = useState("1500");
  const aroundMut = useScrapeAround();

  // 2. Houses Catalog
  const [houseUrl, setHouseUrl] = useState(
    "/catalog/houses/ekaterinburg/ul_akademika_postovskogo_17a/3171365",
  );
  const houseMut = useScrapeHouse();

  // 3. Detail
  const [itemUrl, setItemUrl] = useState(
    "/ekaterinburg/kvartiry/3-k._kvartira_75_m_2026_et._7986882804",
  );
  const detailMut = useScrapeDetail();

  // 4. IMV
  const [imvAddress, setImvAddress] = useState(
    "Свердловская обл., Екатеринбург, ул. Чайковского, 66А",
  );
  const [imvRooms, setImvRooms] = useState("2");
  const [imvArea, setImvArea] = useState("42");
  const [imvFloor, setImvFloor] = useState("4");
  const [imvTotalFloors, setImvTotalFloors] = useState("5");
  const [imvHouseType, setImvHouseType] = useState("panel");
  const [imvRenovation, setImvRenovation] = useState("cosmetic");
  const [imvBalcony, setImvBalcony] = useState(true);
  const [imvLoggia, setImvLoggia] = useState(false);
  const imvMut = useScrapeImv();

  // City sweep state
  const [sweepPages, setSweepPages] = useState("3");
  const [sweepTopN, setSweepTopN] = useState("20");
  const [sweepDelay, setSweepDelay] = useState("7");
  const [sweepRadius, setSweepRadius] = useState("1500");
  const [sweepEnrichHouses, setSweepEnrichHouses] = useState(true);
  const sweepMut = useStartCitySweep();
  const sweepRuns = useCitySweepRuns();
  const cancelMut = useCancelCitySweep();

  // Scheduler state
  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(3);
  const [schTopN, setSchTopN] = useState(20);
  const [schDelay, setSchDelay] = useState(7);
  const [schRadius, setSchRadius] = useState(1500);
  const [schEnrich, setSchEnrich] = useState(true);

  useEffect(() => {
    const sweep = schedulesQ.data?.find((s) => s.source === "avito_city_sweep");
    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") setSchEnrich(p.enrich_houses);
  }, [schedulesQ.data]);

  return (
    <>
      <Topbar active="avito" />
      <main className="page scraper-page">
        <h1 className="scraper-h1">Avito скрапер — admin триггер</h1>
        <p className="scraper-subtitle">
          Ручной запуск sub-pipeline&apos;ов AvitoScraper v2. Для бесплатной
          разработки и теста после изменений.
        </p>

        {/* 1. Search around point */}
        <section className="scraper-section">
          <h2>1. Search around (общий /admin/scrape)</h2>
          <p className="scraper-hint">
            Запускает классический cron-pipeline для одного якоря: search →
            save_listings. Использовать как baseline.
          </p>
          <form
            className="scraper-form"
            onSubmit={(e) => {
              e.preventDefault();
              aroundMut.mutate({
                lat: parseFloat(lat),
                lon: parseFloat(lon),
                radius_m: parseInt(radius, 10),
              });
            }}
          >
            <label>
              Lat
              <input
                value={lat}
                onChange={(e) => setLat(e.target.value)}
                required
              />
            </label>
            <label>
              Lon
              <input
                value={lon}
                onChange={(e) => setLon(e.target.value)}
                required
              />
            </label>
            <label>
              Radius (м)
              <input
                value={radius}
                onChange={(e) => setRadius(e.target.value)}
                required
              />
            </label>
            <button type="submit" disabled={aroundMut.isPending}>
              {aroundMut.isPending ? "Скрейпим…" : "Запустить"}
            </button>
          </form>
          <ResultPanel mut={aroundMut} />
        </section>

        {/* 2. Houses Catalog */}
        <section className="scraper-section">
          <h2>2. Houses Catalog enrichment</h2>
          <p className="scraper-hint">
            Парсит /catalog/houses/&lt;slug&gt;/&lt;id&gt; — характеристики
            дома, отзывы, history.
          </p>
          <form
            className="scraper-form"
            onSubmit={(e) => {
              e.preventDefault();
              houseMut.mutate({ house_url: houseUrl });
            }}
          >
            <label className="full">
              house_url
              <input
                value={houseUrl}
                onChange={(e) => setHouseUrl(e.target.value)}
                required
                placeholder="/catalog/houses/<city>/<slug>/<id>"
              />
            </label>
            <button type="submit" disabled={houseMut.isPending}>
              {houseMut.isPending ? "Парсим…" : "Запустить"}
            </button>
          </form>
          <ResultPanel mut={houseMut} />
        </section>

        {/* 3. Detail */}
        <section className="scraper-section">
          <h2>3. Detail page enrichment</h2>
          <p className="scraper-hint">
            Парсит /ekaterinburg/kvartiry/&lt;...&gt; — 30+ полей
            (kitchen_area, owners_count, etc). UPDATE listings WHERE source_id
            (listing должен уже существовать).
          </p>
          <form
            className="scraper-form"
            onSubmit={(e) => {
              e.preventDefault();
              detailMut.mutate({ item_url: itemUrl });
            }}
          >
            <label className="full">
              item_url
              <input
                value={itemUrl}
                onChange={(e) => setItemUrl(e.target.value)}
                required
                placeholder="/ekaterinburg/kvartiry/.../...."
              />
            </label>
            <button type="submit" disabled={detailMut.isPending}>
              {detailMut.isPending ? "Парсим…" : "Запустить"}
            </button>
          </form>
          <ResultPanel mut={detailMut} />
        </section>

        {/* 4. IMV evaluation */}
        <section className="scraper-section">
          <h2>4. IMV evaluation (debug, без cache)</h2>
          <p className="scraper-hint">
            Прямой вызов Avito IMV API (2 HTTP requests). Production вызов из
            estimator on-demand с 24h cache; здесь — для debug.
          </p>
          <form
            className="scraper-form"
            onSubmit={(e) => {
              e.preventDefault();
              imvMut.mutate({
                address: imvAddress,
                rooms: parseInt(imvRooms, 10),
                area_m2: parseFloat(imvArea),
                floor: parseInt(imvFloor, 10),
                floor_at_home: parseInt(imvTotalFloors, 10),
                house_type: imvHouseType,
                renovation_type: imvRenovation,
                has_balcony: imvBalcony,
                has_loggia: imvLoggia,
              });
            }}
          >
            <label className="full">
              Адрес
              <input
                value={imvAddress}
                onChange={(e) => setImvAddress(e.target.value)}
                required
              />
            </label>
            <label>
              Комн.
              <input
                value={imvRooms}
                onChange={(e) => setImvRooms(e.target.value)}
                required
              />
            </label>
            <label>
              Площадь (м²)
              <input
                value={imvArea}
                onChange={(e) => setImvArea(e.target.value)}
                required
              />
            </label>
            <label>
              Этаж
              <input
                value={imvFloor}
                onChange={(e) => setImvFloor(e.target.value)}
                required
              />
            </label>
            <label>
              Этажей в доме
              <input
                value={imvTotalFloors}
                onChange={(e) => setImvTotalFloors(e.target.value)}
                required
              />
            </label>
            <label>
              Тип дома
              <select
                value={imvHouseType}
                onChange={(e) => setImvHouseType(e.target.value)}
              >
                <option value="panel">панель</option>
                <option value="brick">кирпич</option>
                <option value="monolith">монолит</option>
                <option value="monolith_brick">монолит-кирпич</option>
                <option value="block">блочный</option>
                <option value="wood">дерево</option>
              </select>
            </label>
            <label>
              Ремонт
              <select
                value={imvRenovation}
                onChange={(e) => setImvRenovation(e.target.value)}
              >
                <option value="required">требуется</option>
                <option value="cosmetic">косметический</option>
                <option value="euro">евро</option>
                <option value="designer">дизайнерский</option>
              </select>
            </label>
            <label className="checkbox">
              <input
                type="checkbox"
                checked={imvBalcony}
                onChange={(e) => setImvBalcony(e.target.checked)}
              />
              Балкон
            </label>
            <label className="checkbox">
              <input
                type="checkbox"
                checked={imvLoggia}
                onChange={(e) => setImvLoggia(e.target.checked)}
              />
              Лоджия
            </label>
            <button type="submit" disabled={imvMut.isPending}>
              {imvMut.isPending ? "Запрашиваем Avito…" : "Запустить IMV"}
            </button>
          </form>
          <ResultPanel mut={imvMut} />
        </section>

        {/* 5. City sweep ЕКБ */}
        <section className="scraper-section scraper-section--highlight">
          <h2>5. Полный обход ЕКБ (auto sweep)</h2>
          <p className="scraper-hint">
            Iterate 5 anchors × N pages → save listings → enrich houses + detail top-N.
            Запускается в background (~15-30 мин для defaults). Можно отменить через
            «Отменить» в списке ниже. Cron автоматически запускает каждый день в случайное
            время 02:00-05:00 UTC.
          </p>
          <form
            className="scraper-form"
            onSubmit={(e) => {
              e.preventDefault();
              sweepMut.mutate({
                pages_per_anchor: parseInt(sweepPages, 10),
                detail_top_n: parseInt(sweepTopN, 10),
                request_delay_sec: parseFloat(sweepDelay),
                enrich_houses: sweepEnrichHouses,
                radius_m: parseInt(sweepRadius, 10),
              });
            }}
          >
            <label>
              Страниц на anchor
              <input
                type="number"
                min={1}
                max={10}
                value={sweepPages}
                onChange={(e) => setSweepPages(e.target.value)}
              />
            </label>
            <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>
            <label className="checkbox">
              <input
                type="checkbox"
                checked={sweepEnrichHouses}
                onChange={(e) => setSweepEnrichHouses(e.target.checked)}
              />
              Enrich houses
            </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>
          )}

          {/* Recent runs list */}
          <div className="city-sweep-runs">
            <h3>Recent runs (auto-refresh 5s)</h3>
            {sweepRuns.isPending && <p className="scraper-hint">Загрузка…</p>}
            {sweepRuns.error && (
              <p className="scraper-result scraper-result--error">
                Ошибка загрузки runs: {sweepRuns.error.message}
              </p>
            )}
            {sweepRuns.data && sweepRuns.data.length === 0 && (
              <p className="scraper-hint">Нет запусков пока.</p>
            )}
            {sweepRuns.data && sweepRuns.data.length > 0 && (
              <table className="runs-table">
                <thead>
                  <tr>
                    <th>#</th>
                    <th>Статус</th>
                    <th>Старт</th>
                    <th>Heartbeat</th>
                    <th>Anchors</th>
                    <th>Lots</th>
                    <th>Houses</th>
                    <th>Detail</th>
                    <th>Errors</th>
                    <th>Действие</th>
                  </tr>
                </thead>
                <tbody>
                  {sweepRuns.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>

        {/* 6. Расписание автозапуска */}
        <section className="scraper-section scraper-section--schedule">
          <h2>6. Расписание автозапуска (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>
          )}

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

              {/* Edit form */}
              <form
                className="scraper-form"
                onSubmit={(e) => {
                  e.preventDefault();
                  updateScheduleMut.mutate({
                    source: "avito_city_sweep",
                    payload: {
                      enabled: schEnabled,
                      window_start_hour: mskToUtc(schMskStart),
                      window_end_hour: mskToUtc(schMskEnd),
                      default_params: {
                        pages_per_anchor: schPages,
                        detail_top_n: schTopN,
                        request_delay_sec: schDelay,
                        enrich_houses: schEnrich,
                        radius_m: schRadius,
                      },
                    },
                  });
                }}
              >
                <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>
                <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>
                <label className="checkbox">
                  <input
                    type="checkbox"
                    checked={schEnrich}
                    onChange={(e) => setSchEnrich(e.target.checked)}
                  />
                  Enrich houses
                </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>
      </main>
    </>
  );
}

// ── Helpers ────────────────────────────────────────────────────────────────

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;
  }
}

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;
  }
}

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

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 МСК`;
}
