import { assertExists, DisposableGroup, Slot } from '@blockeditor/global/utils';
import {} from '@blockeditor/store';
import { isCssVariable, } from '../_common/theme/css-variables.js';
import { getThemePropertyValue } from '../_common/theme/utils.js';
import { ConnectorPathGenerator } from '../page-block/edgeless/connector-manager.js';
import { ConnectorElement, ElementCtors, Renderer, } from '../surface-block/index.js';
import { getGroupParent, setGroupParent, } from '../surface-block/managers/group-manager.js';
import { LayerManager } from '../surface-block/managers/layer-manager.js';
import { getSurfaceBlock } from './utils.js';
export class SurfaceRefRenderer {
    get surfaceRenderer() {
        return this._surfaceRenderer;
    }
    get elements() {
        return this._elements;
    }
    get connectorManager() {
        return this._connectorManager;
    }
    constructor(id, page, std) {
        this.id = id;
        this.page = page;
        this.std = std;
        this._surfaceModel = null;
        this._disposables = new DisposableGroup();
        this.slots = {
            surfaceRendererInit: new Slot(),
            surfaceRendererRefresh: new Slot(),
            surfaceModelChanged: new Slot(),
            mounted: new Slot(),
            unmounted: new Slot(),
        };
        this._onElementsChange = (event, elementsMap) => {
            // skip empty event
            if (event.changes.keys.size === 0)
                return;
            const connectors = [];
            event.keysChanged.forEach(id => {
                const change = event.changes.keys.get(id);
                if (!change) {
                    console.error('invalid event', event);
                    return;
                }
                if (change.action === 'add' &&
                    elementsMap.get(id)?.get('type') === 'connector') {
                    connectors.push({ change, id });
                    return;
                }
                else {
                    this._onYEvent(change, id, elementsMap);
                }
            });
            connectors.forEach(({ change, id }) => this._onYEvent(change, id, elementsMap));
        };
        this._onYEvent = (type, id, elementsMap) => {
            if (type.action === 'add') {
                const yElement = elementsMap.get(id);
                const type = yElement.get('type');
                const ElementCtor = ElementCtors[type];
                assertExists(ElementCtor);
                const element = new ElementCtor(yElement, {
                    onElementUpdated() { },
                    pickById: id => this.getModel(id),
                    getGroupParent: getGroupParent,
                    setGroupParent: setGroupParent,
                    removeElement: () => { },
                });
                element.computedValue = this._getCSSPropertyValue;
                element.mount(this._surfaceRenderer);
                this._elements.set(element.id, element);
                this._onElementUpdatedOrAdded(element.id);
            }
            else if (type.action === 'update') {
                console.error('update event on yElements is not supported', event);
            }
            else if (type.action === 'delete') {
                const element = this._elements.get(id);
                if (element) {
                    element.unmount();
                    this._elements.delete(id);
                }
            }
            this._surfaceRenderer.refresh();
            this.slots.surfaceRendererRefresh.emit();
        };
        this._getCSSPropertyValue = (value) => {
            const root = this.std.host;
            if (isCssVariable(value)) {
                const cssValue = getThemePropertyValue(root, value);
                if (cssValue === undefined) {
                    console.error(new Error(`All variables should have a value. Please check for any dirty data or variable renaming.Variable: ${value}`));
                }
                return cssValue ?? value;
            }
            return value;
        };
        const layerManager = new LayerManager();
        const renderer = new Renderer({ layerManager });
        this._surfaceRenderer = renderer;
        this._connectorManager = new ConnectorPathGenerator({
            pickById: id => this.getModel(id),
            refresh: () => renderer.refresh(),
        });
        this._elements = new Map();
    }
    mount() {
        if (this._disposables.disposed) {
            this._disposables = new DisposableGroup();
        }
        this._initSurfaceModel();
        this._initSurfaceRenderer();
        this.slots.mounted.emit();
    }
    unmount() {
        this._disposables.dispose();
        this.slots.unmounted.emit();
    }
    getModel(id) {
        return (this.page.getBlockById(id) ??
            this._elements.get(id) ??
            null);
    }
    _initSurfaceRenderer() {
        this._surfaceRenderer.layerManager.init([
            ...this._elements.values(),
            ...(this._surfaceModel?.children || []),
            ...this.page.getBlockByFlavour('affine:note'),
        ]);
        this.slots.surfaceRendererInit.emit();
    }
    _initSurfaceModel() {
        const init = () => {
            const model = getSurfaceBlock(this.page);
            this._surfaceModel = model;
            this.slots.surfaceModelChanged.emit(model);
            if (!this._surfaceModel)
                return;
            const elementsMap = this._surfaceModel.elements.getValue();
            const onElementsChange = (event) => {
                this._onElementsChange(event, elementsMap);
            };
            elementsMap.observe(onElementsChange);
            this._disposables.add(() => elementsMap.unobserve(onElementsChange));
            this._syncFromExistingContainer(elementsMap);
        };
        init();
        if (!this._surfaceModel) {
            this._disposables.add(this.page.slots.blockUpdated.on(({ type }) => {
                if (type === 'add' &&
                    !this._surfaceModel &&
                    getSurfaceBlock(this.page)) {
                    init();
                }
            }));
        }
    }
    _syncFromExistingContainer(elementsMap) {
        elementsMap.doc?.transact(() => {
            const yConnectors = [];
            const yGroups = [];
            elementsMap.forEach(yElement => {
                const type = yElement.get('type');
                if (type === 'connector') {
                    yConnectors.push(yElement);
                    return;
                }
                if (type === 'group') {
                    yGroups.push(yElement);
                    return;
                }
                this._createElementFromYMap(yElement);
            });
            yConnectors.forEach(yElement => {
                this._createElementFromYMap(yElement);
            });
            yGroups.forEach(yElement => {
                this._createElementFromYMap(yElement);
            });
        });
    }
    _createElementFromYMap(yElement) {
        const type = yElement.get('type');
        const ElementCtor = ElementCtors[type];
        assertExists(ElementCtor);
        const element = new ElementCtor(yElement, {
            onElementUpdated: ({ id }) => {
                const element = this.getModel(id);
                if (element instanceof ConnectorElement &&
                    !this._connectorManager.hasRelatedElement(element)) {
                    this._connectorManager.updatePath(element);
                }
            },
            removeElement: () => { },
            pickById: id => this.getModel(id),
            getGroupParent: getGroupParent,
            setGroupParent: setGroupParent,
        });
        element.computedValue = this._getCSSPropertyValue;
        element.mount(this._surfaceRenderer);
        this._elements.set(element.id, element);
        this._onElementUpdatedOrAdded(element.id);
    }
    _onElementUpdatedOrAdded(id) {
        const element = this.getModel(id);
        if (element instanceof ConnectorElement &&
            this._connectorManager.hasRelatedElement(element)) {
            this._connectorManager.updatePath(element);
        }
    }
}
//# sourceMappingURL=surface-ref-renderer.js.map