<script>
export { MAP_INJECTION_KEY } from "./useGoogleMapsResource";
</script>

<script setup>
import { defu } from "defu";
import { tryUseNuxtApp, useHead, useRuntimeConfig } from "nuxt/app";
import { computed, nextTick, onBeforeUnmount, onMounted, provide, ref, shallowRef, toRaw, useAttrs, useTemplateRef, watch } from "vue";
import { useScriptTriggerElement } from "#nuxt-scripts/composables/useScriptTriggerElement";
import { useScriptGoogleMaps } from "#nuxt-scripts/registry/google-maps";
import { scriptRuntimeConfig, scriptsPrefix } from "#nuxt-scripts/utils";
import ScriptAriaLoadingIndicator from "../ScriptAriaLoadingIndicator.vue";
import { defineDeprecatedAlias, MAP_INJECTION_KEY, waitForMapsReady, warnDeprecatedTopLevelMapProps } from "./useGoogleMapsResource";
const props = defineProps({
  trigger: { type: [String, Array, Boolean], required: false, default: ["mouseenter", "mouseover", "mousedown"] },
  apiKey: { type: String, required: false },
  center: { type: null, required: false },
  zoom: { type: Number, required: false },
  mapOptions: { type: null, required: false },
  region: { type: String, required: false },
  language: { type: String, required: false },
  version: { type: String, required: false },
  width: { type: [Number, String], required: false, default: 640 },
  height: { type: [Number, String], required: false, default: 400 },
  rootAttrs: { type: Object, required: false },
  mapIds: { type: Object, required: false },
  colorMode: { type: String, required: false }
});
const emits = defineEmits(["ready", "error"]);
defineSlots();
const DIGITS_ONLY_RE = /^\d+$/;
const DIGITS_PX_RE = /^\d+px$/i;
const apiKey = props.apiKey || scriptRuntimeConfig("googleMaps")?.apiKey;
const runtimeConfig = useRuntimeConfig();
const nuxtColorMode = computed(() => {
  const value = tryUseNuxtApp()?.$colorMode?.value;
  return value === "dark" || value === "light" ? value : void 0;
});
const currentColorMode = computed(() => props.colorMode || nuxtColorMode.value || "light");
const currentMapId = computed(() => {
  if (!props.mapIds)
    return props.mapOptions?.mapId;
  return props.mapIds[currentColorMode.value] || props.mapIds.light || props.mapOptions?.mapId;
});
const currentColorScheme = computed(() => {
  if (!props.mapIds && !props.colorMode && !nuxtColorMode.value)
    return void 0;
  return currentColorMode.value === "dark" ? "DARK" : "LIGHT";
});
const mapsApi = shallowRef();
if (import.meta.dev) {
  if (!apiKey)
    throw new Error("GoogleMaps requires an API key. Enable it in your nuxt.config:\n\n  scripts: {\n    registry: {\n      googleMaps: true\n    }\n  }\n\nThen set NUXT_PUBLIC_SCRIPTS_GOOGLE_MAPS_API_KEY in your .env file.\n\nAlternatively, pass `api-key` directly on the <ScriptGoogleMaps> component (note: this exposes the key client-side).");
  const attrs = useAttrs();
  const removedProps = {
    markers: "Use child <ScriptGoogleMapsMarker> components instead.",
    centerMarker: 'Use a child <ScriptGoogleMapsMarker :position="center" /> instead.',
    placeholderOptions: "Use <ScriptGoogleMapsStaticMap> inside the #placeholder slot instead.",
    placeholderAttrs: "Use <ScriptGoogleMapsStaticMap> with :img-attrs instead.",
    aboveTheFold: 'Use <ScriptGoogleMapsStaticMap loading="eager"> inside #placeholder instead.'
  };
  for (const [prop, message] of Object.entries(removedProps)) {
    if (prop in attrs)
      console.warn(`[nuxt-scripts] <ScriptGoogleMaps> prop "${prop}" was removed in v1. ${message} See https://scripts.nuxt.com/docs/migration-guide/v0-to-v1`);
  }
  warnDeprecatedTopLevelMapProps({ center: props.center, zoom: props.zoom });
}
const rootEl = useTemplateRef("rootEl");
const mapEl = useTemplateRef("mapEl");
const centerOverride = ref();
const trigger = useScriptTriggerElement({ trigger: props.trigger, el: rootEl });
const { load, status, onLoaded } = useScriptGoogleMaps({
  apiKey: props.apiKey,
  scriptOptions: {
    trigger
  },
  region: props.region,
  language: props.language,
  v: props.version
});
const options = computed(() => {
  const mapId = props.mapOptions?.styles ? void 0 : currentMapId.value || "DEMO_MAP_ID";
  return defu(
    { center: centerOverride.value, mapId, colorScheme: currentColorScheme.value },
    props.mapOptions,
    { center: props.center, zoom: props.zoom },
    { zoom: 15 }
  );
});
const isMapReady = ref(false);
const slotMounted = ref(true);
const map = shallowRef();
function isLocationQuery(s) {
  return typeof s === "string" && (s.split(",").length > 2 || s.includes("+"));
}
const queryToLatLngCache = /* @__PURE__ */ new Map();
async function resolveQueryToLatLng(query) {
  if (query && typeof query === "object")
    return Promise.resolve(query);
  if (queryToLatLngCache.has(query)) {
    return Promise.resolve(queryToLatLngCache.get(query));
  }
  const endpoints = runtimeConfig.public["nuxt-scripts"]?.endpoints;
  if (endpoints?.googleMaps) {
    const data = await $fetch(`${scriptsPrefix()}/proxy/google-maps-geocode`, {
      params: { address: query }
    });
    if (data.status === "OK" && data.results?.[0]?.geometry?.location) {
      const loc = data.results[0].geometry.location;
      const latLng = { lat: loc.lat, lng: loc.lng };
      queryToLatLngCache.set(query, latLng);
      return latLng;
    }
    throw new Error(`No location found for ${query}`);
  }
  await waitForMapsReady({ mapsApi, map, status, load });
  const placesService = new mapsApi.value.places.PlacesService(map.value);
  const result = await new Promise((resolve, reject) => {
    placesService.findPlaceFromQuery(
      {
        query,
        fields: ["name", "geometry"]
      },
      (results, status2) => {
        if (status2 === "OK" && results?.[0]?.geometry?.location) {
          resolve(results[0].geometry.location);
        } else {
          reject(new Error(`No location found for ${query}`));
        }
      }
    );
  });
  queryToLatLngCache.set(query, result);
  return result;
}
const libraries = /* @__PURE__ */ new Map();
function importLibrary(key) {
  if (libraries.has(key))
    return libraries.get(key);
  const p = mapsApi.value?.importLibrary(key) || new Promise((resolve) => {
    const stop = watch(mapsApi, (api) => {
      if (api) {
        const p2 = api.importLibrary(key);
        resolve(p2);
        stop();
      }
    }, { immediate: true });
  });
  const cached = Promise.resolve(p).catch((err) => {
    libraries.delete(key);
    throw err;
  });
  libraries.set(key, cached);
  return cached;
}
const exposed = {
  mapsApi,
  // Plain alias for production. In dev, replaced below with a getter that
  // emits a one-shot deprecation warning. Both forms return the same
  // shallow ref as `mapsApi`.
  googleMaps: mapsApi,
  map,
  resolveQueryToLatLng,
  importLibrary
};
if (import.meta.dev) {
  defineDeprecatedAlias(
    exposed,
    "googleMaps",
    "mapsApi",
    '[nuxt-scripts] <ScriptGoogleMaps> expose key "googleMaps" is deprecated; use "mapsApi" instead. See https://scripts.nuxt.com/docs/migration-guide/v0-to-v1'
  );
}
defineExpose(exposed);
let activeInfoWindow;
provide(MAP_INJECTION_KEY, {
  map,
  mapsApi,
  activateInfoWindow(iw) {
    if (activeInfoWindow && activeInfoWindow !== iw) {
      activeInfoWindow.close();
    }
    activeInfoWindow = iw;
  }
});
onMounted(() => {
  watch(isMapReady, (v) => {
    if (v) {
      emits("ready", exposed);
    }
  });
  watch(status, (v) => {
    if (v === "error") {
      emits("error");
    }
  });
  watch(options, () => {
    if (!map.value)
      return;
    const { center: _, zoom: __, mapId: ___, colorScheme: ____, ...rest } = options.value;
    map.value.setOptions(rest);
  });
  watch([currentMapId, currentColorScheme], async ([newMapId, newScheme], [oldMapId, oldScheme]) => {
    if (!map.value || !mapsApi.value || !mapEl.value)
      return;
    if (newMapId === oldMapId && newScheme === oldScheme)
      return;
    const center = map.value.getCenter();
    const zoom = map.value.getZoom();
    if (center)
      centerOverride.value = { lat: center.lat(), lng: center.lng() };
    map.value.unbindAll();
    map.value = void 0;
    slotMounted.value = false;
    if (mapEl.value)
      mapEl.value.innerHTML = "";
    await nextTick();
    if (!mapEl.value || !mapsApi.value)
      return;
    const _options = {
      ...options.value,
      center: center ? { lat: center.lat(), lng: center.lng() } : options.value.center,
      zoom: zoom ?? options.value.zoom
    };
    map.value = new mapsApi.value.Map(mapEl.value, _options);
    slotMounted.value = true;
    emits("ready", exposed);
  });
  watch(() => options.value.zoom, (zoom) => {
    if (map.value && zoom != null)
      map.value.setZoom(zoom);
  });
  watch([() => props.center, () => props.mapOptions?.center], () => {
    centerOverride.value = void 0;
  });
  watch([() => options.value.center, isMapReady, map], async (next) => {
    if (!map.value) {
      return;
    }
    let center = toRaw(next[0]);
    if (center) {
      if (isLocationQuery(center) && isMapReady.value) {
        center = await resolveQueryToLatLng(center);
      }
      const current = map.value.getCenter();
      if (current) {
        const newLat = typeof center.lat === "function" ? center.lat() : center.lat;
        const newLng = typeof center.lng === "function" ? center.lng() : center.lng;
        if (current.lat() === newLat && current.lng() === newLng)
          return;
      }
      map.value.setCenter(center);
    }
  }, {
    immediate: true
  });
  onLoaded(async (instance) => {
    mapsApi.value = await instance.maps;
    const center = options.value.center;
    const _options = {
      ...options.value,
      // @ts-expect-error broken
      center: !center || isLocationQuery(center) ? void 0 : center
    };
    map.value = new mapsApi.value.Map(mapEl.value, _options);
    if (center && isLocationQuery(center)) {
      centerOverride.value = await resolveQueryToLatLng(center);
      if (centerOverride.value)
        map.value?.setCenter(centerOverride.value);
    }
    isMapReady.value = true;
  });
});
if (import.meta.server) {
  useHead({
    link: [
      {
        rel: "dns-prefetch",
        href: "https://maps.googleapis.com"
      }
    ]
  });
}
function toCssUnit(value) {
  if (value === void 0)
    return void 0;
  if (typeof value === "number")
    return `${value}px`;
  return value;
}
function isPixelValue(value) {
  if (typeof value === "number")
    return true;
  if (typeof value === "string")
    return DIGITS_ONLY_RE.test(value) || DIGITS_PX_RE.test(value);
  return false;
}
const rootAttrs = computed(() => {
  return defu(props.rootAttrs, {
    "aria-busy": status.value === "loading",
    "aria-label": status.value === "awaitingLoad" ? "Google Maps" : status.value === "loading" ? "Google Maps Loading" : "Google Maps",
    "aria-live": "polite",
    "role": "application",
    "style": {
      cursor: "pointer",
      position: "relative",
      maxWidth: "100%",
      width: toCssUnit(props.width),
      height: isPixelValue(props.width) && isPixelValue(props.height) ? "auto" : toCssUnit(props.height),
      aspectRatio: isPixelValue(props.width) && isPixelValue(props.height) ? `${props.width}/${props.height}` : void 0
    },
    ...trigger instanceof Promise ? trigger.ssrAttrs || {} : {}
  });
});
onBeforeUnmount(() => {
  map.value?.unbindAll();
  map.value = void 0;
  libraries.clear();
  queryToLatLngCache.clear();
});
</script>

<template>
  <div ref="rootEl" v-bind="rootAttrs">
    <div v-show="isMapReady" ref="mapEl" :style="{ width: '100%', height: '100%', maxWidth: '100%' }" />
    <slot v-if="!isMapReady" name="placeholder" />
    <slot v-if="status !== 'awaitingLoad' && !isMapReady" name="loading">
      <ScriptAriaLoadingIndicator />
    </slot>
    <slot v-if="status === 'awaitingLoad'" name="awaitingLoad" />
    <slot v-else-if="status === 'error'" name="error" />
    <slot v-if="slotMounted" />
  </div>
</template>
