package wiki

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/zzet/gortex/internal/analysis"
	"github.com/zzet/gortex/internal/contracts"
	"github.com/zzet/gortex/internal/graph"
)

// renderRepoIndex emits the per-repo index page (wiki/<slug>/index.md).
// Lists communities in size order with links to per-community pages,
// the architecture diagram, the analysis pages, and the docs bundle.
func (gen *Generator) renderRepoIndex(ctx context.Context) (string, error) {
	var b strings.Builder
	repoLabel := gen.opts.Repo
	if repoLabel == "" {
		repoLabel = "Repository"
	}
	b.WriteString("---\n")
	fmt.Fprintf(&b, "title: %s wiki\n", repoLabel)
	fmt.Fprintf(&b, "generated_at: %s\n", time.Now().UTC().Format(time.RFC3339))
	fmt.Fprintf(&b, "node_count: %d\n", gen.nodeCount)
	fmt.Fprintf(&b, "edge_count: %d\n", gen.edgeCount)
	b.WriteString("---\n\n")

	fmt.Fprintf(&b, "# %s\n\n", repoLabel)
	fmt.Fprintf(&b,
		"Auto-generated by `gortex wiki` on %s. %d nodes, %d edges across %d files.\n\n",
		time.Now().UTC().Format("2006-01-02"), gen.nodeCount, gen.edgeCount, gen.fileCount)

	b.WriteString("## At a Glance\n\n")
	b.WriteString("| Metric | Value |\n")
	b.WriteString("|--------|-------|\n")
	fmt.Fprintf(&b, "| Files | %d |\n", gen.fileCount)
	fmt.Fprintf(&b, "| Symbols | %d |\n", gen.symbolCount)
	fmt.Fprintf(&b, "| Relationships | %d |\n", gen.edgeCount)
	fmt.Fprintf(&b, "| Communities | %d |\n", len(gen.kept))
	fmt.Fprintf(&b, "| Processes | %d |\n", gen.processCount)
	fmt.Fprintf(&b, "| Contracts | %d |\n", gen.contractCount)
	fmt.Fprintf(&b, "| Hotspots | %d |\n", len(gen.hotspots))
	fmt.Fprintf(&b, "| Cycles | %d |\n", len(gen.cycles))
	b.WriteString("\n")

	if len(gen.kept) > 0 {
		b.WriteString("## Community Map\n\n")
		b.WriteString("```mermaid\n")
		b.WriteString(RenderCommunityGraph(gen.graph, gen.communities, CommunityGraphOpts{
			MinSize: gen.opts.MinCommunity,
			Max:     gen.opts.MaxCommunities,
		}))
		if !strings.HasSuffix(b.String(), "\n") {
			b.WriteString("\n")
		}
		b.WriteString("```\n\n")

		b.WriteString("## Communities\n\n")
		b.WriteString("| Community | Size | Cohesion | Files | Page |\n")
		b.WriteString("|-----------|------|----------|-------|------|\n")
		for _, c := range gen.kept {
			page := gen.communityPagePath(c)
			fmt.Fprintf(&b, "| %s | %d | %.0f%% | %d | [%s](%s) |\n",
				escapeTableCell(c.Label), c.Size, c.Cohesion*100, len(c.Files),
				"open", page)
		}
		b.WriteString("\n")
	}

	b.WriteString("## Sections\n\n")
	b.WriteString("- [Architecture](architecture.md) — community-level overview\n")
	if !gen.opts.NoProcesses {
		b.WriteString("- [Processes](processes/) — execution flows with sequence diagrams\n")
	}
	if !gen.opts.NoContracts {
		b.WriteString("- [API Contracts](contracts/api-surface.md) — HTTP/gRPC/GraphQL surfaces\n")
	}
	b.WriteString("- [Hotspots](analysis/hotspots.md) — high-coupling symbols\n")
	b.WriteString("- [Cycles](analysis/cycles.md) — circular dependencies\n")
	b.WriteString("- [Semantic](analysis/semantic.md) — edge confidence & coverage\n")
	if !gen.opts.NoDocs && gen.docsBundle != "" {
		b.WriteString("- [Changelog & ownership](changelog.md) — `gortex docs` bundle\n")
	}
	b.WriteString("\n")

	b.WriteString("## How to Explore\n\n")
	b.WriteString("```\n")
	b.WriteString("smart_context with task: \"understand this codebase\"\n")
	b.WriteString("get_repo_outline\n")
	if len(gen.kept) > 0 {
		fmt.Fprintf(&b, "get_communities with id: %q\n", gen.kept[0].ID)
	}
	b.WriteString("```\n")

	body := b.String()
	if gen.opts.Enhance && gen.opts.Enhancer != nil {
		out, err := gen.opts.Enhancer.Enhance(ctx, EnhanceSection{
			Kind:        "architecture",
			PageTitle:   repoLabel + " index",
			RawMarkdown: body,
		})
		if err == nil && strings.TrimSpace(out) != "" {
			body = out
		}
	}
	return body, nil
}

