/**
 * Copyright (c) 2025-2026, Michael Yang 杨福海 (fuhai999@gmail.com).
 * <p>
 * Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.gnu.org/licenses/lgpl-3.0.txt
 * <p>
 * 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 com.easyagents.flow.core.chain.repository;

import com.easyagents.flow.core.chain.runtime.ExecutionBudgetExceededException;

import java.util.Iterator;
import java.util.List;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

/**
 * 循环节点累计结果仓储。
 * <p>
 * 累计结果独立于高频更新的节点状态保存，避免每轮迭代重复序列化全部历史结果。
 */
public interface LoopResultRepository {

    /**
     * 流式保存不可随机访问的循环输入。
     *
     * @param resultId 循环结果 ID
     * @param items 原始输入
     * @return 输入元素数量
     */
    int storeInput(String resultId, Iterable<?> items);

    /**
     * 在已启用的迭代预算内流式保存循环输入。
     *
     * <p>实现会在读取第 {@code maxItems + 1} 个元素前终止，避免超大或无限 Iterable
     * 先产生无界 I/O。具体仓储应在下游写入异常时清理已落盘的部分分块。</p>
     *
     * @param resultId 循环结果 ID
     * @param items 原始输入
     * @param maxItems 最大元素数；小于等于零表示不限制
     * @return 输入元素数量
     */
    default int storeInput(String resultId, Iterable<?> items, long maxItems) {
        if (maxItems <= 0L) {
            return storeInput(resultId, items);
        }
        Iterable<?> bounded = () -> new Iterator<Object>() {
            private final Iterator<?> delegate = items.iterator();
            private long count;

            @Override
            public boolean hasNext() {
                return delegate.hasNext();
            }

            @Override
            public Object next() {
                if (count >= maxItems) {
                    throw new ExecutionBudgetExceededException(
                            "Loop iteration budget exceeded while storing input "
                                    + resultId
                                    + ": more than "
                                    + maxItems);
                }
                count++;
                return delegate.next();
            }
        };
        return storeInput(resultId, bounded);
    }

    /**
     * 在实例锁和触发器认领均有效时流式保存循环输入。
     *
     * @param instanceId 工作流实例 ID
     * @param lockFencingToken 当前实例锁 token
     * @param claimId 当前触发器 ID
     * @param claimGeneration 当前触发器认领代际
     * @param resultId 循环结果 ID
     * @param items 原始输入
     * @param maxItems 最大元素数；小于等于零表示不限制
     * @return 输入元素数量
     */
    default int storeInput(
            String instanceId,
            long lockFencingToken,
            String claimId,
            long claimGeneration,
            String resultId,
            Iterable<?> items,
            long maxItems) {
        return storeInput(resultId, items, maxItems);
    }

    /**
     * 在生产者主动推送数据时流式保存循环输入。
     *
     * <p>缺省实现用于本地兼容仓储；分布式仓储应覆盖此方法并边接收边分块写入，
     * 避免先构造完整列表。</p>
     *
     * @param instanceId 工作流实例 ID
     * @param lockFencingToken 当前实例锁 token
     * @param claimId 当前触发器 ID
     * @param claimGeneration 当前触发器认领代际
     * @param resultId 循环结果 ID
     * @param producer 输入生产者
     * @param maxItems 最大元素数；小于等于零表示不限制
     * @return 输入元素数量
     */
    default int storeProducedInput(
            String instanceId,
            long lockFencingToken,
            String claimId,
            long claimGeneration,
            String resultId,
            InputProducer producer,
            long maxItems) {
        List<Object> items = new java.util.ArrayList<>();
        producer.produce(item -> {
            if (maxItems > 0L
                    && items.size() >= maxItems) {
                throw new ExecutionBudgetExceededException(
                        "Loop iteration budget exceeded while storing input "
                                + resultId
                                + ": more than "
                                + maxItems);
            }
            items.add(item);
        });
        return storeInput(
                instanceId,
                lockFencingToken,
                claimId,
                claimGeneration,
                resultId,
                items,
                0L);
    }

    /**
     * 主动向循环输入仓储推送元素的生产者。
     */
    @FunctionalInterface
    interface InputProducer {

        /**
         * 生产并按原顺序推送输入元素。
         *
         * @param sink 单元素接收器
         */
        void produce(Consumer<Object> sink);
    }

