import type {
  FilterSpecification,
  GeoJSONSource,
  GeoJSONSourceSpecification,
  StyleSpecification
} from "maplibre-gl";
import type { FeatureCollection, Geometry, LineString, Point, Polygon } from "geojson";
import { createCircleCoordinates } from "./geo";
import { MAP_STYLE_TOKENS } from "./map-colors";
import { WORKBENCH_INTERACTION_BEFORE_ID } from "./layers";

export const DRAWING_SOURCE_IDS = {
  features: "workbench-drawing-features",
  draft: "workbench-drawing-draft"
} as const;

export const DRAWING_INTERACTIVE_LAYER_IDS = [
  "workbench-drawing-selected-polygon-outline",
  "workbench-drawing-selected-line",
  "workbench-drawing-selected-point",
  "workbench-drawing-polygon-fill",
  "workbench-drawing-polygon-outline",
  "workbench-drawing-line",
  "workbench-drawing-point"
];

export type DrawingFeatureProperties = {
  id: string;
  label: string;
  kind: "point" | "line" | "polygon" | "circle";
  createdAt: string;
};

export type DrawingFeatureCollection = FeatureCollection<Geometry, DrawingFeatureProperties>;

export const emptyDrawingFeatureCollection: DrawingFeatureCollection = {
  type: "FeatureCollection",
  features: []
};

export const drawingSources = {
  features: {
    type: "geojson",
    data: emptyDrawingFeatureCollection
  } satisfies GeoJSONSourceSpecification,
  draft: {
    type: "geojson",
    data: emptyDrawingFeatureCollection
  } satisfies GeoJSONSourceSpecification
};

export const drawingLayers: StyleSpecification["layers"] = [
  {
    id: "workbench-drawing-polygon-fill",
    type: "fill",
    source: DRAWING_SOURCE_IDS.features,
    filter: ["==", ["geometry-type"], "Polygon"],
    paint: {
      "fill-color": MAP_STYLE_TOKENS.tool.geometry,
      "fill-opacity": 0.16
    }
  },
  {
    id: "workbench-drawing-polygon-outline",
    type: "line",
    source: DRAWING_SOURCE_IDS.features,
    filter: ["==", ["geometry-type"], "Polygon"],
    paint: {
      "line-color": MAP_STYLE_TOKENS.tool.geometry,
      "line-width": 2,
      "line-opacity": 0.82
    }
  },
  {
    id: "workbench-drawing-line",
    type: "line",
    source: DRAWING_SOURCE_IDS.features,
    filter: ["==", ["geometry-type"], "LineString"],
    paint: {
      "line-color": MAP_STYLE_TOKENS.tool.geometry,
      "line-width": 3,
      "line-opacity": 0.88
    }
  },
  {
    id: "workbench-drawing-point",
    type: "circle",
    source: DRAWING_SOURCE_IDS.features,
    filter: ["==", ["geometry-type"], "Point"],
    paint: {
      "circle-color": MAP_STYLE_TOKENS.tool.geometry,
      "circle-radius": 6,
      "circle-stroke-color": MAP_STYLE_TOKENS.canvas.casing,
      "circle-stroke-width": 2
    }
  },
  {
    id: "workbench-drawing-selected-polygon-outline",
    type: "line",
    source: DRAWING_SOURCE_IDS.features,
    filter: ["all", ["==", ["geometry-type"], "Polygon"], ["==", ["get", "id"], ""]],
    paint: {
      "line-color": MAP_STYLE_TOKENS.state.selected,
      "line-width": 4,
      "line-opacity": 0.86
    }
  },
  {
    id: "workbench-drawing-selected-line",
    type: "line",
    source: DRAWING_SOURCE_IDS.features,
    filter: ["all", ["==", ["geometry-type"], "LineString"], ["==", ["get", "id"], ""]],
    paint: {
      "line-color": MAP_STYLE_TOKENS.state.selected,
      "line-width": 6,
      "line-opacity": 0.72
    }
  },
  {
    id: "workbench-drawing-selected-point",
    type: "circle",
    source: DRAWING_SOURCE_IDS.features,
    filter: ["all", ["==", ["geometry-type"], "Point"], ["==", ["get", "id"], ""]],
    paint: {
      "circle-color": MAP_STYLE_TOKENS.state.selected,
      "circle-radius": 10,
      "circle-opacity": 0.2,
      "circle-stroke-color": MAP_STYLE_TOKENS.state.selected,
      "circle-stroke-width": 2
    }
  },
  {
    id: "workbench-drawing-draft-polygon-fill",
    type: "fill",
    source: DRAWING_SOURCE_IDS.draft,
    filter: ["==", ["geometry-type"], "Polygon"],
    paint: {
      "fill-color": MAP_STYLE_TOKENS.tool.geometry,
      "fill-opacity": 0.12
    }
  },
  {
    id: "workbench-drawing-draft-line",
    type: "line",
    source: DRAWING_SOURCE_IDS.draft,
    filter: ["any", ["==", ["geometry-type"], "LineString"], ["==", ["geometry-type"], "Polygon"]],
    paint: {
      "line-color": MAP_STYLE_TOKENS.tool.geometry,
      "line-width": 2,
      "line-dasharray": [2, 2],
      "line-opacity": 0.9
    }
  },
  {
    id: "workbench-drawing-draft-vertex",
    type: "circle",
    source: DRAWING_SOURCE_IDS.draft,
    filter: ["==", ["geometry-type"], "Point"],
    paint: {
      "circle-color": MAP_STYLE_TOKENS.state.selected,
      "circle-radius": 4,
      "circle-stroke-color": MAP_STYLE_TOKENS.canvas.casing,
      "circle-stroke-width": 1.5
    }
  }
];

