"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SPEC_ITEM_HEIGHT = exports.SPEC_ITEM_NAME_HEIGHT = exports.SPEC_ITEM_MINIGRAPH_HEIGHT = exports.SPEC_ITEM_MINIGRAPH_WIDTH = exports.DEFAULT_STYLE = void 0;
const addListener_1 = require("../../../../../client/addListener");
const component_1 = require("../../../../../client/component");
const mergeStyle_1 = require("../../../../../client/component/mergeStyle");
const dnd_1 = require("../../../../../client/dnd");
const change_1 = require("../../../../../client/event/change");
const custom_1 = require("../../../../../client/event/custom");
const dragdrop_1 = require("../../../../../client/event/drag/dragdrop");
const dragenter_1 = require("../../../../../client/event/drag/dragenter");
const dragleave_1 = require("../../../../../client/event/drag/dragleave");
const dragover_1 = require("../../../../../client/event/drag/dragover");
const blur_1 = require("../../../../../client/event/focus/blur");
const focus_1 = require("../../../../../client/event/focus/focus");
const input_1 = require("../../../../../client/event/input");
const click_1 = require("../../../../../client/event/pointer/click");
const pointercancel_1 = require("../../../../../client/event/pointer/pointercancel");
const pointerenter_1 = require("../../../../../client/event/pointer/pointerenter");
const pointerleave_1 = require("../../../../../client/event/pointer/pointerleave");
const findRef_1 = require("../../../../../client/findRef");
const modes_1 = require("../../../../../client/graph/shortcut/modes");
const MAX_Z_INDEX_1 = require("../../../../../client/MAX_Z_INDEX");
const parentElement_1 = require("../../../../../client/platform/web/parentElement");
const theme_1 = require("../../../../../client/theme");
const geometry_1 = require("../../../../../client/util/geometry");
const getPosition_1 = require("../../../../../client/util/style/getPosition");
const emptyBundleSpec_1 = require("../../../../../spec/emptyBundleSpec");
const emptySpec_1 = require("../../../../../spec/emptySpec");
const callAll_1 = require("../../../../../util/call/callAll");
const id_1 = require("../../../../../util/id");
const object_1 = require("../../../../../util/object");
const getPlainTextWidth_1 = require("../../../../../util/text/getPlainTextWidth");
const f_1 = require("../../../../core/object/ForEachKeyValue/f");
const Component_1 = require("../../Div/Component");
const Component_2 = require("../../value/TextField/Component");
const Component_3 = require("../Editor/Component");
const Component_4 = require("../Minigraph/Component");
exports.DEFAULT_STYLE = {
    display: 'flex',
    flexDirection: 'column',
};
exports.SPEC_ITEM_MINIGRAPH_WIDTH = 234;
exports.SPEC_ITEM_MINIGRAPH_HEIGHT = 81;
exports.SPEC_ITEM_NAME_HEIGHT = 18;
exports.SPEC_ITEM_HEIGHT = exports.SPEC_ITEM_MINIGRAPH_HEIGHT + exports.SPEC_ITEM_NAME_HEIGHT;
// RETURN
let channel = null;
if (globalThis.BroadcastChannel) {
    channel = new BroadcastChannel('graph');
}
class SaveControl extends component_1.Component {
    constructor($props, $system) {
        super($props, $system);
        this._mode = 'none';
        this._graph_search_hidden = true;
        this._modes = null;
        this._hovered_spec_id = null;
        this._hovered_spec_item = null;
        this._prevent_pointer_enter = false;
        this._editing_name_spec_id = null;
        this._spec_item = {};
        this._spec_item_name = {};
        this._spec_item_minigraph = {};
        this._spec_item_unlisten = {};
        this._spec_item_bundle = {
            local: {},
            user: {},
            shared: {},
        };
        this._new_spec_item = null;
        this._unlisten_mode_keyboard = undefined;
        this._render_spec_item = (bundle, spec_id, style = {}) => {
            const { $theme } = this.$context;
            const { spec } = bundle;
            const { name } = spec;
            const spec_item_screen_minigraph = new Component_4.default({
                width: exports.SPEC_ITEM_MINIGRAPH_WIDTH,
                height: exports.SPEC_ITEM_MINIGRAPH_HEIGHT,
                bundle,
            }, this.$system);
            this._spec_item_minigraph[spec_id] = spec_item_screen_minigraph;
            this._spec_item_bundle[spec_id] = bundle;
            const spec_item_screen = new Component_1.default({
                style: {
                    position: 'relative',
                    height: `${exports.SPEC_ITEM_MINIGRAPH_HEIGHT}px`,
                    display: 'flex',
                    justifyContent: 'center',
                },
            }, this.$system);
            spec_item_screen.appendChild(spec_item_screen_minigraph);
            const refresh_spec_item_name_width = (value) => {
                const name_width = (0, getPlainTextWidth_1.getTextWidth)(value, 15, Component_3.UNIT_NAME_MAX_SIZE);
                (0, mergeStyle_1.default)(spec_item_name, { width: `${name_width + 4}px` });
            };
            const display_name = name || '';
            const spec_item_name = new Component_2.default({
                value: display_name,
                style: {
                    fontSize: '15px',
                    height: `${exports.SPEC_ITEM_NAME_HEIGHT}px`,
                    width: `${(0, getPlainTextWidth_1.getTextWidth)(display_name, 15, Component_3.UNIT_NAME_MAX_SIZE)}`,
                    textAlign: 'center',
                    fontWeight: '600',
                    marginTop: '3px',
                    marginBottom: '3px',
                    maxWidth: `${exports.SPEC_ITEM_MINIGRAPH_WIDTH}px`,
                    pointerEvents: this._mode === 'none' ? 'auto' : 'none',
                    // ...userSelect('none'),
                },
            }, this.$system);
            const unlisten_name = spec_item_name.addEventListeners([
                (0, input_1.makeInputListener)((value) => {
                    refresh_spec_item_name_width(value);
                }),
                (0, change_1.makeChangeListener)((value) => {
                    if (value.length > 0) {
                        const { proxy } = this._client;
                        const next_bundle = Object.assign(Object.assign({}, bundle), { spec: Object.assign(Object.assign({}, spec), { name: value }) });
                        this._spec_item_bundle[spec_id] = next_bundle;
                        proxy.put(spec_id, next_bundle);
                        if (channel) {
                            channel.postMessage({
                                type: 'put',
                                data: { id: spec_id, data: bundle },
                            });
                        }
                    }
                    else {
                        const value = spec.name || '';
                        spec_item_name.setProp('value', value);
                        refresh_spec_item_name_width(value);
                    }
                }),
                (0, focus_1.makeFocusListener)(() => {
                    this._editing_name_spec_id = spec_id;
                    this._disable_modes();
                }),
                (0, blur_1.makeBlurListener)(() => {
                    this._editing_name_spec_id = null;
                    this._enable_modes();
                }),
            ]);
            this._spec_item_name[spec_id] = spec_item_name;
            refresh_spec_item_name_width(display_name);
            const unhover = () => {
                this._unhover_item();
            };
            const spec_item = new Component_1.default({
                style: Object.assign({ height: 'fit-content', display: 'flex', flexDirection: 'column', alignItems: 'center', pointerEvents: this._mode === 'none' ? 'none' : 'auto' }, style),
            }, this.$system);
            spec_item.appendChild(spec_item_screen);
            spec_item.appendChild(spec_item_name);
            const unlisten_item = spec_item.addEventListeners([
                (0, pointerenter_1.makePointerEnterListener)(({ pointerId }) => {
                    if (this._prevent_pointer_enter) {
                        this._prevent_pointer_enter = false;
                        return;
                    }
                    // console.log('pointerenter')
                    this._hover_item(spec_id);
                }),
                (0, pointerleave_1.makePointerLeaveListener)(unhover),
                (0, pointercancel_1.makePointerCancelListener)(unhover),
                (0, click_1.makeClickListener)({
                    onClick: () => {
                        switch (this._mode) {
                            case 'remove':
                                if (this._hovered_spec_item) {
                                    this._unhover_item();
                                }
                                this._remove_spec(spec_id);
                                break;
                            case 'add':
                            case 'data':
                                if (this._new_spec_item === spec_item) {
                                    this._store_add_spec(spec_id, bundle);
                                    (0, mergeStyle_1.default)(this._new_spec_item, {
                                        opacity: '1',
                                    });
                                    this._new_spec_item = null;
                                }
                        }
                    },
                    onLongPress: (event, _event) => {
                        if (this._mode === 'add' ||
                            this._mode === 'change' ||
                            this._mode === 'remove' ||
                            this._mode === 'data') {
                            const { showLongPress } = this.$system;
                            const { $theme, $color } = this.$context;
                            const { pointerId } = _event;
                            const bundle = this._spec_item_bundle[spec_id];
                            const { screenX, screenY } = event;
                            const releasePointerCapture = this._root.setPointerCapture(pointerId);
                            showLongPress(screenX, screenY, {
                                stroke: (0, theme_1.getThemeModeColor)($theme, this._mode, $color),
                            });
                            const color = (0, theme_1.getThemeModeColor)($theme, this._mode, $color);
                            const svg = spec_item_screen_minigraph._minimap._map_el.$element.cloneNode(true);
                            const width = exports.SPEC_ITEM_MINIGRAPH_WIDTH;
                            const height = exports.SPEC_ITEM_MINIGRAPH_HEIGHT;
                            svg.setAttribute('stroke', color);
                            svg.style.color = color;
                            svg.style.position = 'fixed';
                            svg.style.left = `${screenX}px`;
                            svg.style.top = `${screenY}px`;
                            svg.style.zIndex = `${MAX_Z_INDEX_1.MAX_Z_INDEX}`;
                            svg.style.width = `${width}px`;
                            svg.style.height = `${height}px`;
                            svg.style.pointerEvents = 'none';
                            svg.style.transform = 'translate(-50%, -50%)';
                            if (this._mode === 'remove' || this._mode === 'change') {
                                this._prevent_pointer_enter = true;
                                this._remove_spec(spec_id);
                            }
                            const { units } = spec;
                            for (const unit_id in units) {
                                const { x, y } = spec_item_screen_minigraph._nodes[unit_id];
                                units[unit_id].metadata = units[unit_id].metadata || {};
                                units[unit_id].metadata.position = { x, y };
                            }
                            const onDrag = (screenX, screenY) => {
                                svg.style.left = `${screenX}px`;
                                svg.style.top = `${screenY}px`;
                            };
                            const onDrop = (foundTarget) => {
                                //
                            };
                            const cancelDnd = (0, dnd_1.startDragAndDrop)(this.$system, svg, pointerId, bundle, onDrag, onDrop);
                            const new_spec = (0, object_1.clone)((0, emptySpec_1.emptyGraphSpec)());
                            new_spec.name = '';
                            const new_bundle = {
                                spec: new_spec,
                                specs: {},
                            };
                            this._add_new_spec_item(new_bundle);
                        }
                    },
                }),
            ]);
            const unlisten = (0, callAll_1.callAll)([unlisten_item, unlisten_name]);
            this._spec_item_unlisten[spec_id] = unlisten;
            return spec_item;
        };
        this._enable_modes = () => {
            // console.log('GraphControl', '_enable_modes')
            this._unlisten_mode_keyboard = (0, modes_1.enableModeKeyboard)(this._root, (mode) => {
                this._set_mode(mode);
                if (this._modes) {
                    this._modes.setProp('mode', mode);
                }
            });
        };
        this._disable_modes = () => {
            // console.log('GraphControl', '_disable_modes')
            if (this._unlisten_mode_keyboard) {
                this._unlisten_mode_keyboard();
                this._unlisten_mode_keyboard = undefined;
            }
        };
        this._add_spec = (spec_id, bundle, style = {}, i) => {
            this._store_add_spec(spec_id, bundle);
            this._ui_add_spec(spec_id, bundle, style, i);
        };
        this._store_add_spec = (id, bundle) => {
            const { proxy } = this._client;
            proxy.add(id, bundle);
            if (channel) {
                channel.postMessage({ type: 'add', data: { id, data: bundle } });
            }
        };
        this._ui_add_spec = (spec_id, bundle, style = {}, i = 0) => {
            const spec_item = this._render_spec_item(bundle, spec_id, style);
            this._spec_item[spec_id] = spec_item;
            this._root.insertChild(spec_item, i);
            return spec_item;
        };
        this._remove_spec = (spec_id) => {
            this._ui_delete_spec(spec_id);
            this._store_remove_spec(spec_id);
        };
        this._store_remove_spec = (id) => {
            const { proxy } = this._client;
            proxy.delete(id);
            if (channel) {
                channel.postMessage({ type: 'delete', data: { id } });
            }
        };
        this._refresh_spec_item_name_width = (spec_id, value) => {
            const spec_item_name = this._spec_item_name[spec_id];
            const name_width = (0, getPlainTextWidth_1.getTextWidth)(value, 15, Component_3.UNIT_NAME_MAX_SIZE);
            (0, mergeStyle_1.default)(spec_item_name, { width: `${name_width + 4}px` });
        };
        this._refresh_spec_item_color = (spec_id) => {
            const { $theme } = this.$context;
            const spec_item = this._spec_item[spec_id];
            let color;
            if (spec_item === this._hovered_spec_item) {
                color = (0, theme_1.getThemeModeColor)($theme, this._mode, 'currentColor');
            }
            else if (this._new_spec_item === spec_item) {
                color = (0, theme_1.getThemeModeColor)($theme, this._mode, 'currentColor');
            }
            else {
                color = 'currentColor';
            }
            (0, mergeStyle_1.default)(spec_item, { color });
        };
        this._ui_delete_spec = (spec_id) => {
            const spec_item = this._spec_item[spec_id];
            delete this._spec_item[spec_id];
            delete this._spec_item_name[spec_id];
            delete this._spec_item_minigraph[spec_id];
            delete this._spec_item_bundle[spec_id];
            const unlisten = this._spec_item_unlisten[spec_id];
            delete this._spec_item_unlisten[spec_id];
            unlisten();
            this._root.removeChild(spec_item);
        };
        this._hover_item = (spec_id) => {
            // console.log('SaveControl', '_hover_item', spec_id)
            this._hovered_spec_id = spec_id;
            this._hovered_spec_item = this._spec_item[spec_id];
            this._refresh_spec_item_color(spec_id);
        };
        this._unhover_item = () => {
            // console.log('SaveControl', '_unhover_item')
            const spec_id = this._hovered_spec_id;
            if (spec_id) {
                this._hovered_spec_id = null;
                this._hovered_spec_item = null;
                if (this._spec_item[spec_id]) {
                    this._refresh_spec_item_color(spec_id);
                }
            }
        };
        this._set_mode = (mode) => {
            this._mode = mode;
            const { $theme } = this.$context;
            if (mode === 'add' ||
                mode === 'remove' ||
                mode === 'change' ||
                mode === 'data') {
                (0, mergeStyle_1.default)(this._root, {
                    touchAction: 'none',
                });
            }
            else {
                (0, mergeStyle_1.default)(this._root, {
                    touchAction: 'auto',
                });
                if (this._hovered_spec_item) {
                    this._unhover_item();
                }
                if (this._new_spec_item) {
                    this._remove_new_spec_item();
                }
            }
            for (const spec_id in this._spec_item) {
                this._refresh_spec_item_pointer_events(spec_id);
            }
            if (this._hovered_spec_item) {
                (0, mergeStyle_1.default)(this._hovered_spec_item, {
                    color: (0, theme_1.getThemeModeColor)($theme, this._mode, 'currentColor'),
                });
            }
        };
        this._on_graph_mode = ({ mode }) => {
            // console.log('SaveControl', '_on_graph_mode', mode)
            this._set_mode(mode);
        };
        this._add_new_spec_item = (bundle, i = 0) => {
            // console.log('Save', '_add_new_spec_item')
            if (this._new_spec_item) {
                return;
            }
            const { $theme } = this.$context;
            const spec_id = (0, id_1.uuid)();
            const spec_item = this._ui_add_spec(spec_id, bundle, { pointerEvents: 'none' }, i);
            this._new_spec_item = spec_item;
            this._new_spec_item_spec_id = spec_id;
        };
        this._remove_new_spec_item = () => {
            // console.log('Save', '_remove_new_spec_item')
            const i = this._root.removeChild(this._new_spec_item);
            this._new_spec_item = null;
            this._new_spec_item_spec_id = null;
            return i;
        };
        this._move_new_spec_item = (i) => {
            // console.log('Save', '_move_new_spec_item', i)
            if (this._new_spec_item) {
                this._root.removeChild(this._new_spec_item);
                this._root.insertChild(this._new_spec_item, i);
            }
        };
        this._swap_new_spec_item = (bundle) => {
            // console.log('GraphControl', '_swap_new_spec_item')
            const i = this._remove_new_spec_item();
            const { spec } = bundle;
            const { id } = spec;
            let next_id = id;
            if (this._spec_item_bundle[id]) {
                next_id = (0, id_1.uuid)();
                bundle.spec.id = next_id;
            }
            this._ui_add_spec(id, bundle, {}, i);
            this._refresh_spec_item_name_width(id, spec.name);
            this._refresh_spec_item_color(id);
        };
        this._refresh_spec_item_pointer_events = (spec_id) => {
            // console.log('GraphControl', '_refresh_spec_item_pointer_events')
            const spec_item_name = this._spec_item_name[spec_id];
            const spec_item = this._spec_item[spec_id];
            if (this._mode === 'none') {
                (0, mergeStyle_1.default)(spec_item, { pointerEvents: 'none' });
                (0, mergeStyle_1.default)(spec_item_name, { pointerEvents: 'all' });
            }
            else {
                (0, mergeStyle_1.default)(spec_item, { pointerEvents: 'auto' });
                (0, mergeStyle_1.default)(spec_item_name, { pointerEvents: 'none' });
            }
        };
        this._listen_store = () => {
            const { emitter } = this._client;
            emitter.addListener('reset', (bundles) => {
                this._root.removeChildren();
                (0, f_1.default)(bundles, (bundle, bundle_id) => {
                    this._ui_add_spec(bundle_id, bundle);
                });
            });
            emitter.addListener('add', (bundle_id, bundle) => {
                this._ui_add_spec(bundle_id, bundle);
            });
            emitter.addListener('put', (bundle_id, bundle) => {
                // TODO
                this._ui_delete_spec(bundle_id);
                this._ui_add_spec(bundle_id, bundle);
            });
            emitter.addListener('delete', (bundle_id) => {
                this._ui_delete_spec(bundle_id);
            });
        };
        this._fill_list = () => {
            const { proxy } = this._client;
            proxy
                .getAll()
                .then((bundles) => {
                (0, f_1.default)(bundles, (spec, spec_id) => {
                    this._ui_add_spec(spec_id, spec);
                });
            })
                .catch((err) => {
                // TODO
                console.log('err', err);
            });
        };
        const { theme } = $system;
        const { style } = $props;
        const root = new Component_1.default({
            className: 'save-control-root',
            style: Object.assign({ position: 'absolute', border: '1px solid currentColor', borderRadius: '3px', padding: '9px', overflow: 'auto', background: (0, theme_1.setAlpha)((0, theme_1.themeBackgroundColor)(theme), 0.75) }, style),
            tabIndex: 0,
        }, this.$system);
        this._root = root;
        const _relative_i = (clientY) => {
            const list_relative_position = (0, getPosition_1.getRelativePosition)(this._root.$element, this.$context.$element);
            const list_position = (0, geometry_1.addVector)({ x: this.$context.$x, y: this.$context.$y }, list_relative_position);
            const dy = clientY - list_position.y;
            const i = Math.floor(dy / exports.SPEC_ITEM_HEIGHT);
            return i;
        };
        // RETURN
        this._root.$element.setAttribute('dropTarget', 'true');
        this._root.addEventListeners([
            (0, dragenter_1.makeDragEnterListener)((event) => {
                const { clientY } = event;
                if (this._mode === 'add' ||
                    this._mode === 'data' ||
                    this._mode === 'remove') {
                    const i = _relative_i(clientY);
                    this._add_new_spec_item((0, emptyBundleSpec_1.emptyBundleSpec)({ spec: { name: '' } }), i);
                }
            }),
            (0, dragover_1.makeDragOverListener)((event) => {
                const { clientY } = event;
                if (this._mode === 'add' ||
                    this._mode === 'data' ||
                    this._mode === 'remove') {
                    const i = _relative_i(clientY);
                    this._move_new_spec_item(i);
                }
            }),
            (0, dragdrop_1.makeDragDropListener)((event) => {
                const { pointerType, data: bundle } = event;
                let spec_id;
                const drop_new_spec_item = () => {
                    if (this._new_spec_item) {
                        spec_id = this._new_spec_item_spec_id;
                        this._swap_new_spec_item(bundle);
                        this._store_add_spec(spec_id, bundle);
                    }
                };
                const drag_back_bundle = {};
                if (this._mode === 'remove' ||
                    this._mode === 'change' ||
                    this._mode === 'add' ||
                    this._mode === 'data') {
                    drop_new_spec_item();
                }
                if (spec_id) {
                    if (pointerType === 'mouse') {
                        this._hover_item(spec_id);
                    }
                    // if (this._mode === 'change') {
                    // }
                }
                this._root.dispatchEvent('dragback', drag_back_bundle);
            }),
            (0, dragleave_1.makeDragLeaveListener)((event) => {
                if (this._mode === 'add' || this._mode === 'remove') {
                    if (this._new_spec_item) {
                        this._remove_new_spec_item();
                    }
                }
            }),
        ]);
        const $element = (0, parentElement_1.default)($system);
        this.$element = $element;
        this.$slot = {
            default: root,
        };
        this.$subComponent = {
            root,
        };
        this.$unbundled = false;
        this.registerRoot(root);
    }
    onPropChanged(prop, current) {
        if (prop === 'style') {
            this._root.setProp('style', Object.assign(Object.assign({}, exports.DEFAULT_STYLE), current));
        }
    }
    onMount() {
        const { graph } = this.$system;
        const graph_service = graph(this.$system, {});
        const modes = (0, findRef_1.findRef)(this, 'modes');
        this._modes = modes;
        this._enable_modes();
        const graphEnterModeListener = (0, custom_1.makeCustomListener)('_graph_mode', this._on_graph_mode, true);
        this._context_unlisten = (0, addListener_1.addListeners)(this.$context, [
            graphEnterModeListener,
            (0, custom_1.makeCustomListener)('themechanged', () => {
                const { $theme } = this.$context;
                (0, mergeStyle_1.default)(this._root, {
                    background: (0, theme_1.setAlpha)((0, theme_1.themeBackgroundColor)($theme), 0.75),
                });
            }),
        ]);
        const store = graph_service;
        this._client = store.connect();
        this._fill_list();
        this._listen_store();
        const on_message = (data) => {
            const { type, data: _data } = data;
            switch (type) {
                case 'add':
                    {
                        const { id, data } = _data;
                        this._ui_add_spec(id, data);
                    }
                    break;
                case 'delete':
                    {
                        const { id } = _data;
                        this._ui_delete_spec(id);
                    }
                    break;
                case 'put':
                    {
                        const { id, data } = _data;
                        // TODO put
                        this._ui_delete_spec(id);
                        this._ui_add_spec(id, data);
                    }
                    break;
            }
        };
        if (channel) {
            channel.addEventListener('message', (event) => {
                const { source, data } = event;
                if (source !== window) {
                    on_message(data);
                }
            });
        }
    }
    onUnmount() {
        this._disable_modes();
        this._context_unlisten();
    }
}
exports.default = SaveControl;
//# sourceMappingURL=Component.js.map