import { h, } from "@stencil/core";
import { PlatformServiceName, } from "@limetech/lime-web-components";
import { deleteNode, replaceNode, updateArgs, } from "./rule-operations";
import { extractGroup, RuleNodeView, } from "./rule-editor-views";
import { createPrimitiveOptions } from "./view-helpers";
/**
 * Tree editor for {@link Rule}.
 *
 * Renders a `Rule` value as a tree of nodes. Lets an author pick
 * primitive refs in `all` / `any` groups, toggle negation, delete
 * nodes, and edit per-primitive args via the editor declared on each
 * primitive's metadata.
 *
 * The component is value-in / value-out. Pass `value` and listen for
 * `change`; persistence is the caller's concern.
 *
 * Pass `availableContexts` to forward the list of context keys the
 * caller knows will be available where the rule runs. The component
 * forwards it to `RuleRegistry.validate` so `missing-slot` diagnostics
 * surface inline on the offending nodes.
 *
 * @exampleComponent limebb-example-rule-editor
 *
 * @alpha
 * @private
 */
export class RuleEditor {
    constructor() {
        this.issues = [];
        this.openChipPath = null;
        this.metadataById = new Map();
        this.primitiveOptions = [];
        this.normalizedCache = null;
        this.handleDelete = (path) => {
            this.openChipPath = null;
            this.emitIfChanged(deleteNode(this.normalizedValue, path));
        };
        this.handleUpdateArgs = (path, args) => {
            this.emitIfChanged(updateArgs(this.normalizedValue, path, args));
        };
        this.handleReplaceNode = (path, replacement) => {
            this.emitIfChanged(replaceNode(this.normalizedValue, path, replacement));
        };
        this.handleChipInteract = (path) => {
            this.openChipPath = path;
        };
        this.handlePopoverClose = () => {
            this.openChipPath = null;
        };
    }
    componentWillLoad() {
        this.indexMetadata();
        this.runValidation();
    }
    componentDidLoad() {
        this.emitNormalization();
    }
    onValueChange() {
        if (this.emitNormalization()) {
            return;
        }
        this.runValidation();
    }
    onAvailableContextsChange() {
        this.runValidation();
    }
    render() {
        const containerClasses = {
            'rule-editor': true,
            'rule-editor--disabled': this.disabled,
        };
        return (h("div", { key: '8e0da39f6544c86de0143e91a6c48c9f2654315f', class: containerClasses }, this.renderLabel(), h(RuleNodeView, { key: 'cd093f5dc5a9b747036f581e6230f1205cf834e6', node: this.normalizedValue, path: [], ctx: this.createRenderContext() }), this.renderHelperText()));
    }
    renderLabel() {
        if (!this.label) {
            return null;
        }
        return h("div", { class: "rule-editor__label" }, this.label);
    }
    renderHelperText() {
        if (!this.helperText) {
            return null;
        }
        return h("div", { class: "rule-editor__helper-text" }, this.helperText);
    }
    createRenderContext() {
        return {
            issues: this.issues,
            metadataById: this.metadataById,
            primitiveOptions: this.primitiveOptions,
            isMutable: !this.readonly && !this.disabled,
            readonly: this.readonly,
            disabled: this.disabled,
            platform: this.platform,
            context: this.context,
            translator: this.platform.get(PlatformServiceName.Translate),
            openChipPath: this.openChipPath,
            onDelete: this.handleDelete,
            onUpdateArgs: this.handleUpdateArgs,
            onReplaceNode: this.handleReplaceNode,
            onChipInteract: this.handleChipInteract,
            onPopoverClose: this.handlePopoverClose,
        };
    }
    indexMetadata() {
        var _a, _b, _c;
        const metadata = (_c = (_b = (_a = this.ruleRegistry).listMetadata) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : [];
        this.metadataById = new Map(metadata.map((item) => [item.id, item]));
        this.primitiveOptions = createPrimitiveOptions(this.metadataById, this.platform.get(PlatformServiceName.Translate));
    }
    runValidation() {
        if (!this.value) {
            this.issues = [];
            return;
        }
        const result = this.ruleRegistry.validate(this.normalizedValue, this.availableContexts);
        this.issues = result.ok ? [] : result.issues;
    }
    emitIfChanged(next) {
        if (next === this.normalizedValue) {
            return;
        }
        this.change.emit(collapseEmptyRoot(next));
    }
    /**
     * Wrap any non-group `value` in a top-level `all` so the editor
     * always has a group to render against. When `value` is unset,
     * returns a fresh empty `all`-group as a render-only placeholder —
     * `emitNormalization` will *not* push that placeholder back to the
     * caller, so an unset value stays unset until the user edits.
     *
     * Memoized so callers that read this several times in one handler
     * see the same reference — the identity check in `emitIfChanged`
     * depends on it.
     */
    get normalizedValue() {
        if (this.normalizedCache !== null &&
            this.normalizedCache.source === this.value) {
            return this.normalizedCache.normalized;
        }
        const normalized = normalize(this.value);
        this.normalizedCache = { source: this.value, normalized };
        return normalized;
    }
    /**
     * If `value` wasn't already a group, emit the normalized form so
     * the caller's stored state catches up. Skipped when `value` is
     * unset — we render a placeholder group locally but don't push it
     * back to the caller, so callers can't tell "no rule yet" apart
     * from "user cleared a rule" by inspecting the emitted shape.
     * Returns `true` when an emit happened so callers can short-circuit.
     */
    emitNormalization() {
        if (!this.value) {
            return false;
        }
        const normalized = this.normalizedValue;
        if (normalized === this.value) {
            return false;
        }
        this.change.emit(normalized);
        return true;
    }
    get ruleRegistry() {
        return this.platform.get(PlatformServiceName.RuleRegistry);
    }
    static get is() { return "limebb-rule-editor"; }
    static get encapsulation() { return "shadow"; }
    static get originalStyleUrls() {
        return {
            "$": ["rule-editor.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["rule-editor.css"]
        };
    }
    static get properties() {
        return {
            "platform": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "LimeWebComponentPlatform",
                    "resolved": "LimeWebComponentPlatform",
                    "references": {
                        "LimeWebComponentPlatform": {
                            "location": "import",
                            "path": "@limetech/lime-web-components",
                            "id": "node_modules::LimeWebComponentPlatform",
                            "referenceLocation": "LimeWebComponentPlatform"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Reference to the platform service container.\n\nUse this to access all platform services like repositories, HTTP client,\ncommand bus, navigator, and more. The platform instance is shared across\nall components in the same application context."
                },
                "getter": false,
                "setter": false
            },
            "context": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "LimeWebComponentContext",
                    "resolved": "LimeWebComponentContext",
                    "references": {
                        "LimeWebComponentContext": {
                            "location": "import",
                            "path": "@limetech/lime-web-components",
                            "id": "node_modules::LimeWebComponentContext",
                            "referenceLocation": "LimeWebComponentContext"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The context describing where this component is running.\n\nThe context provides information about the current limetype and record ID\n(if viewing/editing a specific record). Components can use this to load\nrelevant data and understand their operating environment."
                },
                "getter": false,
                "setter": false
            },
            "value": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "Rule | undefined",
                    "resolved": "RuleAll | RuleAny | RuleNot | RuleRef | undefined",
                    "references": {
                        "Rule": {
                            "location": "import",
                            "path": "@limetech/lime-web-components",
                            "id": "node_modules::Rule",
                            "referenceLocation": "Rule"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "inheritdoc",
                            "text": undefined
                        }],
                    "text": "The value of the current property"
                },
                "getter": false,
                "setter": false
            },
            "availableContexts": {
                "type": "unknown",
                "mutable": false,
                "complexType": {
                    "original": "(keyof ContextMap)[]",
                    "resolved": "(keyof ContextMap)[] | undefined",
                    "references": {
                        "ContextMap": {
                            "location": "import",
                            "path": "@limetech/lime-web-components",
                            "id": "node_modules::ContextMap",
                            "referenceLocation": "ContextMap"
                        }
                    }
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Context keys the caller knows will be available where the rule\nruns. Forwarded to the rule registry's `validate` method so\n`missing-slot` issues surface inline. Leave unset when the\nevaluation context is not known (typical for editors that target\nthe saved-rule library)."
                },
                "getter": false,
                "setter": false
            },
            "required": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "inheritdoc",
                            "text": undefined
                        }],
                    "text": "Whether or not the current property is required"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "required"
            },
            "readonly": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "inheritdoc",
                            "text": undefined
                        }],
                    "text": "Whether or not the current property is readonly\nThe mutation UI (delete and negate toggles, add-group button) is\nhidden when set. The args editor for each ref is still rendered,\nwith `readonly` forwarded to it."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "readonly"
            },
            "disabled": {
                "type": "boolean",
                "mutable": false,
                "complexType": {
                    "original": "boolean",
                    "resolved": "boolean",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "inheritdoc",
                            "text": undefined
                        }],
                    "text": "Whether or not the current property is disabled\nThe mutation UI is hidden when set. The args editor is rendered\nwith `disabled` forwarded to it."
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "disabled"
            },
            "label": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "inheritdoc",
                            "text": undefined
                        }],
                    "text": "The label of the current property"
                },
                "getter": false,
                "setter": false,
                "reflect": true,
                "attribute": "label"
            },
            "helperText": {
                "type": "string",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [{
                            "name": "inheritdoc",
                            "text": undefined
                        }],
                    "text": "The helper text for the current property"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "attribute": "helper-text"
            }
        };
    }
    static get states() {
        return {
            "issues": {},
            "openChipPath": {}
        };
    }
    static get events() {
        return [{
                "method": "change",
                "name": "change",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the rule tree is mutated. Detail is the new rule, or\n`undefined` when the user has cleared the editor to an empty root\ngroup \u2014 callers can persist `undefined` as \"no rule\" without\nhaving to inspect the rule shape themselves."
                },
                "complexType": {
                    "original": "Rule | undefined",
                    "resolved": "RuleAll | RuleAny | RuleNot | RuleRef | undefined",
                    "references": {
                        "Rule": {
                            "location": "import",
                            "path": "@limetech/lime-web-components",
                            "id": "node_modules::Rule",
                            "referenceLocation": "Rule"
                        }
                    }
                }
            }];
    }
    static get watchers() {
        return [{
                "propName": "value",
                "methodName": "onValueChange"
            }, {
                "propName": "availableContexts",
                "methodName": "onAvailableContextsChange"
            }];
    }
}
function normalize(value) {
    if (!value) {
        return { type: 'all', rules: [] };
    }
    if (extractGroup(value)) {
        return value;
    }
    return { type: 'all', rules: [value] };
}
/**
 * Treat an empty root `all`/`any` as "no rule". Mapping that back to
 * `undefined` on emit keeps the form contract a clean
 * `Rule | undefined` — callers don't have to inspect rule shape to
 * tell "user cleared this" from "user has a real rule". A negated
 * empty group (`not(all())` etc.) is left intact — vacuous either
 * direction, but the user explicitly opted into the wrapper, so we
 * don't second-guess.
 * @param rule
 */
function collapseEmptyRoot(rule) {
    if (rule.type !== 'all' && rule.type !== 'any') {
        return rule;
    }
    return rule.rules.length === 0 ? undefined : rule;
}
