import { Map as OlMap } from "ol";
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
import VectorTileSource from "ol/source/VectorTile";
import { FlatStyleLike } from "ol/style/flat";

import { config } from "@/config/config";
import {
  VectorTileStyleSession,
  versionedPropertyCase,
} from "@components/olmap/core/vectorTileStyleSession";
import { getAreaColor } from "./utils";

const JUNCTION_LAYER_VALUE = "junctions";
const RENDER_OWNER_KEY = "junction-area-render-owner";

export type JunctionAreaRenderPayload = {
  nodeAreaMap: Record<string, string>;
  areaIds?: string[];
  areaColors?: Record<string, string>;
};

type ApplyJunctionAreaRenderOptions = {
  propertyKey?: string;
};

const DEFAULT_PROPERTY_KEY = "junction_area_render_index";

const getJunctionLayer = (map: OlMap) =>
  map
    .getAllLayers()
    .find(
      (layer) =>
        layer instanceof WebGLVectorTileLayer &&
        layer.get("value") === JUNCTION_LAYER_VALUE,
    ) as WebGLVectorTileLayer | undefined;

export const applyJunctionAreaRender = (
  map: OlMap,
  payload: JunctionAreaRenderPayload,
  options: ApplyJunctionAreaRenderOptions = {},
) => {
  const propertyKey = options.propertyKey ?? DEFAULT_PROPERTY_KEY;
  const junctionLayer = getJunctionLayer(map);
  if (!junctionLayer) {
    return () => {};
  }

  const source = junctionLayer.getSource() as VectorTileSource | null;
  if (!source) {
    return () => {};
  }

  const ownerId = `${propertyKey}-${Date.now().toString(36)}-${Math.random()
    .toString(36)
    .slice(2, 8)}`;

  const normalizedNodeAreaMap = Object.fromEntries(
    Object.entries(payload.nodeAreaMap ?? {}).map(([nodeId, areaId]) => [
      String(nodeId),
      String(areaId),
    ]),
  );

  const areaIds = (
    payload.areaIds?.length
      ? payload.areaIds
      : Array.from(new Set(Object.values(normalizedNodeAreaMap)))
  )
    .map(String)
    .filter(Boolean);

  if (Object.keys(normalizedNodeAreaMap).length === 0 || areaIds.length === 0) {
    junctionLayer.setStyle(config.MAP_DEFAULT_STYLE as FlatStyleLike);
    return () => {};
  }

  const areaIdToIndex = new Map<string, number>();
  areaIds.forEach((areaId, index) => {
    areaIdToIndex.set(areaId, index + 1);
  });

  const nodeAreaIndexMap = new Map<string, number>();
  Object.entries(normalizedNodeAreaMap).forEach(([nodeId, areaId]) => {
    const areaIndex = areaIdToIndex.get(areaId);
    if (areaIndex !== undefined) {
      nodeAreaIndexMap.set(nodeId, areaIndex);
    }
  });

  const fillCases: any[] = [];
  areaIds.forEach((areaId, index) => {
    fillCases.push(
      ["==", ["get", propertyKey], index + 1],
      payload.areaColors?.[areaId] ?? getAreaColor(areaId),
    );
  });

  const defaultFillColor = String(config.MAP_DEFAULT_STYLE["circle-fill-color"]);
  const defaultStrokeColor = String(
    config.MAP_DEFAULT_STYLE["circle-stroke-color"],
  );

  junctionLayer.set(RENDER_OWNER_KEY, ownerId);
  const session = new VectorTileStyleSession({
    layer: junctionLayer,
    source,
    propertyKey,
    defaultStyle: config.MAP_DEFAULT_STYLE as FlatStyleLike,
    buildStyle: (statePropertyKey, versionKey, version) =>
      ({
        ...config.MAP_DEFAULT_STYLE,
        "circle-fill-color": versionedPropertyCase(
          statePropertyKey,
          versionKey,
          version,
          fillCases,
          defaultFillColor,
        ),
        "circle-stroke-color": versionedPropertyCase(
          statePropertyKey,
          versionKey,
          version,
          fillCases,
          defaultStrokeColor,
        ),
      }) as FlatStyleLike,
  });
  session.commit(nodeAreaIndexMap);

  return () => {
    session.dispose();
    if (junctionLayer.get(RENDER_OWNER_KEY) === ownerId) {
      junctionLayer.unset(RENDER_OWNER_KEY, true);
      junctionLayer.setStyle(config.MAP_DEFAULT_STYLE as FlatStyleLike);
    }
  };
};
