interface Block {
  type: string;
  x: number;
  y: number;
  width: number;
  height: number;
  color: string;
  zIndex: number;
}

export class Canvas implements AfterViewInit {
  @ViewChild('canvasWrapper') canvaWrapperRef!: ElementRef;
  @ViewChild('previewWarehouse')
  previewWarehouseRef!: ElementRef<HTMLCanvasElement>;
  @ViewChild('components') componentsRef!: ElementRef;
  @ViewChild('controlls') controllsRef!: ElementRef;

  private ctx!: CanvasRenderingContext2D;
  canvasWrapperEl: any;
  previewWarehouseEl: any;
  canvasHeight: number = 0;
  canvasWidth: number = 0;
  scale: number = 1;
  resizeHandleSize: number = 10;
  currentCursor: string = 'default';
  currentShapeIndex: number | null = null;
  isDragging: boolean = false;
  isResizing: boolean = false;
  startX: number = 0;
  startY: number = 0;
  resizeCorner: string | null = null;
  selectedComponent: { [key: string]: any } = {};
  blocks: Block[] = [];
  selectedBlock: Block | null = null;
  draggingBlock: Block | null = null;
  resizingBlock: Block | null = null;
  resizeDirection: string | null = null;
  dragOffsetX: number = 0;
  dragOffsetY: number = 0;

  components: any[] = [
    {
      name: 'pallet',
      height: 100,
      width: 300,
      color: '#86efac',
    },
    {
      name: 'block',
      height: 60,
      width: 200,
      color: '#d6d3d1',
    },
    {
      name: 'shelves',
      height: 100,
      width: 300,
      color: '#fff',
    },
    {
      name: 'storageRack',
      height: 80,
      width: 400,
      color: '#bfdbfe',
    },
    {
      name: 'equipment',
      height: 40,
      width: 80,
      color: '#e9d5ff',
    },
    {
      name: 'officeSpace',
      height: 200,
      width: 300,
      color: '#cdcdff',
    },
    {
      name: 'loadingDock',
      height: 200,
      width: 300,
      color: '#fecaca',
    },
    {
      name: 'safetyZone',
      height: 150,
      width: 150,
      color: '#fed7aa',
    },
    {
      name: 'bulb',
      height: 50,
      width: 100,
      color: '#fff5ad',
    },
    {
      name: 'lines',
      height: 140,
      width: 300,
      color: '#acff90',
    },
  ];

  // for controlling the process
  isShowProcess: boolean = true;
  currentProcessIndex: number = 0;
  process: any[] = [
    {
      title: 'Welcome to Our App !👋',
      message:
        "Let's take a quick tour to show you around and help you get started.",
      top: '41%',
      left: '40%',
      iTop: true,
      iRight: false,
      id: 0,
    },
    {
      title: 'Please, click any of component from here',
      message:
        'It will reflect one the right side box, where you can see the preview!',
      iTop: false,
      iRight: false,
      id: 1,
    },
    {
      title: 'Controll the sizing of preview',
      message: 'Form here you can zoom in and zoom out the preivew',
      iTop: false,
      iRight: false,
      id: 2,
    },
    {
      title: 'Drag and Drop',
      message:
        'You can change the position of the component by dragging inside the preivew box.',
      iTop: false,
      iRight: false,
      id: 3,
    },
  ];
  controlPos: any;
  componentPos: any;
  previewWarehousePos: any;

  componentsEl: any;
  controllsEl: any;

  constructor() {}

