import type { VNode } from 'vue';
import type { _ButtonArgsBase, ButtonLayout, ButtonSize } from './button-layout';
import { Prop, toNative, Watch } from 'vue-facing-decorator';
import TsxComponent, { Component } from '../../app/vuetsx';
import { LaddaLite } from '../../common/ladda-lite';
import Button from './button';

interface LaddaButtonArgs extends _ButtonArgsBase {
	clicked: (e: any) => Promise<any>;
	loading?: boolean;
}

@Component
class LaddaButtonComponent extends TsxComponent<LaddaButtonArgs> implements LaddaButtonArgs {
	@Prop() cssClass!: string;
	@Prop() layout!: ButtonLayout;
	@Prop() text!: string | VNode;
	@Prop() tooltip!: string;
	@Prop() size!: ButtonSize;
	@Prop() round!: boolean;
	@Prop() outlined!: boolean;
	@Prop() icon!: string;
	@Prop() iconOnRight!: boolean;
	@Prop() disabled!: boolean;
	@Prop() pulsate!: boolean;
	@Prop() loading!: boolean;
	@Prop() clicked: (e: any) => Promise<any>;

	// Whether the spinner should currently be showing. Tracked so `updated()` can
	// restore the ladda DOM after a Vue re-render strips it (Ladda mutates this
	// Vue-owned <button> out-of-band, so any patch can wipe its classes/wrappers).
	private _spinning: boolean = false;

	@Watch('loading')
	onLoadingChanged(val: boolean) {
		if (val) {
			this.spin(this.$el as HTMLElement);
		} else {
			this.unspin(this.$el as HTMLElement);
		}
	}

	mounted() {
		if (this.loading) {
			this.spin(this.$el as HTMLElement);
		}
	}

	updated() {
		// Vue just patched this button. If we're mid-spin and the patch stripped
		// ladda's class or spinner wrapper, re-assert it (ensureLadda is idempotent,
		// so this is a no-op when nothing was lost).
		if (!this._spinning) {
			return;
		}

		const el = this.$el as HTMLElement;
		if (!el.classList.contains('ladda-button') || el.querySelector('.ladda-spinner') == null) {
			LaddaLite.showSpin(el);
		}
	}

	private spin(target: HTMLElement) {
		this._spinning = true;
		LaddaLite.showSpin(target);
	}

	private unspin(target: HTMLElement) {
		this._spinning = false;
		LaddaLite.hideSpin(target);
	}

	handleButtonClicked(e) {
		let targetElem = e.target as HTMLElement;
		if (targetElem.nodeName.toLowerCase() != 'button') {
			targetElem = targetElem.closest('button') as HTMLElement;
		}

		this.spin(targetElem);

		this.clicked(e)
			.then(() => {
				this.unspin(targetElem);
			})
			.catch(() => {
				this.unspin(targetElem);
			});
	}

	render(_h) {
		return (
			<Button
				clicked={(e) => {
					this.handleButtonClicked(e);
				}}
				cssClass={this.cssClass}
				layout={this.layout}
				text={this.text}
				size={this.size}
				round={this.round}
				outlined={this.outlined}
				icon={this.icon}
				iconOnRight={this.iconOnRight}
				tooltip={this.tooltip}
				disabled={this.disabled}
				pulsate={this.pulsate}
			/>
		);
	}
}

const LaddaButton = toNative(LaddaButtonComponent);
export default LaddaButton;
