import { Injectable, signal } from '@angular/core';

const STORAGE_KEY = 'metoyou_debug_console_layout';
const DEFAULT_HEIGHT = 520;
const DEFAULT_WIDTH = 832;
const MIN_HEIGHT = 260;
const MIN_WIDTH = 460;

interface PersistedLayout {
  height: number;
  width: number;
}

@Injectable({ providedIn: 'root' })
export class DebugConsoleResizeService {
  readonly panelHeight = signal(DEFAULT_HEIGHT);
  readonly panelWidth = signal(DEFAULT_WIDTH);
  readonly panelLeft = signal(0);
  readonly panelTop = signal(0);

  private dragging = false;
  private resizingTop = false;
  private resizingBottom = false;
  private resizingLeft = false;
  private resizingRight = false;
  private resizingCorner = false;
  private resizeOriginX = 0;
  private resizeOriginY = 0;
  private resizeOriginHeight = DEFAULT_HEIGHT;
  private resizeOriginWidth = DEFAULT_WIDTH;
  private panelOriginLeft = 0;
  private panelOriginTop = 0;

  constructor() {
    this.loadLayout();
  }

  get isResizing(): boolean {
    return this.resizingTop || this.resizingBottom || this.resizingLeft || this.resizingRight || this.resizingCorner;
  }

  get isDragging(): boolean {
    return this.dragging;
  }

  startTopResize(event: MouseEvent): void {
    event.preventDefault();
    event.stopPropagation();
    this.resizingTop = true;
    this.resizeOriginY = event.clientY;
    this.resizeOriginHeight = this.panelHeight();
    this.panelOriginTop = this.panelTop();
  }

  startBottomResize(event: MouseEvent): void {
    event.preventDefault();
    event.stopPropagation();
    this.resizingBottom = true;
    this.resizeOriginY = event.clientY;
    this.resizeOriginHeight = this.panelHeight();
  }

  startLeftResize(event: MouseEvent): void {
    event.preventDefault();
    event.stopPropagation();
    this.resizingLeft = true;
    this.resizeOriginX = event.clientX;
    this.resizeOriginWidth = this.panelWidth();
    this.panelOriginLeft = this.panelLeft();
  }

  startRightResize(event: MouseEvent): void {
    event.preventDefault();
    event.stopPropagation();
    this.resizingRight = true;
    this.resizeOriginX = event.clientX;
    this.resizeOriginWidth = this.panelWidth();
  }

  startCornerResize(event: MouseEvent): void {
    event.preventDefault();
    event.stopPropagation();
    this.resizingCorner = true;
    this.resizeOriginX = event.clientX;
    this.resizeOriginY = event.clientY;
    this.resizeOriginWidth = this.panelWidth();
    this.resizeOriginHeight = this.panelHeight();
  }

  startDrag(event: MouseEvent): void {
    event.preventDefault();
    event.stopPropagation();
    this.dragging = true;
    this.resizeOriginX = event.clientX;
    this.resizeOriginY = event.clientY;
    this.panelOriginLeft = this.panelLeft();
    this.panelOriginTop = this.panelTop();
  }

  onMouseMove(event: MouseEvent, detached: boolean): void {
    if (this.dragging) {
      this.updateDetachedPosition(event);
      return;
    }

    if (this.resizingCorner) {
      this.updateCornerResize(event, detached);
      return;
    }

    if (this.resizingLeft) {
      this.updateLeftResize(event, detached);
      return;
    }

    if (this.resizingRight) {
      this.updateRightResize(event, detached);
      return;
    }

    if (this.resizingTop) {
      this.updateTopResize(event, detached);
      return;
    }

    if (this.resizingBottom) {
      this.updateBottomResize(event, detached);
    }
  }

  onMouseUp(): void {
    const wasActive = this.isResizing || this.dragging;

    this.dragging = false;
    this.resizingTop = false;
    this.resizingBottom = false;
    this.resizingLeft = false;
    this.resizingRight = false;
    this.resizingCorner = false;

    if (wasActive)
      this.persistLayout();
  }

  syncBounds(detached: boolean): void {
    this.panelWidth.update((width) => this.clampWidth(width, detached));
    this.panelHeight.update((height) => this.clampHeight(height, detached));

    if (detached)
      this.clampDetachedPosition();
  }

  initializeDetachedPosition(): void {
    if (this.panelLeft() > 0 || this.panelTop() > 0) {
      this.clampDetachedPosition();
      return;
    }

    this.panelLeft.set(this.getMaxLeft(this.panelWidth()));
    this.panelTop.set(
      this.clamp(window.innerHeight - this.panelHeight() - 96, 16, this.getMaxTop(this.panelHeight()))
    );
  }

