import * as THREE from "three";
import {
  beginHistoryWindowDrag,
  bringHistoryWindowToFront,
  copyTextToClipboard,
  destroyHistoryWindow,
  endHistoryWindowDrag,
  openHistoryWindow,
  refreshHistoryWindows,
  updateHistoryWindowDrag,
} from "./viewerHistoryManager";
import type { HistoryWindowState } from "./viewerHudState";
import type { Selectable, WorldState } from "./viewerTypes";

export interface ViewerHistoryWindowContext {
  historyWindows: HistoryWindowState[];
  getWorld: () => WorldState | undefined;
  getHistoryWindowCounter: () => number;
  setHistoryWindowCounter: (value: number) => void;
  getHistoryWindowZCounter: () => number;
  setHistoryWindowZCounter: (value: number) => void;
  getHistoryWindowDragId: () => string | undefined;
  setHistoryWindowDragId: (value: string | undefined) => void;
  getHistoryWindowDragPointerId: () => number | undefined;
  setHistoryWindowDragPointerId: (value: number | undefined) => void;
  historyWindowDragOffset: THREE.Vector2;
  renderRecentEvents: (entityKind: string, entityId: string) => string;
}

export class ViewerHistoryWindowController {
  constructor(private readonly context: ViewerHistoryWindowContext) {}

  openHistoryWindow(target: Selectable) {
    const nextCounter = openHistoryWindow(
      this.context.historyWindows,
      target,
      this.context.getHistoryWindowCounter() + 1,
      (windowState) => this.bringHistoryWindowToFront(windowState),
      () => this.refreshHistoryWindows(),
    );
    this.context.setHistoryWindowCounter(nextCounter);
  }

  refreshHistoryWindows() {
    refreshHistoryWindows(
      this.context.getWorld(),
      this.context.historyWindows,
      this.context.renderRecentEvents,
      (id) => this.destroyHistoryWindow(id),
    );
  }

  readonly onHistoryLayerClick = (event: MouseEvent) => {
    const target = event.target;
    if (!(target instanceof HTMLElement)) {
      return;
    }

    const windowEl = target.closest<HTMLElement>("[data-history-window-id]");
    const windowId = windowEl?.dataset.historyWindowId;
    if (!windowId) {
      return;
    }

    if (target.closest(".history-window-copy")) {
      void this.copyHistoryWindowContent(windowId);
      return;
    }

    if (target.closest(".history-window-close")) {
      this.destroyHistoryWindow(windowId);
      return;
    }

    const windowState = this.context.historyWindows.find((candidate) => candidate.id === windowId);
    if (windowState) {
      this.bringHistoryWindowToFront(windowState);
    }
  };

  readonly onHistoryLayerPointerDown = (event: PointerEvent) => {
    const target = event.target;
    if (!(target instanceof HTMLElement)) {
      return;
    }

    const windowEl = target.closest<HTMLElement>("[data-history-window-id]");
    const windowId = windowEl?.dataset.historyWindowId;
    if (!windowEl || !windowId) {
      return;
    }

    const windowState = this.context.historyWindows.find((candidate) => candidate.id === windowId);
    if (!windowState) {
      return;
    }

    this.bringHistoryWindowToFront(windowState);
    if (!target.closest(".history-window-header") || target.closest("button")) {
      return;
    }

    const nextState = beginHistoryWindowDrag(
      this.context.historyWindows,
      this.context.historyWindowDragOffset,
      event.pointerId,
      windowId,
      event.clientX,
      event.clientY,
    );
    this.context.setHistoryWindowDragId(nextState.historyWindowDragId);
    this.context.setHistoryWindowDragPointerId(nextState.historyWindowDragPointerId);
  };

  readonly onHistoryWindowPointerMove = (event: PointerEvent) => {
    updateHistoryWindowDrag(
      this.context.historyWindows,
      this.context.getHistoryWindowDragId(),
      this.context.getHistoryWindowDragPointerId(),
      this.context.historyWindowDragOffset,
      event.pointerId,
      event.clientX,
      event.clientY,
    );
  };

  readonly onHistoryWindowPointerUp = (event: PointerEvent) => {
    const nextState = endHistoryWindowDrag(
      this.context.historyWindows,
      this.context.getHistoryWindowDragId(),
      this.context.getHistoryWindowDragPointerId(),
      event.pointerId,
    );
    this.context.setHistoryWindowDragId(nextState.historyWindowDragId);
    this.context.setHistoryWindowDragPointerId(nextState.historyWindowDragPointerId);
  };

  private destroyHistoryWindow(id: string) {
    const nextState = destroyHistoryWindow(
      this.context.historyWindows,
      this.context.getHistoryWindowDragId(),
      this.context.getHistoryWindowDragPointerId(),
      id,
    );
    this.context.setHistoryWindowDragId(nextState.historyWindowDragId);
    this.context.setHistoryWindowDragPointerId(nextState.historyWindowDragPointerId);
  }

  private async copyHistoryWindowContent(windowId: string) {
    const windowState = this.context.historyWindows.find((candidate) => candidate.id === windowId);
    if (!windowState?.text) {
      return;
    }

    try {
      await copyTextToClipboard(windowState.text);
      windowState.copyLabel = "Copied";
      window.setTimeout(() => {
        windowState.copyLabel = "Copy";
      }, 1200);
    } catch {
      windowState.copyLabel = "Failed";
      window.setTimeout(() => {
        windowState.copyLabel = "Copy";
      }, 1200);
    }
  }

  private bringHistoryWindowToFront(windowState: HistoryWindowState) {
    const nextZIndex = this.context.getHistoryWindowZCounter() + 1;
    this.context.setHistoryWindowZCounter(nextZIndex);
    bringHistoryWindowToFront(windowState, nextZIndex);
  }
}
