/**
 * 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;

import com.easyagents.flow.core.util.StringUtil;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;


public class ChainDefinition implements Serializable {
    private static final long serialVersionUID = -3183115191738959423L;

    protected String id;
    protected String name;
    protected String description;
    protected List<Node> nodes;
    protected List<Edge> edges;
    /**
     * 由节点和边派生的只读图索引，不参与序列化。
     */
    private transient volatile GraphIndex graphIndex;

    public ChainDefinition() {
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public List<Node> getNodes() {
        return nodes;
    }

    public void setNodes(List<Node> nodes) {
        this.nodes = nodes;
        invalidateGraphIndex();
    }

    public List<Edge> getEdges() {
        return edges;
    }

    public void setEdges(List<Edge> edges) {
        this.edges = edges;
        invalidateGraphIndex();
    }

    /**
     * 获取指定节点的全部出边。
     *
     * @param nodeId 节点 ID
     * @return 保持定义顺序的出边副本
     */
    public List<Edge> getOutwardEdge(String nodeId) {
        List<Edge> outwardEdges = graphIndex().outwardEdgesByNode.get(nodeId);
        return outwardEdges == null ? Collections.emptyList() : new ArrayList<>(outwardEdges);
    }


    /**
     * 获取指定节点的全部入边。
     *
     * @param nodeId 节点 ID
     * @return 保持定义顺序的入边副本
     */
    public List<Edge> getInwardEdge(String nodeId) {
        List<Edge> inwardEdges = graphIndex().inwardEdgesByNode.get(nodeId);
        return inwardEdges == null ? Collections.emptyList() : new ArrayList<>(inwardEdges);
    }

    /**
     * 获取循环节点已编译的直属分支调度描述。
     *
     * @param loopNodeId 循环节点 ID
     * @return 保持定义顺序的不可变调度描述
     */
    public List<LoopChildDispatch> getLoopChildDispatches(
            String loopNodeId) {
        List<LoopChildDispatch> dispatches =
                graphIndex().loopChildrenByNode.get(loopNodeId);
        return dispatches == null
                ? Collections.emptyList()
                : dispatches;
    }

    public void addNode(Node node) {
        if (nodes == null) {
            this.nodes = new ArrayList<>();
        }

        if (StringUtil.noText(node.getId())) {
            node.setId(UUID.randomUUID().toString());
        }

        nodes.add(node);
        invalidateGraphIndex();
    }


    /**
     * 按 ID 获取节点。
     *
     * @param id 节点 ID
     * @return 对应节点，不存在时返回 {@code null}
     */
    public Node getNodeById(String id) {
        if (id == null || StringUtil.noText(id)) {
            return null;
        }
        return graphIndex().nodeById.get(id);
    }


    public void addEdge(Edge edge) {
        if (this.edges == null) {
            this.edges = new ArrayList<>();
        }
        this.edges.add(edge);
        invalidateGraphIndex();
    }


    /**
     * 按 ID 获取边。
     *
     * @param edgeId 边 ID
     * @return 对应边，不存在时返回 {@code null}
     */
    public Edge getEdgeById(String edgeId) {
        if (StringUtil.noText(edgeId)) {
            return null;
        }
        return graphIndex().edgeById.get(edgeId);
    }

    /**
     * 获取没有入边的开始节点。
     *
     * @return 保持定义顺序的开始节点副本
     */
    public List<Node> getStartNodes() {
        if (nodes == null || nodes.isEmpty()) {
            return null;
        }
        return new ArrayList<>(graphIndex().startNodes);
    }


    public List<Parameter> getStartParameters() {
        List<Node> startNodes = this.getStartNodes();
        if (startNodes == null || startNodes.isEmpty()) {
            return Collections.emptyList();
        }

        List<Parameter> parameters = new ArrayList<>();
        for (Node node : startNodes) {
            List<Parameter> nodeParameters = node.getParameters();
            if (nodeParameters != null) parameters.addAll(nodeParameters);
        }
        return parameters;
    }

    /**
     * 使派生图索引失效。
     */
    private void invalidateGraphIndex() {
        graphIndex = null;
    }

    /**
     * 获取当前节点和边对应的只读图索引。
     *
     * @return 图索引
     */
    private GraphIndex graphIndex() {
        GraphIndex current = graphIndex;
        if (current != null) {
            return current;
        }
        synchronized (this) {
            current = graphIndex;
            if (current == null) {
                current = GraphIndex.build(nodes, edges);
                graphIndex = current;
            }
            return current;
        }
    }

    /**
     * 工作流定义的派生图索引。
     */
    private static final class GraphIndex {
        private final Map<String, Node> nodeById;
        private final Map<String, Edge> edgeById;
        private final Map<String, List<Edge>> outwardEdgesByNode;
        private final Map<String, List<Edge>> inwardEdgesByNode;
        private final Map<String, List<LoopChildDispatch>>
                loopChildrenByNode;
        private final List<Node> startNodes;

        /**
         * 创建不可变图索引。
         *
         * @param nodeById           节点索引
         * @param edgeById           边索引
         * @param outwardEdgesByNode 出边索引
         * @param inwardEdgesByNode  入边索引
         * @param startNodes         开始节点
         */
        private GraphIndex(Map<String, Node> nodeById,
                           Map<String, Edge> edgeById,
                           Map<String, List<Edge>> outwardEdgesByNode,
                           Map<String, List<Edge>> inwardEdgesByNode,
                           Map<String, List<LoopChildDispatch>>
                                   loopChildrenByNode,
                           List<Node> startNodes) {
            this.nodeById = nodeById;
            this.edgeById = edgeById;
            this.outwardEdgesByNode = outwardEdgesByNode;
            this.inwardEdgesByNode = inwardEdgesByNode;
            this.loopChildrenByNode = loopChildrenByNode;
            this.startNodes = startNodes;
        }

        /**
         * 根据节点和边构建索引。
         *
         * @param nodes 节点列表
         * @param edges 边列表
         * @return 构建完成的图索引
         */
        private static GraphIndex build(List<Node> nodes, List<Edge> edges) {
            Map<String, Node> nodeById = new HashMap<>();
            Map<String, Edge> edgeById = new HashMap<>();
            Map<String, List<Edge>> outwardEdgesByNode = new HashMap<>();
            Map<String, List<Edge>> inwardEdgesByNode = new HashMap<>();

            if (nodes != null) {
                for (Node node : nodes) {
                    if (node != null && StringUtil.hasText(node.getId())) {
                        // 保持旧实现遇到重复 ID 时返回第一个节点的行为。
                        nodeById.putIfAbsent(node.getId(), node);
                    }
                }
            }

            if (edges != null) {
                for (Edge edge : edges) {
                    if (edge == null) {
                        continue;
                    }
                    if (StringUtil.hasText(edge.getId())) {
                        edgeById.putIfAbsent(edge.getId(), edge);
                    }
                    if (StringUtil.hasText(edge.getSource())) {
                        outwardEdgesByNode
                                .computeIfAbsent(edge.getSource(), ignored -> new ArrayList<>())
                                .add(edge);
                    }
                    if (StringUtil.hasText(edge.getTarget())) {
                        inwardEdgesByNode
                                .computeIfAbsent(edge.getTarget(), ignored -> new ArrayList<>())
                                .add(edge);
                    }
                }
            }

            List<Node> startNodes = new ArrayList<>();
            if (nodes != null) {
                for (Node node : nodes) {
                    if (node != null && !inwardEdgesByNode.containsKey(node.getId())) {
                        startNodes.add(node);
                    }
                }
            }

            freezeEdgeLists(outwardEdgesByNode);
            freezeEdgeLists(inwardEdgesByNode);
            Map<String, List<LoopChildDispatch>>
                    loopChildrenByNode = new HashMap<>();
            for (Map.Entry<String, List<Edge>> entry :
                    outwardEdgesByNode.entrySet()) {
                List<LoopChildDispatch> dispatches = new ArrayList<>();
                for (Edge edge : entry.getValue()) {
                    Node child = nodeById.get(edge.getTarget());
                    if (child != null
                            && Objects.equals(
                            entry.getKey(), child.getParentId())) {
                        String branchId = edge.getId() == null
                                ? child.getId()
                                : edge.getId();
                        dispatches.add(new LoopChildDispatch(
                                child, edge.getId(), branchId));
                    }
                }
                if (!dispatches.isEmpty()) {
                    loopChildrenByNode.put(
                            entry.getKey(),
                            Collections.unmodifiableList(dispatches));
                }
            }
            return new GraphIndex(
                    Collections.unmodifiableMap(nodeById),
                    Collections.unmodifiableMap(edgeById),
                    Collections.unmodifiableMap(outwardEdgesByNode),
                    Collections.unmodifiableMap(inwardEdgesByNode),
                    Collections.unmodifiableMap(loopChildrenByNode),
                    Collections.unmodifiableList(startNodes));
        }

        /**
         * 将邻接表中的边列表转换为只读列表。
         *
         * @param edgesByNode 邻接表
         */
        private static void freezeEdgeLists(Map<String, List<Edge>> edgesByNode) {
            edgesByNode.replaceAll((ignored, value) -> Collections.unmodifiableList(value));
        }
    }

    /**
     * 循环直属分支的预编译调度描述。
     */
    public static final class LoopChildDispatch {

        private final Node node;
        private final String edgeId;
        private final String branchId;

        /**
         * 创建调度描述。
         *
         * @param node 目标节点
         * @param edgeId 边 ID
         * @param branchId 稳定分支 ID
         */
        private LoopChildDispatch(
                Node node, String edgeId, String branchId) {
            this.node = node;
            this.edgeId = edgeId;
            this.branchId = branchId;
        }

        /**
         * @return 目标节点
         */
        public Node getNode() {
            return node;
        }

        /**
         * @return 边 ID
         */
        public String getEdgeId() {
            return edgeId;
        }

        /**
         * @return 稳定分支 ID
         */
        public String getBranchId() {
            return branchId;
        }
    }


    @Override
    public String toString() {
        return "ChainDefinition{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", description='" + description + '\'' +
                ", nodes=" + nodes +
                ", edges=" + edges +
                '}';
    }
}