// renderCommunityPage emits one community article.
func (gen *Generator) renderCommunityPage(ctx context.Context, c analysis.Community) (string, error) {
	var b strings.Builder
	label := c.Label
	if label == "" {
		label = c.ID
	}
	b.WriteString("---\n")
	fmt.Fprintf(&b, "title: %s\n", label)
	fmt.Fprintf(&b, "community_id: %s\n", c.ID)
	fmt.Fprintf(&b, "symbols: %d\n", c.Size)
	fmt.Fprintf(&b, "files: %d\n", len(c.Files))
	b.WriteString("---\n\n")

	fmt.Fprintf(&b, "# %s\n\n", label)
	fmt.Fprintf(&b, "%d symbols across %d files | %.0f%% cohesion | Community `%s`\n\n",
		c.Size, len(c.Files), c.Cohesion*100, c.ID)

	b.WriteString("## Files\n\n")
	if len(c.Files) == 0 {
		b.WriteString("_No files in this community._\n\n")
	} else {
		fileSyms := gen.buildFileSymbolMap(c)
		// Stable ordering.
		files := make([]string, 0, len(c.Files))
		files = append(files, c.Files...)
		sort.Strings(files)

		b.WriteString("| File | Symbols |\n")
		b.WriteString("|------|---------|\n")
		for _, f := range files {
			syms := fileSyms[f]
			if len(syms) > 8 {
				syms = append(syms[:8], "...")
			}
			fmt.Fprintf(&b, "| `%s` | %s |\n", f, strings.Join(syms, ", "))
		}
		b.WriteString("\n")
	}

	// Entry points.
	entries := gen.findEntryPoints(c)
	if len(entries) > 0 {
		b.WriteString("## Entry Points\n\n")
		for _, ep := range entries {
			fmt.Fprintf(&b, "- `%s`\n", ep)
		}
		b.WriteString("\n")
	}

	// Execution flows that touch this community.
	if !gen.opts.NoProcesses {
		procs := gen.processesTouching(c)
		if len(procs) > 0 {
			b.WriteString("## Execution Flows\n\n")
			for _, p := range procs {
				fmt.Fprintf(&b, "- [%s](../processes/%s.md) — %d steps from `%s`\n",
					p.Name, slugify(p.ID+"-"+p.Name), p.StepCount, p.EntryPoint)
			}
			b.WriteString("\n")
		}
	}

	// Cross-community connections.
	if cm := gen.crossComm[c.ID]; len(cm) > 0 {
		type pair struct {
			id, label string
			count     int
		}
		var pairs []pair
		for cid, n := range cm {
			pairs = append(pairs, pair{id: cid, label: gen.communityLabel(cid), count: n})
		}
		sort.Slice(pairs, func(i, j int) bool {
			if pairs[i].count != pairs[j].count {
				return pairs[i].count > pairs[j].count
			}
			return pairs[i].label < pairs[j].label
		})
		b.WriteString("## Connected Communities\n\n")
		b.WriteString("| Community | Cross-edges |\n")
		b.WriteString("|-----------|-------------|\n")
		for _, p := range pairs {
			page := "../" + gen.communityPagePathByID(p.id)
			fmt.Fprintf(&b, "| [%s](%s) | %d |\n", escapeTableCell(p.label), page, p.count)
		}
		b.WriteString("\n")
	}

	b.WriteString("## How to Explore\n\n")
	b.WriteString("```\n")
	fmt.Fprintf(&b, "get_communities with id: %q\n", c.ID)
	fmt.Fprintf(&b, "smart_context with task: \"understand %s\"\n", label)
	if len(entries) > 0 {
		fmt.Fprintf(&b, "find_usages with id: %q\n", entries[0])
	}
	b.WriteString("```\n")

	body := b.String()
	if gen.opts.Enhance && gen.opts.Enhancer != nil {
		anchors := make([]string, 0, len(c.Members))
		if len(c.Members) > 8 {
			anchors = append(anchors, c.Members[:8]...)
		} else {
			anchors = append(anchors, c.Members...)
		}
		out, err := gen.opts.Enhancer.Enhance(ctx, EnhanceSection{
			Kind:            "community",
			PageTitle:       label,
			AnchorSymbolIDs: anchors,
			RawMarkdown:     body,
			Context:         fmt.Sprintf("size=%d cohesion=%.2f files=%d", c.Size, c.Cohesion, len(c.Files)),
		})
		if err == nil && strings.TrimSpace(out) != "" {
			body = out
		}
	}
	return body, nil
}

