<template>
  <a-spin :spinning="loading">
    <div class="guns-map-picker-header">
      <div class="guns-map-picker-header-search">
        <a-auto-complete
          allow-clear
          v-model:value="keywords"
          :options="suggestionData"
          :placeholder="searchPlaceholder"
          @select="onSearchSelect"
          @search="onSearch"
        />
      </div>
      <a-button type="primary" :loading="confirmLoading" @click="onConfirm">
        确定
      </a-button>
    </div>
    <div class="guns-map-picker-body">
      <div class="guns-map-picker-main">
        <div ref="mapRef" :style="{ height }"></div>
        <template v-if="searchType === 0">
          <PlusOutlined class="guns-map-picker-main-plus" />
          <img :class="centerIconClass" :src="markerSrc" alt="" />
        </template>
      </div>
      <ASpin :spinning="poiLoading">
        <div class="guns-map-picker-poi-list" :style="{ height }">
          <div
            v-for="item in data"
            :key="item.key"
            :class="['guns-map-picker-poi-item', { active: item.selected }]"
            @click="onItemClick(item)"
          >
            <EnvironmentOutlined class="guns-map-picker-poi-item-icon" />
            <div class="guns-map-picker-poi-item-title">{{ item.name }}</div>
            <div v-if="item.address" class="guns-map-picker-poi-item-address">
              {{ item.address }}
            </div>
            <CheckCircleOutlined class="guns-map-picker-poi-item-check" />
          </div>
        </div>
      </ASpin>
    </div>
  </a-spin>
</template>

<script setup name="MapView">
import AMapLoader from '@amap/amap-jsapi-loader';
import { message } from 'ant-design-vue/es';
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
const ICON_CLASS = 'guns-map-picker-main-icon';
const props = defineProps({
  // 地图的高度
  height: {
    type: String,
    default: '450px'
  },
  // 地图默认中心点
  center: Array,
  // 地图初始缩放级别
  zoom: {
    type: Number,
    default: 11
  },
  // 地图选中后缩放级别
  chooseZoom: {
    type: Number,
    default: 17
  },
  // POI 检索最大数量
  poiSize: {
    type: Number,
    default: 30
  },
  // POI 检索兴趣点类别
  poiType: {
    type: String,
    default: ''
  },
  // POI 检索关键字
  poiKeywords: {
    type: String,
    default: ''
  },
  // POI 检索半径
  poiRadius: {
    type: Number,
    default: 1000
  },
  // 是否返回行政区
  needCity: Boolean,
  // 是否强制选择
  forceChoose: {
    type: Boolean,
    default: true
  },
  // 输入建议的城市范围
  suggestionCity: {
    type: String,
    default: '全国'
  },
  // 地点检索类型, 0: POI 检索, 1: 关键字检索
  searchType: {
    type: Number,
    default: 0,
    validator: value => {
      return [0, 1].includes(value);
    }
  },
  // 搜索框提示文本
  searchPlaceholder: String,
  // 地图中心图标地址
  markerSrc: {
    type: String,
    default: 'https://3gimg.qq.com/lightmap/components/locationPicker2/image/marker.png'
  },
  // 高德地图 key
  mapKey: String,
  // 高德地图版本号
  mapVersion: {
    type: String,
    default: '2.0'
  },
  // 地图风格
  mapStyle: String,
  // 是否暗黑主题
  darkMode: Boolean,
});

const emits = defineEmits(['done', 'map-done']);
// 地图 ref
const mapRef = ref(null);
// 初始化 loading
const loading = ref(false);
// POI 列表 loading
const poiLoading = ref(false);
// 确定按钮 loading
const confirmLoading = ref(false);
// POI 数据
const data = ref([]);
// 输入建议数据
const suggestionData = ref([]);

// 地图中心图标 class
const centerIconClass = ref([ICON_CLASS]);

// 输入建议关键字
const keywords = ref('');

// 上次输入建议关键字
let lastSuggestion = '';

// 选中的搜索建议
let selectedSuggestion = null;

// 是否是点击 POI 列表移动地图
let isItemClickMove = false;

// 地图实例
let mapIns = null;

// POI 检索实例
let placeSearchIns = null;

// 输入建议实例
let autoCompleteIns = null;

// 地图中心标记点
let centerMarker = null;