    /**
     * 按序号读取已保存的循环输入。
     *
     * @param resultId 循环结果 ID
     * @param index 从零开始的序号
     * @return 输入元素
     */
    Object loadInputItem(String resultId, int index);

    /**
     * 在业务参数读取边界透明还原完整循环输入。
     *
     * @param reference 循环输入引用
     * @return 与原输入顺序一致的列表
     */
    default List<Object> loadInput(LoopInputReference reference) {
        List<Object> items =
                new java.util.ArrayList<>(
                        reference.getItemCount());
        for (int index = 0;
             index < reference.getItemCount();
             index++) {
            items.add(loadInputItem(
                    reference.getResultId(), index));
        }
        return items;
    }

    /**
     * 清理循环输入。
     *
     * @param resultId 循环结果 ID
     */
    default void removeInput(String resultId) {
    }

    /**
     * 释放指定循环结果的进程内活跃缓存。
     *
     * <p>该操作不得删除已经持久化的输入、输出分块或改变结果引用语义，仅用于在
     * 循环完成后及时归还本机缓存空间。</p>
     *
     * @param resultId 循环结果 ID
     */
    default void releaseActiveCache(String resultId) {
    }

    /**
     * 在实例锁和触发器认领均有效时清理循环输入。
     *
     * @param instanceId 工作流实例 ID
     * @param lockFencingToken 当前实例锁 token
     * @param claimId 当前触发器 ID
     * @param claimGeneration 当前触发器认领代际
     * @param resultId 循环结果 ID
     */
    default void removeInput(
            String instanceId,
            long lockFencingToken,
            String claimId,
            long claimGeneration,
            String resultId) {
        removeInput(resultId);
    }

    /**
     * 追加一轮循环输出。
     *
     * @param resultId 循环结果 ID
     * @param iterationIndex 从零开始的迭代序号
     * @param outputValues 本轮输出
     */
    void append(String resultId, int iterationIndex, Map<String, Object> outputValues);

    /**
     * 在当前触发器 fencing token 仍有效时追加循环输出。
     *
     * @param instanceId 工作流实例 ID
     * @param fencingToken 当前触发器 token；非持久化执行为 {@code 0}
     * @param resultId 循环结果 ID
     * @param iterationIndex 迭代序号
     * @param outputValues 本轮输出
     */
    default void append(
            String instanceId,
            long fencingToken,
            String resultId,
            int iterationIndex,
            Map<String, Object> outputValues) {
        append(resultId, iterationIndex, outputValues);
    }

    /**
     * 在实例锁和当前触发器认领租约均有效时追加循环输出。
     *
     * @param instanceId 工作流实例 ID
     * @param lockFencingToken 当前实例锁 token；本地仓储调用为 {@code 0}
     * @param claimId 当前触发器 ID；非持久化执行为 {@code null}
     * @param claimGeneration 当前认领代际；非持久化执行为 {@code 0}
     * @param resultId 循环结果 ID
     * @param iterationIndex 迭代序号
     * @param outputValues 本轮输出
     */
    default void append(
            String instanceId,
            long lockFencingToken,
            String claimId,
            long claimGeneration,
            String resultId,
            int iterationIndex,
            Map<String, Object> outputValues) {
        append(instanceId, lockFencingToken, resultId, iterationIndex, outputValues);
    }

    /**
     * 加载完整循环累计结果。
     *
     * @param resultId 循环结果 ID
     * @param iterationCount 已累计的迭代数
     * @param outputNames 输出名称，顺序与工作流定义一致
     * @return 按输出名称聚合的结果列表
     */
    Map<String, Object> load(String resultId, int iterationCount, List<String> outputNames);

    /**
     * 为每个循环输出创建轻量引用。
     *
     * @param resultId 循环结果 ID
     * @param iterationCount 已累计迭代数
     * @param outputNames 输出名称
     * @return 输出名称到轻量引用的映射
     */
    default Map<String, Object> references(
            String resultId, int iterationCount, List<String> outputNames) {
        return references(
                resultId,
                iterationCount,
                outputNames,
                java.util.Collections.emptySet());
    }

