package detectors

import (
	"regexp"
	"strings"

	"bit.admedia.com/scm/ad/adpilot-indexing-code-parser.com/internal/service/parser/analyzers/common"
)

// Context carries parse inputs for framework detection.
type Context struct {
	Source   []byte
	FilePath string
	Language string
	Builder  *common.Builder
}

// Detector enriches a parsed file with framework-specific graph metadata.
type Detector interface {
	ID() string
	Framework() string
	Languages() []string
	Apply(ctx Context) bool
}

// Registry holds framework detectors keyed by language.
type Registry struct {
	byLanguage map[string][]Detector
}

func NewRegistry(detectors ...Detector) *Registry {
	r := &Registry{byLanguage: make(map[string][]Detector)}
	for _, d := range detectors {
		for _, lang := range d.Languages() {
			r.byLanguage[lang] = append(r.byLanguage[lang], d)
		}
	}
	return r
}

func (r *Registry) ApplyAll(ctx Context) {
	if r == nil {
		return
	}
	for _, d := range r.byLanguage[ctx.Language] {
		if d.Apply(ctx) {
			ctx.Builder.MergeFrameworkMetadata(d.Framework(), d.ID())
		}
	}
}

// --- JavaScript / TypeScript detectors ---

type reactDetector struct{}

func NewReactDetector() Detector { return reactDetector{} }

func (reactDetector) ID() string        { return "react" }
func (reactDetector) Framework() string { return "react" }
func (reactDetector) Languages() []string {
	return []string{"javascript", "typescript"}
}

var (
	reactComponentPattern = regexp.MustCompile(`(?:export\s+)?(?:default\s+)?function\s+([A-Z][A-Za-z0-9_]*)\s*\(`)
	reactArrowPattern     = regexp.MustCompile(`(?:export\s+)?(?:const|let)\s+([A-Z][A-Za-z0-9_]*)\s*=\s*(?:\([^)]*\)|[A-Za-z0-9_]+)\s*=>`)
)

func (reactDetector) Apply(ctx Context) bool {
	text := string(ctx.Source)
	if !strings.Contains(text, "react") && !strings.Contains(text, "jsx") && !strings.Contains(text, "tsx") {
		return false
	}
	found := false
	lines := common.Lines(ctx.Source)
	for i, line := range lines {
		if m := reactComponentPattern.FindStringSubmatch(line); len(m) > 1 {
			ctx.Builder.AddComponent(m[1], i+1, i+1, map[string]string{"framework": "react"})
			found = true
		}
		if m := reactArrowPattern.FindStringSubmatch(line); len(m) > 1 {
			ctx.Builder.AddComponent(m[1], i+1, i+1, map[string]string{"framework": "react"})
			found = true
		}
	}
	return found
}

type nextjsDetector struct{}

func NewNextJSDetector() Detector { return nextjsDetector{} }

func (nextjsDetector) ID() string        { return "nextjs" }
func (nextjsDetector) Framework() string { return "nextjs" }
func (nextjsDetector) Languages() []string {
	return []string{"javascript", "typescript"}
}

func (nextjsDetector) Apply(ctx Context) bool {
	path := ctx.FilePath
	if !strings.Contains(path, "pages/") && !strings.Contains(path, "app/") {
		return false
	}
	route := inferNextRoute(path)
	if route == "" {
		return false
	}
	ctx.Builder.AddRoute(route, path, 1, len(common.Lines(ctx.Source)), map[string]string{
		"framework":      "nextjs",
		"component_type": "page",
	})
	return true
}

