import { useEffect, useRef, useState } from "react";
import { Icon } from "@nodedc/ui-react";

import type { ObservationSourceDelivery } from "../core/runtime/contracts";

type PlayerStatus = "connecting" | "buffering" | "playing" | "error";

export interface CameraLeaseRetryBudget {
  deliveryId: string;
  count: number;
}

const CAMERA_LEASE_RETRY_DELAYS = [400, 1_000, 2_000] as const;

export function resetCameraLeaseRetryBudget(deliveryId: string): CameraLeaseRetryBudget {
  return { deliveryId, count: 0 };
}

export function consumeCameraLeaseRetry(
  current: CameraLeaseRetryBudget,
  deliveryId: string,
): { budget: CameraLeaseRetryBudget; delay: number | null } {
  const count = current.deliveryId === deliveryId ? current.count : 0;
  const delay = CAMERA_LEASE_RETRY_DELAYS[count] ?? null;
  return {
    budget: { deliveryId, count: delay === null ? count : count + 1 },
    delay,
  };
}

function websocketUrl(path: string): string {
  const url = new URL(path, window.location.href);
  if (url.protocol === "http:") url.protocol = "ws:";
  if (url.protocol === "https:") url.protocol = "wss:";
  if (url.protocol !== "ws:" && url.protocol !== "wss:") {
    throw new Error("Адаптер вернул неподдерживаемый адрес видеопотока.");
  }
  return url.toString();
}