export function ensureDrawingLayers(map: {
  getSource: (id: string) => unknown;
  addSource: (id: string, source: GeoJSONSourceSpecification) => void;
  getLayer: (id: string) => unknown;
  addLayer: (layer: NonNullable<StyleSpecification["layers"]>[number], beforeId?: string) => void;
}) {
  if (!map.getSource(DRAWING_SOURCE_IDS.features)) {
    map.addSource(DRAWING_SOURCE_IDS.features, drawingSources.features);
  }

  if (!map.getSource(DRAWING_SOURCE_IDS.draft)) {
    map.addSource(DRAWING_SOURCE_IDS.draft, drawingSources.draft);
  }

  drawingLayers.forEach((layer) => {
    if (!map.getLayer(layer.id)) {
      map.addLayer(layer, WORKBENCH_INTERACTION_BEFORE_ID);
    }
  });
}

export function setDrawingSourceData(
  map: { getSource: (id: string) => unknown },
  sourceId: string,
  data: DrawingFeatureCollection
) {
  const source = map.getSource(sourceId) as GeoJSONSource | undefined;
  source?.setData(data);
}

export function setSelectedDrawingFeatureId(
  map: { getLayer: (id: string) => unknown; setFilter: (id: string, filter?: FilterSpecification) => void },
  featureId: string | null
) {
  const id = featureId ?? "";

  [
    ["workbench-drawing-selected-polygon-outline", "Polygon"],
    ["workbench-drawing-selected-line", "LineString"],
    ["workbench-drawing-selected-point", "Point"]
  ].forEach(([layerId, geometryType]) => {
    if (map.getLayer(layerId)) {
      map.setFilter(layerId, ["all", ["==", ["geometry-type"], geometryType], ["==", ["get", "id"], id]]);
    }
  });
}

export function createPointFeature(
  id: string,
  coordinates: [number, number],
  label: string
): DrawingFeatureCollection["features"][number] {
  return {
    type: "Feature",
    id,
    properties: createDrawingProperties(id, label, "point"),
    geometry: {
      type: "Point",
      coordinates
    } satisfies Point
  };
}

export function createLineFeature(
  id: string,
  coordinates: [number, number][],
  label: string
): DrawingFeatureCollection["features"][number] {
  return {
    type: "Feature",
    id,
    properties: createDrawingProperties(id, label, "line"),
    geometry: {
      type: "LineString",
      coordinates
    } satisfies LineString
  };
}

export function createPolygonFeature(
  id: string,
  coordinates: [number, number][],
  label: string
): DrawingFeatureCollection["features"][number] {
  return {
    type: "Feature",
    id,
    properties: createDrawingProperties(id, label, "polygon"),
    geometry: {
      type: "Polygon",
      coordinates: [[...coordinates, coordinates[0]]]
    } satisfies Polygon
  };
}

export function createCircleFeature(
  id: string,
  center: [number, number],
  radiusMeters: number,
  label: string
): DrawingFeatureCollection["features"][number] {
  const coordinates = createCircleCoordinates(center, radiusMeters);

  return {
    type: "Feature",
    id,
    properties: createDrawingProperties(id, label, "circle"),
    geometry: {
      type: "Polygon",
      coordinates: [coordinates]
    } satisfies Polygon
  };
}

function createDrawingProperties(
  id: string,
  label: string,
  kind: DrawingFeatureProperties["kind"]
): DrawingFeatureProperties {
  return {
    id,
    label,
    kind,
    createdAt: new Date().toISOString()
  };
}