/* 渲染地图 */
const renderMap = () => {
  if (!props.mapKey || mapIns) {
    return;
  }
  AMapLoader.load({
    key: props.mapKey,
    version: props.mapVersion,
    plugins: ['AMap.PlaceSearch', 'AMap.AutoComplete']
  })
    .then(AMap => {
      destroyAll();

      // 获取输入建议实例
      autoCompleteIns = new AMap.AutoComplete({
        city: props.suggestionCity
      });

      // 获取 POI 检索实例
      placeSearchIns = new AMap.PlaceSearch({
        type: props.poiType,
        pageSize: props.poiSize,
        pageIndex: 1
      });

      // 地图主题
      const mapStyle = (() => {
        if (props.mapStyle) {
          return props.mapStyle;
        }
        if (props.darkMode) {
          return 'amap://styles/dark';
        }
      })();

      // 渲染地图
      mapIns = new AMap.Map(mapRef.value, {
        zoom: props.zoom, // 初缩放级别
        center: props.center, // 初始中心点
        resizeEnable: true, // 监控地图容器尺寸变化
        mapStyle
      });

      // 地图加载完成事件
      mapIns.on('complete', () => {
        loading.value = false;
        const { lng, lat } = mapIns.getCenter();
        searchPOI(lng, lat);
      });

      // 地图移动结束事件
      mapIns.on('moveend', () => {
        if (isItemClickMove) {
          isItemClickMove = false;
        } else if (props.searchType === 0) {
          bounceIcon();
          const { lng, lat } = mapIns.getCenter();
          searchPOI(lng, lat);
        }
      });

      // 地图中心标记点
      centerMarker = new AMap.Marker({
        icon: new AMap.Icon({
          image: props.markerSrc,
          size: new AMap.Size(26, 36.5),
          imageSize: new AMap.Size(26, 36.5)
        }),
        offset: new AMap.Pixel(-13, -36.5)
      });

      emits('map-done', mapIns);
    })
    .catch(e => {
      console.error(e);
    });
};

/* 搜索建议 */
const onSearch = value => {
  if (!value || lastSuggestion === value) {
    return;
  }
  lastSuggestion = value;
  if (props.searchType !== 0) {
    poiLoading.value = true;
  }
  searchKeywords(value)
    .then(result => {
      if (props.searchType !== 0) {
        // 关键字检索模式
        data.value = result;
        poiLoading.value = false;
        removeCenterMarker();
      } else {
        // POI 检索模式
        suggestionData.value = result;
      }
    })
    .catch(e => {
      console.error(e);
      poiLoading.value = false;
    });
};

/* 搜索建议选中事件 */
const onSearchSelect = (_value, item) => {
  if (!data.value.length || data.value[0].name !== item.name) {
    data.value = [
      { ...item, selected: true },
      ...data.value.map(d => {
        return { ...d, selected: false };
      })
    ];
  }
  setMapCenter(item.location.lng, item.location.lat, props.chooseZoom);
  selectedSuggestion = item;
};

/* poi 列表 item 点击事件 */
const onItemClick = item => {
  isItemClickMove = true;
  data.value = data.value.map(d => {
    return {
      ...d,
      selected: d === item
    };
  });
  const { lng, lat } = item.location;
  setMapCenter(lng, lat, props.chooseZoom);
  if (props.searchType === 0) {
    bounceIcon(); // POI 检索类型地图中心图标跳动
  } else {
    showCenterMarker(lng, lat); // 非 POI 检索类型地图中心添加标记
  }
};

/* 确定按钮点击事件 */
const onConfirm = () => {
  const selected = getSelected();
  if (!selected) {
    // 强制选择
    if (props.forceChoose) {
      message.error('请点击列表选中位置');
      return;
    }
    // 未选择使用地图中心点
    confirmLoading.value = true;
    getMapCenter(props.needCity)
      .then(result => {
        confirmLoading.value = false;
        emits('done', result);
      })
      .catch(e => {
        console.error(e);
        confirmLoading.value = false;
        emits('done', {});
      });
    return;
  }
  // 封装返回结果
  const location = {
    ...selected.location,
    name: selected.name,
    address: selected.address || ''
  };
  if (props.needCity) {
    // 获取行政区
    confirmLoading.value = true;
    setMapCenter(location.lng, location.lat);
    getMapCenter(true)
      .then(({ city }) => {
        confirmLoading.value = false;
        location.city = city;
        emits('done', location);
      })
      .catch(e => {
        console.error(e);
        confirmLoading.value = false;
        emits('done', location);
      });
  } else {
    emits('done', location);
  }
};

