// Package core contiene funciones puras para el parallel executor.
// Sin side effects: parseo de planes, análisis de dependencias, agrupamiento.
package core

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"

	df "github.com/lucasdataproyects/devfactory/core"
)

// Issue representa una issue parseada del plan de ejecución.
type Issue struct {
	Number int
	Slug   string
	Title  string
	Group  int
	Deps   []int
}

// ExecutionPlan es el plan completo parseado del markdown.
type ExecutionPlan struct {
	Groups []IssueGroup
	Total  int
}

// IssueGroup es un conjunto de issues ejecutables en paralelo.
type IssueGroup struct {
	Number int
	Name   string
	Issues []Issue
}

// WorktreeSpec define la especificación para crear un worktree.
type WorktreeSpec struct {
	Issue      Issue
	BranchName string
	WorkDir    string
}

// ExecutionResult es el resultado de ejecutar una issue.
type ExecutionResult struct {
	Issue    Issue
	Success  bool
	Duration string
	Error    string
	LogFile  string
}

var (
	groupHeaderRe = regexp.MustCompile(`^##\s+Grupo\s+(\d+)`)
	issueLineRe   = regexp.MustCompile(`-\s+Issue\s+#(\d{4})\s*-?\s*(.*)`)
	depRe         = regexp.MustCompile(`#(\d{4})`)
)

// ParsePlan parsea el contenido markdown de PARALLEL_EXECUTION_ORDER.md.
// Función pura: recibe string, retorna Result[ExecutionPlan].
func ParsePlan(content string) df.Result[ExecutionPlan] {
	lines := strings.Split(content, "\n")
	if len(lines) == 0 {
		return df.Err[ExecutionPlan](errors.New("empty plan"))
	}

	groups := parsePlanGroups(lines)
	if len(groups) == 0 {
		return df.Err[ExecutionPlan](errors.New("no groups found in plan"))
	}

	total := df.Reduce(groups, 0, func(acc int, g IssueGroup) int {
		return acc + len(g.Issues)
	})

	return df.Ok(ExecutionPlan{
		Groups: groups,
		Total:  total,
	})
}

// parsePlanGroups extrae los grupos del markdown.
func parsePlanGroups(lines []string) []IssueGroup {
	var groups []IssueGroup
	var currentGroup *IssueGroup

	for _, line := range lines {
		trimmed := strings.TrimSpace(line)

		if matches := groupHeaderRe.FindStringSubmatch(trimmed); len(matches) > 1 {
			if currentGroup != nil {
				groups = append(groups, *currentGroup)
			}
			num, _ := strconv.Atoi(matches[1])
			currentGroup = &IssueGroup{
				Number: num,
				Name:   trimmed,
			}
			continue
		}

		if currentGroup != nil {
			if matches := issueLineRe.FindStringSubmatch(trimmed); len(matches) > 1 {
				num, _ := strconv.Atoi(matches[1])
				title := strings.TrimSpace(matches[2])
				deps := extractDeps(title, num)
				issue := Issue{
					Number: num,
					Slug:   issueSlug(num, title),
					Title:  title,
					Group:  currentGroup.Number,
					Deps:   deps,
				}
				currentGroup.Issues = append(currentGroup.Issues, issue)
			}
		}
	}

	if currentGroup != nil && len(currentGroup.Issues) > 0 {
		groups = append(groups, *currentGroup)
	}

	return groups
}

// extractDeps extrae números de issues referenciadas como dependencias.
func extractDeps(text string, selfNum int) []int {
	matches := depRe.FindAllStringSubmatch(text, -1)
	return df.FilterSlice(
		df.MapSlice(matches, func(m []string) int {
			n, _ := strconv.Atoi(m[1])
			return n
		}),
		func(n int) bool { return n != selfNum },
	)
}

// issueSlug genera el slug de branch para una issue.
func issueSlug(num int, title string) string {
	slug := strings.ToLower(title)
	slug = regexp.MustCompile(`[^a-z0-9\s-]`).ReplaceAllString(slug, "")
	slug = regexp.MustCompile(`\s+`).ReplaceAllString(slug, "-")
	slug = regexp.MustCompile(`-{2,}`).ReplaceAllString(slug, "-")
	slug = strings.Trim(slug, "-")

	words := strings.SplitN(slug, "-", 5)
	if len(words) > 4 {
		words = words[:4]
	}
	slug = strings.Join(words, "-")

	if slug == "" {
		slug = "issue"
	}

	return formatIssueNum(num) + "-" + slug
}

func formatIssueNum(n int) string {
	return fmt.Sprintf("%04d", n)
}

// FilterGroup filtra el plan para ejecutar solo un grupo específico.
func FilterGroup(plan ExecutionPlan, groupNum int) df.Result[ExecutionPlan] {
	filtered := df.FilterSlice(plan.Groups, func(g IssueGroup) bool {
		return g.Number == groupNum
	})
	if len(filtered) == 0 {
		return df.Err[ExecutionPlan](fmt.Errorf("group not found: %d", groupNum))
	}
	total := len(filtered[0].Issues)
	return df.Ok(ExecutionPlan{Groups: filtered, Total: total})
}

// BuildWorktreeSpecs genera las specs de worktrees para un grupo de issues.
// Función pura: recibe issues y base path, retorna specs.
func BuildWorktreeSpecs(issues []Issue, basePath string) []WorktreeSpec {
	return df.MapSlice(issues, func(issue Issue) WorktreeSpec {
		branch := "issue/" + issue.Slug
		workDir := basePath + "/worktrees/issue-" + formatIssueNum(issue.Number)
		return WorktreeSpec{
			Issue:      issue,
			BranchName: branch,
			WorkDir:    workDir,
		}
	})
}

// ParseIssueFiles parsea los archivos de issues del directorio dev/issues/.
// Retorna issues con dependencias extraídas del contenido de cada archivo.
func ParseIssueFiles(files map[string]string) []Issue {
	var issues []Issue

	numRe := regexp.MustCompile(`^(\d{3,4})[a-z]?-(.+)\.md$`)

	for filename, content := range files {
		matches := numRe.FindStringSubmatch(filename)
		if len(matches) < 3 {
			continue
		}

		num, _ := strconv.Atoi(matches[1])
		slug := matches[2]

		// Extraer título de la primera línea
		title := slug
		for _, line := range strings.SplitN(content, "\n", 5) {
			if strings.HasPrefix(line, "# ") {
				title = strings.TrimPrefix(line, "# ")
				title = strings.TrimSpace(title)
				break
			}
		}

		// Extraer dependencias de "Bloqueada por" o tabla de dependencias
		deps := extractAllDeps(content, num)

		// Solo incluir si está pendiente
		if isIssuePending(content) {
			issues = append(issues, Issue{
				Number: num,
				Slug:   slug,
				Title:  title,
				Deps:   deps,
			})
		}
	}

	return issues
}

func extractAllDeps(content string, selfNum int) []int {
	allMatches := depRe.FindAllStringSubmatch(content, -1)
	seen := make(map[int]bool)
	var deps []int
	for _, m := range allMatches {
		n, _ := strconv.Atoi(m[1])
		if n != selfNum && !seen[n] {
			seen[n] = true
			deps = append(deps, n)
		}
	}
	return deps
}

func isIssuePending(content string) bool {
	lower := strings.ToLower(content)
	return strings.Contains(lower, "pendiente") ||
		strings.Contains(lower, "🟡") ||
		(!strings.Contains(lower, "completado") && !strings.Contains(lower, "✅"))
}
