package roadmapsync

import (
	"bufio"
	"fmt"
	"html"
	"path"
	"strings"
)

// PlaneProjection is the provider-neutral, HTML-safe view of a develop
// Milestone that should be written into the Plane work item. Title follows the
// `[milestone-slug] Milestone Title` form (Milestone doc line 103); BodyHTML is
// the Milestone goal/scope rendered as escaped HTML. No provider secret or
// token is ever placed here.
type PlaneProjection struct {
	Title    string
	BodyHTML string
}

// MilestoneSlug derives the milestone id used in the Plane title from the
// Milestone file path: the file name without its .md extension
// (agent-roadmap/phase/<phase>/milestones/<slug>.md -> <slug>). It returns ""
// when the path is empty or not a .md file so a caller never builds a
// `[] Title` prefix from a bad path.
func MilestoneSlug(milestonePath string) string {
	cleaned := normalizeRepoPath(milestonePath)
	if cleaned == "" {
		return ""
	}
	base := path.Base(cleaned)
	if !strings.HasSuffix(base, ".md") {
		return ""
	}
	return strings.TrimSuffix(base, ".md")
}

// FormatPlaneProjection turns a develop Milestone Markdown document into the
// Plane title/body projection. milestonePath supplies the slug used in the
// title prefix. The body carries the Milestone title, the "## 목표"/"## 범위"
// sections when present, and is HTML-escaped so untrusted Markdown text never
// injects markup into Plane. The formatter is deliberately minimal: it does not
// attempt full Markdown rendering, only a safe, readable summary.
func FormatPlaneProjection(milestonePath, markdown string) PlaneProjection {
	slug := MilestoneSlug(milestonePath)
	title := extractMilestoneTitle(markdown)

	displayTitle := title
	if slug != "" {
		shown := title
		if strings.TrimSpace(shown) == "" {
			shown = slug
		}
		displayTitle = fmt.Sprintf("[%s] %s", slug, shown)
	}

	return PlaneProjection{
		Title:    strings.TrimSpace(displayTitle),
		BodyHTML: buildBodyHTML(title, markdown),
	}
}

// extractMilestoneTitle returns the text of the first level-1 heading
// ("# Title") in the document, or "" when there is none.
func extractMilestoneTitle(markdown string) string {
	scanner := bufio.NewScanner(strings.NewReader(markdown))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if strings.HasPrefix(line, "# ") {
			return strings.TrimSpace(strings.TrimPrefix(line, "#"))
		}
	}
	return ""
}

// sectionLabels are the Milestone section headings whose body lines are carried
// into the Plane projection, in output order. Matched case-insensitively after
// stripping heading markers.
var sectionLabels = []string{"목표", "범위"}

// buildBodyHTML renders the Milestone title plus the goal/scope sections as
// escaped HTML paragraphs. Lines under a tracked section are emitted until the
// next heading. Everything is HTML-escaped, so neither Markdown text nor any
// stray provider metadata is rendered as live markup.
func buildBodyHTML(title, markdown string) string {
	var b strings.Builder
	if t := strings.TrimSpace(title); t != "" {
		b.WriteString("<h2>")
		b.WriteString(html.EscapeString(t))
		b.WriteString("</h2>")
	}

	sections := collectSections(markdown)
	for _, label := range sectionLabels {
		lines, ok := sections[label]
		if !ok || len(lines) == 0 {
			continue
		}
		b.WriteString("<h3>")
		b.WriteString(html.EscapeString(label))
		b.WriteString("</h3>")
		for _, line := range lines {
			b.WriteString("<p>")
			b.WriteString(html.EscapeString(line))
			b.WriteString("</p>")
		}
	}
	return b.String()
}

// collectSections groups non-empty body lines under each tracked section
// heading. A heading line that matches one of sectionLabels (after trimming
// `#`) opens that section; any heading closes the current one. Keys are the
// matched label.
func collectSections(markdown string) map[string][]string {
	want := map[string]bool{}
	for _, l := range sectionLabels {
		want[l] = true
	}

	sections := map[string][]string{}
	current := ""
	scanner := bufio.NewScanner(strings.NewReader(markdown))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" {
			continue
		}
		if strings.HasPrefix(line, "#") {
			heading := strings.TrimSpace(strings.TrimLeft(line, "#"))
			if want[heading] {
				current = heading
			} else {
				current = ""
			}
			continue
		}
		if current == "" {
			continue
		}
		text := strings.TrimSpace(strings.TrimLeft(line, "-*"))
		if text == "" {
			continue
		}
		sections[current] = append(sections[current], text)
	}
	return sections
}
