package executor

import (
	"fmt"

	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/dag"
)

type PhysicPlanBuilder struct {
	renders []DagNodeRender
}

func NewPhysicPlanBuilder() *PhysicPlanBuilder {
	return &PhysicPlanBuilder{}
}

func (builder *PhysicPlanBuilder) RegisterRender(render DagNodeRender) {
	builder.renders = append(builder.renders, render)
}

func (builder *PhysicPlanBuilder) BuildPlan(planName string, logicPlan dag.Dag, execId string) (dag.Dag, error) {
	logicPlan = logicPlan.Clone()
	if logicPlan.CheckLoop(planName) {
		return nil, fmt.Errorf("PhysicPlanBuilder.BuildPlan: plan `%v` has refer-loop", planName)
	}
	err := logicPlan.Iterate(planName, func(name string, node *dag.DagNode) (err error) {
		if node == nil {
			node, err = logicPlan.NewNode(name)
			if err != nil {
				return err
			}
		}
		for i := len(builder.renders) - 1; i >= 0; i-- {
			render := builder.renders[i]
			err := render.Render(node)
			if err != nil {
				return err
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	nodeNames := logicPlan.GetNotFoundNodes(planName)
	if len(nodeNames) != 0 {
		return nil, fmt.Errorf("PhysicPlanBuilder.BuildPlan: don't know how to handle dag nodes `%v`", nodeNames)
	}
	return logicPlan, nil
}

type DagNodeRender interface {
	Render(node *dag.DagNode) error
}

type DagNodeRenderByActions struct {
	actionLib *actions.ActionLibrary
}

func NewDagNodeRenderByActions(actionLib *actions.ActionLibrary) *DagNodeRenderByActions {
	return &DagNodeRenderByActions{
		actionLib: actionLib,
	}
}

func (render *DagNodeRenderByActions) Render(node *dag.DagNode) error {
	if node.DagAction != nil {
		return nil
	}
	action := render.actionLib.Get(node.Name)
	if action == nil {
		return nil
	}
	// Actions in the library are shared by all executions. Clone stateful wrappers
	// (notably cancellation-aware async actions) so an abort in one physical plan
	// cannot affect another execution.
	if cloneable, ok := action.(interface{ CloneAction() actions.Action }); ok {
		action = cloneable.CloneAction()
	}
	return node.AssignAction(action, nil)
}