    /**
     * 为每个循环输出创建带聚合策略的轻量引用。
     *
     * @param resultId 循环结果 ID
     * @param iterationCount 已累计迭代数
     * @param outputNames 输出名称
     * @param flattenedOutputNames 启用一层扁平聚合的输出名称
     * @return 输出名称到轻量引用的映射
     */
    default Map<String, Object> references(
            String resultId,
            int iterationCount,
            List<String> outputNames,
            Set<String> flattenedOutputNames) {
        Map<String, Object> references = new LinkedHashMap<>();
        if (outputNames != null) {
            for (String outputName : outputNames) {
                references.put(
                        outputName,
                        new LoopResultReference(
                                resultId,
                                iterationCount,
                                outputName,
                                flattenedOutputNames != null
                                        && flattenedOutputNames.contains(outputName)));
            }
        }
        return references;
    }

    /**
     * 解析单个循环输出引用。
     *
     * @param reference 循环输出引用
     * @return 未启用时返回按轮次累计的列表，启用时返回只扁平一层的数组
     */
    default Object resolve(LoopResultReference reference) {
        Object output = load(
                reference.getResultId(),
                reference.getIterationCount(),
                List.of(reference.getOutputName()))
                .get(reference.getOutputName());
        return ReferenceResolver.applyAggregation(reference, output);
    }

    /**
     * 递归解析业务输出中的循环引用，供参数读取和 API 边界透明还原。
     *
     * @param value 待解析值
     * @return 不包含循环引用的业务值
     */
    default Object resolveReferences(Object value) {
        return ReferenceResolver.resolve(this, value);
    }

    /**
     * 单次递归解析中的批量读取器。
     */
    final class ReferenceResolver {

        private ReferenceResolver() {
        }

        /**
         * 收集同一循环结果的全部输出名称，并按结果组批量加载一次。
         *
         * @param repository 循环结果仓储
         * @param value 待解析值
         * @return 已透明还原的值
         */
        static Object resolve(LoopResultRepository repository, Object value) {
            Map<GroupKey, java.util.LinkedHashSet<String>> outputNames =
                    new LinkedHashMap<>();
            java.util.LinkedHashMap<String, LoopInputReference> inputReferences =
                    new java.util.LinkedHashMap<>();
            collect(value, outputNames, inputReferences);
            Map<GroupKey, Map<String, Object>> loaded = new LinkedHashMap<>();
            outputNames.forEach((key, names) -> loaded.put(
                    key,
                    repository.load(
                            key.resultId,
                            key.iterationCount,
                            new java.util.ArrayList<>(names))));
            Map<String, List<Object>> loadedInputs =
                    new LinkedHashMap<>();
            inputReferences.forEach((resultId, reference) ->
                    loadedInputs.put(
                            resultId,
                            repository.loadInput(reference)));
            return replace(value, loaded, loadedInputs);
        }

        /**
         * 递归收集循环结果引用。
         *
         * @param value 当前值
         * @param outputNames 分组后的输出名称
         */
        private static void collect(
                Object value,
                Map<GroupKey, java.util.LinkedHashSet<String>> outputNames,
                Map<String, LoopInputReference> inputReferences) {
            if (value instanceof LoopResultReference) {
                LoopResultReference reference = (LoopResultReference) value;
                GroupKey key = new GroupKey(
                        reference.getResultId(), reference.getIterationCount());
                outputNames.computeIfAbsent(
                        key, ignored -> new java.util.LinkedHashSet<>())
                        .add(reference.getOutputName());
                return;
            }
            if (value instanceof LoopInputReference) {
                LoopInputReference reference =
                        (LoopInputReference) value;
                inputReferences.putIfAbsent(
                        reference.getResultId(), reference);
                return;
            }
            if (value instanceof Map<?, ?>) {
                ((Map<?, ?>) value).values().forEach(
                        item -> collect(
                                item, outputNames, inputReferences));
                return;
            }
            if (value instanceof List<?>) {
                ((List<?>) value).forEach(item -> collect(
                        item, outputNames, inputReferences));
            }
        }