  private updateTopResize(event: MouseEvent, detached: boolean): void {
    const delta = this.resizeOriginY - event.clientY;
    const nextHeight = this.clampHeight(this.resizeOriginHeight + delta, detached);

    this.panelHeight.set(nextHeight);

    if (!detached)
      return;

    const originBottom = this.panelOriginTop + this.resizeOriginHeight;

    this.panelTop.set(this.clamp(originBottom - nextHeight, 16, this.getMaxTop(nextHeight)));
  }

  private updateBottomResize(event: MouseEvent, detached: boolean): void {
    const delta = event.clientY - this.resizeOriginY;

    this.panelHeight.set(this.clampHeight(this.resizeOriginHeight + delta, detached));
  }

  private updateLeftResize(event: MouseEvent, detached: boolean): void {
    const delta = this.resizeOriginX - event.clientX;
    const nextWidth = this.clampWidth(this.resizeOriginWidth + delta, detached);

    this.panelWidth.set(nextWidth);

    if (!detached)
      return;

    const originRight = this.panelOriginLeft + this.resizeOriginWidth;

    this.panelLeft.set(this.clamp(originRight - nextWidth, 16, this.getMaxLeft(nextWidth)));
  }

  private updateRightResize(event: MouseEvent, detached: boolean): void {
    const delta = event.clientX - this.resizeOriginX;

    this.panelWidth.set(this.clampWidth(this.resizeOriginWidth + delta, detached));
  }

  private updateCornerResize(event: MouseEvent, detached: boolean): void {
    const deltaX = event.clientX - this.resizeOriginX;
    const deltaY = event.clientY - this.resizeOriginY;

    this.panelWidth.set(this.clampWidth(this.resizeOriginWidth + deltaX, detached));
    this.panelHeight.set(this.clampHeight(this.resizeOriginHeight + deltaY, detached));
  }

  private updateDetachedPosition(event: MouseEvent): void {
    const nextLeft = this.panelOriginLeft + (event.clientX - this.resizeOriginX);
    const nextTop = this.panelOriginTop + (event.clientY - this.resizeOriginY);

    this.panelLeft.set(this.clamp(nextLeft, 16, this.getMaxLeft(this.panelWidth())));
    this.panelTop.set(this.clamp(nextTop, 16, this.getMaxTop(this.panelHeight())));
  }

  private clampHeight(height: number, detached?: boolean): number {
    const maxHeight = detached
      ? Math.max(MIN_HEIGHT, window.innerHeight - 32)
      : Math.floor(window.innerHeight * 0.75);

    return Math.min(Math.max(height, MIN_HEIGHT), maxHeight);
  }

  private clampWidth(width: number, _detached?: boolean): number {
    const maxWidth = Math.max(MIN_WIDTH, window.innerWidth - 32);
    const minWidth = Math.min(MIN_WIDTH, maxWidth);

    return Math.min(Math.max(width, minWidth), maxWidth);
  }

  private clampDetachedPosition(): void {
    this.panelLeft.set(this.clamp(this.panelLeft(), 16, this.getMaxLeft(this.panelWidth())));
    this.panelTop.set(this.clamp(this.panelTop(), 16, this.getMaxTop(this.panelHeight())));
  }

  private getMaxLeft(width: number): number {
    return Math.max(16, window.innerWidth - width - 16);
  }

  private getMaxTop(height: number): number {
    return Math.max(16, window.innerHeight - height - 16);
  }

  private clamp(value: number, min: number, max: number): number {
    return Math.min(Math.max(value, min), max);
  }

  private loadLayout(): void {
    try {
      const raw = localStorage.getItem(STORAGE_KEY);

      if (!raw)
        return;

      const parsed = JSON.parse(raw) as PersistedLayout;

      if (typeof parsed.height === 'number' && parsed.height >= MIN_HEIGHT)
        this.panelHeight.set(parsed.height);

      if (typeof parsed.width === 'number' && parsed.width >= MIN_WIDTH)
        this.panelWidth.set(parsed.width);
    } catch {
      // Ignore corrupted storage
    }
  }

  private persistLayout(): void {
    try {
      const layout: PersistedLayout = {
        height: this.panelHeight(),
        width: this.panelWidth()
      };

      localStorage.setItem(STORAGE_KEY, JSON.stringify(layout));
    } catch {
      // Ignore storage failures
    }
  }
}