func inferNextRoute(path string) string {
	path = strings.ReplaceAll(path, "\\", "/")
	if idx := strings.Index(path, "app/"); idx >= 0 {
		rel := strings.TrimSuffix(path[idx+4:], ".tsx")
		rel = strings.TrimSuffix(rel, ".ts")
		rel = strings.TrimSuffix(rel, ".jsx")
		rel = strings.TrimSuffix(rel, ".js")
		if rel == "page" || strings.HasSuffix(rel, "/page") {
			rel = strings.TrimSuffix(rel, "/page")
			if rel == "" {
				return "/"
			}
			return "/" + rel
		}
	}
	if idx := strings.Index(path, "pages/"); idx >= 0 {
		rel := strings.TrimSuffix(path[idx+6:], ".tsx")
		rel = strings.TrimSuffix(rel, ".ts")
		rel = strings.TrimSuffix(rel, ".jsx")
		rel = strings.TrimSuffix(rel, ".js")
		if rel == "index" {
			return "/"
		}
		return "/" + rel
	}
	return ""
}

type vueDetector struct{}

func NewVueDetector() Detector { return vueDetector{} }

func (vueDetector) ID() string        { return "vue" }
func (vueDetector) Framework() string { return "vue" }
func (vueDetector) Languages() []string {
	return []string{"javascript", "typescript", "vue"}
}

var vueComponentNamePattern = regexp.MustCompile(`(?:export\s+default\s+)?defineComponent\s*\(\s*\{[\s\S]*?name\s*:\s*['"]([^'"]+)['"]`)

func (vueDetector) Apply(ctx Context) bool {
	text := string(ctx.Source)
	if strings.HasSuffix(ctx.FilePath, ".vue") || strings.Contains(text, "defineComponent") || strings.Contains(text, "<script setup") {
		name := strings.TrimSuffix(lastPathSegment(ctx.FilePath), ".vue")
		if m := vueComponentNamePattern.FindStringSubmatch(text); len(m) > 1 {
			name = m[1]
		}
		ctx.Builder.AddComponent(name, 1, len(common.Lines(ctx.Source)), map[string]string{
			"framework":      "vue",
			"component_type": "sfc",
		})
		return true
	}
	return false
}

type angularDetector struct{}

func NewAngularDetector() Detector { return angularDetector{} }

func (angularDetector) ID() string        { return "angular" }
func (angularDetector) Framework() string { return "angular" }
func (angularDetector) Languages() []string {
	return []string{"javascript", "typescript"}
}

var (
	angularComponentPattern = regexp.MustCompile(`@Component\s*\(\s*\{`)
	angularClassPattern     = regexp.MustCompile(`export\s+class\s+([A-Z][A-Za-z0-9_]*)`)
)

func (angularDetector) Apply(ctx Context) bool {
	text := string(ctx.Source)
	if !strings.Contains(text, "@angular/") && !angularComponentPattern.MatchString(text) {
		return false
	}
	found := false
	lines := common.Lines(ctx.Source)
	for i, line := range lines {
		if angularComponentPattern.MatchString(line) {
			for j := i; j < len(lines) && j < i+5; j++ {
				if m := angularClassPattern.FindStringSubmatch(lines[j]); len(m) > 1 {
					ctx.Builder.AddComponent(m[1], i+1, j+1, map[string]string{
						"framework":      "angular",
						"component_type": "component",
					})
					found = true
					break
				}
			}
		}
	}
	return found
}

type flutterDetector struct{}

func NewFlutterDetector() Detector { return flutterDetector{} }

func (flutterDetector) ID() string        { return "flutter" }
func (flutterDetector) Framework() string { return "flutter" }
func (flutterDetector) Languages() []string {
	return []string{"dart"}
}

var flutterWidgetPattern = regexp.MustCompile(`class\s+([A-Za-z_][A-Za-z0-9_]*)\s+extends\s+(?:StatelessWidget|StatefulWidget|ConsumerWidget|HookWidget)`)

func (flutterDetector) Apply(ctx Context) bool {
	text := string(ctx.Source)
	if !strings.Contains(text, "package:flutter/") && !strings.Contains(text, "StatelessWidget") && !strings.Contains(text, "StatefulWidget") {
		return false
	}
	found := false
	lines := common.Lines(ctx.Source)
	for i, line := range lines {
		if m := flutterWidgetPattern.FindStringSubmatch(line); len(m) > 1 {
			ctx.Builder.AddComponent(m[1], i+1, findDetectorBraceBlockEnd(lines, i), map[string]string{
				"framework":      "flutter",
				"component_type": "widget",
			})
			found = true
		}
	}
	return found
}

