/*
 * Copyright (c) 2025 nosqlbench
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.nosqlbench.nb.api.expr;

import java.net.URI;
import java.util.Map;

/**
 * Lightweight facade that prescans text for Groovy expression sigils before invoking the
 * heavyweight {@link GroovyExpressionProcessor}. This avoids building the expression runtime
 * when workloads contain no substitutions.
 *
 * <p>Expressions can be safely embedded in YAML using standard YAML string formats:</p>
 * <pre>
 * # Using pipe literal (recommended for multi-line expressions):
 * key: |
 *   {{=
 *   def data = []
 *   for (int i = 1; i <= 3; i++) {
 *       data << i * i
 *   }
 *   return data.join('-')
 *   }}
 *
 * # Using quoted strings:
 * key: "{{= 1 + 2 }}"
 *
 * # Using folded scalar:
 * key: >
 *   {{= someExpression() }}
 * </pre>
 */
public final class ExprPreprocessor {

    private static final String SIGIL_START = "{{";

    private final GroovyExpressionProcessor processor;

    /**
     * Create a new expression preprocessor with its own Groovy expression processor.
     * The lifetime of the processor is tied to this preprocessor instance.
     */
    public ExprPreprocessor() {
        this.processor = new GroovyExpressionProcessor();
    }

    /**
     * Render the provided source through the expression system only when a substitution sigil is
     * detected. Otherwise, the original source is returned unchanged.
     */
    public String process(String source, URI sourceUri, Map<String, ?> parameters) {
        if (!containsExpressions(source)) {
            return source;
        }
        return processor.process(source, sourceUri, parameters);
    }

    /**
     * Render the provided source through the expression system and return both the transformed
     * output and the binding context. When no substitution sigil is detected, returns the original
     * source with an empty binding.
     *
     * @param source raw workload text
     * @param sourceUri workload origin if available
     * @param parameters parameters supplied alongside the workload
     * @return ProcessingResult containing both output and binding context
     */
    public ProcessingResult processWithContext(String source, URI sourceUri, Map<String, ?> parameters) {
        if (!containsExpressions(source)) {
            return new ProcessingResult(source, new groovy.lang.Binding());
        }
        return processor.processWithContext(source, sourceUri, parameters);
    }

    /**
     * @return {@code true} when the source contains the {@code {{}} sigil that requires
     * expression expansion. This detects all expression formats: {@code {{=}}, {@code {{var = ...}}},
     * {@code {{@var}}}, etc.
     */
    public boolean containsExpressions(String source) {
        return source != null && source.contains(SIGIL_START);
    }
}