// renderArchitecturePage emits architecture.md.
func (gen *Generator) renderArchitecturePage(ctx context.Context) (string, error) {
	var b strings.Builder
	b.WriteString("---\n")
	b.WriteString("title: Architecture\n")
	b.WriteString("---\n\n")
	b.WriteString("# Architecture Overview\n\n")
	if len(gen.kept) == 0 {
		b.WriteString("_No communities of sufficient size were detected._\n")
	} else {
		fmt.Fprintf(&b, "This codebase decomposes into %d functional communities. The diagram below shows the largest %d and the cross-community calls between them.\n\n",
			len(gen.kept), len(gen.kept))
		b.WriteString("```mermaid\n")
		b.WriteString(RenderArchitecture(gen.graph, gen.communities, CommunityGraphOpts{
			MinSize: gen.opts.MinCommunity,
			Max:     gen.opts.MaxCommunities,
		}))
		if !strings.HasSuffix(b.String(), "\n") {
			b.WriteString("\n")
		}
		b.WriteString("```\n\n")

		b.WriteString("## Communities by Size\n\n")
		b.WriteString("| Community | Files | Symbols | Cohesion | Hub |\n")
		b.WriteString("|-----------|-------|---------|----------|-----|\n")
		for _, c := range gen.kept {
			hub := c.Hub
			if hub == "" {
				hub = "—"
			}
			page := gen.communityPagePath(c)
			fmt.Fprintf(&b, "| [%s](%s) | %d | %d | %.0f%% | `%s` |\n",
				escapeTableCell(c.Label), page,
				len(c.Files), c.Size, c.Cohesion*100, escapeTableCell(hub))
		}
		b.WriteString("\n")
	}

	body := b.String()
	if gen.opts.Enhance && gen.opts.Enhancer != nil {
		out, err := gen.opts.Enhancer.Enhance(ctx, EnhanceSection{
			Kind:        "architecture",
			PageTitle:   "Architecture overview",
			RawMarkdown: body,
		})
		if err == nil && strings.TrimSpace(out) != "" {
			body = out
		}
	}
	return body, nil
}

