import type { Map as OlMap } from "ol";

const RESOURCE_SCOPE_KEY = "tjwater_resource_scope";
const PERSISTENT_SCOPE = "persistent";

type MapResource = {
  get?: (key: string) => unknown;
  set?: (key: string, value: unknown, silent?: boolean) => void;
  dispose?: () => void;
};

export const markMapResourcePersistent = <T extends MapResource>(resource: T): T => {
  resource.set?.(RESOURCE_SCOPE_KEY, PERSISTENT_SCOPE, true);
  return resource;
};

export const isMapResourcePersistent = (resource: MapResource) =>
  resource.get?.(RESOURCE_SCOPE_KEY) === PERSISTENT_SCOPE;

const removeTransientResources = <T extends MapResource>(
  resources: T[],
  remove: (resource: T) => void,
) => {
  [...resources].forEach((resource) => {
    if (isMapResourcePersistent(resource)) return;
    remove(resource);
    resource.dispose?.();
  });
};

type ReleaseLayerOptions = {
  disposeLayer: boolean;
  disposeSource: boolean;
};

const releaseLayer = (layer: any, options: ReleaseLayerOptions) => {
  const childLayers = layer.getLayers?.().getArray?.();
  if (Array.isArray(childLayers)) {
    [...childLayers].forEach((childLayer) => releaseLayer(childLayer, options));
    layer.getLayers().clear();
  }

  const source = layer.getSource?.();
  if (options.disposeSource) {
    try {
      source?.clear?.();
    } catch {
      // Some third-party sources do not support explicit cache clearing.
    }
    if (options.disposeLayer) {
      try {
        source?.dispose?.();
      } catch {
        // Source may already be disposed by its owning layer.
      }
    }
  }
  if (options.disposeLayer) {
    try {
      layer.dispose?.();
    } catch {
      // Cleanup is deliberately idempotent for overlapping route unmounts.
    }
  }
};

export const cleanupTransientMapResources = (map: OlMap) => {
  [...map.getLayers().getArray()].forEach((layer) => {
    if (isMapResourcePersistent(layer)) return;
    map.removeLayer(layer);
    releaseLayer(layer, { disposeLayer: true, disposeSource: true });
  });

  removeTransientResources(map.getInteractions().getArray(), (interaction) =>
    map.removeInteraction(interaction),
  );
  removeTransientResources(map.getControls().getArray(), (control) =>
    map.removeControl(control),
  );
  removeTransientResources(map.getOverlays().getArray(), (overlay) =>
    map.removeOverlay(overlay),
  );
};

export const disposeMapResources = (
  map: OlMap,
  options: { disposeLayers?: boolean; disposeSources?: boolean } = {},
) => {
  const disposeLayers = options.disposeLayers ?? true;
  const disposeSources = options.disposeSources ?? true;
  [...map.getLayers().getArray()].forEach((layer) => {
    map.removeLayer(layer);
    releaseLayer(layer, {
      disposeLayer: disposeLayers,
      disposeSource: disposeSources,
    });
  });
  map.getInteractions().clear();
  map.getControls().clear();
  map.getOverlays().clear();
  map.setTarget(undefined);
  map.dispose();
};
