package main

import (
	"html"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

func ChangelogPage(w http.ResponseWriter, r *http.Request) {
	markdown := readChangelog()
	body := `
<header class="tool-header">
  <h2 class="tool-title">Changelog</h2>
  <p class="tool-description">Loaded from README.md.</p>
</header>
<section class="tool-panel changelog-panel">
  <div class="panel-body changelog-content">
` + renderChangelogMarkdown(markdown) + `
  </div>
</section>
<style>
  .changelog-panel { max-width: 840px; }
  .changelog-content h1 { margin: 0 0 14px; font-size: 20px; line-height: 1.25; }
  .changelog-content h2 { margin: 18px 0 8px; padding-top: 12px; border-top: 1px solid #333; font-size: 15px; line-height: 1.3; }
  .changelog-content h3 { margin: 14px 0 6px; font-size: 13px; line-height: 1.3; }
  .changelog-content ul { margin: 6px 0 12px; padding-left: 20px; }
  .changelog-content li { margin: 4px 0; line-height: 1.45; }
  .changelog-content li.nested { margin-left: 18px; }
  .changelog-content p { margin: 8px 0; line-height: 1.5; }
  .changelog-content code { font-family: Consolas, monospace; font-size: 0.95em; background: rgba(128,128,128,0.12); padding: 1px 4px; border-radius: 3px; }
  .changelog-block { margin: 8px 0 18px; padding: 10px 12px; border: 1px solid #333; border-radius: 4px; background: rgba(128,128,128,0.08); }
  .change-section { margin: 8px 0 4px; font-weight: 600; }
  .changelog-block ul { margin: 4px 0 10px; }
</style>`

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	_, _ = w.Write([]byte(Page("/changelog", "Changelog", body)))
}

func readChangelog() string {
	if embeddedReadme != "" {
		return embeddedReadme
	}
	for _, start := range changelogSearchRoots() {
		for _, candidate := range changelogCandidates(start) {
			data, err := os.ReadFile(candidate)
			if err == nil {
				return string(data)
			}
		}
	}
	return "# Changelog\n\nREADME.md was not found."
}

func changelogSearchRoots() []string {
	roots := []string{}
	if wd, err := os.Getwd(); err == nil {
		roots = append(roots, wd)
	}
	roots = append(roots, appDir())
	return roots
}

func changelogCandidates(start string) []string {
	candidates := []string{}
	dir := filepath.Clean(start)
	for i := 0; i < 5; i++ {
		candidates = append(candidates, filepath.Join(dir, "README.md"))
		parent := filepath.Dir(dir)
		if parent == dir {
			break
		}
		dir = parent
	}
	return candidates
}

func renderChangelogMarkdown(markdown string) string {
	lines := strings.Split(strings.ReplaceAll(markdown, "\r\n", "\n"), "\n")
	var out strings.Builder
	inList := false
	inCodeBlock := false
	codeLines := []string{}
	closeList := func() {
		if inList {
			out.WriteString("</ul>")
			inList = false
		}
	}

	for _, rawLine := range lines {
		if strings.TrimSpace(rawLine) == "```" {
			closeList()
			if inCodeBlock {
				out.WriteString(renderChangelogBlock(codeLines))
				codeLines = []string{}
				inCodeBlock = false
			} else {
				inCodeBlock = true
			}
			continue
		}
		if inCodeBlock {
			codeLines = append(codeLines, rawLine)
			continue
		}

		line := strings.TrimSpace(rawLine)
		if line == "" {
			closeList()
			continue
		}
		if strings.HasPrefix(line, "### ") {
			closeList()
			out.WriteString("<h2>" + renderInlineMarkdown(strings.TrimSpace(line[4:])) + "</h2>")
			continue
		}
		if strings.HasPrefix(line, "## ") {
			closeList()
			out.WriteString("<h2>" + renderInlineMarkdown(strings.TrimSpace(line[3:])) + "</h2>")
			continue
		}
		if strings.HasPrefix(line, "# ") {
			closeList()
			out.WriteString("<h1>" + renderInlineMarkdown(strings.TrimSpace(line[2:])) + "</h1>")
			continue
		}
		if strings.HasPrefix(line, "**") && strings.HasSuffix(line, "**") && len(line) > 4 {
			closeList()
			out.WriteString("<h2>" + html.EscapeString(strings.TrimSuffix(strings.TrimPrefix(line, "**"), "**")) + "</h2>")
			continue
		}
		if strings.HasPrefix(line, "- ") {
			if !inList {
				out.WriteString("<ul>")
				inList = true
			}
			out.WriteString("<li>" + renderInlineMarkdown(strings.TrimSpace(line[2:])) + "</li>")
			continue
		}
		closeList()
		out.WriteString("<p>" + renderInlineMarkdown(line) + "</p>")
	}
	if inCodeBlock {
		out.WriteString(renderChangelogBlock(codeLines))
	}
	closeList()
	return out.String()
}

func renderChangelogBlock(lines []string) string {
	var out strings.Builder
	inList := false
	closeList := func() {
		if inList {
			out.WriteString("</ul>")
			inList = false
		}
	}

	out.WriteString(`<div class="changelog-block">`)
	for _, rawLine := range lines {
		line := strings.TrimSpace(rawLine)
		if line == "" {
			closeList()
			continue
		}
		if strings.HasPrefix(line, "- ") {
			if !inList {
				out.WriteString("<ul>")
				inList = true
			}
			className := ""
			if leadingSpaceCount(rawLine) >= 8 {
				className = ` class="nested"`
			}
			out.WriteString("<li" + className + ">" + renderInlineMarkdown(strings.TrimSpace(line[2:])) + "</li>")
			continue
		}
		closeList()
		out.WriteString(`<div class="change-section">` + renderInlineMarkdown(line) + `</div>`)
	}
	closeList()
	out.WriteString("</div>")
	return out.String()
}

func renderInlineMarkdown(value string) string {
	escaped := html.EscapeString(value)
	escaped = replaceDelimited(escaped, "**", "strong")
	escaped = replaceDelimited(escaped, "`", "code")
	return escaped
}

func replaceDelimited(value, marker, tag string) string {
	var out strings.Builder
	for {
		start := strings.Index(value, marker)
		if start < 0 {
			out.WriteString(value)
			break
		}
		end := strings.Index(value[start+len(marker):], marker)
		if end < 0 {
			out.WriteString(value)
			break
		}
		end += start + len(marker)
		out.WriteString(value[:start])
		out.WriteString("<" + tag + ">")
		out.WriteString(value[start+len(marker) : end])
		out.WriteString("</" + tag + ">")
		value = value[end+len(marker):]
	}
	return out.String()
}

func leadingSpaceCount(value string) int {
	count := 0
	for _, ch := range value {
		if ch != ' ' {
			break
		}
		count++
	}
	return count
}