// renderHotspotsPage emits analysis/hotspots.md.
func (gen *Generator) renderHotspotsPage(_ context.Context) (string, error) {
	var b strings.Builder
	b.WriteString("---\n")
	b.WriteString("title: Hotspots\n")
	b.WriteString("---\n\n")
	b.WriteString("# Hotspots — High-Coupling Symbols\n\n")
	b.WriteString("Symbols ranked by `(fan_in * 2) + (fan_out * 1.5) + (community_crossings * 3)`, normalised to 0..100.\n\n")
	if len(gen.hotspots) == 0 {
		b.WriteString("_No hotspots above the auto-threshold (mean + 2σ)._\n")
		return b.String(), nil
	}
	b.WriteString("| # | Symbol | Kind | Fan-in | Fan-out | Crossings | Score |\n")
	b.WriteString("|---|--------|------|--------|---------|-----------|-------|\n")
	limit := 30
	if len(gen.hotspots) < limit {
		limit = len(gen.hotspots)
	}
	for i, h := range gen.hotspots[:limit] {
		display := h.Name
		if h.FilePath != "" {
			display = fmt.Sprintf("%s — `%s:%d`", h.Name, h.FilePath, h.Line)
		}
		fmt.Fprintf(&b, "| %d | %s | %s | %d | %d | %d | %.2f |\n",
			i+1, escapeTableCell(display), h.Kind, h.FanIn, h.FanOut, h.CommunityCrossings, h.ComplexityScore)
	}
	b.WriteString("\nUse `verify_change` before modifying any of these signatures — their blast radius is wide.\n")
	return b.String(), nil
}

// renderCyclesPage emits analysis/cycles.md.
func (gen *Generator) renderCyclesPage(_ context.Context) (string, error) {
	var b strings.Builder
	b.WriteString("---\n")
	b.WriteString("title: Cycles\n")
	b.WriteString("---\n\n")
	b.WriteString("# Circular Dependencies\n\n")
	if len(gen.cycles) == 0 {
		b.WriteString("_No cycles detected._\n")
		return b.String(), nil
	}
	fmt.Fprintf(&b, "%d cycle(s) detected. Higher severity = harder to fix and bigger refactor surface.\n\n", len(gen.cycles))
	b.WriteString("| # | Kind | Severity | Path length | First node |\n")
	b.WriteString("|---|------|----------|-------------|------------|\n")
	for i, c := range gen.cycles {
		first := ""
		if len(c.Path) > 0 {
			first = c.Path[0]
		}
		fmt.Fprintf(&b, "| %d | %s | %d | %d | `%s` |\n",
			i+1, c.Kind, c.Severity, len(c.Path), escapeTableCell(first))
	}
	b.WriteString("\n")
	limit := 5
	if len(gen.cycles) < limit {
		limit = len(gen.cycles)
	}
	for i := 0; i < limit; i++ {
		c := gen.cycles[i]
		fmt.Fprintf(&b, "## Cycle %d — %s (severity %d)\n\n", i+1, c.Kind, c.Severity)
		b.WriteString("```\n")
		for _, n := range c.Path {
			fmt.Fprintf(&b, "  %s\n", n)
		}
		if len(c.Path) > 0 {
			fmt.Fprintf(&b, "  ↑ back to %s\n", c.Path[0])
		}
		b.WriteString("```\n\n")
	}
	return b.String(), nil
}