  ngAfterViewInit() {
    this.canvasWrapperEl = this.canvaWrapperRef.nativeElement;
    this.previewWarehouseEl = this.previewWarehouseRef.nativeElement;
    this.ctx = this.previewWarehouseEl.getContext(
      '2d'
    ) as CanvasRenderingContext2D;

    this.canvasHeight = this.canvasWrapperEl.clientHeight;
    this.canvasWidth = this.canvasWrapperEl.clientWidth + 10;
    this.setCanvasDimensions();
    this.drawGridLine();

    this.previewWarehouseEl.style.cursor = this.currentCursor;

    // Add event listeners
    this.previewWarehouseEl.addEventListener(
      'mousedown',
      this.onMouseDown.bind(this)
    );
    this.previewWarehouseEl.addEventListener(
      'mouseup',
      this.onMouseUp.bind(this)
    );
    this.previewWarehouseEl.addEventListener(
      'mouseout',
      this.onMouseOut.bind(this)
    );
    this.previewWarehouseEl.addEventListener(
      'mousemove',
      this.onMouseMove.bind(this)
    );

    // for process
    this.controllsEl = this.controllsRef.nativeElement;
    this.componentsEl = this.componentsRef.nativeElement;
    this.getPositions();
    this.onSetProcessPosition();
  }

  drawGridLine() {
    this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
    this.ctx.save();
    this.ctx.scale(this.scale, this.scale);

    const width = this.canvasWidth / this.scale;
    const height = this.canvasHeight / this.scale;
    const gridSize = 15;

    this.ctx.strokeStyle = '#ddd';
    this.ctx.lineWidth = 1 / this.scale;

    // Draw grid lines
    for (let x = 0; x <= width; x += gridSize) {
      this.ctx.beginPath();
      this.ctx.moveTo(x, 0);
      this.ctx.lineTo(x, height);
      this.ctx.stroke();
    }

    for (let y = 0; y <= height; y += gridSize) {
      this.ctx.beginPath();
      this.ctx.moveTo(0, y);
      this.ctx.lineTo(width, y);
      this.ctx.stroke();
    }

    this.drawShapes();
    this.ctx.restore();
  }

  drawShapes(): void {
    this.blocks.forEach((block) => {
      this.drawBlock(block);
    });
  }

  drawBlock(block: Block) {
    this.ctx.fillStyle = block.color;
    this.ctx.fillRect(block.x, block.y, block.width, block.height);
    this.ctx.fillStyle = 'black';
    this.ctx.font = `${15 / this.scale}px Arial`;
    this.ctx.textAlign = 'center';
    this.ctx.textBaseline = 'middle';
    this.ctx.fillText(
      block.type,
      block.x + block.width / 2,
      block.y + block.height / 2
    );

    if (this.selectedBlock === block) {
      this.ctx.strokeStyle = 'black';
      this.ctx.setLineDash([5, 3]);
      this.ctx.lineWidth = 2 / this.scale;
      this.ctx.strokeRect(block.x, block.y, block.width, block.height);
      this.ctx.setLineDash([]);

      // Only draw resize handles if not dragging
      if (!this.draggingBlock) {
        this.drawResizeHandles(block);
      }
    }
  }

  drawResizeHandles(block: Block): void {
    const handleSize = this.resizeHandleSize / this.scale;
    const directions = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
    const handles = this.getResizeHandles(block);

    this.ctx.fillStyle = '#000'; // Black color for resize handles

    directions.forEach((dir) => {
      const handle = handles[dir];
      if (handle) {
        this.ctx.fillRect(handle.x, handle.y, handleSize, handleSize);
      }
    });
  }

  getResizeHandles(block: Block): Record<string, { x: number; y: number }> {
    const handleSize = this.resizeHandleSize / this.scale;
    const halfSize = handleSize / 2;
    const { x, y, width, height } = block;

    return {
      nw: { x: x - halfSize, y: y - halfSize },
      n: { x: x + width / 2 - halfSize, y: y - halfSize },
      ne: { x: x + width - halfSize, y: y - halfSize },
      e: { x: x + width - halfSize, y: y + height / 2 - halfSize },
      se: { x: x + width - halfSize, y: y + height - halfSize },
      s: { x: x + width / 2 - halfSize, y: y + height - halfSize },
      sw: { x: x - halfSize, y: y + height - halfSize },
      w: { x: x - halfSize, y: y + height / 2 - halfSize },
    };
  }

