"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FormulaEditor = void 0;
const i18n_1 = __importDefault(require("../../../../../../../locales/i18n.js"));
/**
 * 公式编辑器 - 基于 CodeMirror 6
 * 支持函数自动补全、字段引用 Tag、语法高亮
 */
const react_1 = __importStar(require("react"));
const view_1 = require("@codemirror/view");
const state_1 = require("@codemirror/state");
const commands_1 = require("@codemirror/commands");
const autocomplete_1 = require("@codemirror/autocomplete");
const language_1 = require("@codemirror/language");
const formula_constants_1 = require("./formula-constants");
require("./formula-editor.scss");
function createFieldTagWidget(fieldTitle, dataIndex) {
    const widget = Object.create(view_1.WidgetType.prototype);
    widget.fieldTitle = fieldTitle;
    widget.dataIndex = dataIndex;
    widget.toDOM = () => {
        const span = document.createElement('span');
        span.className = 'formula-field-tag';
        span.textContent = fieldTitle;
        span.setAttribute('data-field', dataIndex);
        span.contentEditable = 'false';
        return span;
    };
    widget.eq = (other) => dataIndex === other.dataIndex;
    widget.ignoreEvent = () => false;
    return widget;
}
function buildFieldDecorations(view, fields) {
    const decorations = [];
    const doc = view.state.doc.toString();
    const regex = /\{([^}]+)\}/g;
    let match;
    while ((match = regex.exec(doc)) !== null) {
        const dataIndex = match[1].trim();
        const field = fields.find((f) => f.dataIndex === dataIndex);
        if (field) {
            decorations.push({
                from: match.index,
                to: match.index + match[0].length,
                deco: view_1.Decoration.replace({
                    widget: createFieldTagWidget(field.title, field.dataIndex),
                }),
            });
        }
    }
    decorations.sort((a, b) => a.from - b.from);
    return view_1.Decoration.set(decorations.map((d) => d.deco.range(d.from, d.to)));
}
function createFieldDecoPlugin(fields) {
    return view_1.ViewPlugin.fromClass(class {
        constructor(view) {
            this.decorations = buildFieldDecorations(view, fields);
        }
        update(update) {
            if (update.docChanged || update.viewportChanged) {
                this.decorations = buildFieldDecorations(update.view, fields);
            }
        }
    }, {
        decorations: (v) => v.decorations,
    });
}
function findFieldTokenAt(doc, pos) {
    const regex = /\{([^}]+)\}/g;
    let match;
    while ((match = regex.exec(doc)) !== null) {
        const from = match.index;
        const to = from + match[0].length;
        if (pos > from && pos <= to)
            return { from, to };
    }
    return null;
}
function findFieldTokenForDelete(doc, pos) {
    const regex = /\{([^}]+)\}/g;
    let match;
    while ((match = regex.exec(doc)) !== null) {
        const from = match.index;
        const to = from + match[0].length;
        if (pos >= from && pos < to)
            return { from, to };
    }
    return null;
}
function normalizeFieldReferences(text, fields) {
    const titleToDataIndex = new Map();
    for (const f of fields) {
        titleToDataIndex.set(f.title, f.dataIndex);
        titleToDataIndex.set(f.dataIndex, f.dataIndex);
    }
    return text.replace(/[{[\[]([^\]{}]+)[}\]]/g, (_match, inner) => {
        const trimmed = inner.trim();
        const dataIndex = titleToDataIndex.get(trimmed);
        if (dataIndex)
            return `{${dataIndex}}`;
        return _match;
    });
}
function createPasteHandler(fields) {
    return view_1.EditorView.domEventHandlers({
        paste(event, view) {
            var _a;
            const clipboardText = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text/plain');
            if (!clipboardText)
                return false;
            const normalized = normalizeFieldReferences(clipboardText, fields);
            if (normalized === clipboardText)
                return false;
            event.preventDefault();
            const { from, to } = view.state.selection.main;
            view.dispatch({
                changes: { from, to, insert: normalized },
                selection: { anchor: from + normalized.length },
            });
            return true;
        },
    });
}
function createFormulaCompletionSource(fields) {
    return (context) => {
        const word = context.matchBefore(/[\w\u4e00-\u9fff\u3400-\u4dbf]+/);
        if (!word)
            return null;
        if (word.from === word.to && !context.explicit)
            return null;
        const inputText = word.text.toLowerCase();
        const functionOptions = formula_constants_1.FORMULA_FUNCTIONS.filter((fn) => fn.name.toLowerCase().includes(inputText)).map((fn) => ({
            label: fn.name,
            type: 'function',
            detail: fn.description,
            info: i18n_1.default.get({
                id: 'ExampleFnExample',
                dm: '{fnSyntax}\\n示例: {fnExample}',
            }, { fnSyntax: fn.syntax, fnExample: fn.example }),
            apply: `${fn.name}(`,
            boost: fn.category === 'math' ? 2 : fn.category === 'logical' ? 1 : 0,
        }));
        const fieldOptions = fields
            .filter((field) => field.title.toLowerCase().includes(inputText))
            .map((field) => ({
            label: field.title,
            type: 'variable',
            detail: field.component || i18n_1.default.get({ id: 'FieldReference', dm: '字段引用' }),
            apply: `{${field.dataIndex}}`,
            boost: 3,
        }));
        return {
            from: word.from,
            options: [...fieldOptions, ...functionOptions],
            filter: false,
        };
    };
}
exports.FormulaEditor = (0, react_1.forwardRef)(({ value = '', onChange, fields = [], placeholder, hasError, minHeight = 120 }, ref) => {
    const containerRef = (0, react_1.useRef)(null);
    const viewRef = (0, react_1.useRef)(null);
    const fieldsRef = (0, react_1.useRef)(fields);
    const onChangeRef = (0, react_1.useRef)(onChange);
    const fieldDecoCompartment = (0, react_1.useRef)(new state_1.Compartment());
    const autocompletionCompartment = (0, react_1.useRef)(new state_1.Compartment());
    const pasteHandlerCompartment = (0, react_1.useRef)(new state_1.Compartment());
    fieldsRef.current = fields;
    onChangeRef.current = onChange;
    (0, react_1.useImperativeHandle)(ref, () => ({
        focus: () => { var _a; return (_a = viewRef.current) === null || _a === void 0 ? void 0 : _a.focus(); },
        insertField: (field) => {
            const view = viewRef.current;
            if (!view)
                return;
            const { from, to } = view.state.selection.main;
            const insert = `{${field.dataIndex}}`;
            view.dispatch({
                changes: { from, to, insert },
                selection: { anchor: from + insert.length },
            });
            view.focus();
        },
        insertText: (text) => {
            const view = viewRef.current;
            if (!view)
                return;
            const { from, to } = view.state.selection.main;
            view.dispatch({
                changes: { from, to, insert: text },
                selection: { anchor: from + text.length },
            });
            view.focus();
        },
        getValue: () => { var _a; return ((_a = viewRef.current) === null || _a === void 0 ? void 0 : _a.state.doc.toString()) || ''; },
    }));
    const createView = (0, react_1.useCallback)(() => {
        if (!containerRef.current)
            return;
        if (viewRef.current) {
            viewRef.current.destroy();
        }
        const updateListener = view_1.EditorView.updateListener.of((update) => {
            var _a;
            if (update.docChanged) {
                (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, update.state.doc.toString());
            }
        });
        const placeholderExt = placeholder
            ? view_1.EditorView.contentAttributes.of({ 'data-placeholder': placeholder })
            : [];
        const atomicFieldKeymap = view_1.keymap.of([
            {
                key: 'ArrowLeft',
                run: (view) => {
                    const { from, to } = view.state.selection.main;
                    if (from !== to)
                        return false;
                    const doc = view.state.doc.toString();
                    const token = findFieldTokenAt(doc, from);
                    if (!token)
                        return false;
                    view.dispatch({ selection: { anchor: token.from } });
                    return true;
                },
            },
            {
                key: 'ArrowRight',
                run: (view) => {
                    const { from, to } = view.state.selection.main;
                    if (from !== to)
                        return false;
                    const doc = view.state.doc.toString();
                    const token = findFieldTokenForDelete(doc, from);
                    if (!token)
                        return false;
                    view.dispatch({ selection: { anchor: token.to } });
                    return true;
                },
            },
            {
                key: 'Backspace',
                run: (view) => {
                    const { from, to } = view.state.selection.main;
                    if (from !== to)
                        return false;
                    const doc = view.state.doc.toString();
                    const token = findFieldTokenAt(doc, from);
                    if (!token)
                        return false;
                    view.dispatch({ changes: { from: token.from, to: token.to } });
                    return true;
                },
            },
            {
                key: 'Delete',
                run: (view) => {
                    const { from, to } = view.state.selection.main;
                    if (from !== to)
                        return false;
                    const doc = view.state.doc.toString();
                    const token = findFieldTokenForDelete(doc, from);
                    if (!token)
                        return false;
                    view.dispatch({ changes: { from: token.from, to: token.to } });
                    return true;
                },
            },
        ]);
        const state = state_1.EditorState.create({
            doc: value,
            extensions: [
                (0, commands_1.history)(),
                (0, language_1.bracketMatching)(),
                autocompletionCompartment.current.of((0, autocomplete_1.autocompletion)({
                    override: [createFormulaCompletionSource(fields)],
                    activateOnTyping: true,
                })),
                atomicFieldKeymap,
                view_1.keymap.of([...commands_1.defaultKeymap, ...commands_1.historyKeymap]),
                fieldDecoCompartment.current.of(createFieldDecoPlugin(fields)),
                pasteHandlerCompartment.current.of(createPasteHandler(fields)),
                updateListener,
                placeholderExt,
                view_1.EditorView.lineWrapping,
                view_1.EditorView.theme({
                    '&': {
                        minHeight: `${minHeight}px`,
                        maxHeight: '300px',
                        fontSize: '13px',
                        fontFamily: 'Menlo, Monaco, "Courier New", monospace',
                    },
                    '.cm-content': {
                        padding: '12px',
                        minHeight: `${minHeight - 24}px`,
                    },
                    '.cm-scroller': {
                        overflow: 'auto',
                    },
                    '&.cm-focused': {
                        outline: 'none',
                    },
                    '.cm-placeholder': {
                        color: '#bbb',
                        fontStyle: 'italic',
                    },
                }),
            ],
        });
        const view = new view_1.EditorView({
            state,
            parent: containerRef.current,
        });
        viewRef.current = view;
    }, []); // eslint-disable-line react-hooks/exhaustive-deps
    (0, react_1.useEffect)(() => {
        createView();
        return () => {
            var _a;
            (_a = viewRef.current) === null || _a === void 0 ? void 0 : _a.destroy();
            viewRef.current = null;
        };
    }, [createView]);
    (0, react_1.useEffect)(() => {
        const view = viewRef.current;
        if (!view)
            return;
        view.dispatch({
            effects: [
                fieldDecoCompartment.current.reconfigure(createFieldDecoPlugin(fields)),
                autocompletionCompartment.current.reconfigure((0, autocomplete_1.autocompletion)({
                    override: [createFormulaCompletionSource(fields)],
                    activateOnTyping: true,
                })),
                pasteHandlerCompartment.current.reconfigure(createPasteHandler(fields)),
            ],
        });
    }, [fields]);
    (0, react_1.useEffect)(() => {
        const view = viewRef.current;
        if (!view)
            return;
        const currentValue = view.state.doc.toString();
        if (currentValue !== value) {
            view.dispatch({
                changes: { from: 0, to: currentValue.length, insert: value },
            });
        }
    }, [value]);
    return (react_1.default.createElement("div", { ref: containerRef, className: `formula-editor-container${hasError ? ' formula-editor-error' : ''}` }));
});
exports.FormulaEditor.displayName = 'FormulaEditor';
