import { CommonModule } from '@angular/common'
import {
	AfterViewInit,
	Component,
	Directive,
	ElementRef,
	NgModule,
	TemplateRef,
	inject,
	viewChild,
	contentChildren,
	input,
} from '@angular/core'
import { TinyGesture } from '../utils'

type SwipeDirection = 'Left' | 'Right'

@Directive({
	selector: '[kadiSwipeAction]',
	standalone: false,
})
export class SwipeActionDirective {
	templateRef = inject<TemplateRef<HTMLElement>>(TemplateRef)

	public readonly kadiSwipeAction = input<SwipeDirection>('Right')
}

/**
 * Adds swipe actions to an element
 *
 * @usageNotes
 * ```html
 * <kadi-swipe-actions>
 *   <ng-template kadiSwipeAction="Left">
 *     <button>Left Action</button>
 *   </ng-template>
 *   <div>Content</div>
 *   <ng-template kadiSwipeAction="Right">
 *     <button>Right Action</button>
 *   </ng-template>
 * </kadi-swipe-actions>
 * ```
 */
@Component({
	selector: 'kadi-swipe-actions',
	template: `
		<div class="relative bg-white">
			@if (leftActionsTemplate) {
				<div
					#leftTemplate
					class="reveal-left">
					<ng-container [ngTemplateOutlet]="leftActionsTemplate" />
				</div>
			}
			<div
				#swipeItem
				class="swipe-item">
				<ng-content />
			</div>
			@if (rightActionsTemplate) {
				<div
					#rightTemplate
					class="reveal-right">
					<ng-container [ngTemplateOutlet]="rightActionsTemplate" />
				</div>
			}
		</div>
	`,
	styles: [
		`
			.reveal-left,
			.reveal-right {
				position: absolute;
				top: 0;
				z-index: 1;
			}
			.reveal-left {
				text-align: left;
				left: 0;
			}
			.reveal-right {
				right: 0;
				text-align: right;
			}
			.swipe-item {
				position: relative;
				width: 100%;
				display: flex;
				flex-wrap: nowrap;
				background-color: white;
				z-index: 2;
				user-select: none;
				transition: all;
			}
		`,
	],
	standalone: false,
})
export class SwipeActionsComponent implements AfterViewInit {
	readonly swipeItemRef = viewChild.required<ElementRef<HTMLElement>>('swipeItem')
	readonly leftTemplateRef = viewChild<ElementRef<HTMLElement>>('leftTemplate')
	readonly rightTemplateRef = viewChild<ElementRef<HTMLElement>>('rightTemplate')

	readonly actionTemplates = contentChildren(SwipeActionDirective)

	leftActionsTemplate: TemplateRef<HTMLElement> | undefined
	rightActionsTemplate: TemplateRef<HTMLElement> | undefined

	decelerationOnOverflow = 4
	leftRevealWidth = 50
	rightRevealWidth = 50

	ngAfterViewInit(): void {
		const left = this.actionTemplates().find((x) => x.kadiSwipeAction() === 'Left')
		if (left) this.leftActionsTemplate = left.templateRef

		const right = this.actionTemplates().find((x) => x.kadiSwipeAction() === 'Right')
		if (right) this.rightActionsTemplate = right.templateRef

		this.initSlider(this.swipeItemRef().nativeElement)
	}

	private initSlider(target: HTMLElement) {
		let animationFrame: number | null
		let swiped = false
		let swipeOffset = 0
		let startOffset = 0

		const gesture = new TinyGesture(target)

		gesture.on('panmove', (event) => {
			if (animationFrame) return
			event.preventDefault()

			const leftTemplateRef = this.leftTemplateRef()
			if (leftTemplateRef) {
				this.leftRevealWidth = (leftTemplateRef.nativeElement as HTMLElement).clientWidth
			}

			const rightTemplateRef = this.rightTemplateRef()
			if (rightTemplateRef) {
				this.rightRevealWidth = (rightTemplateRef.nativeElement as HTMLElement).clientWidth
			}

			animationFrame = window.requestAnimationFrame(() => {
				const newX = this.getX(startOffset + (gesture.touchMoveX ?? 0))
				const direction: SwipeDirection = newX > 0 ? 'Left' : 'Right'

				if (!this.leftActionsTemplate && direction === 'Left') return
				if (!this.rightActionsTemplate && direction === 'Right') return

				const revealWidth = direction === 'Left' ? this.leftRevealWidth : this.rightRevealWidth

				target.style.transform = 'translateX(' + newX + 'px)'
				if (newX >= revealWidth || newX <= -revealWidth) {
					swipeOffset = newX < 0 ? -revealWidth : revealWidth
					swiped = true
				} else {
					swipeOffset = 0
					swiped = false
				}

				window.requestAnimationFrame(() => {
					target.style.transition = 'translateX(0)'
				})
				animationFrame = null
			})
		})

		gesture.on('panend', () => {
			if (animationFrame) window.cancelAnimationFrame(animationFrame)
			animationFrame = null

			window.requestAnimationFrame(() => {
				target.style.transition = 'transform .2s ease-in'
				if (swiped) {
					startOffset = swipeOffset
					target.style.transform = 'translateX(' + swiped + 'px)'
				} else {
					startOffset = 0
					target.style.transform = 'translateX(0)'
				}
			})
		})
	}

	private getX(x: number): number {
		if (x < this.leftRevealWidth && x > -this.leftRevealWidth) {
			return x
		}
		if (x < -this.leftRevealWidth) {
			return (x + this.leftRevealWidth) / this.decelerationOnOverflow - this.leftRevealWidth
		}
		if (x > this.leftRevealWidth) {
			return (x - this.leftRevealWidth) / this.decelerationOnOverflow + this.leftRevealWidth
		}
		return 0
	}
}

@NgModule({
	declarations: [SwipeActionDirective, SwipeActionsComponent],
	imports: [CommonModule],
	exports: [SwipeActionDirective, SwipeActionsComponent],
})
export class SwipeActionsModule {}
