// Utility functions for Dyynaform consumers

import { FormControl, FormGroup, ValidationErrors } from '@angular/forms';
import { ValueTransformer } from './interfaces';
import { Observable, of, merge } from 'rxjs';
import { filter, map, mapTo, switchMap, } from 'rxjs/internal/operators';


// Dropdown Modified Input - Starts With / Contains / Matches
const standardModifiers = ['Starts with', 'Contains', 'Matches'];
const standardTransformer: ValueTransformer = {
	inputFn: val => {
		let modifier = 'Starts with';
		if (/^%.*?%$/.test(val)) {
			modifier = 'Contains';
		} else if (/^[^%].*?[^%]$/.test(val)) {
			modifier = 'Matches';
		} else if (/^%.*/.test(val)) {
			modifier = 'Starts with';
		}
		const transformedVal = val.replace(/%/g, '').trim();
		return { modifier, value: transformedVal };
	},
	outputFn: (mod, val) => {
		let transformedValue;
		switch (mod) {
			case 'Starts with':
				transformedValue = `${val}%`;
				break;
			case 'Contains':
				transformedValue = `%${val}%`;
				break;
			case 'Matches':
			default:
				transformedValue = val;
				break;
		}
		return transformedValue;
	}
};

// Generate array from CSV string
const toArrTag = (str: TemplateStringsArray): string[] => str[0].split(',').map(key => key.trim());

// Pad array
const arrayPad = (length: number) => (arr: string[]): any[] => [...arr, ...Array(length - arr.length).fill('')];

// Utility function for casting an array to metadata (useful for components that render FormGroups)
const arrayToMeta = array => array.map(val => ({ name: val, 'value' : val }));

// Exclude 'fieldsToExclude' from obj, returning a new object
// fieldsToExclude can be an array of field keys or a CSV of field keys
const excludeFields = (obj: StringMap<any>, fieldsToExclude: string | string[]) => {
	const ex = Array.isArray(fieldsToExclude) ? fieldsToExclude : fieldsToExclude.split(',').map(key => key.trim());
	return Object.entries(obj).reduce(
		(res, [key, val]) => ex.includes(key) ? res : { ...res, [key]: val },
		{}
	);
};

// Higher order function that takes a bound test function and failure flag
// and returns an AsyncValidator-compatible function
// that in turn returns an Observable of ValidationErrors
const makeAsyncTest = (boundFnName: string, failureFlag: string): (fc: FormControl | FormGroup) => Observable<ValidationErrors> => {
	let initialValue;
	return function(fc: FormControl | FormGroup | 'RESET'): Observable<ValidationErrors> {
		if (fc === 'RESET') {
			// Special value that resets the initial value
			initialValue = '';
			return of({});
		}
		if (fc.pristine) {
			initialValue = fc.value;
		}
		return merge(
			of(fc).pipe(
				filter(_fc => _fc.value !== initialValue),
				switchMap(_fc => this[boundFnName](_fc.value, _fc.root)),
				map(res => res ? {} : { [failureFlag]: true } )
			),
			of(fc).pipe(
				filter(_fc => _fc.value === initialValue),
				mapTo({})
			)
		);
	};
};

export { standardModifiers, standardTransformer, toArrTag, arrayPad, arrayToMeta, excludeFields, makeAsyncTest };