export type TrustedMapAction =
  | {
      type: "locate_features";
      featureIds: string[];
      layer?: string;
      fallbackText?: string;
    }
  | {
      type: "zoom_to_map";
      center: [number, number];
      zoom?: number;
      durationMs?: number;
      fallbackText?: string;
    }
  | {
      type: "apply_layer_style";
      layerGroupId: string;
      layerId?: string;
      visible?: boolean;
      fallbackText?: string;
    }
  | {
      type: "render_junctions";
      renderRef: string;
      fallbackText?: string;
    };

export function toTrustedMapAction(action: string, params: unknown, fallbackText?: string): TrustedMapAction | null {
  if (!isRecord(params)) {
    return null;
  }

  if (action === "locate_features" || action in LEGACY_LOCATE_ACTION_LAYERS) {
    const featureIds = readLocateIds(params);
    if (featureIds.length === 0 || featureIds.length > 100 || featureIds.some((id) => id.length > 128)) return null;
    const layer =
      stringValue(params.layer ?? params.target_layer ?? params.targetLayer) ??
      LEGACY_LOCATE_ACTION_LAYERS[action];
    if (layer && !ALLOWED_LOCATE_LAYERS.has(layer)) return null;
    return {
      type: "locate_features",
      featureIds,
      layer,
      fallbackText
    };
  }

  if (action === "zoom_to_map") {
    const center = parseCenter(params);
    if (!center) {
      return null;
    }

    const zoom = numberValue(params.zoom);
    const durationMs = numberValue(params.duration_ms ?? params.durationMs);
    if (center[0] < -180 || center[0] > 180 || center[1] < -90 || center[1] > 90 ||
      (zoom !== undefined && (zoom < 0 || zoom > 24))) return null;

    return {
      type: "zoom_to_map",
      center,
      zoom,
      ...(durationMs === undefined ? {} : { durationMs }),
      fallbackText
    };
  }

  if (action === "apply_layer_style") {
    const layerGroupId = stringValue(params.layer_group_id ?? params.layerGroupId);
    const layerId = stringValue(params.layer_id ?? params.layerId);
    const resolvedLayerGroupId = layerGroupId ?? layerId;
    const visible = booleanValue(params.visible);
    if (
      !resolvedLayerGroupId ||
      visible === undefined ||
      (layerGroupId && !ALLOWED_LAYER_GROUP_IDS.has(layerGroupId)) ||
      (layerId && !ALLOWED_LAYER_IDS.has(layerId)) ||
      (layerGroupId && layerId && layerGroupId !== layerId)
    ) return null;
    return {
      type: "apply_layer_style",
      layerGroupId: resolvedLayerGroupId,
      layerId,
      visible,
      fallbackText
    };
  }

  if (action === "render_junctions") {
    const renderRef = stringValue(params.render_ref ?? params.renderRef);
    if (!renderRef || !RESULT_REF_PATTERN.test(renderRef)) {
      return null;
    }

    return {
      type: "render_junctions",
      renderRef,
      fallbackText
    };
  }

  return null;
}

function parseCenter(params: Record<string, unknown>): [number, number] | null {
  const sourceCrs = params.source_crs ?? params.sourceCrs;

  if (Array.isArray(params.center) && params.center.length >= 2) {
    const lng = numberValue(params.center[0]);
    const lat = numberValue(params.center[1]);
    return lng === undefined || lat === undefined ? null : normalizeCenter(lng, lat, sourceCrs);
  }

  const rawCoordinate = params.coordinate ?? params.coordinates;
  if (Array.isArray(rawCoordinate) && rawCoordinate.length >= 2) {
    const lng = numberValue(rawCoordinate[0]);
    const lat = numberValue(rawCoordinate[1]);
    return lng === undefined || lat === undefined ? null : normalizeCenter(lng, lat, sourceCrs);
  }

  const lng = numberValue(params.lng ?? params.lon ?? params.longitude ?? params.x);
  const lat = numberValue(params.lat ?? params.latitude ?? params.y);
  return lng === undefined || lat === undefined ? null : normalizeCenter(lng, lat, sourceCrs);
}

function normalizeCenter(x: number, y: number, sourceCrs: unknown): [number, number] {
  if (sourceCrs === "EPSG:3857") {
    return webMercatorToLngLat(x, y);
  }

  return [x, y];
}

function webMercatorToLngLat(x: number, y: number): [number, number] {
  const lng = (x / WEB_MERCATOR_RADIUS) * (180 / Math.PI);
  const lat = (2 * Math.atan(Math.exp(y / WEB_MERCATOR_RADIUS)) - Math.PI / 2) * (180 / Math.PI);

  return [lng, lat];
}

function stringValue(value: unknown) {
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
}

function numberValue(value: unknown) {
  if (typeof value === "number" && Number.isFinite(value)) {
    return value;
  }

  if (typeof value === "string" && value.trim()) {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : undefined;
  }

  return undefined;
}

function booleanValue(value: unknown) {
  return typeof value === "boolean" ? value : undefined;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null;
}

const LEGACY_LOCATE_ACTION_LAYERS: Record<string, string> = {
  locate_junctions: "geo_junctions_mat",
  locate_pipes: "geo_pipes_mat",
  locate_valves: "geo_valves",
  locate_reservoirs: "geo_reservoirs",
  locate_scada: "geo_scada",
  locate_pumps: "geo_pumps",
  locate_tanks: "geo_tanks"
};

const RESULT_REF_PATTERN = /^res-[A-Za-z0-9_-]{8,128}$/;
const SUPPLY_SOURCE_IDS = ["junctions", "pipes", "valves", "reservoirs", "scada"];
const ALLOWED_LAYER_IDS = new Set(SUPPLY_SOURCE_IDS);
const ALLOWED_LAYER_GROUP_IDS = new Set([...SUPPLY_SOURCE_IDS, "simulation"]);
const ALLOWED_LOCATE_LAYERS = new Set([
  ...SUPPLY_SOURCE_IDS,
  "pumps",
  "tanks",
  "geo_junctions_mat",
  "geo_pipes_mat",
  "geo_valves",
  "geo_reservoirs",
  "geo_scada",
  "geo_pumps",
  "geo_tanks"
]);
const WEB_MERCATOR_RADIUS = 6378137;

const LOCATE_ID_PARAM_KEYS = [
  "feature_ids",
  "feature_id",
  "featureIds",
  "featureId",
  "ids",
  "id",
  "node_ids",
  "node_id",
  "junction_ids",
  "junction_id",
  "pipe_ids",
  "pipe_id",
  "valve_ids",
  "valve_id",
  "reservoir_ids",
  "reservoir_id",
  "scada_ids",
  "scada_id",
  "pump_ids",
  "pump_id",
  "tank_ids",
  "tank_id"
] as const;

function readLocateIds(params: Record<string, unknown>) {
  for (const key of LOCATE_ID_PARAM_KEYS) {
    const value = params[key];
    const ids = normalizeIds(value);
    if (ids.length > 0) {
      return ids;
    }
  }

  return [];
}

function normalizeIds(value: unknown) {
  if (Array.isArray(value)) {
    return value.map((item) => String(item).trim()).filter(Boolean);
  }

  if (typeof value === "string" || typeof value === "number") {
    return String(value)
      .split(",")
      .map((item) => item.trim())
      .filter(Boolean);
  }

  return [];
}
