import { DOCUMENT } from '@angular/common';
import { Component, EventEmitter, Inject, OnInit, Output, Input, OnChanges, ViewChild } from '@angular/core';
import { BaseComponent } from '@common/base/base.component';
import { interval, Subject, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators';
import { type } from 'os';
import { SimpleChanges } from '@angular/core';
import { ToCEntry, ToCEntryType } from '../dataset-description.component';
import { FormArray, FormGroup } from '@angular/forms';
import { VisibilityRulesService } from '../visibility-rules/visibility-rules.service';
import { Rule } from '@app/core/model/dataset-profile-definition/rule';
import { TableOfContentsInternal } from './table-of-contents-internal/table-of-contents-internal';

export interface Link {
	/* id of the section*/
	id: string;
	/* header type h3/h4 */
	type: string;
	/* If the anchor is in view of the page */
	active: boolean;
	/* name of the anchor */
	name: string;
	/* top offset px of the anchor */
	top: number;
	page: number;
	section: number;
	show: boolean;
	selected: boolean;
}

@Component({
	selector: 'table-of-contents',
	styleUrls: ['./table-of-contents.scss'],
	templateUrl: './table-of-contents.html'
})
export class TableOfContents extends BaseComponent implements OnInit, OnChanges {

	@ViewChild('internalTable') internalTable: TableOfContentsInternal;


	@Input() links: Link[];
	container: string;
	headerSelectors = '.toc-page-header, .toc-section-header, .toc-compositeField-header';
	@Output() stepFound = new EventEmitter<LinkToScroll>();
	@Output() currentLinks = new EventEmitter<Link[]>();
	@Output() entrySelected = new EventEmitter<any>();
	subscription: Subscription;
	linksSubject: Subject<HTMLElement[]> = new Subject<HTMLElement[]>();

	@Input() isActive: boolean;

	tocentries: ToCEntry[] = null;
	@Input() TOCENTRY_ID_PREFIX = '';
	// visibilityRules:Rule[] = [];
	@Input() visibilityRules:Rule[] = [];
	@Input() showErrors: boolean = false;
	@Input() selectedFieldsetId:string;

	private _tocentrySelected:ToCEntry = null;
	private _intersectionObserver: IntersectionObserver;
	private _actOnObservation: boolean = true;
	public hiddenEntries:string[] = [];
	get tocentrySelected(){
		return this.hasFocus?this._tocentrySelected: null;
	}
	set tocentrySelected(value){
		this._tocentrySelected = value;
	}

	@Input() formGroup: FormGroup;
	@Input() hasFocus: boolean = false;
	@Input() visibilityRulesService: VisibilityRulesService;
	show: boolean = false;

	constructor(
		@Inject(DOCUMENT) private _document: Document,
		// public visibilityRulesService: VisibilityRulesService
		) {
		super();
	}


	ngOnInit(): void {

		// this.visibilityRulesService.visibilityChange
		// .pipe(takeUntil(this._destroyed))
		// .pipe(debounceTime(200))
		// .subscribe(_=>{
		// 	if(this.hasFocus){
		// 		this._resetObserver();
		// 		this.hiddenEntries = this._findHiddenEntries(this.tocentries);
		// 	}
		// })

		if(this.formGroup){
			this.tocentries = this.getTocEntries(this.formGroup.get('datasetProfileDefinition'));
			const fg = this.formGroup.get('datasetProfileDefinition');
			// this.visibilityRulesService.buildVisibilityRules(this.visibilityRules, fg);
			if(this.visibilityRulesService){
				this.hiddenEntries = this._findHiddenEntries(this.tocentries);
			}

		}else{

			//emit value every 500ms
			const source = interval(500);
			this.subscription = source.subscribe(val => {
				const headers = Array.from(this._document.querySelectorAll(this.headerSelectors)) as HTMLElement[];
				this.linksSubject.next(headers);
			});

			if (!this.links || this.links.length === 0) {
				this.linksSubject.asObservable()
					.pipe(distinctUntilChanged((p: HTMLElement[], q: HTMLElement[]) => JSON.stringify(p) == JSON.stringify(q)))
					.subscribe(headers => {
						const links: Array<Link> = [];

						if (headers.length) {
							let page;
							let section;
							let show
							for (const header of headers) {
								let name;
								let id;
								if (header.classList.contains('toc-page-header')) {  // deprecated after removing stepper
									name = header.innerText.trim().replace(/^link/, '');
									id = header.id;
									page = header.id.split('_')[1];
									section = undefined;
									show = true;
								} else if (header.classList.contains('toc-section-header')) {
									name = header.childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].nodeValue.trim().replace(/^link/, '');
									id = header.id;
									page = header.id.split('.')[1];
									section = header.id;
									if (header.id.split('.')[4]) { show = false; }
									else { show = true; }
								} else if (header.classList.contains('toc-compositeField-header')) {
									name = (header.childNodes[0]).nodeValue.trim().replace(/^link/, '');
									id = header.id;
									// id = header.parentElement.parentElement.parentElement.id;
									show = false;
								}
								const { top } = header.getBoundingClientRect();
								links.push({
									name,
									id,
									type: header.tagName.toLowerCase(),
									top: top,
									active: false,
									page: page,
									section: section,
									show: show,
									selected: false
								});
							}
						}
						this.links = links;
						// Initialize selected for button next on dataset wizard component editor
						this.links.length > 0 ? this.links[0].selected = true : null;
					})
			}
		}

	}

	private _findHiddenEntries(tocentries:ToCEntry[]):string[]{
		if(!tocentries) return [];

		const invisibleEntries:string[] = []
		tocentries.forEach(entry=>{
			if(entry.type === ToCEntryType.FieldSet){
				const isVisible = this.visibilityRulesService.checkElementVisibility(entry.id);
				if(!isVisible){
					invisibleEntries.push(entry.id);
				}else{
					//check field inputs
					const fields = entry.form.get('fields') as FormArray;

					const oneFieldAtLeastIsVisible = fields.controls.some(field=> this.visibilityRulesService.checkElementVisibility(field.get('id').value));
					if(!oneFieldAtLeastIsVisible){
						invisibleEntries.push(entry.id);
					}
				}
			}else{
				const hiddenEntries = this._findHiddenEntries(entry.subEntries);

				if(entry.subEntries&&  (entry.subEntries.every(e=> hiddenEntries.includes(e.id)))){
					//all children all hidden then hide parent node;
					invisibleEntries.push(entry.id);
				}else{
					invisibleEntries.push(...hiddenEntries);
				}
			}
		})

		return invisibleEntries;
	}

	private _visibilityRulesSubscription:Subscription;
	ngOnChanges(changes: SimpleChanges) {

		if(this.selectedFieldsetId){
			this.onToCentrySelected(this._findTocEntryById(this.selectedFieldsetId,this.tocentries), false);
			this._actOnObservation = false;
			setTimeout(() => {
				this._actOnObservation = true;
			}, 1000);

		}


		if(changes['hasFocus'] && changes.hasFocus.currentValue){
			this._resetObserver();

		}

		if('visibilityRulesService'){
			if(this._visibilityRulesSubscription){
				this._visibilityRulesSubscription.unsubscribe();
				this._visibilityRulesSubscription = null;
			}

			if(!this.visibilityRulesService) return;

			this._visibilityRulesSubscription = this.visibilityRulesService.visibilityChange
				.pipe(takeUntil(this._destroyed))
				.pipe(debounceTime(200))
				.subscribe(_=>{
					if(this.hasFocus){
						this._resetObserver();
						this.hiddenEntries = this._findHiddenEntries(this.tocentries);
					}
				});
			this.hiddenEntries = this._findHiddenEntries(this.tocentries);
		}
		// if (!this.isActive && this.links && this.links.length > 0) {
		// 	this.links.forEach(link => {
		// 		link.selected = false;
		// 	})
		// 	this.links[0].selected = true;
		// }
	}


	private _resetObserver(){
		if(this._intersectionObserver){//clean up
			this._intersectionObserver.disconnect();
			this._intersectionObserver = null;
		}

		const options = {
			root: null,
			rootMargin: '-38% 0px -60% 0px',
			threshold: 0
		  }

		this._intersectionObserver = new IntersectionObserver((entries,observer)=>{
			if(!this._actOnObservation){
				return;
			}

			entries.forEach(ie=>{
				if(ie.isIntersecting){
					try{
						const target_id = ie.target.id.replace(this.TOCENTRY_ID_PREFIX,'');
						if(this.visibilityRulesService.checkElementVisibility(target_id)){
							this.onToCentrySelected(this._findTocEntryById(target_id,this.tocentries));
						}
					}catch{

					}
				}
			})
		}, options);

		const fieldsetsEtries = this._getAllFieldSets(this.tocentries);

		fieldsetsEtries.forEach(e=>{
			if(e.type === ToCEntryType.FieldSet){
				try{
					const targetElement = document.getElementById(this.TOCENTRY_ID_PREFIX+e.id);
					this._intersectionObserver.observe(targetElement);
				}catch{
					console.log('element not found');
				}
			}
		});
	}

	goToStep(link: Link) {
		this.stepFound.emit({
			page: link.page,
			section: link.section
		});
		this.currentLinks.emit(this.links);

		setTimeout(() => {
			const target = document.getElementById(link.id);
			target.scrollIntoView(true);

			var scrolledY = window.scrollY;
			if (scrolledY) {
				window.scroll(0, scrolledY - 70);
			}
		}, 500);
	}

	toggle(headerLink: Link) {
		const headerPage = +headerLink.name.split(" ", 1);
		let innerPage;
		for (const link of this.links) {
			link.selected = false;
			if (link.type === 'mat-expansion-panel') {
				innerPage = +link.name.split(".", 1)[0];
				if (isNaN(innerPage)) { innerPage = +link.name.split(" ", 1) }
			} else if (link.type === 'h5') {
				innerPage = +link.name.split(".", 1)[0];
			}
			if (headerPage === innerPage && (link.type !== 'mat-expansion-panel' || (link.type === 'mat-expansion-panel' && link.id.split(".")[4]))) {
				link.show = !link.show;
			}
		}
		headerLink.selected = true;
	}

	// getIndex(link: Link): number {
	// 	return +link.id.split("_", 2)[1];
	// }


	private _buildRecursively(form: FormGroup,whatAmI:ToCEntryType):ToCEntry{
		if(!form) return null;

		switch(whatAmI){
			case ToCEntryType.Section:
				const sections = form.get('sections') as FormArray;
				const fieldsets = form.get('compositeFields') as FormArray;


				const tempResult:ToCEntry[] = [];

				if(sections &&sections.length){
					sections.controls.forEach(section=>{
						tempResult.push(this._buildRecursively(section as FormGroup, ToCEntryType.Section));
					});

				}else if(fieldsets && fieldsets.length){
					fieldsets.controls.forEach(fieldset=>{
						tempResult.push(this._buildRecursively(fieldset as FormGroup, ToCEntryType.FieldSet));
					});
				}
				return {
					form: form,
					id: form.get('id').value,
					label: form.get('title').value,
					numbering: '',
					subEntries:tempResult,
					subEntriesType: sections &&sections.length? ToCEntryType.Section: ToCEntryType.FieldSet,
					type: ToCEntryType.Section,
					ordinal: form.get('ordinal').value
				}
			case ToCEntryType.FieldSet:
				return {
					form: form,
					label:form.get('title').value,
					id: form.get('id').value,
					numbering:'s',
					subEntries:[],
					subEntriesType: ToCEntryType.Field,
					type: ToCEntryType.FieldSet,
					ordinal: form.get('ordinal').value
				}
		}
	}

	private _sortByOrdinal(tocentries: ToCEntry[]){

		if(!tocentries || !tocentries.length) return;

		tocentries.sort(this._customCompare);
		tocentries.forEach(entry=>{
			this._sortByOrdinal(entry.subEntries);
		});
	}

	private _customCompare(a,b){
		return a.ordinal - b.ordinal;
	}

	private _calculateNumbering(tocentries: ToCEntry[], depth:number[] = []){
		if(!tocentries || !tocentries.length){
			return;
		}

		let  prefixNumbering = depth.length? depth.join('.'): '';

		if(depth.length) prefixNumbering = prefixNumbering+".";
		tocentries.forEach((entry,i)=>{
			entry.numbering = prefixNumbering + (i+1);
			this._calculateNumbering(entry.subEntries, [...depth, i+1])
		});
	}


	getTocEntries(form): ToCEntry[] {
		if (form == null) { return []; }
		const result: ToCEntry[] = [];

		//build parent pages
		(form.get('pages') as FormArray).controls.forEach((pageElement, i) => {
			result.push({
				id: i+'id',
				label: pageElement.get('title').value,
				type: ToCEntryType.Page,
				form: pageElement,
				numbering: (i + 1).toString(),
				subEntriesType: ToCEntryType.Section,
				subEntries:[],
				ordinal: pageElement.get('ordinal').value
			} as ToCEntry)
		});



		result.forEach((entry,i)=>{

			const sections = entry.form.get('sections') as FormArray;

			sections.controls.forEach(section=>{
				const tempResults = this._buildRecursively(section as FormGroup,ToCEntryType.Section);
				entry.subEntries.push(tempResults);
			});

		});

		this._sortByOrdinal(result);
		//calculate numbering
		this._calculateNumbering(result);
		return result;

	}

	onToCentrySelected(entry: ToCEntry = null, execute: boolean = true){
		this.tocentrySelected = entry;
		this.entrySelected.emit({entry: entry, execute: execute});
	}

	private _findTocEntryById(id: string, tocentries: ToCEntry[]): ToCEntry{
		if(!tocentries || !tocentries.length){
			return null;
		}

		let tocEntryFound = tocentries.find(entry=>entry.id === id);

		if(tocEntryFound){
			return tocEntryFound;
		}

		for(let entry of tocentries){
			const result = this._findTocEntryById(id, entry.subEntries);
			if(result){
				tocEntryFound = result;
				break;
			}
		}

		return tocEntryFound? tocEntryFound: null;
	}
	/**
	 * Get all filedsets in a tocentry array;
	 * @param entries Tocentries to search in
	 * @returns The tocentries that are Fieldsets provided in the entries
	 */
	 private _getAllFieldSets(entries: ToCEntry[]):ToCEntry[]{

		const fieldsets:ToCEntry[] = [];
		if(!entries || !entries.length) return fieldsets;


		entries.forEach(e=>{
			if(e.type === ToCEntryType.FieldSet){
				fieldsets.push(e);
			}else{
				fieldsets.push(...this._getAllFieldSets(e.subEntries));
			}
		});
		return fieldsets;
	}


	public hasVisibleInvalidFields():boolean {
		if(!this.internalTable || !this.tocentries){
			return false;
		}
		return this.tocentries.some(e => this.internalTable.invalidChildsVisible(e));
	}

}

export interface LinkToScroll {
	page: number;
	section: number;
}
