import {
	Directive,
	ElementRef,
	HostBinding,
	Input,
	OnChanges,
	OnDestroy,
	Renderer2,
	SimpleChanges,
	inject,
	output,
	input,
} from '@angular/core'
import { AnyType } from '@core/types'
import { ResizeElementDirection, ResizeElementEvent, ResizeElementPosition } from './types'

@Directive({
	standalone: true,
	// eslint-disable-next-line @angular-eslint/directive-selector
	selector: '[resize], [resizeStart], [resizeEnd]',
})
export class ResizeElementDirective implements OnChanges, OnDestroy {
	private readonly elementRef = inject(ElementRef)
	private readonly renderer2 = inject(Renderer2)

	private mouseClickListener!: () => void
	private mouseUpListener!: () => void
	private mouseMoveListener!: () => void

	private targetElementWidthValue!: number
	private targetElementHeightValue!: number

	private targetElementTopValue!: number
	private targetElementLeftValue!: number

	private originalEvent!: MouseEvent

	public readonly targetElement = input.required<HTMLElement | ElementRef>()
	public readonly direction = input.required<ResizeElementDirection>()
	public readonly proportionalResize = input<boolean>()
	public readonly rect = input<ResizeElementPosition>()
	public readonly applyClass = input('resizing')

	public readonly resizeStart = output<ResizeElementEvent>()
	// eslint-disable-next-line @angular-eslint/no-output-native
	public readonly resize = output<ResizeElementEvent>()
	public readonly resizeEnd = output<ResizeElementEvent>()

	// TODO: Skipped for migration because:
	//  This input is used in combination with `@HostBinding` and migrating would
	//  break.
	@Input() @HostBinding('attr.draggable') public useDrag: AnyType

	constructor() {
		this.listenMouseDownEvent()
	}

	public ngOnChanges(changes: SimpleChanges) {
		if (changes['useDrag']) this.listenMouseDownEvent()
	}

	public ngOnDestroy() {
		if (this.mouseClickListener) this.mouseClickListener()
		if (this.mouseUpListener) this.mouseUpListener()
		if (this.mouseMoveListener) this.mouseMoveListener()
	}

	public listenMouseDownEvent(): void {
		if (this.mouseClickListener) this.mouseClickListener()

		const event = this.useDrag ? 'dragstart' : 'mousedown'
		this.mouseClickListener = this.renderer2.listen(this.elementRef.nativeElement, event, (evt) =>
			this.onMouseDown(evt),
		)
	}

	public onMouseDown(evt: MouseEvent): void {
		evt.preventDefault()

		this.setOriginalData(evt)

		this.resizeStart.emit(this.generateValuesForEvent(evt))

		this.mouseUpListener = this.renderer2.listen('document', 'mouseup', (event) => this.onMouseUp(event))
		this.mouseMoveListener = this.renderer2.listen('document', 'mousemove', (event) => this.onMouseMove(event))
		this.renderer2.addClass(this.targetNativeElement, this.applyClass())
	}

	private onMouseUp(evt: MouseEvent): void {
		const eventValues = this.generateValuesForEvent(evt)
		this.resize.emit(eventValues)
		this.mouseMoveListener()
		this.mouseUpListener()

		this.renderer2.removeClass(this.targetNativeElement, this.applyClass())
		this.resizeEnd.emit(eventValues)
	}

	private onMouseMove(evt: MouseEvent): void {
		this.resize.emit(this.generateValuesForEvent(evt))
	}

	private setOriginalData(originalEvent: MouseEvent) {
		this.originalEvent = originalEvent

		if (this.targetElement()) {
			const dataSource = this.targetNativeElement
			this.targetElementWidthValue = dataSource.offsetWidth
			this.targetElementHeightValue = dataSource.offsetHeight
			this.targetElementTopValue = dataSource.offsetTop
			this.targetElementLeftValue = dataSource.offsetLeft
		} else {
			this.targetElementWidthValue = 0
			this.targetElementHeightValue = 0
			this.targetElementTopValue = 0
			this.targetElementLeftValue = 0
		}
	}

