import {
  Component,
  HostListener,
  computed,
  inject,
  input,
  output,
  signal
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProfileAvatarFacade } from '../../application/services/profile-avatar.facade';
import {
  EditableProfileAvatarSource,
  ProcessedProfileAvatar,
  ProfileAvatarTransform,
  PROFILE_AVATAR_EDITOR_FRAME_SIZE,
  clampProfileAvatarTransform,
  resolveProfileAvatarBaseScale
} from '../../domain/profile-avatar.models';

@Component({
  selector: 'app-profile-avatar-editor',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './profile-avatar-editor.component.html'
})
export class ProfileAvatarEditorComponent {
  readonly source = input.required<EditableProfileAvatarSource>();

  readonly cancelled = output<undefined>();
  readonly confirmed = output<ProcessedProfileAvatar>();

  readonly frameSize = PROFILE_AVATAR_EDITOR_FRAME_SIZE;
  readonly processing = signal(false);
  readonly errorMessage = signal<string | null>(null);
  readonly preservesAnimation = computed(() => this.source().preservesAnimation);
  readonly transform = signal<ProfileAvatarTransform>({ zoom: 1,
    offsetX: 0,
    offsetY: 0 });
  readonly clampedTransform = computed(() => clampProfileAvatarTransform(this.source(), this.transform()));
  readonly imageTransform = computed(() => {
    const source = this.source();
    const transform = this.clampedTransform();
    const scale = resolveProfileAvatarBaseScale(source, this.frameSize) * transform.zoom;

    return `translate(-50%, -50%) translate(${transform.offsetX}px, ${transform.offsetY}px) scale(${scale})`;
  });

  private readonly avatar = inject(ProfileAvatarFacade);
  private dragPointerId: number | null = null;
  private dragOrigin: { x: number; y: number; offsetX: number; offsetY: number } | null = null;

  @HostListener('document:keydown.escape')
  onEscape(): void {
    if (!this.processing()) {
      this.cancelled.emit(undefined);
    }
  }

  onZoomChange(value: string): void {
    if (this.preservesAnimation()) {
      return;
    }

    const zoom = Number(value);

    this.transform.update((current) => ({
      ...current,
      zoom
    }));
  }

  onZoomInput(event: Event): void {
    this.onZoomChange((event.target as HTMLInputElement).value);
  }

  zoomBy(delta: number): void {
    if (this.preservesAnimation()) {
      return;
    }

    this.transform.update((current) => ({
      ...current,
      zoom: current.zoom + delta
    }));
  }

  onWheel(event: WheelEvent): void {
    if (this.preservesAnimation()) {
      return;
    }

    event.preventDefault();
    this.zoomBy(event.deltaY < 0 ? 0.08 : -0.08);
  }

  onPointerDown(event: PointerEvent): void {
    if (this.processing() || this.preservesAnimation()) {
      return;
    }

    const currentTarget = event.currentTarget as HTMLElement | null;
    const currentTransform = this.clampedTransform();

    currentTarget?.setPointerCapture(event.pointerId);
    this.dragPointerId = event.pointerId;
    this.dragOrigin = {
      x: event.clientX,
      y: event.clientY,
      offsetX: currentTransform.offsetX,
      offsetY: currentTransform.offsetY
    };
  }

  onPointerMove(event: PointerEvent): void {
    if (this.dragPointerId !== event.pointerId || !this.dragOrigin || this.processing() || this.preservesAnimation()) {
      return;
    }

    this.transform.set(clampProfileAvatarTransform(this.source(), {
      zoom: this.clampedTransform().zoom,
      offsetX: this.dragOrigin.offsetX + (event.clientX - this.dragOrigin.x),
      offsetY: this.dragOrigin.offsetY + (event.clientY - this.dragOrigin.y)
    }));
  }

  onPointerUp(event: PointerEvent): void {
    if (this.dragPointerId !== event.pointerId) {
      return;
    }

    const currentTarget = event.currentTarget as HTMLElement | null;

    currentTarget?.releasePointerCapture(event.pointerId);
    this.dragPointerId = null;
    this.dragOrigin = null;
  }

  async confirm(): Promise<void> {
    if (this.processing()) {
      return;
    }

    this.processing.set(true);
    this.errorMessage.set(null);

    try {
      const avatar = await this.avatar.processEditableSource(this.source(), this.clampedTransform());

      this.confirmed.emit(avatar);
    } catch {
      this.errorMessage.set('Failed to process profile image.');
    } finally {
      this.processing.set(false);
    }
  }
}
