<template>
  <div class="qupu-map">
    <van-overlay class-name="image-overlay" :show="resizing">
      <div class="overlay-loading">
        <van-loading type="spinner" />
      </div>
    </van-overlay>
    <div class="qupu-map-action" v-if="!readonly">
      <span style="margin-right: 5px;">{{showControls ? '关闭': '打开'}}工具栏</span>
      <van-switch v-model="showControls" @click="toggleControls()" size="14" />
      <span style="margin-left: 10px;">
        <van-radio-group v-if="showControls" v-model="controlPosition" direction="horizontal">
          <van-radio icon-size="16" name="topleft">左边</van-radio>
          <van-radio icon-size="16" name="topright">右边</van-radio>
        </van-radio-group>
      </span>
    </div>
    <div class="qupu-map-move-tool">
      <small>自动滚动速度：</small>
      <van-slider v-model="mapScrollSpeed" bar-height="10px" step="10" inactive-color="#1989fa">
        <template #button>
          <div class="scroll-button">{{mapScrollSpeed}}</div>
        </template>
      </van-slider>
    </div>
    <div class="qupu-map-main" ref="mapEl"></div>
  </div>
</template>


<script type="module" lang="ts">
import {defineComponent, getCurrentInstance, onBeforeUnmount, onMounted, ref, watch} from 'vue'
import {ActionSheet, Button, Dialog, Field, Form, Loading, Overlay, Radio, RadioGroup, showFailToast, Slider, Switch} from 'vant'
import {Comment, ResourceNote, ResourceNoteTypes} from "~/types";
import '@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css';
import * as L from 'leaflet'
import {LeafletMouseEvent} from 'leaflet'
import '@geoman-io/leaflet-geoman-free';
import {get_search_params, ImageHelper} from "~/utils";
import {draw_comment_icon, draw_resource, get_rect_right_top, latlngs2multi_pos} from '~/utils/geo';
import {UpyunAccess} from '~/utils/upyun';
import useComment from "~/store/comment";


