package cms

import (
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

func LoadDocuments(root string) ([]Document, error) {
	contentRoot := filepath.Join(root, "content")
	var docs []Document
	if _, err := os.Stat(contentRoot); err != nil {
		return nil, err
	}
	err := filepath.WalkDir(contentRoot, func(path string, entry fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if entry.IsDir() || filepath.Ext(path) != ".gmi" {
			return nil
		}
		data, err := os.ReadFile(path)
		if err != nil {
			return err
		}
		doc, err := ParseDocument(path, string(data))
		if err != nil {
			return err
		}
		docs = append(docs, doc)
		return nil
	})
	if err != nil {
		return nil, err
	}
	sort.Slice(docs, func(i, j int) bool {
		if docs[i].Type != docs[j].Type {
			return docs[i].Type < docs[j].Type
		}
		if docs[i].Date != docs[j].Date {
			return docs[i].Date > docs[j].Date
		}
		return docs[i].Slug < docs[j].Slug
	})
	return docs, nil
}

func ParseDocument(path, data string) (Document, error) {
	meta, body, hasMeta := splitFrontmatter(data)
	doc := Document{
		SourcePath: path,
		Body:       strings.TrimLeft(body, "\n"),
	}
	if hasMeta {
		table, err := parseMetadata(meta)
		if err != nil {
			return Document{}, fmt.Errorf("%s: parse frontmatter: %w", path, err)
		}
		doc.Type = table.values["type"]
		doc.Slug = table.values["slug"]
		doc.Title = table.values["title"]
		doc.Date = table.values["date"]
		doc.Section = table.values["section"]
		doc.Tags = table.lists["tags"]
		doc.Draft = table.values["draft"] == "true"
	}
	if doc.Type == "" {
		doc.Type = inferType(path)
	}
	if doc.Slug == "" {
		doc.Slug = inferSlug(path)
	}
	if doc.Title == "" {
		doc.Title = inferTitle(doc.Body, doc.Slug)
	}
	if doc.Type == "article" {
		if doc.Section == "" {
			doc.Section = "articles"
		}
		if doc.Date == "" {
			doc.Date = inferDate(path)
		}
	}
	return doc, nil
}

func writeDocument(path string, doc Document) error {
	var b strings.Builder
	b.WriteString("+++\n")
	b.WriteString("title = " + formatString(doc.Title) + "\n")
	b.WriteString("type = " + formatString(doc.Type) + "\n")
	b.WriteString("slug = " + formatString(doc.Slug) + "\n")
	if doc.Date != "" {
		b.WriteString("date = " + formatString(doc.Date) + "\n")
	}
	if doc.Section != "" {
		b.WriteString("section = " + formatString(doc.Section) + "\n")
	}
	if len(doc.Tags) > 0 {
		b.WriteString("tags = " + formatStringList(doc.Tags) + "\n")
	}
	if doc.Draft {
		b.WriteString("draft = true\n")
	}
	b.WriteString("+++\n\n")
	b.WriteString(strings.TrimLeft(doc.Body, "\n"))
	return writeNewFile(path, []byte(b.String()))
}

func splitFrontmatter(data string) (string, string, bool) {
	data = strings.ReplaceAll(data, "\r\n", "\n")
	delimiter := ""
	switch {
	case strings.HasPrefix(data, "+++\n"):
		delimiter = "+++"
	case strings.HasPrefix(data, "---\n"):
		delimiter = "---"
	default:
		return "", data, false
	}
	rest := strings.TrimPrefix(data, delimiter+"\n")
	idx := strings.Index(rest, "\n"+delimiter+"\n")
	if idx < 0 {
		return "", data, false
	}
	meta := rest[:idx]
	body := rest[idx+len("\n"+delimiter+"\n"):]
	return meta, body, true
}

func inferType(path string) string {
	clean := filepath.ToSlash(path)
	if strings.Contains(clean, "/articles/") {
		return "article"
	}
	return "page"
}

func inferSlug(path string) string {
	name := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
	if len(name) > len("2006-01-02-") && name[4] == '-' && name[7] == '-' && name[10] == '-' {
		name = name[11:]
	}
	return slugify(name)
}

func inferDate(path string) string {
	name := filepath.Base(path)
	if len(name) >= len("2006-01-02") && name[4] == '-' && name[7] == '-' {
		return name[:10]
	}
	return ""
}

func inferTitle(body, slug string) string {
	for _, line := range strings.Split(body, "\n") {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "# ") {
			return strings.TrimSpace(strings.TrimPrefix(line, "# "))
		}
	}
	return titleize(slug)
}
