import { useLayoutEffect, useState, type RefObject } from "react";
import { WorkspaceWindow } from "@nodedc/ui-react";

import type { ObservationWindowRect } from "../core/observation/useObservationLayout";
import type { ObservationSourceDescriptor } from "../core/runtime/contracts";
import { ObservationMedia, observationSourceStatusLabel } from "./ObservationSources";
import type { RecordedObservationPlayback } from "./RecordedFmp4Player";
import type {
  RecordedAdmissionPhase,
  RecordedCameraAdmissionState,
} from "../core/observation/recordedSessionAdmission";

const WINDOW_WIDTH = 336;
const WINDOW_HEIGHT = 210;
const WINDOW_GAP = 16;
const WINDOW_INSET = 18;
const TIMELINE_CLEARANCE = 64;

type ClosestTarget = { closest: (selectors: string) => unknown };

export function shouldCaptureWorkspacePointer(
  button: number,
  target: ClosestTarget | null,
): boolean {
  if (button !== 0 || !target) return false;
  if (target.closest(".nodedc-workspace-window__resize")) return true;
  if (!target.closest(".nodedc-workspace-window__head")) return false;
  return !target.closest("button, input, select, textarea, a");
}

export function initialObservationWindowRect(
  index: number,
  count = 1,
  bounds: { width: number; height: number } = { width: 1280, height: 720 },
): ObservationWindowRect {
  const availableWidth = Math.max(0, bounds.width - WINDOW_INSET * 2);
  const width = Math.max(1, Math.min(WINDOW_WIDTH, availableWidth || WINDOW_WIDTH));
  const availableHeight = Math.max(0, bounds.height - WINDOW_INSET - TIMELINE_CLEARANCE);
  const maxColumns = Math.max(
    1,
    Math.floor((availableWidth + WINDOW_GAP) / (width + WINDOW_GAP)),
  );
  const columns = Math.max(1, Math.min(Math.max(1, count), maxColumns));
  const rows = Math.max(1, Math.ceil(Math.max(1, count) / columns));
  const rowHeight = Math.max(
    1,
    (availableHeight - Math.max(0, rows - 1) * WINDOW_GAP) / rows,
  );
  const height = Math.max(1, Math.min(WINDOW_HEIGHT, rowHeight));
  const row = Math.floor(index / columns);
  const column = index % columns;
  const rowItemCount = Math.min(columns, Math.max(1, count - row * columns));
  const rowWidth = rowItemCount * width + Math.max(0, rowItemCount - 1) * WINDOW_GAP;
  const rowStart = Math.max(0, bounds.width - WINDOW_INSET - rowWidth);

  return {
    x: rowStart + Math.min(column, rowItemCount - 1) * (width + WINDOW_GAP),
    y: Math.max(
      0,
      bounds.height - TIMELINE_CLEARANCE - height - row * (height + WINDOW_GAP),
    ),
    width,
    height,
  };
}

export function FloatingObservationWindow({
  source,
  index,
  count,
  boundsRef,
  rect,
  maximized,
  active,
  hidden = false,
  onRectChange,
  onMaximizedChange,
  onActivate,
  onClose,
  playback,
  prepareRecorded,
  recordedSessionGate,
  recordedAdmissionKey,
  onRecordedAdmissionChange,
}: {
  source: ObservationSourceDescriptor;
  index: number;
  count: number;
  boundsRef: RefObject<HTMLElement | null>;
  rect?: ObservationWindowRect;
  maximized: boolean;
  active: boolean;
  hidden?: boolean;
  onRectChange: (rect: ObservationWindowRect) => void;
  onMaximizedChange: (maximized: boolean) => void;
  onActivate: () => void;
  onClose: () => void;
  playback?: RecordedObservationPlayback | null;
  prepareRecorded?: boolean;
  recordedSessionGate?: RecordedAdmissionPhase;
  recordedAdmissionKey?: string | null;
  onRecordedAdmissionChange?: (
    sourceId: string,
    state: RecordedCameraAdmissionState,
  ) => void;
}) {
  const [bounds, setBounds] = useState<{ width: number; height: number } | null>(null);

  useLayoutEffect(() => {
    const element = boundsRef.current;
    if (!element) return;

    const measure = () => {
      const next = { width: element.clientWidth, height: element.clientHeight };
      setBounds((current) => (
        current?.width === next.width && current.height === next.height ? current : next
      ));
    };

    measure();
    const observer = new ResizeObserver(measure);
    observer.observe(element);
    return () => observer.disconnect();
  }, [boundsRef]);

  const initialRect = bounds ? initialObservationWindowRect(index, count, bounds) : null;
  const windowRect = rect ?? initialRect;
  if (!windowRect) return null;

  return (
    <WorkspaceWindow
      boundsRef={boundsRef}
      rect={windowRect}
      onRectChange={onRectChange}
      maximized={maximized}
      onMaximizedChange={onMaximizedChange}
      onActivate={onActivate}
      onClose={onClose}
      title={source.label}
      subtitle={source.description}
      status={(
        <span className="floating-observation-window__status">
          <i data-availability={source.availability} aria-hidden="true" />
          {observationSourceStatusLabel(source)}
        </span>
      )}
      footer={(
        <span className="floating-observation-window__footer">
          <span>{source.endpointLabel || source.transport}</span>
          <span>{source.capabilities.timelineMode === "live-only" ? "Эфир без буфера" : "Временная шкала"}</span>
        </span>
      )}
      minWidth={Math.min(280, windowRect.width)}
      minHeight={Math.min(190, windowRect.height)}
      resizable={source.capabilities.resizable}
      active={active}
      zIndex={maximized ? 15 : active ? 9 : 7}
      className={`floating-observation-window${hidden ? " floating-observation-window--hidden" : ""}`}
      onPointerDownCapture={(event) => {
        if (!shouldCaptureWorkspacePointer(event.button, event.target as HTMLElement)) return;
        try {
          event.currentTarget.setPointerCapture(event.pointerId);
        } catch {
          // Pointer capture can fail if the browser ended the pointer between
          // dispatch and capture. The donor's window listeners remain fallback.
        }
      }}
      closeLabel={`Закрыть ${source.label}`}
      maximizeLabel={`Развернуть ${source.label}`}
      restoreLabel={`Восстановить ${source.label}`}
      moveLabel={`Переместить ${source.label}`}
      resizeLabel={`Изменить размер ${source.label}`}
    >
      <ObservationMedia
        source={source}
        playback={playback}
        prepareRecorded={prepareRecorded}
        recordedSessionGate={recordedSessionGate}
        recordedAdmissionKey={recordedAdmissionKey}
        onRecordedAdmissionChange={onRecordedAdmissionChange}
      />
    </WorkspaceWindow>
  );
}