export default defineComponent({
  name: "qupu-map",
  props: ['backgroundUrl', 'marks', 'readonly', 'floating', 'video_id'],
  emits: ['pause', 'update', 'draw'],
  components: {
    [Dialog.name]: Dialog,
    [Form.name]: Form,
    [Field.name]: Field,
    [Button.name]: Button,
    [Overlay.name]: Overlay,
    [Loading.name]: Loading,
    [ActionSheet.name]: ActionSheet,
    [Switch.name]: Switch,
    [Radio.name]: Radio,
    [RadioGroup.name]: RadioGroup,
    [Slider.name]: Slider
  },
  setup(props) {
    const marks = ref(props.marks || [])
    const qupu = ref(props.backgroundUrl || "");
    let map_data = ref<L.Map | null>(null)
    const resizing = ref(false);
    const mapEl = ref<HTMLDivElement | null>(null)
    const markers: {[key: string]: L.Layer} = {};
    const caches: {[key: string]: ResourceNote} = {};
    let readonly = ref(props.readonly || false);
    const drawing = ref(false);
    const showControls = ref(false);
    const controlPosition = ref("topleft")
    const color = ref<string>("red")
    const mapScrollSpeed = ref(0)
    const timer = ref(0)

    const {get_comments}  = useComment()

    watch(() => props.readonly, () => {
      readonly.value = props.readonly;
      if (readonly.value && showControls.value) {
        toggleControls()
        showControls.value = false
      }
      // add comment icon to rects without comments icon
      Object.entries(markers).forEach(([key, marker]) => {
        if (key.startsWith("R")) {
          let data = caches[key];
          // @ts-ignore
          if (!readonly.value && !marker._comment) {
            // @ts-ignore
            let latlngs = marker._latlngs;
            // Rectangle latlngs need to get first value of array
            if (latlngs[0] instanceof Array) {
              latlngs = latlngs[0]
            }
            // @ts-ignore
            marker._comment = draw_comment_icon(map_data.value as L.Map, latlngs, data, pause);
            // @ts-ignore
          } else if (readonly.value && marker._comment && !data.comments?.length) {
            // @ts-ignore
            marker._comment.remove();
            // @ts-ignore
            delete marker._comment;
          }
        }
      })
    })

    let letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')

    const instance = getCurrentInstance();

    const pause = (data: ResourceNote) => {
      if (drawing.value) return;
      if (readonly.value && !data.comments?.length) return;
      let el = document.getElementById(props.video_id) as HTMLVideoElement;
      if (el && !readonly.value) {
        if (el.currentTime > 0) {
          el.pause();
        }
        data.time_pos = {time: el.currentTime};
      }
      instance?.emit('pause', data);
    }

    watch(() => props.marks, () => {
      marks.value = props.marks;
      draw();
    }, {deep: true})

    watch(() => props.backgroundUrl, () => {
      qupu.value = props.backgroundUrl;
    })

    const BaseWidth = 360;

    const getBounds = async (map: L.Map, url: string) => {
      let {width, height} = await ImageHelper.load(url);
      let scale = Math.round((width / BaseWidth) * 10) / 10;

      let southWest = map.unproject([0, height / scale], 1);
      let northEast = map.unproject([BaseWidth, 0], 1)
      return L.latLngBounds(southWest, northEast)
    }

    const marker_add = (serial: string, data: ResourceNote, marker: L.Layer) => {
      caches[serial] = data;
      markers[serial] = marker;
    }

    const marker_remove = (serial: string) => {
      let marker = markers[serial];
      // @ts-ignore
      if (marker._comment) {
        // @ts-ignore
        marker._comment.remove();
      }
      marker.remove();
      delete caches[serial];
      delete markers[serial];
    }

    const draw = () => {
      let map = map_data.value as L.Map;
      if (!map) return;
      let serials = new Set(marks.value.map((x: ResourceNote) => x.serial_no));
      Object.keys(markers).forEach(k => {
        if (!serials.has(k)) {
          marker_remove(k)
        }
      })
      marks.value.forEach((data: ResourceNote) => {
        let { serial_no, note_type } = data;
        if (markers[serial_no]) {
          if (caches[serial_no] == data) {
            return;
          }
          marker_remove(serial_no)
        }

        let marker = draw_resource(map, data, pause, readonly.value);
        if (marker) {
          marker_add(serial_no, data, marker);

          if (note_type && note_type !== ResourceNoteTypes.Alphabet) {
            marker.on("pm:update", e => {
              // @ts-ignore
              let latlngs = marker._latlngs;
              // Rectangle latlngs need to get first value of array
              if (latlngs[0] instanceof Array) {
                latlngs = latlngs[0]
              }
              data.resource_pos = {
                vals: [latlngs2multi_pos(latlngs, note_type as ResourceNoteTypes)]
              }
              // @ts-ignore
              if (marker?._comment) {
                // @ts-ignore
                marker._comment.setLatLng(get_rect_right_top(latlngs))
              }
            })

            marker.on("pm:change", e => {
              // @ts-ignore
              let latlngs = marker._latlngs;
              // Rectangle latlngs need to get first value of array
              if (latlngs[0] instanceof Array) {
                latlngs = latlngs[0]
              }
              // @ts-ignore
              if (marker?._comment) {
                // @ts-ignore
                marker._comment.setLatLng(get_rect_right_top(latlngs))
              }
            })

            marker.on("pm:remove", e => {
              // @ts-ignore
              if (marker?._comment) {
                // @ts-ignore
                marker._comment.remove()
              }
              remove_layer(serial_no)
            })
          }
        } else {
          // remove dirty data
          remove_layer(serial_no)
        }
      })
    }

    const nextNoteNumber = (note_type: ResourceNoteTypes) => {
      let  prefix = note_type[0];
      return prefix + (marks.value.reduce((a: number, {serial_no}: ResourceNote) => {
        let n = Number(serial_no.replace(prefix, ""))
        if (Number.isNaN(n)) return a;
        return Math.max(a, n);
      }, 0) + 1);
    }


    const remove_layer = (serial: string) => {
      let index = marks.value.findIndex((x: ResourceNote)  => x.serial_no === serial);
      marks.value.splice(index, 1);
      instance?.emit("update", marks.value);
    }

    const add_controls = () => {
      let map = map_data.value as L.Map;
      map.pm.addControls({
        position: controlPosition.value as string,
        drawMarker: false,
        drawText: false,
        drawCircleMarker: false,
        drawPolygon: false,
        drawCircle: false,
        oneBlock: true,
        // dragMode: false,
        rotateMode: false,
        cutPolygon: false,
      })
      let colorButton = map.pm.Toolbar.createCustomControl({
        name: "Color",
        block: "custom",
        title: "设定颜色",
        className: "leaflet-pm-icon-color",
        actions: [
         'cancel',
          {
            name: "red",
            text: '红',
            onClick(e) {
              color.value = "red";
              colorButton.toggle();
            }
          },
          {
            name: "blue",
            text: '蓝',
            onClick(e) {
              color.value = "blue"
              colorButton.toggle();
            }
          },
          {
            name: "yellow",
            text: '黄',
            onClick(e) {
              color.value = "yellow"
              colorButton.toggle();
            }
          }
        ]
      });
    }

    onMounted(async () => {
      let signedUrl = await UpyunAccess.getSign(qupu.value);
      console.log('qupu url...', qupu.value, signedUrl);
      let map = L.map(mapEl.value as HTMLDivElement, {
        center: [0, 0], zoom: window.innerWidth / BaseWidth, zoomControl: false, zoomDelta: 0.1, crs: L.CRS.Simple,
        zoomAnimation: false // zoom animation will cause error in android
      })
      map_data.value = map;
      let bounds = await getBounds(map, signedUrl as string);
      let background = L.imageOverlay(signedUrl as string, bounds).addTo(map);
      background.on("load", () => {
        console.log('qupu loaded!')
      })
      background.on("error", (e) => {
        console.error("qupu load error!", e)
      })
      map.invalidateSize(true);
      map.setMaxBounds(bounds)
      console.log(bounds)
      draw()

      const video = document.getElementById(props.video_id) as HTMLVideoElement;

      add_controls();

      map.pm.setGlobalOptions({snappable: false})

      if (map.pm.controlsVisible()) {
        map.pm.toggleControls();
      }

      map.pm.setLang('zh')

      map.on("click", (e: LeafletMouseEvent) => {
        if (readonly.value || drawing.value) return;
        let lastLetterIndex = marks.value.reduce((a: number, c: ResourceNote) => Math.max(a, letters.indexOf(c.serial_no)), -1);
        let letter = letters[lastLetterIndex + 1];
        let data: ResourceNote = {
          serial_no: letter, comments: [] as Comment[], lat_pos: e.latlng, note_type: ResourceNoteTypes.Alphabet,
        };
        if (color.value) {
          data.meta = {meta: {color: color.value}}
        }
        let marker = draw_resource(map, data, pause, readonly.value);
        if (marker) {
          marks.value.push(data)
          marker_add(letter, data, marker);
          pause(data)
        }
      })

      // Android上的键盘弹起和收回会触发resize事件，导致批注时地图移位
      // window.addEventListener('resize', async () => {
      //   resizing.value = true;
      //   map.setZoom(window.innerWidth / BaseWidth)
      //   map.invalidateSize(true);
      //   map.fitBounds(bounds)
      //   draw()
      //   setTimeout(() => {
      //     resizing.value = false;
      //   }, 100)
      //
      // })

      const toggle_drawing = (enabled: boolean) => {
        drawing.value = enabled;
        if (video) {
          if (enabled) {
            video.pause();
          } else if (video.currentTime > 0.01) {
            // video position set to 0.01 by default to load resource
            video.play()
          }
        }
        instance?.emit("draw", enabled);
      }

      map.on("pm:drawstart", e => {
        toggle_drawing(true)
      })

      map.on("pm:drawend", e => {
        toggle_drawing(false)
      })

      map.on("pm:globaleditmodetoggled", e => {
        toggle_drawing(e.enabled)
      })

      map.on("pm:globalremovalmodetoggled", e => {
        toggle_drawing(e.enabled)
      })

      map.on("pm:create", e => {
        // currently only support Line/Rectangle shape, handle shape in the future when need
        let {layer, shape} = e;
        layer.pm.enable({
          removeLayerBelowMinVertexCount: false,
          preventMarkerRemoval: true // prevent to remove line with contextmenu event
        });
        map.pm.enableGlobalEditMode()

        let note_type = {
          "Line": ResourceNoteTypes.Line,
          "Rectangle": ResourceNoteTypes.Rect
        }[shape]
        let serial = nextNoteNumber(note_type as ResourceNoteTypes);
        let latlngs = layer._latlngs;
        // Rectangle latlngs need to get first value of array
        if (latlngs[0] instanceof Array) {
          latlngs = latlngs[0]
        }
        let data = {
          resource_pos: {vals: [latlngs2multi_pos(latlngs, note_type as ResourceNoteTypes)]},
          comments: [] as Comment[], serial_no: serial, note_type
        }
        if (color.value) {
          data.meta = {meta: {color: color.value}}
        }
        marks.value.push(data);
        marker_add(serial, data, layer);

        // do not support click for line and rectangle
        // layer.on("click", e => {
        //   console.log("clicked...", e)
        // })

        layer.on("pm:update", e => {
          let latlngs = layer._latlngs;
          // Rectangle latlngs need to get first value of array
          if (latlngs[0] instanceof Array) {
            latlngs = latlngs[0]
          }
          data.resource_pos.vals = [latlngs2multi_pos(latlngs, note_type as ResourceNoteTypes)]

          if (marker?._comment) {
            marker._comment.setLatLng(get_rect_right_top(latlngs))
          }
        })

        layer.on("pm:change", e => {
          let latlngs = layer._latlngs;
          // Rectangle latlngs need to get first value of array
          if (latlngs[0] instanceof Array) {
            latlngs = latlngs[0]
          }
          if (marker?._comment) {
            marker._comment.setLatLng(get_rect_right_top(latlngs))
          }
        })

        layer.on("pm:remove", e => {
          if (layer?._comment) {
            layer._comment.remove()
          }
          remove_layer(serial)
        })

      })

      // 仅针对最底层的曲谱地图上的标点触发定位弹窗操作
      if (!props.floating) {
        let params = get_search_params();
        // comment on resource note
        if(params.serial) {
          let marker = markers[params.serial];
          marker.fire("click")
        }
        // resource notes added to comment
        if (params.ref_id) {
          for(let i = 0; i < marks.value.length; i++) {
            let { id, serial_no } = marks.value[i];
            let comments = get_comments(id);
            if(comments.some(c => c.id === params.ref_id)) {
              let marker = markers[serial_no]
              marker.fire("click")
              break;
            }
          }
        }
      }

      timer.value = window.setInterval(() => {
        if (!map) return;
        let offset = mapScrollSpeed.value;
        if (!offset) {
          return;
        }
        let centerLatLng = map.getCenter();
        let center = map.project(centerLatLng);
        center = new L.point(center.x, center.y + offset / 10);
        let size = map.getSize();
        let bottom = map.unproject(new L.point(center.x, center.y + size.y / 2));
        if (bounds.contains(bottom)) {
          let target = map.unproject(center);
          map.panTo(target);
        }
      }, 200)

    });

    watch(() => controlPosition.value, (cur, prev) => {
      let map = map_data.value as L.Map;
      let positions = map.pm.Toolbar.getBlockPositions()
      for(let p in positions) {
        map.pm.Toolbar.setBlockPosition(p, cur)
      }
    })

    const toggleControls = () => {
      let map = map_data.value as L.Map;
      if (drawing.value && map.pm.controlsVisible()) {
        showFailToast("请先结束当前标注操作！");
        return;
      }
      map.pm.toggleControls();
    }

    onBeforeUnmount(() => {
      if (timer.value) {
        window.clearInterval(timer.value)
      }
      if (map_data.value) {
        (map_data.value as L.Map).remove();
      }
    })

    return {resizing, mapEl, toggleControls, showControls, controlPosition, mapScrollSpeed}
  },
});
</script>

