package main

import (
	"fmt"
	"sort"
	"strings"
)

// printSummary writes a human-readable status block to stdout (always shown,
// regardless of pass/fail).
func printSummary(
	modules map[string]*Module,
	sccs []SCC,
	accepted map[string]bool,
	edges []EdgeViolation,
	edgeBaseline map[string]bool,
	leaks []string,
	leakBaseline map[string]bool,
	dialect []DialectHit,
	dialectBaseline map[string]map[string]bool,
	warnings []string,
) {
	fmt.Println("boundarygate — module boundary report")
	fmt.Println("=====================================")

	// Module count.
	var present, missing int
	for _, m := range modules {
		if m.Missing {
			missing++
		} else {
			present++
		}
	}
	fmt.Printf("Modules: %d first-party (%d present, %d missing/unreachable)\n", present+missing, present, missing)

	// SCCs.
	fmt.Printf("\nNon-trivial SCCs (cycles): %d\n", len(sccs))
	for _, s := range sccs {
		mark := "NEW/unaccepted"
		if accepted[s.Key()] {
			mark = "accepted"
		}
		fmt.Printf("  [%s] {%s}\n", mark, join(s.Members, ", "))
	}

	// Forbidden edges.
	fmt.Printf("\nForbidden-edge violations: %d\n", len(edges))
	for _, v := range edges {
		state := "NEW"
		if edgeBaseline[v.Key()] {
			state = "baselined"
		}
		fmt.Printf("  [%s] %s\n", state, v.Key())
	}

	// Contract runtime leaks.
	fmt.Printf("\nContract runtime leaks (database/sql|net/http): %d file(s)\n", len(leaks))
	for _, f := range leaks {
		state := "NEW"
		if leakBaseline[f] {
			state = "baselined"
		}
		fmt.Printf("  [%s] %s\n", state, f)
	}

	// L0 dialect tokens.
	var newDialect int
	perModule := map[string]int{}
	for _, h := range dialect {
		perModule[h.Module]++
		if len(dialectNewTokens(h, dialectBaseline)) > 0 {
			newDialect++
		}
	}
	fmt.Printf("\nL0 dialect-token hits: %d file(s) total, %d NEW (beyond baseline)\n", len(dialect), newDialect)
	var mods []string
	for m := range perModule {
		mods = append(mods, m)
	}
	sort.Strings(mods)
	for _, m := range mods {
		fmt.Printf("  %-34s %d\n", short(m), perModule[m])
	}

	if len(warnings) > 0 {
		fmt.Printf("\nWarnings (%d):\n", len(warnings))
		for _, w := range warnings {
			fmt.Println("  ! " + w)
		}
	}
}

