import {
	Overlay,
	OverlayConfig,
	OverlayRef
} from '@angular/cdk/overlay';
import {
	ComponentPortal,
	PortalInjector
} from '@angular/cdk/portal';
import {
	Component,
	ElementRef,
	EventEmitter,
	forwardRef,
	HostListener,
	Injector,
	Input,
	OnChanges,
	OnDestroy,
	OnInit,
	Output,
	SimpleChanges
} from '@angular/core';
import {
	ControlValueAccessor,
	NG_VALUE_ACCESSOR
} from '@angular/forms';

// Сенсор
import { ResizeSensor } from 'css-element-queries';

// Интерфейсы
import { IPowerSelect } from '../../interfaces';

// Токены
import { POWER_SELECT_DATA } from '../../tokens';

// Перечисления
import { PowerSelectType } from '../../enums';

// Компоненты
import { CrmPowerSelectGroupPopupComponent } from '../../components/crm-power-select-group-popup/crm-power-select-group-popup.component';
import { CrmPowerSelectPopupComponent } from '../../components/crm-power-select-popup/crm-power-select-popup.component';
import { CrmPowerSelectTreePopupComponent } from '../../components/crm-power-select-tree-popup/crm-power-select-tree-popup.component';

// Селектор
@Component({
	selector: 'crm-power-select',
	templateUrl: './crm-power-select.component.html',
	styleUrls: ['./crm-power-select.component.scss'],
	providers: [{
		provide: NG_VALUE_ACCESSOR,
		useExisting: forwardRef(() => CrmPowerSelectComponent),
		multi: true
	}]
})
export class CrmPowerSelectComponent
	implements OnInit, OnDestroy, OnChanges, ControlValueAccessor {

	constructor(
		private readonly el: ElementRef,
		private readonly injector: Injector,
		private readonly overlay: Overlay
	) {
		this.selectItem.subscribe((selected: IPowerSelect) => {
			this.selected = selected;
		});
	}

	// Тип слайдера
	@Input() public type: PowerSelectType = PowerSelectType.List;

	// Список элементов
	@Input() public items: IPowerSelect[];

	// Выбраный элемент
	private _selected: IPowerSelect;
	@Input() public set selected(value: IPowerSelect) {
		this._selected = value;
		this.POPUP_DATA.selected = value;

		if (this.onChange) {
			this.onChange(value);
		}
	}
	public get selected(): IPowerSelect {
		return this._selected;
	}

	// Кнопка
	@Input() public button: any;

	// Событие нажатия кнопки
	@Output() public buttonClick: EventEmitter<any> = new EventEmitter<any>();

	// Событие выбора элемента
	@Output() public selectItem: EventEmitter<IPowerSelect> = new EventEmitter<IPowerSelect>();

	// Событие поиска
	@Output() public searchTerms: EventEmitter<string> = new EventEmitter<string>();

	// Вызовем когда значение изменится
	private onChange: (value: IPowerSelect) => void;

	// Вызовем при любом дествии пользователя с контроллом
	private onTouched: () => void;

	// Флаг, отключено
	public disabled: boolean = false;

	// Конфигурация всплывающего окна
	private overlayConfig: OverlayConfig;

	// Ссылка на открытое окно
	private overlayRef: OverlayRef;

	// Данные всплывающего окна
	private readonly POPUP_DATA: any = {
		loaded: new EventEmitter<void>(),
		items: [],
		selected: null,
		button: this.button,
		buttonClick: this.buttonClick,
		selectItem: this.selectItem,
		searchTerms: this.searchTerms,
		width: 0
	};

	// Сенсор изменения размера
	private resizeSensor: any;

	// --------------------------------------------------------------------------
	// Обновить размер
	private updateSize(): void {
		this.POPUP_DATA.width =
			this.el &&
			this.el.nativeElement &&
			this.el.nativeElement.offsetWidth;
	}

	// Обработчик открытия всплывающего окна
	public onOpenPopup(): void {
		if (!this.disabled) {
			this.open();
		}
	}

	// Открыть окно
	private open(): void {
		if (!this.overlayRef || !this.overlayRef.hasAttached()) {

			// Создание слоя всплывающего окна
			this.overlayRef = this.overlay.create(this.overlayConfig);

			// Токены
			const injectionTokens = new WeakMap();
			injectionTokens.set(OverlayRef, this.overlayRef);
			injectionTokens.set(POWER_SELECT_DATA, this.POPUP_DATA);

			// Создание инжектора
			const injector = new PortalInjector(this.injector, injectionTokens);

			// Компонент
			let component = CrmPowerSelectPopupComponent;
			switch (this.type) {
				case PowerSelectType.GroupList:
					component = CrmPowerSelectGroupPopupComponent;
					break;
				case PowerSelectType.Tree:
					component = CrmPowerSelectTreePopupComponent;
					break;
				default:
			}

			// Создание контейнера
			const containerPortal = new ComponentPortal(
				component,
				null,
				injector
			);

			// Добавить к слою
			this.overlayRef.attach(containerPortal);
		}
	}

	// Закрыть окно
	private close(): void {
		if (this.overlayRef) {
			this.overlayRef.dispose();
		}
	}

	// Добавить сенсор
	private addSensor(): void {
		if (this.el && this.el.nativeElement) {
			this.resizeSensor = new ResizeSensor(
				this.el.nativeElement,
				this.updateSize.bind(this)
			);
		}
	}

	// Удалить сенсор
	private removeSensor(): void {
		if (this.resizeSensor) { this.resizeSensor.detach(); }
	}

	// Создать конфигурацию
	private createConfig(): void {

		// Стратегия
		const strategy = this.overlay.position()
			.connectedTo(
				this.el,
				{
					originX: 'start',
					originY: this.type === PowerSelectType.Tree
						? 'bottom'
						: 'top'
					},
				{ overlayX: 'start', overlayY: 'top' }
			);

		// Конфигурация
		this.overlayConfig = new OverlayConfig({
			positionStrategy: strategy
		});
	}

	// --------------------------------------------------------------------------
	// Реакция на клик хост-элемента
	@HostListener('click') public onClick(): void {
		if (this.onTouched) {
			this.onTouched();
		}
	}

	// Вызовет форма, если значение изменилось извне
	public writeValue(selected: IPowerSelect): void {
		this.selected = selected;
	}

	// Сохраняем обратный вызов для изменений
	public registerOnChange(fn: any): void {
		this.onChange = fn;
	}

	// Сохраняем обратный вызов для "касаний"
	public registerOnTouched(fn: any): void {
		this.onTouched = fn;
	}

	// Установка состояния disabled
	public setDisabledState(isDisabled: boolean): void {
		this.disabled = isDisabled;
	}

	// --------------------------------------------------------------------------
	// HOOKS
	// Инициализация
	public ngOnInit(): void {
		this.createConfig();
		this.addSensor();
		this.updateSize();
	}

	// Уничтожение
	public ngOnDestroy(): void {
		this.removeSensor();
		this.close();
	}

	// Изменение входных параметров
	public ngOnChanges(changes: SimpleChanges): void {
		if (changes.items) {
			this.POPUP_DATA.items = this.items;
			this.POPUP_DATA.loaded.emit();
		}
		if (changes.selected) {
			this.POPUP_DATA.selected = this.selected;
		}
		if (changes.button) {
			this.POPUP_DATA.button = this.button;
		}
	}
}