/* 关键字检索 */
const searchKeywords = value => {
  return new Promise((resolve, reject) => {
    if (!autoCompleteIns) {
      reject(new Error('AutoComplete instance is null'));
      return;
    }
    autoCompleteIns.search(value, (_status, result) => {
      if (!result?.tips) {
        resolve([]);
        return;
      }
      const data = result.tips
        .filter(d => !!d.location)
        .map(d => {
          const label = `${d.name}(${d.district})`;
          return {
            ...d,
            label,
            value: label,
            key: d.id || label,
            address: Array.isArray(d.address) ? d.address[0] : d.address
          };
        });
      resolve(data);
    });
  });
};

/* 检索 POI 数据 */
const searchPOI = (lng, lat) => {
  poiLoading.value = true;
  searchNearBy(lng, lat)
    .then(result => {
      if (selectedSuggestion) {
        // 选中的输入建议不在 POI 中则添加
        if (result.length === 0 || result[0].name !== selectedSuggestion.name) {
          data.value = [{ ...selectedSuggestion, selected: true }, ...result];
        } else {
          data.value = result.map((d, i) => {
            return { ...d, selected: i === 0 };
          });
        }
      } else {
        data.value = result;
      }
      poiLoading.value = false;
    })
    .catch(e => {
      console.error(e);
      poiLoading.value = false;
      data.value = [];
    });
};

/* 检索附近兴趣点 */
const searchNearBy = (lng, lat) => {
  return new Promise((resolve, reject) => {
    if (!placeSearchIns) {
      reject(new Error('PlaceSearch instance is null'));
      return;
    }
    placeSearchIns.searchNearBy(props.poiKeywords, [lng, lat], props.poiRadius, (status, result) => {
      if (status === 'complete' && result?.poiList?.pois) {
        const data = result.poiList.pois
          .filter(d => !!d.location)
          .map((d, i) => {
            return { ...d, key: d.id || `${d.name}-${i}` };
          });
        resolve(data);
      } else if (status === 'no_data') {
        resolve([]);
      } else {
        reject(new Error(status));
      }
    });
  });
};

/* 获取选中的 POI 数据 */
const getSelected = () => {
  return data.value.find(d => d.selected);
};

/* 让图标跳动 */
const bounceIcon = () => {
  centerIconClass.value = [ICON_CLASS];
  nextTick(() => {
    setTimeout(() => {
      centerIconClass.value = [ICON_CLASS, 'guns-map-picker-anim-bounce'];
    }, 0);
  });
};

/* 移除地图中心标记点 */
const removeCenterMarker = () => {
  if (centerMarker && mapIns) {
    mapIns.remove(centerMarker);
  }
};

/* 添加地图中心标记点 */
const showCenterMarker = (lng, lat) => {
  if (!centerMarker) {
    console.error('centerMarker is null');
    return;
  }
  if (!mapIns) {
    console.error('map instance is null');
    return;
  }
  if (lng != null && lat != null) {
    centerMarker.setPosition([lng, lat]);
    mapIns.add(centerMarker);
  } else {
    removeCenterMarker();
  }
};

/* 设置地图中心点 */
const setMapCenter = (lng, lat, zoom) => {
  if (mapIns && lng != null && lat != null) {
    if (zoom == null) {
      mapIns.setCenter([lng, lat]);
    } else {
      mapIns.setZoomAndCenter(zoom, [lng, lat]);
    }
  }
};

/* 获取地图中心点 */
const getMapCenter = needCity => {
  return new Promise((resolve, reject) => {
    if (!mapIns) {
      reject(new Error('map instance is null'));
      return;
    }
    const result = mapIns.getCenter();
    if (needCity) {
      mapIns.getCity(city => {
        result.city = city;
        resolve(result);
      });
    } else {
      resolve(result);
    }
  });
};

/* 切换地图主题 */
const changeMapStyle = value => {
  if (mapIns) {
    if (typeof value === 'boolean') {
      if (value) {
        mapIns.setMapStyle('amap://styles/dark');
      } else {
        mapIns.setMapStyle('amap://styles/normal');
      }
    } else if (value) {
      mapIns.setMapStyle(value);
    }
  }
};

/* 销毁地图 */
const destroyMap = () => {
  mapIns && mapIns.destroy();
  centerMarker = null;
  placeSearchIns = null;
  autoCompleteIns = null;
  mapIns = null;
};

