package programchecker

import (
	"fmt"
	"strings"
)

// checkResourceNamespace makes Flow's resource namespace deterministic before
// type inference or runtime resolution can choose an accidental precedence.
// Formula resources are immutable declarations; writable output/assignment
// roots may target an explicit variable, but may never reuse a formula name.
func (c *flowChecker) checkResourceNamespace() {
	formulaClaims := map[string]int{}
	declaredClaims := map[string]string{}
	rememberDeclared := func(name, owner string) {
		name = strings.TrimSpace(name)
		if name == "" {
			return
		}
		if _, exists := declaredClaims[name]; !exists {
			declaredClaims[name] = owner
		}
	}
	variableClaims := map[string]int{}
	for i, variable := range c.meta.Variables {
		name := strings.TrimSpace(variable.Name)
		if prior, exists := variableClaims[name]; name != "" && exists {
			c.addDuplicateFlowResourceDiagnostic(name, fmt.Sprintf("/variables/%d/name", i), fmt.Sprintf("variable at /variables/%d/name", prior))
		} else if name != "" {
			variableClaims[name] = i
		}
		rememberDeclared(name, fmt.Sprintf("variable at /variables/%d/name", i))
	}
	for i, resource := range c.meta.Constants {
		rememberDeclared(resource.Name, fmt.Sprintf("constant at /constants/%d/name", i))
	}
	for i, resource := range c.meta.TextTemplates {
		rememberDeclared(resource.Name, fmt.Sprintf("text template at /textTemplates/%d/name", i))
	}
	for i, resource := range c.meta.Choices {
		rememberDeclared(resource.Name, fmt.Sprintf("choice at /choices/%d/name", i))
	}
	for i, resource := range c.meta.Stages {
		rememberDeclared(resource.Name, fmt.Sprintf("stage at /stages/%d/name", i))
	}
	for i, resource := range c.meta.Formulas {
		name := strings.TrimSpace(resource.Name)
		if name == "" {
			continue
		}
		if prior, ok := formulaClaims[name]; ok {
			c.addDuplicateFlowResourceDiagnostic(name, fmt.Sprintf("/formulas/%d/name", i), fmt.Sprintf("formula at /formulas/%d/name", prior))
		} else {
			formulaClaims[name] = i
		}
		if prior, ok := declaredClaims[name]; ok {
			c.addDuplicateFlowResourceDiagnostic(name, fmt.Sprintf("/formulas/%d/name", i), prior)
		}
	}
	claimWritable := func(ref, pointer, owner string) {
		root := flowReferenceRoot(ref)
		if root == "" || !flowReferenceRootIsDeclarable(root) {
			return
		}
		if formulaIndex, ok := formulaClaims[root]; ok {
			c.addDuplicateFlowResourceDiagnostic(root, pointer, fmt.Sprintf("formula at /formulas/%d/name", formulaIndex)+"; "+owner+" cannot write an immutable formula resource")
		}
	}
	for i, lookup := range c.meta.RecordLookups {
		claimWritable(lookup.Name, fmt.Sprintf("/recordLookups/%d/name", i), "record lookup output")
		claimWritable(lookup.OutputReference, fmt.Sprintf("/recordLookups/%d/outputReference", i), "record lookup output")
		for j, output := range lookup.OutputAssignments {
			claimWritable(output.AssignToReference, fmt.Sprintf("/recordLookups/%d/outputAssignments/%d/assignToReference", i, j), "record lookup output")
		}
	}
	for i, create := range c.meta.RecordCreates {
		if create.StoreOutputAutomatically {
			claimWritable(create.Name, fmt.Sprintf("/recordCreates/%d/name", i), "record create output")
		}
		claimWritable(create.AssignRecordIdToReference, fmt.Sprintf("/recordCreates/%d/assignRecordIdToReference", i), "record create output")
	}
	for i, action := range c.meta.ActionCalls {
		for j, output := range action.OutputParameters {
			claimWritable(flowActionOutputTarget(output.Value), fmt.Sprintf("/actionCalls/%d/outputParameters/%d/value", i, j), "action output")
		}
	}
	for i, subflow := range c.meta.Subflows {
		for j, output := range subflow.OutputAssignments {
			claimWritable(output.AssignToReference, fmt.Sprintf("/subflows/%d/outputAssignments/%d/assignToReference", i, j), "subflow output")
		}
	}
	for i, decision := range c.meta.Decisions {
		for j, rule := range decision.Rules {
			claimWritable(rule.Name, fmt.Sprintf("/decisions/%d/rules/%d/name", i, j), "decision outcome")
		}
	}
	for i, assignment := range c.meta.Assignments {
		for j, item := range assignment.Items {
			claimWritable(item.AssignToReference, fmt.Sprintf("/assignments/%d/assignmentItems/%d/assignToReference", i, j), "assignment")
		}
	}
	for i, loop := range c.meta.Loops {
		iterator := flowLoopIteratorReference(loop)
		pointer := fmt.Sprintf("/loops/%d/assignNextValueToReference", i)
		if strings.TrimSpace(loop.AssignNextValueToReference) == "" {
			pointer = fmt.Sprintf("/loops/%d/name", i)
		}
		claimWritable(iterator, pointer, "loop iterator")
	}
}

func (c *flowChecker) addDuplicateFlowResourceDiagnostic(name, pointer, conflict string) {
	c.addDiagnostic(Diagnostic{
		Code:        "metadata_program.duplicate_flow_resource",
		Severity:    SeverityError,
		JSONPointer: pointer,
		ElementName: name,
		Reference:   name,
		Message:     fmt.Sprintf("flow resource name %q is not unique: conflicts with %s", name, conflict),
	})
}
