// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {Overlay, type ResetData} from './common.js';
import {type Delegate, DragResizeHandler, ResizerType} from './drag_resize_handler.js';
import {type ContainerQueryHighlight, drawContainerQueryHighlight} from './highlight_container_query.js';
import {drawLayoutFlexContainerHighlight, type FlexContainerHighlight} from './highlight_flex_common.js';
import {drawLayoutGridHighlight, type GridHighlight} from './highlight_grid_common.js';
import {drawIsolatedElementHighlight, type IsolatedElementHighlight} from './highlight_isolated_element.js';
import {drawScrollSnapHighlight, type ScrollSnapHighlight} from './highlight_scroll_snap.js';
import type {GreenDevAnchorsHighlight, GreenDevAnchorsOverlay} from './tool_green_dev_anchors.js';

export interface PersistentToolMessage {
  highlightType: string;
  highlightIndex: number;
  newWidth: string;
  newHeight: string;
  resizerType: ResizerType;
}

interface DraggableMetadata {
  type: ResizerType;
  highlightIndex: number;
  initialWidth?: number;
  initialHeight?: number;
}

function makeDraggableDelegate(overlay: PersistentOverlay): Delegate {
  return {
    getDraggable: (x, y) => {
      const result = overlay.isPointInDraggablePath(x, y);
      if (!result) {
        return;
      }

      return {
        type: result.type,
        initialWidth: result.initialWidth,
        initialHeight: result.initialHeight,
        id: result.highlightIndex,
        update: ({width, height}: {width?: number, height?: number}) => {
          window.InspectorOverlayHost.send({
            highlightType: 'isolatedElement',
            highlightIndex: result.highlightIndex,
            newWidth: `${width}px`,
            newHeight: `${height}px`,
            resizerType: result.type,
          });
        },
      };
    },
  };
}

export class PersistentOverlay extends Overlay {
  private gridLabelState = {gridLayerCounter: 0};

  private gridLabels!: HTMLElement;
  private draggableBorders = new Map<number, {
    widthPath: Path2D,
    heightPath: Path2D,
    bidirectionPath: Path2D,
    highlightIndex: number,
    initialWidth: number,
    initialHeight: number,
  }>();
  private dragHandler?: DragResizeHandler;
  private greenDevAnchorsOverlay?: GreenDevAnchorsOverlay;

  setGreenDevAnchorsOverlay(greenDevAnchorsOverlay: GreenDevAnchorsOverlay) {
    this.greenDevAnchorsOverlay = greenDevAnchorsOverlay;
  }

  override reset(data: ResetData) {
    super.reset(data);
    this.gridLabelState.gridLayerCounter = 0;
    this.gridLabels.innerHTML = '';
    this.greenDevAnchorsOverlay?.reset(data);
  }

  renderGridMarkup() {
    const gridLabels = this.document.createElement('div');
    gridLabels.id = 'grid-label-container';
    this.document.body.append(gridLabels);
    this.gridLabels = gridLabels;
  }

  override install() {
    this.document.body.classList.add('fill');

    const canvas = this.document.createElement('canvas');
    canvas.id = 'canvas';
    canvas.classList.add('fill');
    this.document.body.append(canvas);

    this.renderGridMarkup();

    this.setCanvas(canvas);

    super.install();
    this.dragHandler?.install();
  }

  override uninstall() {
    this.document.body.classList.remove('fill');
    this.document.body.innerHTML = '';
    this.draggableBorders = new Map();
    super.uninstall();
    this.dragHandler?.uninstall();
  }

  drawGridHighlight(highlight: GridHighlight) {
    this.context.save();
    drawLayoutGridHighlight(
        highlight, this.context, this.deviceScaleFactor, this.canvasWidth, this.canvasHeight, this.emulationScaleFactor,
        this.gridLabelState);
    this.context.restore();
  }

  drawFlexContainerHighlight(highlight: FlexContainerHighlight) {
    this.context.save();
    drawLayoutFlexContainerHighlight(highlight, this.context, this.emulationScaleFactor);
    this.context.restore();
  }

  drawScrollSnapHighlight(highlight: ScrollSnapHighlight) {
    this.context.save();
    drawScrollSnapHighlight(highlight, this.context, this.emulationScaleFactor);
    this.context.restore();
  }

  drawContainerQueryHighlight(highlight: ContainerQueryHighlight) {
    this.context.save();
    drawContainerQueryHighlight(highlight, this.context, this.emulationScaleFactor);
    this.context.restore();
  }

  drawGreenDevFloatyAnchors(highlights: GreenDevAnchorsHighlight[]) {
    if (this.greenDevAnchorsOverlay && !this.greenDevAnchorsOverlay.installed) {
      this.greenDevAnchorsOverlay.install();
    }
    this.greenDevAnchorsOverlay?.drawGreenDevAnchors(highlights);
  }

  drawIsolatedElementHighlight(highlight: IsolatedElementHighlight) {
    if (!this.dragHandler) {
      this.dragHandler = new DragResizeHandler(this.document, makeDraggableDelegate(this));
      this.dragHandler.install();
    }

    this.context.save();
    const {widthPath, heightPath, bidirectionPath, currentWidth, currentHeight, highlightIndex} =
        drawIsolatedElementHighlight(
            highlight, this.context, this.canvasWidth, this.canvasHeight, this.emulationScaleFactor);

    this.draggableBorders.set(highlightIndex, {
      widthPath,
      heightPath,
      bidirectionPath,
      highlightIndex,
      initialWidth: currentWidth,
      initialHeight: currentHeight,
    });
    this.context.restore();
  }

  isPointInDraggablePath(x: number, y: number): DraggableMetadata|undefined {
    for (const {widthPath, heightPath, bidirectionPath, highlightIndex, initialWidth, initialHeight} of this
             .draggableBorders.values()) {
      if (this.context.isPointInPath(widthPath, x, y)) {
        return {
          type: ResizerType.WIDTH,
          highlightIndex,
          initialWidth,
        };
      }
      if (this.context.isPointInPath(heightPath, x, y)) {
        return {
          type: ResizerType.HEIGHT,
          highlightIndex,
          initialHeight,
        };
      }
      if (this.context.isPointInPath(bidirectionPath, x, y)) {
        return {
          type: ResizerType.BIDIRECTION,
          highlightIndex,
          initialWidth,
          initialHeight,
        };
      }
    }

    return;
  }
}
