package markdown

import (
	"bytes"
	"fmt"
	"html/template"
	"regexp"
	"slices"
	"strings"

	"github.com/yuin/goldmark"
	"github.com/yuin/goldmark/extension"
	"github.com/yuin/goldmark/parser"
	"github.com/yuin/goldmark/renderer/html"
)

var (
	tagPattern     = regexp.MustCompile(`(^|[\s(])#([a-zA-Z0-9_-]+)\b`)
	firstH1Pattern = regexp.MustCompile(`(?s)^\s*<h1\b[^>]*>.*?</h1>\s*`)
)

type Result struct {
	HTML  template.HTML
	Title string
	Tags  []string
}

type Renderer struct {
	markdown goldmark.Markdown
}

func NewRenderer() *Renderer {
	return &Renderer{
		markdown: goldmark.New(
			goldmark.WithExtensions(
				extension.GFM,
				extension.Footnote,
				extension.DefinitionList,
				extension.Table,
				extension.Strikethrough,
				extension.TaskList,
				extension.Typographer,
			),
			goldmark.WithParserOptions(
				parser.WithAutoHeadingID(),
			),
			goldmark.WithRendererOptions(
				html.WithHardWraps(),
				html.WithUnsafe(),
				html.WithXHTML(),
			),
		),
	}
}

func (r *Renderer) Render(content []byte) (Result, error) {
	title, titleFromHeading := extractTitle(string(content), "")
	return r.renderWithTitle(content, title, titleFromHeading)
}

func (r *Renderer) RenderPath(content []byte, path string) (Result, error) {
	title, titleFromHeading := extractTitle(string(content), path)
	return r.renderWithTitle(content, title, titleFromHeading)
}

func (r *Renderer) renderWithTitle(content []byte, title string, titleFromHeading bool) (Result, error) {
	tags := extractTags(string(content))
	prepared := preprocess(string(content))

	var output bytes.Buffer
	if err := r.markdown.Convert([]byte(prepared), &output); err != nil {
		return Result{}, fmt.Errorf("render markdown: %w", err)
	}

	html := output.String()
	if titleFromHeading {
		html = firstH1Pattern.ReplaceAllString(html, "")
	}

	return Result{
		HTML:  template.HTML(html),
		Title: title,
		Tags:  tags,
	}, nil
}

func preprocess(input string) string {
	lines := strings.Split(input, "\n")
	for index, line := range lines {
		line = rewriteWikiLinks(line)
		line = normalizeAdmonition(line)
		lines[index] = line
	}
	return strings.Join(lines, "\n")
}

func rewriteWikiLinks(line string) string {
	var builder strings.Builder
	remaining := line

	for {
		start := strings.Index(remaining, "[[")
		if start == -1 {
			builder.WriteString(remaining)
			return builder.String()
		}

		end := strings.Index(remaining[start+2:], "]]")
		if end == -1 {
			builder.WriteString(remaining)
			return builder.String()
		}

		builder.WriteString(remaining[:start])
		label := strings.TrimSpace(remaining[start+2 : start+2+end])
		if label == "" {
			builder.WriteString(remaining[start : start+2+end+2])
		} else {
			builder.WriteString(fmt.Sprintf("[%s](/docs/%s)", label, slugify(label)))
		}

		remaining = remaining[start+2+end+2:]
	}
}

func normalizeAdmonition(line string) string {
	switch {
	case strings.HasPrefix(line, "> [!NOTE]"):
		return strings.Replace(line, "> [!NOTE]", "> **Note:**", 1)
	case strings.HasPrefix(line, "> [!TIP]"):
		return strings.Replace(line, "> [!TIP]", "> **Tip:**", 1)
	case strings.HasPrefix(line, "> [!WARNING]"):
		return strings.Replace(line, "> [!WARNING]", "> **Warning:**", 1)
	default:
		return line
	}
}

func extractTitle(content string, path string) (string, bool) {
	for _, line := range strings.Split(content, "\n") {
		trimmed := strings.TrimSpace(line)
		if strings.HasPrefix(trimmed, "# ") {
			return strings.TrimSpace(strings.TrimPrefix(trimmed, "# ")), true
		}
	}
	return titleFromPath(path), false
}

func extractTags(content string) []string {
	matches := tagPattern.FindAllStringSubmatch(content, -1)
	tags := make([]string, 0, len(matches))
	seen := make(map[string]struct{}, len(matches))
	for _, match := range matches {
		if len(match) < 3 {
			continue
		}
		tag := match[2]
		if _, exists := seen[tag]; exists {
			continue
		}
		seen[tag] = struct{}{}
		tags = append(tags, tag)
	}
	slices.Sort(tags)
	return tags
}

func slugify(value string) string {
	value = strings.ToLower(strings.TrimSpace(value))
	var builder strings.Builder
	lastDash := false
	for _, r := range value {
		switch {
		case r >= 'a' && r <= 'z', r >= '0' && r <= '9':
			builder.WriteRune(r)
			lastDash = false
		default:
			if !lastDash {
				builder.WriteByte('-')
				lastDash = true
			}
		}
	}
	return strings.Trim(builder.String(), "-")
}

func titleFromPath(path string) string {
	path = strings.Trim(path, "/")
	path = strings.TrimSuffix(path, ".md")
	if path == "" {
		return "Home"
	}

	base := path
	if strings.HasSuffix(base, "/index") {
		base = strings.TrimSuffix(base, "/index")
	}
	if base == "index" {
		return "Home"
	}
	if idx := strings.LastIndex(base, "/"); idx >= 0 {
		base = base[idx+1:]
	}
	base = strings.ReplaceAll(base, "-", " ")
	base = strings.ReplaceAll(base, "_", " ")
	parts := strings.Fields(base)
	for i, part := range parts {
		if part == strings.ToUpper(part) {
			continue
		}
		parts[i] = strings.ToUpper(part[:1]) + strings.ToLower(part[1:])
	}
	if len(parts) == 0 {
		return "Home"
	}
	return strings.Join(parts, " ")
}
