import { AfterViewInit, Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild, Output, EventEmitter } from '@angular/core';
import { AbstractControl, AbstractControlOptions, FormArray, FormGroup } from '@angular/forms';
import { MatExpansionPanel } from '@angular/material/expansion';
import { MatHorizontalStepper } from '@angular/material/stepper';
import { CompositeField } from '@app/core/model/dataset-profile-definition/composite-field';
import { Field } from '@app/core/model/dataset-profile-definition/field';
import { Rule } from '@app/core/model/dataset-profile-definition/rule';
import { DatasetProfileTableOfContentsInternalSection } from '@app/ui/admin/dataset-profile/table-of-contents/table-of-contents-internal-section/table-of-contents-internal-section';
import { LinkToScroll } from '@app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents';
import { VisibilityRulesService } from '@app/ui/misc/dataset-description-form/visibility-rules/visibility-rules.service';
import { BaseComponent } from '@common/base/base.component';
import { debounceTime, takeUntil } from 'rxjs/operators';
import { VisibilityRuleSource } from './visibility-rules/models/visibility-rule-source';

@Component({
	selector: 'app-dataset-description',
	templateUrl: './dataset-description.component.html',
	styleUrls: ['./dataset-description.component.scss']
})
export class DatasetDescriptionComponent extends BaseComponent implements OnInit, AfterViewInit, OnChanges {

	// @ViewChild('stepper', { static: false }) stepper: MatHorizontalStepper;
	@Input() path: string;
	@Input() visibilityRules: Rule[] = [];
	@Input() datasetProfileId: String;
	@Input() datasetDescription: String;
	@Input() linkToScroll: LinkToScroll;
	@Output() formChanged: EventEmitter<any> = new EventEmitter();
	@Output() fieldsetFocusChange: EventEmitter<string> = new EventEmitter<string>();

	tocentries: ToCEntry[];
	@Input() form: FormGroup;

	@Input() TOCENTRY_ID_PREFIX="";
	@Output() visibilityRulesInstance = new EventEmitter<VisibilityRulesService>();

	public hiddenEntriesIds:string[] = [];

	constructor(
		private visibilityRulesService: VisibilityRulesService,
	) {
		super();
		
	}

	ngOnInit() {
		this.tocentries = this.getTocEntries();
		const rules_to_append = this._enrichWithMultiplicityRules(this.tocentries);

		this.visibilityRulesService.buildVisibilityRules([...this.visibilityRules, ...rules_to_append ], this.form);
		
		// if (this.form) {
		// 	this.form.valueChanges
		// 		.pipe(takeUntil(this._destroyed))
		// 		.subscribe(val => {
		// 			this.formChanged.emit(val);
		// 		});
		// }
		this.visibilityRulesInstance.emit(this.visibilityRulesService);
		



		this.hiddenEntriesIds = this._findHiddenEntries(this.tocentries);

		this.visibilityRulesService.visibilityChange
		.pipe(
			takeUntil(this._destroyed),
			debounceTime(100)
		)
		.subscribe(_=>{
			this.hiddenEntriesIds = this._findHiddenEntries(this.tocentries);
		})
	}

	ngOnChanges(changes: SimpleChanges) {

		// When the form is changed set stepper index to 0.
		// if (this.stepper && changes['form'] && !changes['form'].isFirstChange()) {
		// 	this.stepper.selectedIndex = 0;
		// } else if (this.stepper && changes['linkToScroll'] && changes['linkToScroll'].currentValue) {
		// 	if (changes['linkToScroll'].currentValue.page >= 0) {
		// 		this.stepper.selectedIndex = changes['linkToScroll'].currentValue.page;
		// 	}
		// }
	}

	ngAfterViewInit() {

	}



	onAskedToScroll(panel: MatExpansionPanel, id?:string){
		panel.open();
		this.fieldsetFocusChange.emit(id);
	}



	private _enrichWithMultiplicityRules(tocentries: ToCEntry[]) : Rule[] {
		if (tocentries){
			return tocentries.map(entry => {
				if(entry.type  === ToCEntryType.Field) return []; // * TODO Me to tora implementation den tha ftasei pote edo


				if(entry.type === ToCEntryType.FieldSet){
					// if(multiplicity: )
					try {
						// TODO OTAN KANW HIDE MULTIPLE PEDIO TOTE STO ON SHOW HANO TA VALUES (AUTO MPOREI NA EINAI KAI LEGIT) ('NA DOUME AN ONTOS DIAGRAFONTAI I APLA DEN TA DEIXNOUME')
						// * UPDATE KANEI DESTROY TO COMPONENT H NGIF . PITHANOTATA NA XREIASTEI NA TO KANOUME HIDDEN AN THELOUME KATI ALLO
						const multiplicity = entry.form.get('multiplicity').value;
						if( (multiplicity.max > 1 ) && (multiplicity.min> 0) && (multiplicity.max >= multiplicity.min)){ // has valid multiplicity
							return this._createAndAppendVisibilityRule(entry);
						}
					} catch {

					}
					return [];
				}

				if(entry.subEntries){
					return this._enrichWithMultiplicityRules(entry.subEntries);
				}
			})
			.reduce((r,c)=>{ return [...c, ...r]},[]);
		}
		return [];
	}

