import { _decorator, Component, instantiate, Node, NodePool, tween, Tween, UIOpacity, Widget } from 'cc';
import { BasePopUp } from '../../../A-FRAME/component/ui.pop-up';
import { EPopup, GetPopPrefab, GetPopComponent, PopIns, PopParams, PopReturn } from '../pop-up/popup.config';
const { ccclass, property } = _decorator;

@ccclass('Popup')
export class Popup extends Component {
	private static _ins: Popup;
	/** 全局唯一安全实例 */
	public static get ins() {
		return Popup._ins;
	}

	private hide_map: Map<EPopup, NodePool>;
	private shade_list: UIOpacity[];

	private opacity: number = 200;
	private duration: number = 0.25;
	private get dur_half() {
		return this.duration * 0.5;
	}

	protected onLoad() {
		Popup._ins = this;

		this.init();
	}

	private init() {
		this.node.active = false;
		this.hide_map = new Map();
		this.shade_list = this.node.children.filter(n => n.name === '___shade').map(n => n.getComponent(UIOpacity));
		this.shade_list.forEach(shade => {
			shade.node.active = true;
			shade.opacity = 0;
		});
	}

	private async activePop<P extends EPopup>(p: P): Promise<PopIns<P>> {
		let node: Node;
		if (this.hide_map.has(p) && this.hide_map.get(p).size() > 0) node = this.hide_map.get(p).get();
		else node = instantiate(await GetPopPrefab(p));

		node.active = false;
		node.setParent(this.node);

		//@ts-ignore
		return node.getComponent(await GetPopComponent(p));
	}

	private __shade_ref_: number = 0;
	private addShade() {
		let duration = this.duration * 1;
		this.__shade_ref_++;

		let slist = this.shade_list.map(shade => {
			Tween.stopAllByTarget(shade);
			let wgt = shade.getComponent(Widget);
			if (wgt) wgt.updateAlignment();
			return { shade, z: shade.node.getSiblingIndex() };
		}).sort((a, b) => b.z - a.z);

		let tw_list: Tween<UIOpacity>[] = [];

		if (this.__shade_ref_ === 1) {
			slist.forEach(el => el.shade.opacity = 0);
			tw_list.push(tween(slist[1].shade).to(duration, { opacity: this.opacity }));
		} else {
			let fixed_z = this.__shade_ref_ - 1;
			let fixed_i = slist.findIndex(el => el.z === fixed_z);
			let fixed_shade: UIOpacity;
			if (fixed_i >= 0) fixed_shade = slist.splice(fixed_i, 1)[0].shade;
			else {
				fixed_shade = slist.pop().shade;
				fixed_shade.node.setSiblingIndex(fixed_z);
			}

			slist[0].shade.node.setSiblingIndex(this.__shade_ref_);
			slist[0].shade.opacity = 0;
			fixed_shade.opacity = this.opacity;

			tw_list.push(tween(fixed_shade).to(duration, { opacity: 0 }, {
				progress(start, end, current, ratio) {
					let total = start - end;
					return 255 * total * (1 - ratio) / (255 - total * ratio);
				}
			}));
			tw_list.push(tween(slist[0].shade).to(duration, { opacity: this.opacity }))
		}

		tw_list.forEach(tw => tw.start());
	}

	private cutShade() {
		let duration = this.duration * 1;
		this.__shade_ref_--;

		let slist = this.shade_list.map(shade => {
			Tween.stopAllByTarget(shade);
			return { shade, z: shade.node.getSiblingIndex() };
		}).sort((a, b) => b.z - a.z);

		let tw_list: Tween<UIOpacity>[] = [];

		if (this.__shade_ref_ === 0) tw_list.push(tween(slist[1].shade).to(duration, { opacity: 0 }));
		else {
			let fixed_z = this.__shade_ref_ - 1;
			let fixed_i = slist.findIndex(el => el.z === fixed_z);
			let fixed_shade: UIOpacity;
			if (fixed_i >= 0) fixed_shade = slist.splice(fixed_i, 1)[0].shade;
			else {
				fixed_shade = slist.shift().shade;
				fixed_shade.node.setSiblingIndex(fixed_z);
			}
			fixed_shade.opacity = 0;
			slist[0].shade.opacity = this.opacity;
			slist[0].shade.node.setSiblingIndex(this.__shade_ref_ + 1);

			tw_list.push(tween(fixed_shade).to(duration, { opacity: this.opacity }, {
				progress(start, end, curr, ratio) {
					let total = end - start;
					return 255 * total * ratio / (255 - total * (1 - ratio));
				}
			}));
			tw_list.push(
				tween(slist[0].shade).to(duration, { opacity: 0 })
					.call(() => slist[0].shade.node.setSiblingIndex(this.__shade_ref_ > 1 ? this.__shade_ref_ - 2 : 0))
			);
		}

		tw_list.forEach(tw => tw.start());
	}

	private on_going: EPopup[] = [];

	private async show<P extends EPopup>(p: P, ...args: PopParams<P>): Promise<PopReturn<P>> {
		if (this.on_going.includes(p)) return void 0;
		else this.on_going.push(p);

		if (!this.node.active) this.node.active = true;
		this.addShade();

		let pop = await this.activePop(p);
		mtec.log.tag(`SHOW-POP: chartreuse; ${pop.comp_name}: lightyellow;`);

		let result: any;
		if (pop) {
			result = await pop.show(Popup, ...args);
			Popup.ins.close(p, pop);
		} else result = undefined;

		return result;
	}

	private close<P extends EPopup, C extends BasePopUp<typeof Popup, unknown>>(p: P, comp: C) {
		if (this.on_going.includes(p)) mtec.array.remove(this.on_going, p);

		if (this.node.children.includes(comp.node)) {
			mtec.log.tag(`CLOSE-POP: crimson; ${comp.comp_name}: lightyellow;`);
			this.cutShade();

			comp.node.active = false;
			if (!this.hide_map.has(p)) this.hide_map.set(p, new NodePool());
			this.hide_map.get(p).put(comp.node);
		}

		if (this.__shade_ref_ <= 0 && !(this.on_going.length > 0)) this.node.active = false;
	}

	private static __lock__: boolean = false;
	public static get lock() {
		return Popup.__lock__;
	}
	public static set lock(v: boolean) {
		Popup.__lock__ = v;
	}

	public static get hasPop() {
		return Popup.ins.node.children.length > 2;
	}

	public static async Settingpage(...args: PopParams<EPopup.SETTINGPAGE>) {
		return Popup.ins.show(EPopup.SETTINGPAGE, ...args);
	}

	public static async Gameoverpage(...args: PopParams<EPopup.GAMEOVERPAGE>) {
		return Popup.ins.show(EPopup.GAMEOVERPAGE, ...args);
	}
}

