import { useEffect, useState } from "react";
import {
  Button,
  GlassSurface,
  StatusBadge,
} from "@nodedc/ui-react";

import {
  fetchDatasetGatewayCatalog,
  type DatasetGatewayCatalog,
  type DatasetSource,
  type DatasetSourceAdmissionStatus,
} from "../core/lidar/datasetGateway";
import {
  fetchPolygonRunCatalog,
  type PolygonRunCatalog,
} from "../core/polygon/runArchive";
import { GooseDatasetReview } from "./GooseDatasetReview";
import { RellisDatasetReview } from "./RellisDatasetReview";

const representationLabels: Record<string, string> = {
  "native-scan": "Исходный скан",
  "normalized-scan": "Нормализованный скан",
  "rolling-local-map": "Локальная карта",
};

function errorMessage(error: unknown): string {
  return error instanceof Error && error.message.trim()
    ? error.message
    : "Каталог датасетов недоступен.";
}

function admissionLabel(status: DatasetSourceAdmissionStatus): string {
  const labels: Record<DatasetSourceAdmissionStatus, string> = {
    "blocked-storage-policy": "Worker не настроен",
    "ready-for-download": "Готов к загрузке",
    "ready-for-smoke": "Готов к smoke",
    downloading: "Загружается",
    downloaded: "Загружен",
    verifying: "Проверяется",
    verified: "Проверен",
    "frame-ready": "Dataset готов",
    "smoke-ready": "Smoke готов",
  };
  return labels[status];
}

function formatBytes(value: number): string {
  return new Intl.NumberFormat("ru-RU", {
    style: "unit",
    unit: "gigabyte",
    maximumFractionDigits: 2,
  }).format(value / 1_000_000_000);
}

function isGooseQualificationRun(
  item: PolygonRunCatalog["items"][number],
): boolean {
  return item.kind === "replay_shadow"
    && item.scenarioGeneration.startsWith("goose-3d");
}

function sourceReady(source: DatasetSource): boolean {
  return source.admissionStatus === "frame-ready"
    || source.admissionStatus === "smoke-ready";
}

function sourceDescription(source: DatasetSource): string {
  if (source.sourceKind === "goose") {
    return "Восемь фрагментов и 961 разреженный размеченный оборот VLS-128. Здесь уже опубликовано покадровое сравнение текущего ground baseline и Patchwork++.";
  }
  return "Независимый off-road источник с Ouster OS1 64. Сейчас открыт официальный frame 000104 для проверки reader, осей, ontology и ground-policy; полный benchmark ещё не выполнялся.";
}

function sourceFooter(source: DatasetSource, storageReady: boolean): string {
  if (source.admissionStatus === "frame-ready") {
    return "Native scans и разметка приняты. Можно открыть фрагменты и результаты алгоритмов.";
  }
  if (source.admissionStatus === "smoke-ready") {
    return "Официальный пример совместим. Можно проверить scan, классы и ground mapping.";
  }
  if (source.admissionStatus === "downloading") {
    return "Архив остаётся на D worker; после загрузки проверяются digest, лицензия и point-label alignment.";
  }
  if (!storageReady) {
    return "Сначала нужен допущенный Dataset Root на диске D worker.";
  }
  return source.sourceKind === "rellis"
    ? "Следующий шаг: пропустить официальный 24 МБ example через smoke admission."
    : "Следующий шаг: загрузить validation archive на worker и проверить hash/license.";
}