	private _createAndAppendVisibilityRule(entry: ToCEntry): Rule[]{


		const rules_to_append = [];

		if(entry && (entry.type === ToCEntryType.FieldSet)){

			//childs that are either target or source
			const childIdsWithVisRules = (entry.form.get('fields') as FormArray).controls.reduce((all, s) =>
			{
				const sval = s.value as Field;
				return this.visibilityRules.find(x => (x.targetField === sval.id) || (x.sourceField === sval.id)) ? [...all, sval] : all; 
			},[]) as Field[];
			

			const innerCompositeFieldOriginalIds = (entry.form.get('fields') as FormArray).controls.map( x=> x.get('id').value) as string[];

			//multiplicity items
			const multiplicityItemsValue = entry.form.get('multiplicityItems').value as CompositeField[];
			

			// *********      FIELDS OF FIELDSET ARE EITHER TARGETS OR SOURCES *****


			if( childIdsWithVisRules.length && multiplicityItemsValue && multiplicityItemsValue.length ){
				//check each multiplicity item composite field
				multiplicityItemsValue.forEach( mi =>{

					const multiplicityCompositeFieldIds = mi.fields.map(x => x.id);
					const idMappings = multiplicityCompositeFieldIds.map(x => {
						return {
							original: innerCompositeFieldOriginalIds.find( l => x.includes(l)),
							multiplicityIdValue: x
						}
					}) as {original: string, multiplicityIdValue: string}[];

					//each field of mutliplicity item
					mi.fields.forEach(field =>{


						//get original visibility rules (original field)

						//original id
						const original_id = childIdsWithVisRules.find(x=> field.id.includes(x.id)).id;


						//get vis rules

						//as source
						const original_as_source = this.visibilityRules.filter( x => x.sourceField === original_id);
						const original_as_target = this.visibilityRules.filter( x => x.targetField === original_id);



						if(original_as_source.length){

							//inner dependencies
							const innerDep = original_as_source.filter(x => innerCompositeFieldOriginalIds.includes(x.targetField));
							innerDep.forEach(x =>{
								const newRule = {...x, sourceField: field.id, targetField: idMappings.find(l => l.original === x.targetField).multiplicityIdValue} as Rule;
								rules_to_append.push(newRule);
							})


							//outer dependencies
							const outerDep = original_as_source.filter(x => !innerCompositeFieldOriginalIds.includes(x.targetField));
							outerDep.forEach(x =>{
								const newRule = {...x, sourceField: field.id};
								rules_to_append.push(newRule);
							})
						}



						if( original_as_target.length){

							//inner dependencies
							const innerDep = original_as_target.filter( x=> innerCompositeFieldOriginalIds.includes(x.sourceField));
							innerDep.forEach(x =>{
								const newRule = {...x, targetField: field.id, sourceField: idMappings.find(l => l.original === x.sourceField).multiplicityIdValue} as Rule;
								rules_to_append.push(newRule);
							})
							
							//outer dependencies
							const outerDep = original_as_target.filter( x=> !innerCompositeFieldOriginalIds.includes(x.sourceField));
							outerDep.forEach(x=>{
								const newRule = {...x, targetField: field.id} as Rule;
								rules_to_append.push(newRule);
							})
						}

					})
				});
			}
			
			
			
			
			// ** FIELDSET ITSELF IS TARGET
			// ** source it can never be

			const compositeFieldAsTargetRules = this.visibilityRules.filter(x => x.targetField === entry.id);
			const idCompositeFieldMappings = multiplicityItemsValue.map(x =>{
				return {
					originalValue: entry.id,
					newValue:x.id
				}
			}) as {originalValue: string, newValue: string}[];


			if(compositeFieldAsTargetRules.length){
				
				compositeFieldAsTargetRules.forEach(x =>{
					idCompositeFieldMappings.forEach(l=>{
						const newRule = {...x, targetField: l.newValue};
						rules_to_append.push(newRule);
					});
				});
			}


		}

		return rules_to_append;
	}

	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(): ToCEntry[] {
		if (!this.form) { return []; }
		const result: ToCEntry[] = [];

		//build parent pages
		(this.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;

	}
	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;
	}

}
export interface ToCEntry {
	id: string;
	label: string;
	subEntriesType: ToCEntryType;
	subEntries: ToCEntry[];
	type: ToCEntryType;
	form: AbstractControl;
	numbering: string;
	ordinal: number;
}
export enum ToCEntryType {
	Page = 0,
	Section = 1,
	FieldSet = 2,
	Field = 3
}
