package cms

import (
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"
)

func Check(root string) (CheckReport, error) {
	project, err := LoadProject(root)
	if err != nil {
		return CheckReport{}, err
	}
	var report CheckReport

	if project.Config.Title == "" {
		report.add("ERROR", "capsule.toml", "missing title")
	}
	if len(project.Docs) == 0 {
		report.add("ERROR", "content", "no .gmi documents found")
	}
	if !hasPage(project.Docs, "index") {
		report.add("WARN", "content/pages", "no index page; build will generate one")
	}

	seen := map[string]string{}
	expected := expectedURLs(project)
	for _, doc := range project.Docs {
		if doc.Slug == "" {
			report.add("ERROR", doc.SourcePath, "missing slug")
		}
		key := doc.Type + ":" + doc.Section + ":" + doc.Slug
		if prev, ok := seen[key]; ok {
			report.add("ERROR", doc.SourcePath, "duplicate output with "+prev)
		}
		seen[key] = doc.SourcePath
		checkGemtextLinks(&report, doc, expected)
	}

	for name, menu := range project.Menus {
		for i, item := range menu.Items {
			pathName := filepath.Join("site/menus", name+".toml")
			if item.Label == "" {
				report.add("ERROR", pathName, fmt.Sprintf("menu item %d has empty label", i+1))
			}
			if item.URL == "" {
				report.add("ERROR", pathName, fmt.Sprintf("menu item %d has empty URL", i+1))
				continue
			}
			if !isExternalLink(item.URL) {
				normalized := normalizeLocalURL("/", item.URL)
				if !expected[normalized] {
					report.add("WARN", pathName, fmt.Sprintf("menu item %d local URL may not resolve: %s", i+1, item.URL))
				}
			}
		}
	}

	for name, widget := range project.Widgets {
		pathName := filepath.Join("site/widgets", name+".toml")
		if widget.Type == "" {
			report.add("ERROR", pathName, "missing widget type")
		}
		if widget.Type == "recent" && widget.Section == "" {
			report.add("ERROR", pathName, "recent widget requires section")
		}
	}

	return report, nil
}

func (r *CheckReport) add(level, pathName, message string) {
	r.Issues = append(r.Issues, Issue{Level: level, Path: pathName, Message: message})
}

func hasPage(docs []Document, slug string) bool {
	for _, doc := range docs {
		if doc.Type == "page" && doc.Slug == slug {
			return true
		}
	}
	return false
}

func expectedURLs(project Project) map[string]bool {
	expected := map[string]bool{
		"/":          true,
		"/index.gmi": true,
	}
	for _, doc := range project.Docs {
		if doc.Draft {
			continue
		}
		expected[docURL(doc)] = true
	}
	for section := range articlesBySection(project.Docs) {
		expected["/"+section+"/"] = true
		expected["/"+section+"/index.gmi"] = true
	}
	assetRoot := filepath.Join(project.Root, "assets")
	filepath.WalkDir(assetRoot, func(pathName string, entry os.DirEntry, err error) error {
		if err != nil || entry.IsDir() {
			return nil
		}
		rel, err := filepath.Rel(assetRoot, pathName)
		if err != nil {
			return nil
		}
		expected["/"+filepath.ToSlash(rel)] = true
		return nil
	})
	return expected
}

func checkGemtextLinks(report *CheckReport, doc Document, expected map[string]bool) {
	base := path.Dir(docURL(doc))
	if base == "." {
		base = "/"
	}
	for lineNo, line := range strings.Split(doc.Body, "\n") {
		trimmed := strings.TrimSpace(line)
		if !strings.HasPrefix(trimmed, "=>") {
			continue
		}
		target := strings.TrimSpace(strings.TrimPrefix(trimmed, "=>"))
		if target == "" {
			report.add("ERROR", doc.SourcePath, fmt.Sprintf("line %d: empty Gemtext link", lineNo+1))
			continue
		}
		target = strings.Fields(target)[0]
		if isExternalLink(target) || strings.HasPrefix(target, "#") {
			continue
		}
		normalized := normalizeLocalURL(base, target)
		if !expected[normalized] {
			report.add("WARN", doc.SourcePath, fmt.Sprintf("line %d: local link may not resolve: %s", lineNo+1, target))
		}
	}
}

func isExternalLink(target string) bool {
	return strings.Contains(target, "://") || strings.HasPrefix(target, "mailto:")
}

func normalizeLocalURL(base, target string) string {
	if strings.HasPrefix(target, "/") {
		if strings.HasSuffix(target, "/") {
			return target
		}
		return path.Clean(target)
	}
	joined := path.Join(base, target)
	if strings.HasSuffix(target, "/") {
		return "/" + strings.TrimPrefix(joined, "/") + "/"
	}
	if strings.HasPrefix(joined, "/") {
		return path.Clean(joined)
	}
	return "/" + path.Clean(joined)
}