export function MseFmp4WebSocketPlayer({
  delivery,
  label,
}: {
  delivery: ObservationSourceDelivery & { kind: "mse-fmp4-websocket" };
  label: string;
}) {
  const videoRef = useRef<HTMLVideoElement>(null);
  const leaseRetryRef = useRef(resetCameraLeaseRetryBudget(delivery.id));
  const [attempt, setAttempt] = useState(0);
  const [status, setStatus] = useState<PlayerStatus>("connecting");
  const [message, setMessage] = useState("Подключение к локальному видеопотоку");

  useEffect(() => {
    const video = videoRef.current;
    if (!video) return;

    let disposed = false;
    let socket: WebSocket | null = null;
    let sourceBuffer: SourceBuffer | null = null;
    let objectUrl = "";
    let retryTimer: number | undefined;
    let receivedMedia = false;
    let failed = false;
    const queue: ArrayBuffer[] = [];
    let queuedBytes = 0;

    const fail = (copy: string) => {
      if (disposed || failed) return;
      failed = true;
      setStatus("error");
      setMessage(copy);
      try {
        if (socket && socket.readyState < WebSocket.CLOSING) {
          socket.close(1011, "Live video buffer reset");
        }
      } catch {
        // The manual reconnect button will create a fresh transport and MSE buffer.
      }
    };

    const retryLease = () => {
      const retry = consumeCameraLeaseRetry(leaseRetryRef.current, delivery.id);
      leaseRetryRef.current = retry.budget;
      if (retry.delay === null) {
        fail("Camera adapter ещё занят предыдущим окном. Подключитесь повторно.");
        return;
      }
      setStatus("connecting");
      setMessage("Освобождение предыдущего окна камеры");
      retryTimer = window.setTimeout(() => {
        if (!disposed) setAttempt((value) => value + 1);
      }, retry.delay);
    };

    const onPlaying = () => {
      if (disposed) return;
      leaseRetryRef.current = resetCameraLeaseRetryBudget(delivery.id);
      setStatus("playing");
      setMessage("");
    };

    video.addEventListener("playing", onPlaying);

    const appendNext = () => {
      if (disposed || !sourceBuffer || sourceBuffer.updating || queue.length === 0) return;
      const chunk = queue.shift();
      if (!chunk) return;
      queuedBytes -= chunk.byteLength;
      try {
        sourceBuffer.appendBuffer(chunk);
      } catch (error) {
        fail(error instanceof DOMException && error.name === "QuotaExceededError"
          ? "Live-буфер переполнен и сброшен, чтобы не накапливать задержку."
          : "Не удалось добавить видеосегмент. Повторите подключение.");
      }
    };

    const enqueue = (chunk: ArrayBuffer) => {
      if (disposed || chunk.byteLength === 0) return;
      // Never drop arbitrary fMP4 fragments: the following samples may depend
      // on them. A bounded reset is safer and keeps live latency deterministic.
      if (queuedBytes + chunk.byteLength > 2 * 1024 * 1024) {
        fail("Видеодекодер не успевает за эфиром. Live-буфер сброшен.");
        return;
      }
      queue.push(chunk);
      queuedBytes += chunk.byteLength;
      appendNext();
    };

    const mediaType = delivery.mediaType?.trim();
    if (typeof MediaSource === "undefined") {
      fail("Этот браузер не поддерживает Media Source Extensions.");
      return;
    }
    if (!mediaType || !MediaSource.isTypeSupported(mediaType)) {
      fail(mediaType
        ? `Браузер не поддерживает ${mediaType}`
        : "Адаптер не сообщил MIME/codec fMP4-потока.");
      return;
    }

    setStatus("connecting");
    setMessage("Подключение к локальному видеопотоку");
    const mediaSource = new MediaSource();
    objectUrl = URL.createObjectURL(mediaSource);
    video.src = objectUrl;

    const onSourceOpen = () => {
      if (disposed) return;
      try {
        sourceBuffer = mediaSource.addSourceBuffer(mediaType);
      } catch {
        fail("Не удалось создать MSE-буфер для указанного кодека.");
        return;
      }

      sourceBuffer.addEventListener("updateend", () => {
        if (disposed || !sourceBuffer) return;
        const buffered = sourceBuffer.buffered;
        if (buffered.length > 0) {
          const end = buffered.end(buffered.length - 1);
          const start = buffered.start(0);
          if (end - video.currentTime > 1) video.currentTime = Math.max(0, end - 0.1);
          if (!receivedMedia) {
            receivedMedia = true;
            setStatus("buffering");
            setMessage("Запуск первого декодированного кадра");
            void video.play().catch(() => undefined);
          }
          const removeBefore = end - 3;
          if (removeBefore > start && !sourceBuffer.updating) {
            try {
              sourceBuffer.remove(0, removeBefore);
              return;
            } catch {
              // Continue appending; quota handling performs a clean reset.
            }
          }
        }
        appendNext();
      });
      sourceBuffer.addEventListener("error", () => {
        fail("MSE сообщил об ошибке декодирования видеосегмента.");
      });

      try {
        socket = new WebSocket(websocketUrl(delivery.url));
      } catch (error) {
        fail(error instanceof Error ? error.message : "Некорректный адрес видеопотока.");
        return;
      }
      socket.binaryType = "arraybuffer";
      socket.addEventListener("open", () => {
        if (disposed) return;
        setStatus("buffering");
        setMessage("Ожидание первого видеокадра");
      });
      socket.addEventListener("message", (event) => {
        if (event.data instanceof ArrayBuffer) {
          enqueue(event.data);
        } else if (event.data instanceof Blob) {
          void event.data.arrayBuffer().then(enqueue).catch(() => {
            fail("Получен повреждённый видеосегмент.");
          });
        }
      });
      socket.addEventListener("error", () => {
        fail("Соединение с локальным video adapter потеряно.");
      });
      socket.addEventListener("close", (event) => {
        if (!disposed && !failed && event.code === 1008) {
          retryLease();
        } else if (!disposed) {
          fail(event.code === 1000
            ? "Видеопоток завершён. Можно подключиться повторно."
            : "Видеопоток прерван. Проверьте устройство и повторите подключение.");
        }
      });
    };

    mediaSource.addEventListener("sourceopen", onSourceOpen, { once: true });

    return () => {
      disposed = true;
      if (retryTimer !== undefined) window.clearTimeout(retryTimer);
      queue.length = 0;
      socket?.close(1000, "Источник скрыт оператором");
      video.removeEventListener("playing", onPlaying);
      try {
        if (sourceBuffer?.updating) sourceBuffer.abort();
      } catch {
        // The MediaSource can already be closing.
      }
      try {
        if (mediaSource.readyState === "open") mediaSource.endOfStream();
      } catch {
        // Cleanup must continue even if the browser has already detached MSE.
      }
      video.pause();
      video.removeAttribute("src");
      video.load();
      if (objectUrl) URL.revokeObjectURL(objectUrl);
    };
  }, [attempt, delivery.id, delivery.mediaType, delivery.url]);

  return (
    <div className="mse-fmp4-player" data-status={status}>
      <video
        ref={videoRef}
        className="observation-media__asset"
        aria-label={label}
        autoPlay
        muted
        playsInline
      />
      {status !== "playing" ? (
        <div className="mse-fmp4-player__status" role="status" aria-live="polite">
          <Icon name={status === "error" ? "alert" : "video"} size={20} />
          <strong>{status === "error" ? "Канал прерван" : "Подготовка камеры"}</strong>
          <span>{message}</span>
          {status === "error" ? (
            <button
              type="button"
              onClick={() => {
                leaseRetryRef.current = resetCameraLeaseRetryBudget(delivery.id);
                setAttempt((value) => value + 1);
              }}
            >
              <Icon name="refresh" size={14} />
              Подключиться повторно
            </button>
          ) : null}
        </div>
      ) : null}
    </div>
  );
}
