import { TpFlowNode } from './tp-flow-node.js';

export const panning = function(superClass) {
  return class extends superClass {
    static get properties() {
      return {
        isDragging: { type: Boolean },
        currentX: { type: Number },
        currentY: { type: Number },
        currentNodeX: { type: Number },
        currentNodeY: { type: Number },
        initialX: { type: Number },
        initialY: { type: Number },
        xOffset: { type: Number },
        yOffset: { type: Number },
        highestZIndex: { type: Number }
      };
    }

    constructor() {
      super();
      this.isDragging = false;
      this.currentX = 0;
      this.currentY = 0;
      this.currentNodeX = 0;
      this.currentNodeY = 0;
      this.initialX = 0;
      this.initialY = 0;
      this.xOffset = 0;
      this.yOffset = 0;
      this.targetElement = null;
      this.highestZIndex = 1;
    }

    firstUpdated() {
      this.canvas = this.shadowRoot.querySelector('.canvas');
      this.addEventListener('mousedown', this._startDrag.bind(this));
      document.addEventListener('mousemove', this._drag.bind(this));
      document.addEventListener('mouseup', this._endDrag.bind(this));
      super.firstUpdated();
    }

    _getTopNodeAtPoint(x, y) {
      const nodes = this.shadowRoot.elementsFromPoint(x, y)
        .filter(el => el instanceof TpFlowNode)
        .sort((a, b) => {
          const aZ = parseInt(getComputedStyle(a).zIndex) || 0;
          const bZ = parseInt(getComputedStyle(b).zIndex) || 0;
          return bZ - aZ;
        });
      
      return nodes[0] || null;
    }

    _bringToFront(element) {
      if (!(element instanceof TpFlowNode)) return;
      
      // Get all elements and filter for TpFlowNode instances
      const nodes = Array.from(this.shadowRoot.querySelector('.canvas').children)
        .filter(node => node instanceof TpFlowNode);

      for (const node of nodes) {
        node.style.zIndex = 1;
        node.classList.remove('focused');
      }
      
      element.style.zIndex = 2;
      element.classList.add('focused');
    }

    _startDrag(e) {
      const topNode = this._getTopNodeAtPoint(e.clientX, e.clientY);
      
      if (topNode) {
        this.targetElement = topNode;
        this._bringToFront(topNode);

        // Check if a element with the "drag-node" attribute is part of the event path. Only then we can start dragging.
        if (!e.composedPath().some(el => typeof el.hasAttribute === 'function' && el.hasAttribute('drag-node'))) {
          return;
        }
      } else {
        this.targetElement = this.canvas;
      }
    
      if (this.targetElement) {
        this.isDragging = true;
        
        const transform = window.getComputedStyle(this.targetElement).transform;
        const matrix = new DOMMatrix(transform);
        this.xOffset = matrix.m41;
        this.yOffset = matrix.m42;
        
        if (this.targetElement === this.canvas) {
          this.initialX = e.clientX - this.xOffset;
          this.initialY = e.clientY - this.yOffset;
        } else {
          // For nodes, compensate for scale in initial position too
          this.initialX = e.clientX - (this.xOffset * this.scale);
          this.initialY = e.clientY - (this.yOffset * this.scale);
        }
      }
    }

    _drag(e) {
      if (this.isDragging && this.targetElement) {
        e.preventDefault();
        
        if (this.targetElement === this.canvas) {
          // For canvas panning, no scale compensation needed
          this.currentX = e.clientX - this.initialX;
          this.currentY = e.clientY - this.initialY;
          this.targetElement.style.transform = 
            `translate(${this.currentX}px, ${this.currentY}px) scale(${this.scale})`;
        } else {
          // For nodes, compensate for canvas scale
          this.currentNodeX = (e.clientX - this.initialX) / this.scale;
          this.currentNodeY = (e.clientY - this.initialY) / this.scale;
          this.targetElement.style.transform = 
            `translate(${this.currentNodeX}px, ${this.currentNodeY}px)`;
        }
    
        this.requestUpdate();
      }
    }

    _endDrag() {
      if (this.isDragging && this.targetElement instanceof TpFlowNode) {
        this._dispatchChangeEvent({
          type: 'node-moved',
          data: {
            nodeId: this.targetElement.id,
            position: {
              x: this.currentNodeX,
              y: this.currentNodeY
            }
          }
        });
      }
      
      this.isDragging = false;
      this.targetElement = null;
    }
  };
};