import {
	Component,
	ElementRef,
	EventEmitter,
	Input,
	OnChanges,
	OnDestroy,
	OnInit,
	Output,
	SimpleChanges,
	ViewChild
} from '@angular/core';

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

// Интерфейсы
import {
	IColumn,
	IRowCache,
	ISize,
	ITableOptions
} from '../../interfaces';

// Контент таблицы
@Component({
	selector: 'crm-table-content',
	templateUrl: './crm-table-content.component.html',
	styleUrls: ['./crm-table-content.component.scss']
})
export class CrmTableContentComponent
	implements OnChanges, OnInit, OnDestroy {

	// Строки
	@Input() public rows: any[];

	// Конфигурация таблицы
	@Input() public options: ITableOptions;

	// Закрепленные столбцы
	@Input() public pinnedColumns: IColumn[];

	// Ширина области контента закрепленных столбцов
	@Input() public pinnedCanvasWidth: number;

	// Обычные столбцы
	@Input() public defaultColumns: IColumn[];

	// Ширина области контента обычных столбцов
	@Input() public defaultCanvasWidth: number;

	// Смещение области контента в области отображения
	@Input() public offsetLeft: number;

	// Смещение области контента в области отображения
	@Input() public offsetTop: number;

	// Событие изменения размера defaultView
	@Output() public resizedView = new EventEmitter<ISize>();

	// Событие изменения размера defaultCanvas
	@Output() public resizedCanvas = new EventEmitter<ISize>();

	// Ссылка на область отображения закрепленных столбцов
	@ViewChild('pinnedView') public pinnedView: ElementRef;

	// Ссылка на область отображения обычных столбцов
	@ViewChild('defaultView') public defaultView: ElementRef;

	// Ссылка на область контента обычных столбцов
	@ViewChild('defaultCanvas') public defaultCanvas: ElementRef;

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

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

	// Высота строки по умолчанию
	private readonly DEFAULT_ROW_HEIGHT: number = 32;
	private get defaultRowHeight(): number {
		return (this.options && this.options.defaultRowHeight) || this.DEFAULT_ROW_HEIGHT;
	}

	// Кеш высот строк
	private readonly _rowsCache: WeakMap<object, IRowCache> = new WeakMap<object, IRowCache>();
	public readonly rowsCache: WeakMap<object, number> = new WeakMap<object, number>();

	// Событие изменения высоты ячейки
	public readonly emitterChangeHeight: EventEmitter<any> = new EventEmitter<any>();

	// --------------------------------------------------------------------------
	// Обновить смещения
	private updateOffset(): void {
		const defaultView = this.defaultView && this.defaultView.nativeElement;
		const pinnedView = this.pinnedView && this.pinnedView.nativeElement;

		if (defaultView) {
			defaultView.scrollLeft = this.offsetLeft;
			defaultView.scrollTop = this.offsetTop;
		}
		if (pinnedView) {
			pinnedView.scrollTop = this.offsetTop;
		}
	}

	// Обновить кеш строк
	private updateRowsCache(): void {
		const rows = this.rows || [];

		rows.forEach((row: any): void => {
			if (!this.rowsCache.has(row)) {
				this.rowsCache.set(row, this.defaultRowHeight);
			}
		});
	}

	// Расчет высоты строки
	private calcRowHeight(row: any): void {
		const rowCache = this._rowsCache.get(row);
		if (rowCache) {
			const heightCells = Object.keys(rowCache)
				.map((key: string): number => rowCache[key].height);

			const height = Math.max(...heightCells);
			this.rowsCache.set(row, height);
		}
	}

	// Обработчик изменения размера ячейки
	private onChangeHeight(event: any): void {
		const row = event.row;
		const field = event.column.field;
		const height = event.height || this.defaultRowHeight;

		if (!this._rowsCache.has(row)) {
			this._rowsCache.set(row, {});
		}

		const rowCache = this._rowsCache.get(row);
		rowCache[field] = { field, height };

		this.calcRowHeight(row);
	}

	// Обработчик изменения размера defaultView
	public onResizedView(): void {
		const el = this.defaultView.nativeElement;
		this.resizedView.emit({
			width: el.offsetWidth,
			height: el.offsetHeight
		});
	}

	// Обработчик изменения размера defaultCanvas
	public onResizedCanvas(): void {
		const el = this.defaultCanvas.nativeElement;
		this.resizedCanvas.emit({
			width: el.offsetWidth,
			height: el.offsetHeight
		});
	}

	// Добавить сенсоры
	private addSensors(): void {
		this.resizeSensorView = new ResizeSensor(
			this.defaultView.nativeElement,
			this.onResizedView.bind(this)
		);
		this.resizeSensorCanvas = new ResizeSensor(
			this.defaultCanvas.nativeElement,
			this.onResizedCanvas.bind(this)
		);
	}

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

	// --------------------------------------------------------------------------
	// HOOKS
	// Инициализация
	public ngOnInit(): void {
		this.emitterChangeHeight.subscribe({
			next: this.onChangeHeight.bind(this)
		});

		// Добавить сенсоры
		this.addSensors();

		// Пропуск кадра
		setTimeout((): void => {
			this.onResizedView();
			this.onResizedCanvas();
		});
	}

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

	// Изменение входных параметров
	public ngOnChanges(changes: SimpleChanges): void {
		if (changes.offsetLeft || changes.offsetTop) {
			this.updateOffset();
		}
		if (changes.rows) {
			this.updateRowsCache();
		}
	}
}