// renderSemanticPage emits analysis/semantic.md.
func (gen *Generator) renderSemanticPage(_ context.Context) (string, error) {
	var b strings.Builder
	b.WriteString("---\n")
	b.WriteString("title: Semantic analysis\n")
	b.WriteString("---\n\n")
	b.WriteString("# Semantic Analysis\n\n")
	b.WriteString("Edge confidence distribution across the indexed graph. Higher tiers come from compiler-verified providers (go-types, LSP, SCIP); lower tiers fall back to AST heuristics.\n\n")

	if len(gen.confDist) == 0 {
		b.WriteString("_No confidence-labelled edges in the graph._\n")
		return b.String(), nil
	}

	b.WriteString("## Edge Confidence Distribution\n\n")
	b.WriteString("| Origin | Edges | Percentage |\n")
	b.WriteString("|--------|-------|------------|\n")
	keys := make([]string, 0, len(gen.confDist))
	var total int
	for k, v := range gen.confDist {
		keys = append(keys, k)
		total += v
	}
	sort.Strings(keys)
	for _, k := range keys {
		pct := 0.0
		if total > 0 {
			pct = float64(gen.confDist[k]) / float64(total) * 100
		}
		fmt.Fprintf(&b, "| %s | %d | %.1f%% |\n", k, gen.confDist[k], pct)
	}
	fmt.Fprintf(&b, "| **total** | **%d** | 100%% |\n\n", total)

	if len(gen.semanticProviders) > 0 {
		b.WriteString("## Provider Status\n\n")
		b.WriteString("| Language | Provider | Status |\n")
		b.WriteString("|----------|----------|--------|\n")
		for _, ps := range gen.semanticProviders {
			fmt.Fprintf(&b, "| %s | %s | %s |\n", ps.Language, ps.Name, ps.Status)
		}
		b.WriteString("\n")
	}
	return b.String(), nil
}

// renderContractsPage emits contracts/api-surface.md.
func (gen *Generator) renderContractsPage(_ context.Context) (string, error) {
	var b strings.Builder
	b.WriteString("---\n")
	b.WriteString("title: API contracts\n")
	b.WriteString("---\n\n")
	b.WriteString("# API Contracts\n\n")
	if len(gen.contractList) == 0 {
		b.WriteString("_No contracts detected._\n")
		return b.String(), nil
	}
	// Group by ContractType, then by Role.
	byType := make(map[contracts.ContractType][]contracts.Contract)
	for _, c := range gen.contractList {
		byType[c.Type] = append(byType[c.Type], c)
	}
	types := make([]string, 0, len(byType))
	for t := range byType {
		types = append(types, string(t))
	}
	sort.Strings(types)
	for _, t := range types {
		list := byType[contracts.ContractType(t)]
		fmt.Fprintf(&b, "## %s contracts (%d)\n\n", strings.ToUpper(t), len(list))
		b.WriteString("| Role | ID | Symbol | File:Line |\n")
		b.WriteString("|------|----|--------|-----------|\n")
		// Stable order.
		sort.SliceStable(list, func(i, j int) bool {
			if list[i].Role != list[j].Role {
				return list[i].Role < list[j].Role
			}
			return list[i].ID < list[j].ID
		})
		for _, c := range list {
			loc := c.FilePath
			if c.Line > 0 {
				loc = fmt.Sprintf("%s:%d", c.FilePath, c.Line)
			}
			fmt.Fprintf(&b, "| %s | `%s` | `%s` | `%s` |\n",
				c.Role, escapeTableCell(c.ID), escapeTableCell(c.SymbolID), escapeTableCell(loc))
		}
		b.WriteString("\n")
	}
	return b.String(), nil
}

// renderProcessPage emits one process article.
func (gen *Generator) renderProcessPage(ctx context.Context, p analysis.Process) (string, error) {
	var b strings.Builder
	b.WriteString("---\n")
	fmt.Fprintf(&b, "title: %s\n", p.Name)
	fmt.Fprintf(&b, "process_id: %s\n", p.ID)
	fmt.Fprintf(&b, "steps: %d\n", p.StepCount)
	b.WriteString("---\n\n")

	fmt.Fprintf(&b, "# Process: %s\n\n", p.Name)
	fmt.Fprintf(&b, "%d steps across %d files. Entry: `%s` (score %.2f).\n\n",
		p.StepCount, len(p.Files), p.EntryPoint, p.Score)

	b.WriteString("## Flow\n\n")
	b.WriteString("```mermaid\n")
	b.WriteString(RenderProcessSequence(p, gen.nodeByID, gen.commLabelByNode))
	if !strings.HasSuffix(b.String(), "\n") {
		b.WriteString("\n")
	}
	b.WriteString("```\n\n")

	b.WriteString("## Steps\n\n")
	b.WriteString("| # | Depth | Symbol | File |\n")
	b.WriteString("|---|-------|--------|------|\n")
	for i, s := range p.Steps {
		name, file := s.ID, ""
		if n, ok := gen.nodeByID[s.ID]; ok && n != nil {
			name = n.Name
			file = n.FilePath
		}
		fmt.Fprintf(&b, "| %d | %d | `%s` | `%s` |\n", i+1, s.Depth, escapeTableCell(name), escapeTableCell(file))
	}
	b.WriteString("\n")

	if len(p.Files) > 0 {
		b.WriteString("## Files Touched\n\n")
		for _, f := range p.Files {
			fmt.Fprintf(&b, "- `%s`\n", f)
		}
		b.WriteString("\n")
	}

	body := b.String()
	if gen.opts.Enhance && gen.opts.Enhancer != nil {
		anchors := []string{p.EntryPoint}
		out, err := gen.opts.Enhancer.Enhance(ctx, EnhanceSection{
			Kind:            "process",
			PageTitle:       p.Name,
			AnchorSymbolIDs: anchors,
			RawMarkdown:     body,
		})
		if err == nil && strings.TrimSpace(out) != "" {
			body = out
		}
	}
	return body, nil
}