/* 销毁地图 */
const destroyAll = () => {
  destroyMap();
  data.value = [];
  suggestionData.value = [];
  keywords.value = '';
  lastSuggestion = '';
  selectedSuggestion = null;
  isItemClickMove = false;
};

/* 获取地图实例 */
const getMapIns = () => {
  return mapIns;
};

watch(
  () => props.darkMode,
  darkMode => {
    if (!props.mapStyle) {
      changeMapStyle(darkMode);
    }
  }
);

watch(
  () => props.mapStyle,
  mapStyle => {
    if (mapStyle) {
      changeMapStyle(mapStyle);
    }
  }
);

watch(
  () => props.searchType,
  searchType => {
    keywords.value = '';
    suggestionData.value = [];
    selectedSuggestion = null;
    lastSuggestion = '';
    removeCenterMarker();
    if (searchType === 1) {
      const selected = getSelected();
      if (selected) {
        const { lng, lat } = selected.location;
        showCenterMarker(lng, lat);
      }
    }
  }
);

watch(
  () => props.mapKey,
  () => {
    destroyAll();
    renderMap();
  }
);

onMounted(() => {
  renderMap();
});

onBeforeUnmount(() => {
  destroyAll();
});
</script>

<style scoped lang="less">
@import '../../layout/style/themes/default.less';

/* 工具栏 */
.guns-map-picker-header {
  display: flex;
  align-items: center;
  padding: 8px 14px;

  .guns-map-picker-header-search {
    flex: 1;
    padding-right: 16px;
  }

  .ant-select-auto-complete {
    width: 100%;
    max-width: 220px;
  }

  .guns-map-picker-search-icon {
    color: @text-color-secondary;
  }
}

/* 地图 */
.guns-map-picker-body {
  display: flex;
  border-bottom-left-radius: 2px;
  border-bottom-right-radius: 2px;
  overflow: hidden;

  .guns-map-picker-main {
    flex: 1;
    position: relative;
  }

  .guns-map-picker-main-plus {
    color: #1890ff;
    font-size: 12px;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }

  .guns-map-picker-main-icon {
    width: 26px;
    position: absolute;
    left: 50%;
    bottom: 50%;
    margin-left: -13px;
  }

  // poi 列表
  .guns-map-picker-poi-list {
    width: 300px;
    overflow: auto;
    border-top: 1px solid hsla(0, 0%, 60%, 0.15);
  }

  .guns-map-picker-poi-item {
    padding: 8px 30px 8px 44px;
    border-bottom: 1px solid hsla(0, 0%, 60%, 0.15);
    position: relative;
    cursor: pointer;

    &:hover {
      background: hsla(0, 0%, 60%, 0.05);
    }
  }

  .guns-map-picker-poi-item-icon {
    position: absolute;
    top: 50%;
    left: 14px;
    transform: translateY(-50%);
    font-size: 18px;
    opacity: 0.4;
  }

  .guns-map-picker-poi-item-title {
    font-size: 14px;
  }

  .guns-map-picker-poi-item-address {
    font-size: 12px;
    margin-top: 2px;
    opacity: 0.6;
  }

  .guns-map-picker-poi-item-check {
    position: absolute;
    top: 50%;
    right: 7px;
    transform: translateY(-50%);
    color: #1890ff;
    font-size: 14px;
    display: none;
  }

  .guns-map-picker-poi-item.active .guns-map-picker-poi-item-check {
    display: block;
  }

  // 地图图标跳动动画
  .guns-map-picker-anim-bounce {
    animation: eleMapPickerAnimBounce 500ms;
    animation-direction: alternate;
  }
}

@keyframes eleMapPickerAnimBounce {
  0%,
  60%,
  75%,
  90%,
  to {
    transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
  }
  0%,
  to {
    transform: translate3d(0, 0, 0);
  }
  25% {
    transform: translate3d(0, -10px, 0);
  }
  50% {
    transform: translate3d(0, -20px, 0);
  }
  75% {
    transform: translate3d(0, -10px, 0);
  }
}

/* 小屏幕适应 */
@media screen and (max-width: 768px) {
  .guns-map-picker-responsive {
    &.guns-map-picker-dialog {
      top: 0;
      padding: 0;
    }

    .guns-map-picker-body {
      display: block;

      .guns-map-picker-main > div {
        height: 250px !important;
      }

      .guns-map-picker-poi-list {
        width: auto;
        height: calc(100vh - 370px) !important;
      }
    }
  }
}
</style>
