import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { filter, map, share, take, takeUntil } from 'rxjs/operators';
import { constants, R, time, coord } from '../../common';
import { Handsontable } from '../../common/libs.Handsontable';
import { createProvider } from './provider';
const editors = Handsontable.editors;
const { CSS } = constants;
export class Editor extends editors.TextEditor {
    constructor() {
        super(...arguments);
        this._ = {
            current: undefined,
        };
        this.onCancel = () => {
            if (this.isDisposed) {
                return;
            }
            if (this._.current) {
                this._.current.isCancelled = true;
            }
            const restoreOriginalValue = true;
            this.cancelChanges();
            this.finishEditing(restoreOriginalValue);
            this.close();
        };
        this.onComplete = () => {
            if (this.isDisposed) {
                return;
            }
            time.delay(0, () => {
                const restoreOriginalValue = false;
                this.finishEditing(restoreOriginalValue);
                this.close();
            });
        };
    }
    prepare(row, column, prop, td, originalValue, cellProperties) {
        super.prepare(row, column, prop, td, originalValue, cellProperties);
    }
    get isDisposed() {
        return this.instance.isDestroyed || this.grid.isDisposed;
    }
    get isEditing() {
        return Boolean(this._.current);
    }
    get refs() {
        return this.instance.__gridRefs;
    }
    get grid() {
        return this.refs.grid;
    }
    get cell() {
        return this.grid.cell({ row: this.row, column: this.col });
    }
    get context() {
        return this._.current;
    }
    createElements() {
        if (this.isDisposed) {
            return;
        }
        super.createElements();
        this.textareaStyle.display = 'none';
    }
    async focus() {
    }
    beginEditing(initialValue) {
        if (this.isDisposed) {
            return;
        }
        super.beginEditing(initialValue);
        if (this.isEditing) {
            return;
        }
        const grid = this.grid;
        const initial = initialValue === null ? this.cell.data.value : '';
        const context = this.createContext({ initial });
        const el = this.render(context);
        if (!el) {
            this.onCancel();
            return;
        }
        const row = this.row;
        const column = this.col;
        this._.current = context;
        this.grid.events$
            .pipe(takeUntil(context.end$), filter((e) => e.type === 'GRID/cells/change'), map((e) => e.payload), filter((e) => e.isCancelled), filter((e) => e.changes.some(({ cell }) => cell.row === row && cell.column === column)))
            .subscribe((e) => {
            isCancelled = true;
            this.onCancel();
        });
        let isCancelled = false;
        this.refs.editorEvents$.next({
            type: 'GRID/EDITOR/begin',
            payload: {
                get cell() {
                    return grid.cell({ row, column });
                },
                cancel: () => (isCancelled = true),
            },
        });
        if (isCancelled) {
            return this.onCancel();
        }
        ReactDOM.render(el, this.TEXTAREA_PARENT);
    }
    finishEditing(restoreValue, ctrlDown, callback) {
        if (this.isDisposed) {
            return;
        }
        super.finishEditing(restoreValue, ctrlDown, callback);
        const current = this._.current;
        if (!current) {
            return;
        }
        const grid = this.grid;
        const row = this.row;
        const column = this.col;
        const isCancelled = current.isCancelled ? true : Boolean(restoreValue);
        const from = current.value.from;
        const to = isCancelled ? from : this.getValue();
        const size = current.size;
        ReactDOM.unmountComponentAtNode(this.TEXTAREA_PARENT);
        if (size) {
            const key = coord.cell.toRowKey(this.row);
            const change = {
                [key]: Object.assign(Object.assign({}, grid.data.rows[key]), { props: { grid: { height: size.height } } }),
            };
            grid.changeRows(change, { source: 'UPDATE/cellEdited' }).redraw();
        }
        const value = { from, to };
        const isChanged = !R.equals(value.from, value.to);
        const payload = {
            value,
            size,
            isCancelled,
            isChanged,
            get cell() {
                return grid.cell({ row, column });
            },
            cancel() {
                grid.changeCells({ [payload.cell.key]: from }, { silent: true });
                payload.isCancelled = true;
            },
        };
        const e = { type: 'GRID/EDITOR/end', payload };
        this._.current = undefined;
        this.refs.editorEvents$.next(e);
    }
    getValue() {
        const context = this.context;
        return context ? context.value.to : undefined;
    }
    createContext(args) {
        const grid = this.grid;
        const cell = this.cell;
        const cancel = () => {
            context.isCancelled = true;
            this.onCancel();
            return context;
        };
        const complete = () => {
            this.onComplete();
            return context;
        };
        const end$ = this.refs.editorEvents$.pipe(filter((e) => e.type === 'GRID/EDITOR/end'), map((e) => e), take(1), share());
        const keys$ = grid.events$.pipe(takeUntil(end$), filter((e) => e.type === 'GRID/keydown'), map((e) => e.payload), share());
        keys$
            .pipe(filter((e) => context.autoCancel), filter((e) => e.isEscape))
            .subscribe(cancel);
        const from = this.instance.getDataAtCell(this.row, this.col);
        const value = { from, to: from };
        const context = {
            initial: args.initial,
            isCancelled: false,
            autoCancel: true,
            grid,
            cell,
            keys$,
            end$,
            value,
            complete,
            cancel,
            size: undefined,
            set(args) {
                if (args.value !== undefined) {
                    value.to = args.value;
                }
                if (args.size !== undefined) {
                    const { width, height } = args.size;
                    context.size = { width, height };
                }
                return context;
            },
        };
        return context;
    }
    render(context) {
        const { row, column } = context.cell;
        const { value, props } = context.cell.data;
        const cell = { value, props };
        const el = this.refs.factory.editor({ row, column, cell });
        if (!el) {
            return null;
        }
        const Provider = createProvider(context);
        const className = CSS.CLASS.GRID.EDITOR;
        return (React.createElement(Provider, null,
            React.createElement("div", { className: className }, el)));
    }
}