  onMouseDown(event: MouseEvent) {
    const pos = this.getMousePosition(event);
    const sortedBlocks = [...this.blocks].sort((a, b) => b.zIndex - a.zIndex);

    // Check if we're clicking on a resize handle
    if (this.selectedBlock) {
      const handles = this.getResizeHandles(this.selectedBlock);
      const hs = this.resizeHandleSize / this.scale;
      for (const dir of ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w']) {
        const h = handles[dir];
        if (
          pos.x >= h.x &&
          pos.x <= h.x + hs &&
          pos.y >= h.y &&
          pos.y <= h.y + hs
        ) {
          this.resizingBlock = this.selectedBlock;
          this.resizeDirection = dir;
          this.startX = pos.x;
          this.startY = pos.y;
          this.currentCursor = this.getCursorForDirection(dir);
          this.previewWarehouseEl.style.cursor = this.currentCursor;
          this.drawGridLine();
          return;
        }
      }
    }

    // Check if we're clicking on a block
    for (const block of sortedBlocks) {
      if (
        pos.x >= block.x &&
        pos.x <= block.x + block.width &&
        pos.y >= block.y &&
        pos.y <= block.y + block.height
      ) {
        this.selectedBlock = block;
        this.draggingBlock = block;
        this.startX = pos.x;
        this.startY = pos.y;
        this.dragOffsetX = pos.x - block.x;
        this.dragOffsetY = pos.y - block.y;
        this.currentCursor = 'move';
        this.previewWarehouseEl.style.cursor = this.currentCursor;
        this.drawGridLine();
        return;
      }
    }

    // Clicked on empty space, deselect
    this.selectedBlock = null;
    this.currentCursor = 'default';
    this.previewWarehouseEl.style.cursor = this.currentCursor;
    this.drawGridLine();
  }

  onMouseMove(event: MouseEvent) {
    const pos = this.getMousePosition(event);

    if (this.resizingBlock && this.resizeDirection) {
      this.resizeSelectedBlock(pos);
      this.drawGridLine();
      this.currentCursor = this.getCursorForDirection(this.resizeDirection);
      this.previewWarehouseEl.style.cursor = this.currentCursor;
      return;
    }

    if (this.draggingBlock) {
      const canvasW = this.previewWarehouseEl.width / this.scale;
      const canvasH = this.previewWarehouseEl.height / this.scale;

      let newX = pos.x - this.dragOffsetX;
      let newY = pos.y - this.dragOffsetY;

      // Constrain within canvas bounds
      newX = Math.max(0, Math.min(newX, canvasW - this.draggingBlock.width));
      newY = Math.max(0, Math.min(newY, canvasH - this.draggingBlock.height));

      this.draggingBlock.x = newX;
      this.draggingBlock.y = newY;
      this.currentCursor = 'move';
      this.previewWarehouseEl.style.cursor = this.currentCursor;
      this.drawGridLine();
      return;
    }

    // Check if hovering over a block for resizing
    let isHoveringResizeHandle = false;
    for (const block of this.blocks) {
      if (
        this.selectedBlock === block &&
        pos.x >= block.x &&
        pos.x <= block.x + block.width &&
        pos.y >= block.y &&
        pos.y <= block.y + block.height
      ) {
        const handles = this.getResizeHandles(block);
        const hs = this.resizeHandleSize / this.scale;

        for (const dir of ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w']) {
          const h = handles[dir];
          if (
            pos.x >= h.x &&
            pos.x <= h.x + hs &&
            pos.y >= h.y &&
            pos.y <= h.y + hs
          ) {
            isHoveringResizeHandle = true;
            this.currentCursor = this.getCursorForDirection(dir);
            this.previewWarehouseEl.style.cursor = this.currentCursor;
            return;
          }
        }
      }
    }

    // If not hovering over a resize handle, reset cursor
    if (!isHoveringResizeHandle) {
      this.currentCursor = 'default';
      this.previewWarehouseEl.style.cursor = this.currentCursor;
    }
  }