export function DatasetGatewayWorkspace() {
  const [catalog, setCatalog] = useState<DatasetGatewayCatalog | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [reloadGeneration, setReloadGeneration] = useState(0);
  const [openSourceId, setOpenSourceId] = useState<string | null>(null);
  const [runCatalog, setRunCatalog] = useState<PolygonRunCatalog | null>(null);
  const [selectedRunId, setSelectedRunId] = useState<string | null>(null);
  const [runError, setRunError] = useState<string | null>(null);

  useEffect(() => {
    const controller = new AbortController();
    setLoading(true);
    setError(null);
    void fetchDatasetGatewayCatalog({ signal: controller.signal })
      .then((value) => {
        if (!controller.signal.aborted) setCatalog(value);
      })
      .catch((loadError: unknown) => {
        if (controller.signal.aborted) return;
        setCatalog(null);
        setError(errorMessage(loadError));
      })
      .finally(() => {
        if (!controller.signal.aborted) setLoading(false);
      });
    return () => controller.abort();
  }, [reloadGeneration]);

  useEffect(() => {
    if (!catalog?.sources.some((source) => source.admissionStatus === "downloading")) {
      return;
    }
    const timer = window.setTimeout(
      () => setReloadGeneration((value) => value + 1),
      5_000,
    );
    return () => window.clearTimeout(timer);
  }, [catalog]);

  useEffect(() => {
    const controller = new AbortController();
    setRunError(null);
    void fetchPolygonRunCatalog({ signal: controller.signal })
      .then((value) => {
        if (controller.signal.aborted) return;
        setRunCatalog(value);
        const requested = new URLSearchParams(window.location.search).get("run");
        const gooseRuns = value.items.filter(isGooseQualificationRun);
        const selected = gooseRuns.find((item) => item.runId === requested)
          ?? gooseRuns[0]
          ?? null;
        setSelectedRunId(selected?.runId ?? null);
      })
      .catch((loadError: unknown) => {
        if (!controller.signal.aborted) setRunError(errorMessage(loadError));
      });
    return () => controller.abort();
  }, []);

  const gooseRuns = runCatalog?.items.filter(isGooseQualificationRun) ?? [];
  const openSource = catalog?.sources.find(
    (source) => source.sourceId === openSourceId,
  ) ?? null;

  return (
    <div className="standard-workspace dataset-workspace">
      <section className="dataset-purpose" aria-label="Назначение датасетов">
        <div>
          <span className="section-eyebrow">ЗАЧЕМ ЭТО НУЖНО</span>
          <h2>Независимая проверка алгоритмов</h2>
          <p>
            Каждый источник остаётся отдельным: собственные sensor domain,
            разметка, лицензия и результаты. Совпадение результата на GOOSE и
            RELLIS снижает риск подгонки под один датасет, но не заменяет replay
            реального сенсора и closed-loop симуляцию.
          </p>
        </div>
        <ol aria-label="Как использовать датасет">
          <li><span>01</span>Выбрать источник</li>
          <li><span>02</span>Проверить scan и labels</li>
          <li><span>03</span>Сравнить алгоритмы</li>
          <li><span>04</span>Проверить переносимость</li>
        </ol>
      </section>

      {loading && !catalog ? (
        <GlassSurface className="dataset-state" padding="lg">
          <StatusBadge tone="accent">Проверяем каталог</StatusBadge>
          <h2>Читаем допущенные источники</h2>
          <p>Ни один большой архив не загружается автоматически.</p>
        </GlassSurface>
      ) : error ? (
        <GlassSurface className="dataset-state" padding="lg">
          <StatusBadge tone="danger">Каталог недоступен</StatusBadge>
          <h2>Датасеты не открыты</h2>
          <p>{error}</p>
          <Button
            size="compact"
            variant="secondary"
            onClick={() => setReloadGeneration((value) => value + 1)}
          >
            Повторить
          </Button>
        </GlassSurface>
      ) : catalog ? (
        <>
          <GlassSurface className="dataset-library" padding="lg">
            <header className="dataset-library__heading">
              <div>
                <span className="section-eyebrow">КАТАЛОГ</span>
                <h2>Доступные источники</h2>
              </div>
              <StatusBadge tone={catalog.storage.admitted ? "success" : "warning"}>
                {catalog.storage.admitted ? "Worker готов" : "Worker не настроен"}
              </StatusBadge>
            </header>

            <div className="dataset-library__entries">
              {catalog.sources.map((source) => {
                const downloadProgress = source.archive
                  ? source.archive.bytesTransferred / source.archive.totalBytes
                  : null;
                const opened = source.sourceId === openSourceId;
                return (
                  <article
                    className="dataset-entry"
                    data-source={source.sourceKind}
                    key={source.sourceId}
                  >
                    <div className="dataset-entry__identity">
                      <span className="dataset-entry__mark">
                        {source.sourceKind === "goose" ? "G" : "R"}
                      </span>
                      <div>
                        <span>
                          PUBLIC · OFF-ROAD · LABELED
                          {source.sourceKind === "rellis" ? " · RESEARCH" : ""}
                        </span>
                        <h3>{source.displayName}</h3>
                        <p>{sourceDescription(source)}</p>
                      </div>
                    </div>
                    <dl>
                      <div>
                        <dt>Состояние</dt>
                        <dd>{admissionLabel(source.admissionStatus)}</dd>
                      </div>
                      <div>
                        <dt>Сканер</dt>
                        <dd>{source.sensor}</dd>
                      </div>
                      <div>
                        <dt>Размеченных сканов</dt>
                        <dd>{source.annotatedScanCount.toLocaleString("ru-RU")}</dd>
                      </div>
                      <div>
                        <dt>Лицензия</dt>
                        <dd>{source.license}</dd>
                      </div>
                    </dl>
                    {source.admissionStatus === "downloading"
                    && source.archive
                    && downloadProgress !== null ? (
                      <div
                        className="dataset-download"
                        aria-label={`Загрузка ${source.displayName}`}
                      >
                        <span style={{ width: `${downloadProgress * 100}%` }} />
                        <p>
                          {formatBytes(source.archive.bytesTransferred)}
                          {" из "}
                          {formatBytes(source.archive.totalBytes)}
                        </p>
                      </div>
                    ) : null}
                    <footer>
                      <p>{sourceFooter(source, catalog.storage.admitted)}</p>
                      <div>
                        <Button
                          size="compact"
                          variant="secondary"
                          disabled={!sourceReady(source)}
                          title={
                            sourceReady(source)
                              ? "Открыть содержимое источника"
                              : "Совместимый preview ещё не опубликован"
                          }
                          onClick={() => setOpenSourceId(
                            opened ? null : source.sourceId,
                          )}
                        >
                          {opened ? "Закрыть датасет" : "Открыть датасет"}
                        </Button>
                      </div>
                    </footer>
                  </article>
                );
              })}
            </div>
          </GlassSurface>

          {openSource?.sourceKind === "goose" && selectedRunId ? (
            <>
              {gooseRuns.length > 1 ? (
                <div className="dataset-result-select">
                  <div>
                    <span className="section-eyebrow">РЕЗУЛЬТАТЫ ОБРАБОТКИ</span>
                    <strong>Версия анализа</strong>
                  </div>
                  <select
                    aria-label="Выбрать версию анализа"
                    value={selectedRunId}
                    onChange={(event) => setSelectedRunId(event.target.value)}
                  >
                    {gooseRuns.map((item) => (
                      <option key={item.runId} value={item.runId}>
                        {item.profileGeneration}
                      </option>
                    ))}
                  </select>
                </div>
              ) : null}
              <GooseDatasetReview runId={selectedRunId} />
            </>
          ) : openSource?.sourceKind === "goose" ? (
            <section className="polygon-review-unavailable">
              <StatusBadge tone="warning">Review недоступен</StatusBadge>
              <h3>GOOSE принят, но анализ не опубликован</h3>
              <p>{runError ?? "Нет совместимой версии покадрового анализа."}</p>
            </section>
          ) : openSource?.sourceKind === "rellis" ? (
            <RellisDatasetReview />
          ) : null}

          <details className="dataset-contract">
            <summary>
              <span>Технический контракт</span>
              <small>Представления, storage gate и граница реального сенсора</small>
            </summary>
            <div className="dataset-contract__content">
              <section>
                <h3>Представления</h3>
                <ul>
                  {catalog.representations.map((representation) => (
                    <li key={representation.id}>
                      <strong>
                        {representationLabels[representation.id] ?? representation.title}
                      </strong>
                      <span>{representation.title}</span>
                    </li>
                  ))}
                </ul>
              </section>
              <section>
                <h3>Storage gate</h3>
                <p>{catalog.storage.requiredWindowsRoot}</p>
                <p>{catalog.storage.requiredWslRoot}</p>
              </section>
              <section>
                <h3>Граница реального сенсора</h3>
                <p>
                  Текущий MQTT-вход — vendor-mapped increment, а не native scan.
                  Отсутствующие per-point time и ring/line не восстанавливаются.
                </p>
              </section>
            </div>
          </details>
        </>
      ) : null}
    </div>
  );
}
