text: import CoordinatesConverter from './coordinates-converter.js';
  import style from './style.js';
  import {
      mode,
      goToTarget,
      zonedCleanup,
      zones,
      run,
      repeats,
      confirmation,
      texts
  } from './texts.js'
  
  const LitElement = Object.getPrototypeOf(
      customElements.get("ha-panel-lovelace")
  );
  const html = LitElement.prototype.html;
  
  class XiaomiVacuumMapCard extends LitElement {
      constructor() {
          super();
          this.isMouseDown = false;
          this.rectangles = [];
          this.selectedRectangle = -1;
          this.selectedZones = [];
          this.currRectangle = {x: null, y: null, w: null, h: null};
          this.imageScale = -1;
          this.mode = 0;
          this.vacuumZonedCleanupRepeats = 1;
          this.currPoint = {x: null, y: null};
          this.outdatedConfig = false;
      }
  
      static get properties() {
          return {
              _hass: {},
              _config: {},
              isMouseDown: {},
              rectangles: {},
              selectedRectangle: {},
              selectedZones: {},
              currRectangle: {},
              mode: {},
              vacuumZonedCleanupRepeats: {},
              currPoint: {},
              mapDrawing: {},
          };
      }
  
      set hass(hass) {
          this._hass = hass;
          if (this._config && !this.map_image) {
              this.updateCameraImage();
          }
      }
  
      setConfig(config) {
          const availableModes = new Map();
          this._language = config.language || "en";
          availableModes.set("go_to_target", texts[this._language][goToTarget]);
          availableModes.set("zoned_cleanup", texts[this._language][zonedCleanup]);
          availableModes.set("predefined_zones", texts[this._language][zones]);
  
          if (!config.entity) {
              throw new Error("Missing configuration: entity");
          }
          if (!config.map_image && !config.map_camera) {
              throw new Error("Missing configuration: map_image or map_camera");
          }
          if (config.map_image && config.map_camera) {
              throw new Error("Only one of following properties is allowed: map_image or map_camera");
          }
          if (config.base_position || config.reference_point) {
              this.outdatedConfig = true;
              this._config = config;
              return;
          }
          if (!config.calibration_points || !Array.isArray(config.calibration_points)) {
              throw new Error("Missing configuration: calibration_points");
          }
          if (config.calibration_points.length !== 3) {
              throw new Error("Exactly 3 calibration_points required");
          }
          for (const calibration_point of config.calibration_points) {
              if (calibration_point.map === null) {
                  throw new Error("Missing configuration: calibration_points.map");
              }
              if (calibration_point.map.x === null) {
                  throw new Error("Missing configuration: calibration_points.map.x");
              }
              if (calibration_point.map.y === null) {
                  throw new Error("Missing configuration: calibration_points.map.y");
              }
              if (calibration_point.vacuum === null) {
                  throw new Error("Missing configuration: calibration_points.vacuum");
              }
              if (calibration_point.vacuum.x === null) {
                  throw new Error("Missing configuration: calibration_points.vacuum.x");
              }
              if (calibration_point.vacuum.y === null) {
                  throw new Error("Missing configuration: calibration_points.vacuum.y");
              }
          }
          const p1 = this.getCalibrationPoint(config, 0);
          const p2 = this.getCalibrationPoint(config, 1);
          const p3 = this.getCalibrationPoint(config, 2);
          this.coordinatesConverter = new CoordinatesConverter(p1, p2, p3);
  
          if (config.modes) {
              if (!Array.isArray(config.modes) || config.modes.length < 1 || config.modes.length > 3) {
                  throw new Error("Invalid configuration: modes");
              }
              this.modes = [];
              for (const mode of config.modes) {
                  if (!availableModes.has(mode)) {
                      throw new Error("Invalid mode: " + mode);
                  }
                  this.modes.push(availableModes.get(mode));
              }
          } else {
              this.modes = [
                  texts[this._language][goToTarget],
                  texts[this._language][zonedCleanup],
                  texts[this._language][zones]
              ];
          }
          if (!config.zones || !Array.isArray(config.zones) || config.zones.length === 0 && this.modes.includes(texts[this._language][zones])) {
              this.modes.splice(this.modes.indexOf(texts[this._language][zones]), 1);
          }
          if (config.default_mode) {
              if (!availableModes.has(config.default_mode) || !this.modes.includes(availableModes.get(config.default_mode))) {
                  throw new Error("Invalid default mode: " + config.default_mode);
              }
              this.defaultMode = this.modes.indexOf(availableModes.get(config.default_mode));
          } else {
              this.defaultMode = -1;
          }
          if (config.service && config.service.split(".").length === 2) {
              this.service_domain = config.service.split(".")[0];
              this.service_method = config.service.split(".")[1];
          } else {
              this.service_domain = "vacuum";
              this.service_method = "send_command";
          }
          if (config.map_image) {
              this.map_image = config.map_image;
          }
          this._map_refresh_interval = (config.camera_refresh_interval || 5) * 1000;
          this._config = config;
      }
  
      getConfigurationMigration(config) {
          const diffX = config.reference_point.x - config.base_position.x;
          const diffY = config.reference_point.y - config.base_position.y;
          const shouldSwapAxis = diffX * diffY > 0;
          let unit = shouldSwapAxis ? diffX : diffY;
          if (shouldSwapAxis) {
              const temp = config.base_position.x;
              config.base_position.x = config.base_position.y;
              config.base_position.y = temp;
          }
          const canvasX = config.base_position.x;
          const canvasY = unit + config.base_position.y;
          let x = Math.round(canvasX);
          let y = Math.round(canvasY);
          if (shouldSwapAxis) {
              x = Math.round(canvasY);
              y = Math.round(canvasX);
          }
          return html`
  <ha-card id="xiaomiCard" style="padding: 16px">
  <div class="card-header" style="padding: 8px 0 16px 0;"><div class="name">Xiaomi Vacuum Map card</div></div>
  <h3>Your configuration is outdated</h3>
  <p>Migrate it using following calibration settings:</p>
  <pre><textarea style="width: 100%; height: 22em">calibration_points:
    - vacuum:
        x: 25500
        y: 25500
      map:
        x: ${config.base_position.x}
        y: ${config.base_position.y}
    - vacuum:
        x: 26500
        y: 26500
      map:
        x: ${config.reference_point.x}
        y: ${config.reference_point.y}
    - vacuum:
        x: 25500
        y: 26500
      map:
        x: ${x}
        y: ${y}</textarea></pre>
  </ha-card>`
      }
  
      getCalibrationPoint(config, index) {
          return {
              a: {
                  x: config.calibration_points[index].map.x,
                  y: config.calibration_points[index].map.y
              },
              b: {
                  x: config.calibration_points[index].vacuum.x,
                  y: config.calibration_points[index].vacuum.y
              }
          };
      }
  
      render() {
          if (this.outdatedConfig) {
              return this.getConfigurationMigration(this._config);
          }
          const modesDropdown = this.modes.map(m => html`<paper-item>${m}</paper-item>`);
          const rendered = html`
          ${style}
          <ha-card id="xiaomiCard">
              <div id="mapWrapper">
                  <div id="map">
                      <img id="mapBackground" @load="${() => this.calculateScale()}" src="${this.map_image}">
                      <canvas id="mapDrawing" style="${this.getCanvasStyle()}"
                          @mousemove="${e => this.onMouseMove(e)}"
                          @mousedown="${e => this.onMouseDown(e)}"
                          @mouseup="${e => this.onMouseUp(e)}"
                          @touchstart="${e => this.onTouchStart(e)}"
                          @touchend="${e => this.onTouchEnd(e)}"
                          @touchmove="${e => this.onTouchMove(e)}" />
                  </div>
              </div>
              <div class="dropdownWrapper">
                  <paper-dropdown-menu label="${texts[this._language][mode]}" @value-changed="${e => this.modeSelected(e)}" class="vacuumDropdown" selected="${this.defaultMode}">
                      <paper-listbox slot="dropdown-content" class="dropdown-content" selected="${this.defaultMode}">
                          ${modesDropdown}
                      </paper-listbox>
                  </paper-dropdown-menu>
              </div>
              <p class="buttonsWrapper">
                  <span id="increaseButton" hidden><mwc-button @click="${() => this.vacuumZonedIncreaseButton()}">${texts[this._language][repeats]} ${this.vacuumZonedCleanupRepeats}</mwc-button></span>
                  <mwc-button class="vacuumRunButton" @click="${() => this.vacuumStartButton(true)}">${texts[this._language][run]}</mwc-button>
              </p>
              <div id="toast"><div id="img"><ha-icon icon="mdi:check" style="vertical-align: center"></ha-icon></div><div id="desc">${texts[this._language][confirmation]}</div></div>
          </ha-card>
          `;
          if (this.getMapImage()) {
              this.calculateScale();
          }
          return rendered;
      }
  
      calculateScale() {
          const img = this.getMapImage();
          const canvas = this.getCanvas();
          this.imageScale = img.width / img.naturalWidth;
          const mapHeight = Math.round(this.imageScale * img.naturalHeight);
          img.parentElement.parentElement.style.height = mapHeight + "px";
          canvas.width = img.width;
          canvas.height = mapHeight;
          this.drawCanvas();
      }
  
      onMouseDown(e) {
          const pos = this.getMousePos(e);
          this.isMouseDown = true;
          if (this.mode === 1) {
              this.currPoint.x = pos.x;
              this.currPoint.y = pos.y;
          } else if (this.mode === 2) {
              const {selected, shouldDelete, shouldResize} = this.getSelectedRectangle(pos.x, pos.y);
              this.currRectangle.x = pos.x;
              this.currRectangle.y = pos.y;
              if (shouldDelete) {
                  this.rectangles.splice(selected, 1);
                  this.selectedRectangle = -1;
                  this.isMouseDown = false;
                  this.drawCanvas();
                  return;
              }
              if (shouldResize) {
                  this.currRectangle.x = this.rectangles[selected].x;
                  this.currRectangle.y = this.rectangles[selected].y;
                  this.rectangles.splice(selected, 1);
                  this.drawCanvas();
                  return;
              }
              this.selectedRectangle = selected;
              if (this.selectedRectangle >= 0) {
                  this.currRectangle.w = this.rectangles[this.selectedRectangle].x;
                  this.currRectangle.h = this.rectangles[this.selectedRectangle].y;
              } else {
                  this.currRectangle.w = 0;
                  this.currRectangle.h = 0;
              }
          } else if (this.mode === 3) {
              const selectedZone = this.getSelectedZone(pos.x, pos.y);
              if (selectedZone >= 0) {
                  if (this.selectedZones.includes(selectedZone)) {
                      this.selectedZones.splice(this.selectedZones.indexOf(selectedZone), 1);
                  } else {
                      if (this.selectedZones.length < 5 || this._config.ignore_zones_limit) {
                          this.selectedZones.push(selectedZone);
                      }
                  }
              }
          }
          this.drawCanvas();
      }
  
      onMouseUp(e) {
          this.isMouseDown = false;
          if (this.selectedRectangle >= 0 || this.mode !== 2 || this.mode === 2 && this.rectangles.length >= 5 && !this._config.ignore_zones_limit) {
              this.selectedRectangle = -1;
              this.drawCanvas();
              return;
          }
          const {x, y} = this.getMousePos(e);
          const rx = Math.min(x, this.currRectangle.x);
          const ry = Math.min(y, this.currRectangle.y);
          const rw = Math.max(x, this.currRectangle.x) - rx;
          const rh = Math.max(y, this.currRectangle.y) - ry;
          this.currRectangle.x = rx;
          this.currRectangle.y = ry;
          this.currRectangle.w = rw;
          this.currRectangle.h = rh;
          if (rw > 5 && rh > 5) {
              this.rectangles.push({x: rx, y: ry, w: rw, h: rh});
          }
          this.drawCanvas();
      }
  
      onMouseMove(e) {
          if (this.isMouseDown && this.mode === 2) {
              const {x, y} = this.getMousePos(e);
              if (this.selectedRectangle < 0) {
                  this.currRectangle.w = x - this.currRectangle.x;
                  this.currRectangle.h = y - this.currRectangle.y;
              } else {
                  this.rectangles[this.selectedRectangle].x = this.currRectangle.w + x - this.currRectangle.x;
                  this.rectangles[this.selectedRectangle].y = this.currRectangle.h + y - this.currRectangle.y;
              }
              this.drawCanvas();
          }
      }
  
      onTouchStart(e) {
          if (this.mode === 2) {
              this.onMouseDown(this.convertTouchToMouse(e));
          }
      }
  
      onTouchEnd(e) {
          if (this.mode === 2) {
              this.onMouseUp(this.convertTouchToMouse(e));
          }
      }
  
      onTouchMove(e) {
          if (this.mode === 2) {
              this.onMouseMove(this.convertTouchToMouse(e));
          }
      }
  
      modeSelected(e) {
          const selected = e.detail.value;
          this.mode = 0;
          if (selected === texts[this._language][goToTarget]) {
              this.mode = 1;
          } else if (selected === texts[this._language][zonedCleanup]) {
              this.mode = 2;
          } else if (selected === texts[this._language][zones]) {
              this.mode = 3;
          }
          this.getPredefinedZonesIncreaseButton().hidden = this.mode !== 3 && this.mode !== 2;
          this.drawCanvas();
      }
  
      vacuumZonedIncreaseButton() {
          this.vacuumZonedCleanupRepeats++;
          if (this.vacuumZonedCleanupRepeats > 3) {
              this.vacuumZonedCleanupRepeats = 1;
          }
      }
  
      vacuumStartButton(debug) {
          if (this.mode === 1 && this.currPoint.x != null) {
              this.vacuumGoToPoint(debug);
          } else if (this.mode === 2 && !this.rectangles.empty) {
              this.vacuumStartZonedCleanup(debug);
          } else if (this.mode === 3 && !this.selectedZones.empty) {
              this.vacuumStartPreselectedZonesCleanup(debug);
          }
      }
  
      drawCanvas() {
          const canvas = this.getCanvas();
          const context = canvas.getContext("2d");
          context.clearRect(0, 0, canvas.width, canvas.height);
          context.translate(0.5, 0.5);
          if (this._config.debug) {
              for (const calibration_point of this._config.calibration_points) {
                  const {x, y} = this.convertVacuumToMapCoordinates(calibration_point.vacuum.x, calibration_point.vacuum.y);
                  this.drawCircle(context, x, y, 4, 'red', 1);
              }
          }
          if (this.mode === 1 && this.currPoint.x != null) {
              this.drawCircle(context, this.currPoint.x, this.currPoint.y, 4, 'yellow', 1);
          } else if (this.mode === 2) {
              for (let i = 0; i < this.rectangles.length; i++) {
                  const rect = this.rectangles[i];
                  context.beginPath();
                  if (i === this.selectedRectangle) {
                      context.setLineDash([10, 5]);
                      context.strokeStyle = 'white';
                  } else {
                      context.setLineDash([]);
                      context.strokeStyle = 'white';
                      context.fillStyle = 'rgba(255, 255, 255, 0.25)';
                      context.fillRect(rect.x, rect.y, rect.w, rect.h);
                  }
                  context.rect(rect.x, rect.y, rect.w, rect.h);
                  context.lineWidth = 1;
                  context.stroke();
                  this.drawDelete(context, rect.x + rect.w, rect.y);
                  this.drawResize(context, rect.x + rect.w, rect.y + rect.h);
              }
              if (this.isMouseDown && this.selectedRectangle < 0) {
                  context.beginPath();
                  context.setLineDash([10, 5]);
                  context.strokeStyle = 'white';
                  context.lineWidth = 1;
                  context.rect(this.currRectangle.x, this.currRectangle.y, this.currRectangle.w, this.currRectangle.h);
                  context.stroke();
              }
          } else if (this.mode === 3) {
              for (let i = 0; i < this._config.zones.length; i++) {
                  const zone = this._config.zones[i];
                  for (const rect of zone) {
                      const {x, y, w, h} = this.convertVacuumToMapZone(rect[0], rect[1], rect[2], rect[3]);
                      context.beginPath();
                      context.setLineDash([]);
                      if (!this.selectedZones.includes(i)) {
                          context.strokeStyle = 'red';
                          context.fillStyle = 'rgba(255, 0, 0, 0.25)';
                      } else {
                          context.strokeStyle = 'green';
                          context.fillStyle = 'rgba(0, 255, 0, 0.25)';
                      }
                      context.lineWidth = 1;
                      context.rect(x, y, w, h);
                      context.fillRect(x, y, w, h);
                      context.stroke();
                  }
              }
          }
          context.translate(-0.5, -0.5);
      }
  
      drawCircle(context, x, y, r, style, lineWidth) {
          context.beginPath();
          context.arc(x, y, r, 0, Math.PI * 2);
          context.strokeStyle = style;
          context.lineWidth = lineWidth;
          context.stroke();
      }
  
      drawDelete(context, x, y) {
          context.setLineDash([]);
          this.drawCircle(context, x, y, 8, 'black', 1.2);
          const diff = 4;
          context.moveTo(x - diff, y - diff);
          context.lineTo(x + diff, y + diff);
          context.moveTo(x - diff, y + diff);
          context.lineTo(x + diff, y - diff);
          context.stroke();
      }
  
      drawResize(context, x, y) {
          context.setLineDash([]);
          this.drawCircle(context, x, y, 8, 'black', 1.2);
          const diff = 4;
          context.moveTo(x - diff, y - diff);
          context.lineTo(x + diff, y + diff);
          context.lineTo(x + diff, y + diff - 4);
          context.lineTo(x + diff - 4, y + diff);
          context.lineTo(x + diff, y + diff);
          context.moveTo(x - diff, y - diff);
          context.lineTo(x - diff, y - diff + 4);
          context.lineTo(x - diff + 4, y - diff);
          context.lineTo(x - diff, y - diff);
          context.stroke();
      }
  
      getSelectedRectangle(x, y) {
          let selected = -1;
          let shouldDelete = false;
          let shouldResize = false;
          for (let i = this.rectangles.length - 1; i >= 0; i--) {
              const rect = this.rectangles[i];
              if (Math.pow(x - rect.x - rect.w, 2) + Math.pow(y - rect.y, 2) <= 64) {
                  selected = i;
                  shouldDelete = true;
                  break;
              }
              if (Math.pow(x - rect.x - rect.w, 2) + Math.pow(y - rect.y - rect.h, 2) <= 64) {
                  selected = i;
                  shouldResize = true;
                  break;
              }
              if (x >= rect.x && y >= rect.y && x <= rect.x + rect.w && y <= rect.y + rect.h) {
                  selected = i;
                  break;
              }
          }
          return {selected, shouldDelete, shouldResize};
      }
  
      getSelectedZone(mx, my) {
          let selected = -1;
          for (let i = 0; i < this._config.zones.length && selected === -1; i++) {
              const zone = this._config.zones[i];
              for (const rect of zone) {
                  const {x, y, w, h} = this.convertVacuumToMapZone(rect[0], rect[1], rect[2], rect[3]);
                  if (mx >= x && my >= y && mx <= x + w && my <= y + h) {
                      selected = i;
                      break;
                  }
              }
          }
          return selected;
      }
  
      getCanvasStyle() {
          if (this.mode === 2) return html`touch-action: none;`;
          else return html``;
      }
  
      vacuumGoToPoint(debug) {
          const mapPos = this.convertMapToVacuumCoordinates(this.currPoint.x, this.currPoint.y);
          if (debug && this._config.debug) {
              alert(JSON.stringify([mapPos.x, mapPos.y]));
          } else {
              this._hass.callService(this.service_domain, this.service_method, {
                  entity_id: this._config.entity,
                  command: "app_goto_target",
                  params: [mapPos.x, mapPos.y]
              }).then(() => this.showToast());
          }
      }
  
      vacuumStartZonedCleanup(debug) {
          const zone = [];
          for (const rect of this.rectangles) {
              zone.push(this.convertMapToVacuumRect(rect, this.vacuumZonedCleanupRepeats));
          }
          if (debug && this._config.debug) {
              alert(JSON.stringify(zone));
          } else {
              this._hass.callService(this.service_domain, this.service_method, {
                  entity_id: this._config.entity,
                  command: "app_zoned_clean",
                  params: zone
              }).then(() => this.showToast());
          }
      }
  
      vacuumStartPreselectedZonesCleanup(debug) {
          const zone = [];
          for (let i = 0; i < this.selectedZones.length; i++) {
              const selectedZone = this.selectedZones[i];
              const preselectedZone = this._config.zones[selectedZone];
              for (const rect of preselectedZone) {
                  zone.push([rect[0], rect[1], rect[2], rect[3], this.vacuumZonedCleanupRepeats])
              }
          }
          if (debug && this._config.debug) {
              alert(JSON.stringify(zone));
          } else {
              this._hass.callService(this.service_domain, this.service_method, {
                  entity_id: this._config.entity,
                  command: "app_zoned_clean",
                  params: zone
              }).then(() => this.showToast());
          }
      }
  
      getCardSize() {
          return 5;
      }
  
      convertMapToVacuumRect(rect, repeats) {
          const xy1 = this.convertMapToVacuumCoordinates(rect.x, rect.y);
          const xy2 = this.convertMapToVacuumCoordinates(rect.x + rect.w, rect.y + rect.h);
          const x1 = Math.min(xy1.x, xy2.x);
          const y1 = Math.min(xy1.y, xy2.y);
          const x2 = Math.max(xy1.x, xy2.x);
          const y2 = Math.max(xy1.y, xy2.y);
          return [x1, y1, x2, y2, repeats];
      }
  
      convertMapToVacuumCoordinates(mapX, mapY) {
          const {x, y} = this.coordinatesConverter.convertAB(mapX / this.imageScale, mapY / this.imageScale);
          return {x: Math.round(x), y: Math.round(y)};
      }
  
      convertVacuumToMapZone(vacuumX1, vacuumY1, vacuumX2, vacuumY2) {
          const {x: x1, y: y1} = this.convertVacuumToMapCoordinates(vacuumX1, vacuumY1);
          const {x: x2, y: y2} = this.convertVacuumToMapCoordinates(vacuumX2, vacuumY2);
          let x = Math.min(x1, x2);
          let y = Math.min(y1, y2);
          let w = Math.abs(x2 - x1);
          let h = Math.abs(y2 - y1);
          return {x, y, w, h};
      }
  
      convertVacuumToMapCoordinates(vacuumX, vacuumY) {
          const {x: vX, y: vY} = this.coordinatesConverter.convertBA(vacuumX, vacuumY);
          const x = Math.round(vX * this.imageScale);
          const y = Math.round(vY * this.imageScale);
          return {x, y};
      }
  
      getMapImage() {
          return this.shadowRoot.getElementById("mapBackground");
      }
  
      getCanvas() {
          return this.shadowRoot.getElementById("mapDrawing");
      }
  
      getPredefinedZonesIncreaseButton() {
          return this.shadowRoot.getElementById("increaseButton");
      }
  
      getMousePos(evt) {
          const canvas = this.getCanvas();
          const rect = canvas.getBoundingClientRect();
          return {
              x: Math.round(evt.clientX - rect.left),
              y: Math.round(evt.clientY - rect.top)
          };
      }
  
      convertTouchToMouse(evt) {
          if (evt.cancelable && this.mode === 2) {
              evt.preventDefault();
          }
          return {
              clientX: evt.changedTouches[0].clientX,
              clientY: evt.changedTouches[0].clientY,
              currentTarget: evt.currentTarget
          };
      }
  
      showToast() {
          const x = this.shadowRoot.getElementById("toast");
          x.className = "show";
          setTimeout(function () {
              x.className = x.className.replace("show", "");
          }, 2000);
      }
  
      updateCameraImage() {
          this._hass.callWS({
              type: 'camera_thumbnail',
              entity_id: this._config.map_camera,
          }).then(val => {
              const {content_type: contentType, content} = val;
              this.map_image = `data:${contentType};base64, ${content}`;
              this.requestUpdate();
          })
      }
  
      connectedCallback() {
          super.connectedCallback();
          if (this._config.map_camera) {
              this.thumbUpdater = setInterval(() => this.updateCameraImage(), this._map_refresh_interval);
          }
      }
  
      disconnectedCallback() {
          super.disconnectedCallback();
          if (this._config.map_camera) {
              clearInterval(this.thumbUpdater);
              this.map_image = null;
          }
      }
  }
  
  customElements.define('xiaomi-vacuum-map-card', XiaomiVacuumMapCard);
  