import type { Feature, FeatureCollection, LineString, MultiLineString } from "geojson";
import type { Map as MapLibreMap } from "maplibre-gl";
import type { MapboxOverlay } from "@deck.gl/mapbox";
import { WORKBENCH_INTERACTION_BEFORE_ID } from "./layers";

type FlowFeature = Feature<LineString | MultiLineString, { diameter?: number | string; [key: string]: unknown }>;

export type FlowOverlayState = {
  data: FlowFeature[] | null;
  animationFrameId: number | null;
  overlay: MapboxOverlay | null;
};

export function createFlowOverlayState(): FlowOverlayState {
  return { data: null, animationFrameId: null, overlay: null };
}

export async function showFlowOverlay(
  map: MapLibreMap,
  state: FlowOverlayState,
  loadData: () => Promise<FeatureCollection>,
  reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches
) {
  if (!state.data) {
    const collection = await loadData();
    state.data = collection.features.filter(isFlowFeature);
  }

  const [{ MapboxOverlay }, { PathLayer }, { FlowPathExtension }] = await Promise.all([
    import("@deck.gl/mapbox"),
    import("@deck.gl/layers"),
    import("./flow-path-extension")
  ]);
  const extension = new FlowPathExtension();
  let renderingError: Error | null = null;
  const render = (phase: number) => {
    const layer = new PathLayer<FlowFeature>({
      id: "workbench-network-flow",
      beforeId: WORKBENCH_INTERACTION_BEFORE_ID,
      data: state.data ?? [],
      getPath: (feature: FlowFeature) => feature.geometry.type === "LineString" ? feature.geometry.coordinates : feature.geometry.coordinates.flat(),
      getColor: [15, 118, 110, 210],
      getWidth: (feature: FlowFeature) => Math.min(5, Math.max(1, Number(feature.properties?.diameter ?? 200) / 180)),
      widthUnits: "pixels",
      widthMinPixels: 1,
      widthMaxPixels: 5,
      capRounded: true,
      jointRounded: true,
      pickable: false,
      parameters: { depthTest: false },
      extensions: [extension],
      flowPhase: phase
    } as never);
    state.overlay?.setProps({
      layers: [layer],
      onError: (error) => {
        renderingError = error;
      }
    });
  };

  if (!state.overlay) {
    const overlay = new MapboxOverlay({ interleaved: true, layers: [] });
    map.addControl(overlay);
    state.overlay = overlay;
  }

  stopFlowAnimation(state);
  render(0.2);
  await new Promise<void>((resolve) => window.requestAnimationFrame(() => resolve()));
  if (renderingError) throw renderingError;
  if (!reducedMotion) {
    const startedAt = performance.now();
    const tick = (now: number) => {
      render(((now - startedAt) / 1800) % 1);
      state.animationFrameId = window.requestAnimationFrame(tick);
    };
    state.animationFrameId = window.requestAnimationFrame(tick);
  }
}

export function hideFlowOverlay(map: MapLibreMap, state: FlowOverlayState) {
  stopFlowAnimation(state);
  if (state.overlay) {
    map.removeControl(state.overlay as never);
    state.overlay = null;
  }
}

export function stopFlowAnimation(state: FlowOverlayState) {
  if (state.animationFrameId !== null) {
    window.cancelAnimationFrame(state.animationFrameId);
    state.animationFrameId = null;
  }
}

function isFlowFeature(feature: Feature): feature is FlowFeature {
  return feature.geometry?.type === "LineString" || feature.geometry?.type === "MultiLineString";
}