// writeLedger generates docs/architecture/boundary-ledger.md.
func writeLedger(
	root string,
	modules map[string]*Module,
	allSCCs []SCC,
	nonTrivial []SCC,
	edges []EdgeViolation,
	leaks []string,
	dialect []DialectHit,
) error {
	var b strings.Builder

	b.WriteString("# Boundary Ledger\n\n")
	b.WriteString("> Generated by `tools/boundarygate` (`go run ./tools/boundarygate -update`).\n")
	b.WriteString("> This is the machine-checked snapshot of the module boundary; the gate\n")
	b.WriteString("> (`go run ./tools/boundarygate`) fails CI on any regression beyond the\n")
	b.WriteString("> baselines recorded under `tools/boundarygate/`.\n\n")

	// --- Module list with LOC ---
	b.WriteString("## Modules\n\n")
	b.WriteString("First-party Go modules in the workspace, with non-blank Go LOC (nested\n")
	b.WriteString("modules excluded from a parent's count).\n\n")
	b.WriteString("| Module | Directory | Go LOC | Status |\n")
	b.WriteString("|---|---|---:|---|\n")
	var mpaths []string
	for p := range modules {
		mpaths = append(mpaths, p)
	}
	sort.Strings(mpaths)
	totalLOC := 0
	for _, p := range mpaths {
		m := modules[p]
		status := "present"
		dir := m.RelDir
		if m.Missing {
			status = "missing/unreachable"
			dir = "(not checked out)"
		}
		totalLOC += m.LOC
		b.WriteString(fmt.Sprintf("| `%s` | `%s` | %d | %s |\n", p, dir, m.LOC, status))
	}
	b.WriteString(fmt.Sprintf("\n**Total Go LOC (first-party, present modules): %d**\n\n", totalLOC))

	// --- Dependency adjacency ---
	b.WriteString("## Dependency adjacency (first-party require edges)\n\n")
	b.WriteString("Edges are `require` directives among first-party modules (the real build\n")
	b.WriteString("graph). `replace` directives only rewire resolution and are not edges.\n\n")
	b.WriteString("| Module | First-party requires |\n")
	b.WriteString("|---|---|\n")
	for _, p := range mpaths {
		m := modules[p]
		if m.Missing {
			continue
		}
		edgesOut := m.firstPartyEdges()
		var shortEdges []string
		for _, e := range edgesOut {
			shortEdges = append(shortEdges, "`"+short(e)+"`")
		}
		cell := "—"
		if len(shortEdges) > 0 {
			cell = strings.Join(shortEdges, ", ")
		}
		b.WriteString(fmt.Sprintf("| `%s` | %s |\n", short(p), cell))
	}
	b.WriteString("\n")

	// --- SCCs ---
	b.WriteString("## Strongly connected components (Tarjan)\n\n")
	b.WriteString("Cycles among first-party modules. Accepted components are allowlisted in\n")
	b.WriteString("`tools/boundarygate/accepted_sccs.txt`; the gate fails on a NEW SCC or a\n")
	b.WriteString("new member joining an existing one.\n\n")
	if len(nonTrivial) == 0 {
		b.WriteString("_No non-trivial SCCs (no first-party cycles)._\n\n")
	} else {
		b.WriteString("| SCC members | Size | Accepted |\n")
		b.WriteString("|---|---:|---|\n")
		for _, s := range nonTrivial {
			var ms []string
			for _, m := range s.Members {
				ms = append(ms, "`"+short(m)+"`")
			}
			b.WriteString(fmt.Sprintf("| %s | %d | yes (seeded) |\n", strings.Join(ms, ", "), len(s.Members)))
		}
		b.WriteString("\n")
	}

	// --- Forbidden-edge violations ---
	b.WriteString("## Forbidden-edge violations (current)\n\n")
	b.WriteString("Policy: `contract` is the sink (no first-party imports); `api-host` must not\n")
	b.WriteString("depend on backend/controlplane/engine modules; `controlplane` must not\n")
	b.WriteString("depend on `backend`. Entries below exist today and are baselined in\n")
	b.WriteString("`tools/boundarygate/forbidden_edge_baseline.txt`.\n\n")
	if len(edges) == 0 {
		b.WriteString("_No forbidden-edge violations._\n\n")
	} else {
		b.WriteString("| From | To | Rule |\n")
		b.WriteString("|---|---|---|\n")
		for _, v := range edges {
			b.WriteString(fmt.Sprintf("| `%s` | `%s` | %s |\n", short(v.From), short(v.To), v.Rule))
		}
		b.WriteString("\n")
	}

	// --- Contract runtime leaks ---
	b.WriteString("## Contract runtime leaks (`database/sql` / `net/http`)\n\n")
	b.WriteString("Contract package files that currently import a runtime dependency. The\n")
	b.WriteString("contract module must stay neutral; this list is baselined in\n")
	b.WriteString("`tools/boundarygate/contract_runtime_leak_baseline.txt` and PR1 shrinks it\n")
	b.WriteString("toward empty.\n\n")
	if len(leaks) == 0 {
		b.WriteString("_No contract runtime leaks (baseline empty)._\n\n")
	} else {
		for _, f := range leaks {
			b.WriteString("- `" + f + "`\n")
		}
		b.WriteString("\n")
	}

	// --- L0 dialect tokens ---
	b.WriteString("## L0 dialect-token baseline (per module)\n\n")
	b.WriteString("Product-dialect tokens (`salesforce|soql|sosl|sobject|hubspot|opportunity|\n")
	b.WriteString("tooling`, case-insensitive) found in L0 engine-module source. This freezes\n")
	b.WriteString("the known dialect leak so it cannot grow; baseline is\n")
	b.WriteString("`tools/boundarygate/l0_dialect_baseline.txt`.\n\n")
	perModule := map[string]int{}
	tokenCounts := map[string]int{}
	for _, h := range dialect {
		perModule[h.Module]++
		for _, t := range h.Tokens {
			tokenCounts[t]++
		}
	}
	b.WriteString("| L0 module | Files with dialect tokens |\n")
	b.WriteString("|---|---:|\n")
	var dmods []string
	for m := range l0Modules {
		dmods = append(dmods, m)
	}
	sort.Strings(dmods)
	for _, m := range dmods {
		b.WriteString(fmt.Sprintf("| `%s` | %d |\n", short(m), perModule[m]))
	}
	b.WriteString(fmt.Sprintf("\n**Total L0 dialect-token hit files: %d**\n\n", len(dialect)))

	b.WriteString("Per-token file counts (a file may match multiple tokens):\n\n")
	b.WriteString("| Token | Files |\n|---|---:|\n")
	for _, t := range dialectTokens {
		b.WriteString(fmt.Sprintf("| `%s` | %d |\n", t, tokenCounts[t]))
	}
	b.WriteString("\n")

	// --- How CI runs this ---
	b.WriteString("## How CI runs this\n\n")
	b.WriteString("```sh\n")
	b.WriteString("# From the repo root, in go.work workspace mode (the default).\n")
	b.WriteString("# Exits non-zero on any NEW (non-baselined) violation.\n")
	b.WriteString("GOPRIVATE='github.com/pingcap-inc/*,github.com/nexuscrm/*' \\\n")
	b.WriteString("  go run ./tools/boundarygate\n")
	b.WriteString("```\n\n")
	b.WriteString("> The tool is stdlib-only, so it builds with no module downloads; `GOPRIVATE`\n")
	b.WriteString("> just keeps `go` from contacting a proxy for the private workspace modules.\n")
	b.WriteString("> Do not pass `-mod=mod` in workspace mode — `go` rejects it.\n\n")
	b.WriteString("The tool is its own module (`github.com/nexuscrm/boundarygate`, stdlib only)\n")
	b.WriteString("so it adds nothing to the backend dependency graph. To intentionally accept\n")
	b.WriteString("a boundary change (e.g. a deliberately added cycle) and regenerate this\n")
	b.WriteString("ledger plus the baselines, run `go run ./tools/boundarygate -update`.\n")

	return writeFile(root, ledgerRel, b.String())
}