  getCursorForDirection(direction: string): string {
    switch (direction) {
      case 'nw':
      case 'se':
        return 'nwse-resize';
      case 'ne':
      case 'sw':
        return 'nesw-resize';
      case 'n':
      case 's':
        return 'ns-resize';
      case 'e':
      case 'w':
        return 'ew-resize';
      default:
        return 'default';
    }
  }

  onMouseUp(event: MouseEvent) {
    this.draggingBlock = null;
    this.resizingBlock = null;
    this.resizeDirection = null;
    this.currentCursor = 'default';
    this.previewWarehouseEl.style.cursor = this.currentCursor;
  }

  onMouseOut(event: MouseEvent) {
    this.onMouseUp(event);
  }

  getMousePosition(event: MouseEvent) {
    const rect = this.previewWarehouseEl.getBoundingClientRect();
    return {
      x: (event.clientX - rect.left) / this.scale,
      y: (event.clientY - rect.top) / this.scale,
    };
  }

  resizeSelectedBlock(pos: { x: number; y: number }): void {
    if (!this.resizingBlock || !this.resizeDirection) return;

    const block = this.resizingBlock;
    const minSize = 50;
    const right = block.x + block.width;
    const bottom = block.y + block.height;

    console.log(block);
    if (this.resizeDirection.includes('n')) {
      const newHeight = bottom - pos.y;
      if (newHeight >= minSize && pos.y >= 0) {
        block.y = pos.y;
        block.height = newHeight;
      }
    }
    if (this.resizeDirection.includes('s')) {
      const newHeight = pos.y - block.y;
      if (
        newHeight >= minSize &&
        pos.y <= this.previewWarehouseEl.height / this.scale
      ) {
        block.height = newHeight;
      }
    }
    if (this.resizeDirection.includes('w')) {
      const newWidth = right - pos.x;
      if (newWidth >= minSize && pos.x >= 0) {
        block.x = pos.x;
        block.width = newWidth;
      }
    }
    if (this.resizeDirection.includes('e')) {
      const newWidth = pos.x - block.x;
      if (
        newWidth >= minSize &&
        pos.x <= this.previewWarehouseEl.width / this.scale
      ) {
        block.width = newWidth;
      }
    }
  }

  onAddComponent(type: string) {
    const component = this.components.find((com) => com.name === type);

    if (!component) return;
    const newBlock: Block = {
      type,
      x: this.previewWarehouseEl.width / this.scale / 2 - component.width / 2,
      y: this.previewWarehouseEl.height / this.scale / 2 - component.height / 2,
      width: component.width,
      height: component.height,
      zIndex:
        this.blocks.length > 0
          ? Math.max(...this.blocks.map((s) => s.zIndex)) + 1
          : 0,
      color: component.color,
    };

    this.blocks.push(newBlock);
    this.selectedBlock = newBlock;
    this.drawGridLine();
  }

  zoomIn() {
    this.scale *= 1.2;
    this.drawGridLine();
  }

  zoomOut() {
    this.scale /= 1.2;
    this.drawGridLine();
  }

  setCanvasDimensions() {
    this.previewWarehouseEl.width = this.canvasWidth;
    this.previewWarehouseEl.height = this.canvasHeight;
  }

  @HostListener('window:resize', ['$event'])
  onResize(event: Event) {
    this.canvasHeight = this.canvasWrapperEl.offsetHeight;
    this.canvasWidth = this.canvasWrapperEl.offsetWidth;
    this.setCanvasDimensions();
    this.drawGridLine();
  }

  // this function for getting the position of item where I will show the process
  // this code for showing the process
  getPositions() {
    console.log('called');
    this.controlPos = this.controllsEl.getBoundingClientRect();
    console.log(this.controlPos);
    this.componentPos = this.componentsEl.getBoundingClientRect();
    this.previewWarehousePos = this.previewWarehouseEl.getBoundingClientRect();
    console.log(this.controllsEl, this.componentsEl, this.previewWarehouseEl);
    console.log(
      this.componentPos,
      'component pos',
      this.controlPos,
      'controll pos',
      this.previewWarehousePos,
      'preview pos'
    );
  }