// --- Python detectors ---

type fastAPIDetector struct{}

func NewFastAPIDetector() Detector { return fastAPIDetector{} }

func (fastAPIDetector) ID() string        { return "fastapi" }
func (fastAPIDetector) Framework() string { return "fastapi" }
func (fastAPIDetector) Languages() []string {
	return []string{"python"}
}

var fastAPIRoutePattern = regexp.MustCompile(`@(?:app|router)\.(get|post|put|patch|delete|options|head)\(\s*['"]([^'"]+)['"]`)

func (fastAPIDetector) Apply(ctx Context) bool {
	text := string(ctx.Source)
	if !strings.Contains(text, "fastapi") {
		return false
	}
	found := false
	lines := common.Lines(ctx.Source)
	for i, line := range lines {
		if m := fastAPIRoutePattern.FindStringSubmatch(line); len(m) > 2 {
			route := m[2]
			ctx.Builder.AddRoute(route, "", i+1, i+1, map[string]string{
				"framework": "fastapi",
				"method":    strings.ToUpper(m[1]),
			})
			found = true
		}
	}
	return found
}

type flaskDetector struct{}

func NewFlaskDetector() Detector { return flaskDetector{} }

func (flaskDetector) ID() string        { return "flask" }
func (flaskDetector) Framework() string { return "flask" }
func (flaskDetector) Languages() []string {
	return []string{"python"}
}

var flaskRoutePattern = regexp.MustCompile(`@(?:app\.route|blueprint|bp)\.(?:route)?\(\s*['"]([^'"]+)['"]|@(?:app|blueprint|bp)\.(?:route|get|post|put|delete)\(\s*['"]([^'"]+)['"]`)

func (flaskDetector) Apply(ctx Context) bool {
	text := string(ctx.Source)
	if !strings.Contains(text, "flask") && !strings.Contains(text, "Blueprint") && !strings.Contains(text, "blueprint") {
		return false
	}
	found := false
	lines := common.Lines(ctx.Source)
	for i, line := range lines {
		if m := flaskRoutePattern.FindStringSubmatch(line); len(m) > 1 {
			route := m[1]
			if route == "" && len(m) > 2 {
				route = m[2]
			}
			if route == "" {
				continue
			}
			ctx.Builder.AddRoute(route, "", i+1, i+1, map[string]string{"framework": "flask"})
			found = true
		}
	}
	return found
}

// --- Java detectors ---

type springDetector struct{}

func NewSpringDetector() Detector { return springDetector{} }

func (springDetector) ID() string        { return "spring" }
func (springDetector) Framework() string { return "spring" }
func (springDetector) Languages() []string {
	return []string{"java"}
}

var (
	springMappingPattern    = regexp.MustCompile(`@(Get|Post|Put|Patch|Delete|Request)Mapping\s*\(\s*(?:value\s*=\s*)?["']([^"']+)["']`)
	springControllerPattern = regexp.MustCompile(`@(RestController|Controller)`)
)

func (springDetector) Apply(ctx Context) bool {
	text := string(ctx.Source)
	if !springControllerPattern.MatchString(text) && !strings.Contains(text, "org.springframework") {
		return false
	}
	found := false
	lines := common.Lines(ctx.Source)
	for i, line := range lines {
		if m := springMappingPattern.FindStringSubmatch(line); len(m) > 2 {
			ctx.Builder.AddRoute(m[2], "", i+1, i+1, map[string]string{
				"framework": "spring",
				"method":    strings.ToUpper(strings.TrimSuffix(m[1], "Mapping")),
			})
			found = true
		}
	}
	return found
}

// --- PHP detectors ---

type laravelDetector struct{}

func NewLaravelDetector() Detector { return laravelDetector{} }