	private get targetNativeElement(): HTMLElement {
		const targetElement = this.targetElement()
		return targetElement instanceof ElementRef ? targetElement.nativeElement : targetElement
	}

	private generateValuesForEvent(evt: MouseEvent): ResizeElementEvent {
		const originalXValue = this.originalEvent.clientX
		const originalYValue = this.originalEvent.clientY

		let diffWidthValue = evt.clientX - originalXValue
		let diffHeightValue = evt.clientY - originalYValue
		let diffTopValue = diffHeightValue
		let diffLeftValue = diffWidthValue

		const direction = this.direction()
		switch (direction) {
			case ResizeElementDirection.TOP: {
				diffHeightValue *= -1
				diffWidthValue = 0
				diffLeftValue = 0
				break
			}
			case ResizeElementDirection.TOP_RIGHT: {
				diffHeightValue *= -1
				diffLeftValue = 0
				break
			}
			case ResizeElementDirection.RIGHT: {
				diffHeightValue = 0
				diffTopValue = 0
				diffLeftValue = 0
				break
			}
			case ResizeElementDirection.BOTTOM_RIGHT: {
				diffTopValue = 0
				diffLeftValue = 0
				break
			}
			case ResizeElementDirection.BOTTOM: {
				diffWidthValue = 0
				diffLeftValue = 0
				diffTopValue = 0
				break
			}
			case ResizeElementDirection.BOTTOM_LEFT: {
				diffWidthValue *= -1
				diffTopValue = 0
				break
			}
			case ResizeElementDirection.LEFT: {
				diffWidthValue *= -1
				diffHeightValue = 0
				diffTopValue = 0
				break
			}
			case ResizeElementDirection.TOP_LEFT: {
				diffHeightValue *= -1
				diffWidthValue *= -1
			}
		}

		let currentWidthValue = this.targetElementWidthValue + diffWidthValue
		let currentHeightValue = this.targetElementHeightValue + diffHeightValue

		if (this.proportionalResize()) {
			if (currentWidthValue > currentHeightValue) {
				currentWidthValue = currentHeightValue
			} else {
				currentHeightValue = currentWidthValue
			}
		}

		if (currentHeightValue <= 1) diffTopValue += currentHeightValue
		if (currentWidthValue <= 1) diffLeftValue += currentWidthValue
		if (currentWidthValue <= 0) currentWidthValue = 0
		if (currentHeightValue <= 0) currentHeightValue = 0

		let currentTopValue = this.targetElementTopValue + diffTopValue
		let currentLeftValue = this.targetElementLeftValue + diffLeftValue

		const rect = this.rect()
		if (rect) {
			if (currentTopValue < rect.top) {
				currentHeightValue = this.targetElementHeightValue + this.targetElementTopValue - rect.top
				currentTopValue = rect.top
			}
			if (currentHeightValue + currentTopValue > rect.height) {
				currentHeightValue = rect.height - currentTopValue
			}

			if (currentLeftValue < rect.left) {
				currentWidthValue = this.targetElementWidthValue + this.targetElementLeftValue - rect.left
				currentLeftValue = rect.left
			}
			if (currentWidthValue + currentLeftValue > rect.width) {
				currentWidthValue = rect.width - currentLeftValue
			}
		}

		return {
			originalEvent: this.originalEvent,
			currentWidthValue,
			currentHeightValue,
			currentTopValue,
			currentLeftValue,
			originalWidthValue: this.targetElementWidthValue,
			originalHeightValue: this.targetElementHeightValue,
			originalTopValue: this.targetElementTopValue,
			originalLeftValue: this.targetElementLeftValue,
			differenceWidthValue: currentWidthValue - this.targetElementWidthValue,
			differenceHeightValue: currentHeightValue - this.targetElementHeightValue,
			differenceTopValue: currentTopValue - this.targetElementTopValue,
			differenceLeftValue: currentLeftValue - this.targetElementLeftValue,
			direction: direction,
			target: this.targetElement(),
		}
	}
}