  onSetProcess(currentIndex: number, componentPos: any) {
    let currentProcess = this.process[this.currentProcessIndex];
    let windowWidth = window.innerWidth;
    let top = currentIndex === 3 ? componentPos.y + 10 : componentPos.y;
    let left = currentIndex === 3 ? componentPos.x + 20 : componentPos.x - 320;
    let iTop;
    let iRight;
    if (windowWidth >= 999) {
      iTop = currentIndex === 3 ? true : false;
      iRight = currentIndex === 3 ? false : true;
    }

    if (windowWidth <= 999) {
      currentProcess = {
        ...currentProcess,
        top: `${top + 30}px`,
        left: `20%`,
        iTop: true,
        iRight: false,
      };
    } else {
      currentProcess = {
        ...currentProcess,
        top: `${top}px`,
        left: `${left}px`,
        iRight: iRight,
        iTop: iTop,
      };
    }
    this.process = [
      ...this.process.slice(0, this.currentProcessIndex),
      currentProcess,
      ...this.process.slice(this.currentProcessIndex + 1),
    ];
  }

  onSetProcessPosition() {
    this.getPositions();
    switch (this.currentProcessIndex) {
      case 1:
        this.onSetProcess(this.currentProcessIndex, this.componentPos);
        break;
      case 2:
        this.onSetProcess(this.currentProcessIndex, this.controlPos);
        break;
      case 3:
        this.onSetProcess(this.currentProcessIndex, this.previewWarehousePos);
        break;
      default:
        return;
    }
  }

  onChangeNext() {
    this.currentProcessIndex += 1;
    this.onSetProcessPosition();
    if (this.currentProcessIndex === this.process.length) {
      this.isShowProcess = false;
    }
  }
  onChangePrevious() {
    if (this.currentProcessIndex <= 0) {
      this.currentProcessIndex = 0;
      return;
    }
    this.currentProcessIndex -= 1;
    this.onSetProcessPosition();
  }

  onCancelProcess() {
    this.isShowProcess = false;
    this.currentProcessIndex = 0;
  }

  // ends here
}

resizeSelectedBlock(pos: { x: number; y: number }): void {
    if (!this.resizingBlock || !this.resizeDirection) return;

    const block = this.resizingBlock;
    const minSize = 50;
    const right = block.x + block.width;
    const bottom = block.y + block.height;

    // Find the component in the components array
    const component = this.components.find(com => com.name === block.type);

    if (this.resizeDirection.includes('n')) {
        const newHeight = bottom - pos.y;
        if (newHeight >= minSize && pos.y >= 0) {
            block.y = pos.y;
            block.height = newHeight;
            if (component) {
                component.height = newHeight; // Update the component's height
            }
        }
    }
    if (this.resizeDirection.includes('s')) {
        const newHeight = pos.y - block.y;
        if (newHeight >= minSize && pos.y <= this.previewWarehouseEl.height / this.scale) {
            block.height = newHeight;
            if (component) {
                component.height = newHeight; // Update the component's height
            }
        }
    }
    if (this.resizeDirection.includes('w')) {
        const newWidth = right - pos.x;
        if (newWidth >= minSize && pos.x >= 0) {
            block.x = pos.x;
            block.width = newWidth;
            if (component) {
                component.width = newWidth; // Update the component's width
            }
        }
    }
    if (this.resizeDirection.includes('e')) {
        const newWidth = pos.x - block.x;
        if (newWidth >= minSize && pos.x <= this.previewWarehouseEl.width / this.scale) {
            block.width = newWidth;
            if (component) {
                component.width = newWidth; // Update the component's width
            }
        }
    }
}