package schema

import (
	"encoding/json"
	"path/filepath"
	"strings"
	"unicode"
)

func SanitizeGraph(graph KnowledgeGraph) KnowledgeGraph {
	if graph.Nodes == nil {
		graph.Nodes = []GraphNode{}
	}
	if graph.Edges == nil {
		graph.Edges = []GraphEdge{}
	}
	if graph.Layers == nil {
		graph.Layers = []Layer{}
	}
	if graph.Tour == nil {
		graph.Tour = []TourStep{}
	}
	for i := range graph.Nodes {
		graph.Nodes[i].Type = strings.ToLower(strings.TrimSpace(graph.Nodes[i].Type))
		graph.Nodes[i].Complexity = strings.ToLower(strings.TrimSpace(graph.Nodes[i].Complexity))
		graph.Nodes[i].FilePath = cleanSlashPath(graph.Nodes[i].FilePath)
	}
	for i := range graph.Edges {
		graph.Edges[i].Type = strings.ToLower(strings.TrimSpace(graph.Edges[i].Type))
		graph.Edges[i].Direction = strings.ToLower(strings.TrimSpace(graph.Edges[i].Direction))
	}
	return graph
}

func NormalizeAliases(graph KnowledgeGraph) KnowledgeGraph {
	for i := range graph.Nodes {
		if canonical, ok := NodeTypeAliases[graph.Nodes[i].Type]; ok {
			graph.Nodes[i].Type = canonical
		}
		if canonical, ok := ComplexityAliases[graph.Nodes[i].Complexity]; ok {
			graph.Nodes[i].Complexity = canonical
		}
	}
	for i := range graph.Edges {
		if canonical, ok := EdgeTypeAliases[graph.Edges[i].Type]; ok {
			graph.Edges[i].Type = canonical
		}
		if canonical, ok := DirectionAliases[graph.Edges[i].Direction]; ok {
			graph.Edges[i].Direction = canonical
		}
	}
	return graph
}

func NormalizeNodeID(node GraphNode) string {
	nodeType := strings.TrimSpace(node.Type)
	path := cleanSlashPath(node.FilePath)
	if nodeType == "domain" || nodeType == "flow" || nodeType == "step" {
		if id := strings.TrimSpace(node.ID); strings.HasPrefix(id, nodeType+":") {
			return id
		}
	}
	rawName := idSegment(node.Name)
	slugName := slug(node.Name)
	if rawName == "" {
		rawName = idSegment(node.ID)
	}
	if slugName == "" {
		slugName = slug(node.ID)
	}
	if isSubNodeID(nodeType, path, node.ID) && rawName != "" {
		return nodeType + ":" + path + ":" + rawName
	}
	switch nodeType {
	case "file", "config", "document", "service", "pipeline", "schema", "resource":
		if path != "" {
			return nodeType + ":" + path
		}
	case "function", "class", "table", "endpoint":
		if path != "" && rawName != "" {
			return nodeType + ":" + path + ":" + rawName
		}
	case "module", "concept", "domain", "article", "entity", "topic", "claim", "source":
		if slugName != "" {
			return nodeType + ":" + slugName
		}
	case "flow":
		if slugName != "" {
			return "flow:" + slugName
		}
	case "step":
		if path != "" && slugName != "" {
			if flowSlug := extraString(node.Extra, "parentFlowSlug"); flowSlug != "" {
				return "step:" + slug(flowSlug) + ":" + path + ":" + slugName
			}
			return "step:" + path + ":" + slugName
		}
		if slugName != "" {
			return "step:" + slugName
		}
	}
	if node.ID != "" {
		return strings.TrimSpace(node.ID)
	}
	if rawName != "" {
		return nodeType + ":" + rawName
	}
	return nodeType + ":unknown"
}

func isSubNodeID(nodeType string, path string, id string) bool {
	if path == "" {
		return false
	}
	return strings.HasPrefix(strings.TrimSpace(id), nodeType+":"+path+":")
}

func cleanSlashPath(path string) string {
	path = strings.TrimSpace(path)
	if path == "" {
		return ""
	}
	path = filepath.ToSlash(filepath.Clean(path))
	path = strings.TrimPrefix(path, "./")
	if path == "." {
		return ""
	}
	return path
}

func idSegment(value string) string {
	value = strings.TrimSpace(value)
	value = strings.ReplaceAll(value, ":", "-")
	return value
}

func slug(value string) string {
	value = strings.ToLower(strings.TrimSpace(value))
	var builder strings.Builder
	lastDash := false
	for _, r := range value {
		switch {
		case unicode.IsLetter(r), unicode.IsDigit(r):
			builder.WriteRune(r)
			lastDash = false
		case r == '.', r == '_', r == '-', r == '/', r == ':':
			if !lastDash {
				builder.WriteByte('-')
				lastDash = true
			}
		default:
			if !lastDash {
				builder.WriteByte('-')
				lastDash = true
			}
		}
	}
	return strings.Trim(builder.String(), "-")
}

func extraString(extra map[string]JSON, key string) string {
	if len(extra) == 0 {
		return ""
	}
	var value string
	if err := json.Unmarshal(extra[key], &value); err != nil {
		return ""
	}
	return value
}