<style scoped lang="less">
.leaflet-container {
  font-size: 0.24rem!important;
}

:deep(.active .leaflet-buttons-control-button) {
  box-shadow: inset 0 -1px 5px 2px var(--van-button-primary-background-color);
}

:deep(.leaflet-pm-toolbar .leaflet-pm-icon-letter) {
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NDggNTEyIj48IS0tISBGb250IEF3ZXNvbWUgUHJvIDYuMi4wIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlIChDb21tZXJjaWFsIExpY2Vuc2UpIENvcHlyaWdodCAyMDIyIEZvbnRpY29ucywgSW5jLiAtLT48cGF0aCBkPSJNMjUzLjUgNTEuN0MyNDguNiAzOS44IDIzNi45IDMyIDIyNCAzMnMtMjQuNiA3LjgtMjkuNSAxOS43bC0xMjAgMjg4LTQwIDk2Yy02LjggMTYuMyAuOSAzNSAxNy4yIDQxLjhzMzUtLjkgNDEuOC0xNy4yTDEyNS4zIDM4NEgzMjIuN2wzMS44IDc2LjNjNi44IDE2LjMgMjUuNSAyNCA0MS44IDE3LjJzMjQtMjUuNSAxNy4yLTQxLjhsLTQwLTk2LTEyMC0yODh6TTI5NiAzMjBIMTUybDcyLTE3Mi44TDI5NiAzMjB6Ii8+PC9zdmc+");
}