// renderTopLevelIndex emits the top-level wiki/index.md.
// Single-element repo list today; multi-element when multi-repo lands.
func (gen *Generator) renderTopLevelIndex() string {
	var b strings.Builder
	b.WriteString("---\n")
	b.WriteString("title: Gortex Wiki\n")
	fmt.Fprintf(&b, "generated_at: %s\n", time.Now().UTC().Format(time.RFC3339))
	b.WriteString("---\n\n")
	b.WriteString("# Workspaces\n\n")
	b.WriteString("This wiki indexes one repository today. Multi-repo workspaces will appear under `_workspace/` as they land.\n\n")
	b.WriteString("| Repository | Symbols | Communities | Page |\n")
	b.WriteString("|------------|---------|-------------|------|\n")
	fmt.Fprintf(&b, "| `%s` | %d | %d | [%s](%s/index.md) |\n",
		gen.opts.Repo, gen.symbolCount, len(gen.kept), gen.opts.Repo, gen.opts.Repo)
	return b.String()
}

// escapeTableCell makes a string safe inside a Markdown table cell:
// strip newlines and escape pipes.
func escapeTableCell(s string) string {
	s = strings.ReplaceAll(s, "\n", " ")
	s = strings.ReplaceAll(s, "|", "\\|")
	return s
}

// processesTouching returns processes whose Steps include a member of c.
func (gen *Generator) processesTouching(c analysis.Community) []analysis.Process {
	if gen.processes == nil {
		return nil
	}
	memberSet := make(map[string]bool, len(c.Members))
	for _, m := range c.Members {
		memberSet[m] = true
	}
	var out []analysis.Process
	for _, p := range gen.processes.Processes {
		for _, s := range p.Steps {
			if memberSet[s.ID] {
				out = append(out, p)
				break
			}
		}
	}
	// Cap to 10 most relevant by step count.
	sort.SliceStable(out, func(i, j int) bool { return out[i].StepCount > out[j].StepCount })
	if len(out) > 10 {
		out = out[:10]
	}
	return out
}

// communityPagePath returns the relative path under wiki/<repo>/ for c.
func (gen *Generator) communityPagePath(c analysis.Community) string {
	return gen.communityPagePathByID(c.ID)
}

func (gen *Generator) communityPagePathByID(id string) string {
	for i, c := range gen.kept {
		if c.ID == id {
			slug := slugify(c.Label)
			if slug == "" {
				slug = slugify(c.ID)
			}
			return fmt.Sprintf("communities/%d-%s.md", i+1, slug)
		}
	}
	// Community wasn't kept (filtered out by size or cap) — link to index.
	return "index.md"
}

// processPagePath returns the relative path for a process page.
func processPagePath(p analysis.Process) string {
	return "processes/" + slugify(p.ID+"-"+p.Name) + ".md"
}

// ensure graph import is used.
var _ = (*graph.Graph)(nil)