func (laravelDetector) ID() string        { return "laravel" }
func (laravelDetector) Framework() string { return "laravel" }
func (laravelDetector) Languages() []string {
	return []string{"php"}
}

var laravelRoutePattern = regexp.MustCompile(`Route::(get|post|put|patch|delete|any)\(\s*['"]([^'"]+)['"]`)

func (laravelDetector) Apply(ctx Context) bool {
	text := string(ctx.Source)
	if !strings.Contains(text, "Route::") && !strings.Contains(text, "Illuminate\\") {
		return false
	}
	found := false
	lines := common.Lines(ctx.Source)
	for i, line := range lines {
		if m := laravelRoutePattern.FindStringSubmatch(line); len(m) > 2 {
			ctx.Builder.AddRoute(m[2], "", i+1, i+1, map[string]string{
				"framework": "laravel",
				"method":    strings.ToUpper(m[1]),
			})
			found = true
		}
	}
	return found
}

type symfonyDetector struct{}

func NewSymfonyDetector() Detector { return symfonyDetector{} }

func (symfonyDetector) ID() string        { return "symfony" }
func (symfonyDetector) Framework() string { return "symfony" }
func (symfonyDetector) Languages() []string {
	return []string{"php"}
}

var symfonyRoutePattern = regexp.MustCompile(`#\[Route\s*\(\s*['"]([^'"]+)['"]`)

func (symfonyDetector) Apply(ctx Context) bool {
	text := string(ctx.Source)
	if !strings.Contains(text, "Symfony") && !symfonyRoutePattern.MatchString(text) {
		return false
	}
	found := false
	lines := common.Lines(ctx.Source)
	for i, line := range lines {
		if m := symfonyRoutePattern.FindStringSubmatch(line); len(m) > 1 {
			ctx.Builder.AddRoute(m[1], "", i+1, i+1, map[string]string{"framework": "symfony"})
			found = true
		}
	}
	return found
}

type wordPressDetector struct{}

func NewWordPressDetector() Detector { return wordPressDetector{} }

func (wordPressDetector) ID() string        { return "wordpress" }
func (wordPressDetector) Framework() string { return "wordpress" }
func (wordPressDetector) Languages() []string {
	return []string{"php"}
}

var wpHookPattern = regexp.MustCompile(`add_(action|filter)\(\s*['"]([^'"]+)['"]`)

func (wordPressDetector) Apply(ctx Context) bool {
	text := string(ctx.Source)
	if !strings.Contains(text, "add_action") && !strings.Contains(text, "add_filter") && !strings.Contains(text, "WP_") {
		return false
	}
	found := false
	lines := common.Lines(ctx.Source)
	for i, line := range lines {
		if m := wpHookPattern.FindStringSubmatch(line); len(m) > 2 {
			ctx.Builder.AddRoute(m[2], "", i+1, i+1, map[string]string{
				"framework": "wordpress",
				"hook_type": m[1],
			})
			found = true
		}
	}
	return found
}

func DefaultRegistry() *Registry {
	return NewRegistry(
		NewReactDetector(),
		NewNextJSDetector(),
		NewVueDetector(),
		NewAngularDetector(),
		NewFlutterDetector(),
		NewFastAPIDetector(),
		NewFlaskDetector(),
		NewSpringDetector(),
		NewLaravelDetector(),
		NewSymfonyDetector(),
		NewWordPressDetector(),
	)
}

func lastPathSegment(path string) string {
	path = strings.ReplaceAll(path, "\\", "/")
	if idx := strings.LastIndex(path, "/"); idx >= 0 {
		return path[idx+1:]
	}
	return path
}

func findDetectorBraceBlockEnd(lines []string, start int) int {
	depth := 0
	started := false
	for i := start; i < len(lines); i++ {
		for _, ch := range lines[i] {
			switch ch {
			case '{':
				depth++
				started = true
			case '}':
				depth--
				if started && depth == 0 {
					return i + 1
				}
			}
		}
	}
	return start + 1
}