:deep(.leaflet-pm-toolbar .leaflet-pm-icon-color) {
  background-image: url('data:image/svg+xml;base64, ');
}

:deep(.leaflet-pm-toolbar .leaflet-pm-actions-container .leaflet-pm-action.action-red) {
  color: red;
}

:deep(.leaflet-pm-toolbar .leaflet-pm-actions-container .leaflet-pm-action.action-blue) {
  color: blue;
}

:deep(.leaflet-pm-toolbar .leaflet-pm-actions-container .leaflet-pm-action.action-yellow) {
  color: yellow;
}


:deep(.rect-comment-icon) {
  margin-left: -1px!important;
  margin-top: -12px!important;
}

.qupu-map {
  width: 100%;
  height: 100%;
  position: relative;

  .image-overlay {
    width: 100%;
    height: 100%;
    position: absolute;

    .overlay-loading {
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
    }
  }

  .qupu-map-action {
    display: flex;
    height: 20px;
    align-items: center;
    padding-bottom: 8px;
    font-size: 10px;

    +.qupu-map-move-tool +.qupu-map-main{
      height: calc(100% - 42px);
    }
  }

  .qupu-map-move-tool {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: 20px;
    margin-bottom: 6px;

    small {
      white-space: nowrap;
      font-size: 0.2rem;
      margin-right: 10px;
    }

    .scroll-button {
      width: 16px;
      height: 16px;
      color: var(--van-primary-color);
      font-size: 10px;
      line-height: 16px;
      text-align: center;
      background-color: #FFFFFF;
      border-radius: 16px;
      border: 1px solid #0bc15f;
    }
  }

  .qupu-map-main {
    width: 100%;
    height: calc(100% - 26px);
    background: #FFFFFF;

    :deep(.leaflet-div-icon) {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      background: transparent;
      border: none;
    }
  }
}

</style>
