import { _decorator, CCFloat, CCInteger, Component, instantiate, Node, NodePool, ScrollView, UITransform } from 'cc';
const { ccclass, property } = _decorator;

/**
 * 动态列表（无限列表）
 */
@ccclass('ScrollList')
export class ScrollList extends Component {
	@property(ScrollView)
	private view: ScrollView = null;
	@property(CCInteger)
	private over_item_count: number = 2;
	@property(CCInteger)
	private space_vertical: number = 0;

	private ui_view: UITransform;
	private ui_content: UITransform;
	private render_record: Map<Node, {key: number|string, timestamp: number, index: number}> = new Map();

	private content_top: number;
	private item_height: number;

	private get item_list(){
		return [...this.ui_content.node.children].sort((a, b)=>b.position.y-a.position.y);
	}

	protected onLoad(){
		this.ui_view = this.view.content.parent.getComponent(UITransform);
		this.ui_content = this.view.content.getComponent(UITransform);
		this.content_top = this.ui_content.height * (1-this.ui_content.anchorY);

		this.view.scrollEvents.push(mtec.cc.creatEventHandle({
			target: this.view.node,
			component: 'ScrollList',
			handler: 'onScroll',
		}));
	}

    start() {
		this.item_height = this.ui_content.node.children[0].getComponent(UITransform).height;
		this.ui_content.node.children.forEach((item, index)=>this.moveItemByIndex(item, index));

		if(this.data_list){
			this.fixItemCount(this.data_list.length);
			this.ui_content.height = this.data_list.length * this.item_height + (this.data_list.length-1) * this.space_vertical;
		}
    }

	private onScroll(view: ScrollView){
		let y = view.getScrollOffset().y;

		let start_index = Math.ceil(y / (this.item_height + this.space_vertical));
		start_index = Math.max(0, start_index - this.over_item_count);
		let end_index = start_index + this.ui_content.node.children.length - 1;
		end_index = Math.min(this.data_list.length-1, end_index);

		let item_map = this.ui_content.node.children.map(node=>{
			let index = (this.content_top - node.position.y - 0.5 * this.item_height) / (this.item_height + this.space_vertical);
			return {node, index};
		}).sort((a, b)=>a.index-b.index);

		item_map
		.filter(el=>el.index < start_index)
		.forEach((el, i, ls)=>{
			let index = start_index + this.ui_content.node.children.length - ls.length + i;
			this.moveItemByIndex(el.node, index);
		});

		item_map
		.filter(el=>el.index > end_index)
		.forEach((el, i)=>{
			let index = end_index - this.ui_content.node.children.length + i + 1;
			this.moveItemByIndex(el.node, index);
		});

		item_map
		.filter(el=>el.index >= start_index && el.index <= end_index)
		.forEach((el, i)=>{
			this.moveItemByIndex(el.node, el.index);
		});
	}

	private item_pool = new NodePool();
	private fixItemCount(count: number){
		let ui_demo = this.ui_content.node.children[0].getComponent(UITransform);
		let full_count = Math.ceil(this.ui_view.height / (ui_demo.height + this.space_vertical));
		if(count>full_count) count = full_count + this.over_item_count;

		let diff = count - this.ui_content.node.children.length;
		if(diff > 0){
			for(let i=0; i<diff; i++) (this.item_pool.get() ?? instantiate(ui_demo.node)).setParent(this.ui_content.node);
		}else if(diff < 0){
			this.ui_content.node.children.slice(diff).forEach(el=>this.item_pool.put(el));
		}
	}

	private moveItemByIndex(item: Node, index: number){
		if(!this.data_list){
			item.active = false;
			return void 0;
		}

		let data = this.data_list[index];

		if(!data){
			item.active = false;
			return void 0;
		}else{
			item.active = true;
		}

		let record = this.render_record.get(item);

		if(!record){
			record = {index: -1, key: undefined, timestamp: -1};
			this.render_record.set(item, record);
		}

		if(record.index==index && record.key==data[this.data_key]){
			if((Date.now() - record.timestamp) <= 1000){
				return void 0;
			}
		}

		record.key = data[this.data_key];
		record.index = index;
		record.timestamp = Date.now();
		item.name = String(record.key);

		let y = this.content_top - (index+0.5) * this.item_height - this.space_vertical * index;
		item.setPosition(item.position.x, y, item.position.z);

		this.render_item_call_list.forEach(render=>{
			render(item, this.data_list[index], this.render_item_node_cache);
		});
	}

	private data_list: any[];
	private data_key: string;
	public setDataList<el>(list: el[], key: keyof el){
		this.data_list = list;
		this.data_key = key as string;
		this.render_record.forEach(el=>{
			el.index = -1;
			el.key = undefined;
			el.timestamp = -1;
		});
		this.fixItemCount(list.length);
		this.ui_content.height = list.length * this.item_height + (list.length-1) * this.space_vertical;
	}

	private render_item_node_cache: Map<Node, any> = new Map();
	private render_item_call_list: Array<(node: Node, data: any, node_cache: Map<Node, any>)=>any> = [];
	public onRenderItem(render_call: (node: Node, data: any, node_cache: Map<Node, any>)=>any){
		this.render_item_call_list.push(render_call);
	}

	public get NodeCache(){
		return this.render_item_node_cache;
	}
}