        /**
         * 使用已批量加载的结果递归替换引用。
         *
         * @param value 当前值
         * @param loaded 已加载结果
         * @return 替换后的值
         */
        private static Object replace(
                Object value,
                Map<GroupKey, Map<String, Object>> loaded,
                Map<String, List<Object>> loadedInputs) {
            if (value instanceof LoopResultReference) {
                LoopResultReference reference = (LoopResultReference) value;
                Map<String, Object> outputs = loaded.get(new GroupKey(
                        reference.getResultId(), reference.getIterationCount()));
                Object output = outputs == null
                        ? null
                        : outputs.get(reference.getOutputName());
                return applyAggregation(reference, output);
            }
            if (value instanceof LoopInputReference) {
                return loadedInputs.get(
                        ((LoopInputReference) value)
                                .getResultId());
            }
            if (value instanceof Map<?, ?>) {
                Map<Object, Object> resolved = new LinkedHashMap<>();
                ((Map<?, ?>) value).forEach(
                        (key, item) -> resolved.put(
                                key,
                                replace(
                                        item,
                                        loaded,
                                        loadedInputs)));
                return resolved;
            }
            if (value instanceof List<?>) {
                List<?> list = (List<?>) value;
                java.util.ArrayList<Object> resolved =
                        new java.util.ArrayList<>(list.size());
                for (Object item : list) {
                    resolved.add(replace(
                            item, loaded, loadedInputs));
                }
                return resolved;
            }
            return value;
        }

        /**
         * 根据循环结果引用对累计输出执行一次线性聚合。
         *
         * @param reference 循环结果引用
         * @param output 按轮次累计的原始输出
         * @return 原始输出或只扁平一层后的数组
         * @throws IllegalStateException 启用扁平聚合但某轮值不是数组
         */
        private static Object applyAggregation(
                LoopResultReference reference, Object output) {
            if (!reference.isFlattenAggregation()) {
                return output;
            }
            if (!(output instanceof List<?>)) {
                throw invalidFlattenValue(reference, -1, output);
            }

            List<?> iterationValues = (List<?>) output;
            int flattenedSize = 0;
            int iterationIndex = 0;
            // 使用顺序迭代兼容链表，避免按索引读取退化为 O(n²)。
            for (Object iterationValue : iterationValues) {
                int currentSize;
                if (iterationValue instanceof List<?>) {
                    currentSize = ((List<?>) iterationValue).size();
                } else if (iterationValue != null
                        && iterationValue.getClass().isArray()) {
                    currentSize = java.lang.reflect.Array.getLength(iterationValue);
                } else {
                    throw invalidFlattenValue(
                            reference, iterationIndex, iterationValue);
                }
                try {
                    flattenedSize = Math.addExact(flattenedSize, currentSize);
                } catch (ArithmeticException exception) {
                    throw new IllegalStateException(
                            "Loop output '" + reference.getOutputName()
                                    + "' is too large to flatten",
                            exception);
                }
                iterationIndex++;
            }

            // 先统计容量再顺序追加，避免大数组扩容复制，整体复杂度保持 O(n)。
            java.util.ArrayList<Object> flattened =
                    new java.util.ArrayList<>(flattenedSize);
            for (Object iterationValue : iterationValues) {
                if (iterationValue instanceof List<?>) {
                    flattened.addAll((List<?>) iterationValue);
                    continue;
                }
                int length = java.lang.reflect.Array.getLength(iterationValue);
                for (int index = 0; index < length; index++) {
                    flattened.add(
                            java.lang.reflect.Array.get(iterationValue, index));
                }
            }
            return flattened;
        }

        /**
         * 构造扁平聚合类型错误。
         *
         * @param reference 循环结果引用
         * @param iterationIndex 错误轮次；负数表示累计输出结构错误
         * @param value 实际值
         * @return 类型错误
         */
        private static IllegalStateException invalidFlattenValue(
                LoopResultReference reference,
                int iterationIndex,
                Object value) {
            String actualType = value == null
                    ? "null"
                    : value.getClass().getName();
            String iteration = iterationIndex < 0
                    ? ""
                    : ", iteration " + iterationIndex;
            return new IllegalStateException(
                    "Loop output '" + reference.getOutputName()
                            + "' requires an array for flatten aggregation"
                            + iteration
                            + ", but got " + actualType);
        }

        /**
         * 循环结果批量读取分组键。
         */
        private static final class GroupKey {

            private final String resultId;
            private final int iterationCount;

            private GroupKey(String resultId, int iterationCount) {
                this.resultId = resultId;
                this.iterationCount = iterationCount;
            }

            @Override
            public boolean equals(Object other) {
                if (this == other) {
                    return true;
                }
                if (!(other instanceof GroupKey)) {
                    return false;
                }
                GroupKey that = (GroupKey) other;
                return iterationCount == that.iterationCount
                        && java.util.Objects.equals(resultId, that.resultId);
            }

            @Override
            public int hashCode() {
                return java.util.Objects.hash(resultId, iterationCount);
            }
        }
    }
}
