// Package fixtures detects test-fixture files and surfaces them as
// KindFixture nodes so agents can answer "what fixtures does this
// repo have" or "is this golden file still referenced" with a graph
// query rather than a directory walk.
//
// Scope (v1): per-file path-based detection. A file qualifies as a
// fixture when its path contains a `testdata/` segment — Go's
// well-known convention for test data, also adopted by many other
// ecosystems (Python, Rust, JS). The reference edge from test
// functions to fixtures (`EdgeReferences` per the broader coverage
// spec) is a v2 follow-up; today the fixture node lands without
// an inbound link, which still serves enumeration and cleanup
// queries.
package fixtures

import (
	"path/filepath"
	"strings"

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

// IsFixturePath reports whether a repo-relative file path lives
// under a `testdata/` directory. Forward-slash and back-slash
// separators are both accepted so Windows-indexed paths produce the
// same result as POSIX-indexed ones. Path-only — does not consult
// the filesystem.
func IsFixturePath(filePath string) bool {
	p := filepath.ToSlash(filePath)
	if p == "" {
		return false
	}
	// Match `testdata` as a whole segment, not as a prefix or
	// substring of a longer name (e.g. `mytestdata/`). The two
	// patterns are: leading `testdata/` and any `/testdata/` mid-
	// path.
	if strings.HasPrefix(p, "testdata/") {
		return true
	}
	return strings.Contains(p, "/testdata/")
}

// BuildGraphArtifacts emits a KindFixture node for the given file.
// Returns nil when the path doesn't qualify so the caller can
// unconditionally invoke this without an explicit IsFixturePath
// guard at every call site.
//
// filePath is the unprefixed repo-relative path; applyRepoPrefix
// downstream handles multi-repo namespacing. The fixture node
// shares the same ID as the file node so a single graph query can
// scope to fixtures via kind, and the existing file-shaped node
// from the language extractor (when present) carries its own
// outgoing edges.
//
// We deliberately do not emit a separate node ID — the fixture is
// the file. Keeping a single ID keeps cross-referencing simple
// (any edge that lands on the file path also lands on the fixture
// classification) and avoids the de-dup gymnastics that emitting a
// twin synthetic ID would require.
func BuildGraphArtifacts(filePath, language string) []*graph.Node {
	if !IsFixturePath(filePath) {
		return nil
	}
	filePath = filepath.ToSlash(filePath)
	return []*graph.Node{{
		ID:       filePath,
		Kind:     graph.KindFixture,
		Name:     filepath.Base(filePath),
		FilePath: filePath,
		Language: language,
		Meta: map[string]any{
			"fixture": true,
		},
	}}
}

// TestContractSource reports the test-fixture category of the given
// path, or "" if the path is production code. Used by the contracts
// pipeline to tag — not drop — contracts emitted from synthetic
// fixtures so the dashboard can filter them out by default while
// still keeping them in the graph for drift checks (e.g. a test
// pinned to an old API contract that production has since changed).
//
// Categories:
//   - "testdata"       — paths under testdata/ (Go convention)
//   - "bench_fixtures" — paths under bench/fixtures/ (gortex bench corpus)
//   - "js_fixtures"    — paths under __fixtures__/ (TS/JS convention)
//
// The path may be repo-prefixed (e.g. "gortex/bench/fixtures/...");
// we match the segment regardless of leading prefix. Path-only — does
// not consult the filesystem.
func TestContractSource(filePath string) string {
	if IsFixturePath(filePath) {
		return "testdata"
	}
	p := filepath.ToSlash(filePath)
	if p == "" {
		return ""
	}
	for _, c := range []struct {
		seg, label string
	}{
		{"bench/fixtures/", "bench_fixtures"},
		{"__fixtures__/", "js_fixtures"},
	} {
		if strings.HasPrefix(p, c.seg) || strings.Contains(p, "/"+c.seg) {
			return c.label
		}
	}
	return ""
}

// ReclassifyFileToFixture rewrites an existing KindFile node to
// KindFixture when its path qualifies. Used by the indexer's
// per-file coverage step so the language extractor's emitted file
// node is upgraded in place rather than producing two nodes that
// share an ID. Returns true when reclassification fired so callers
// can skip BuildGraphArtifacts for the same path.
func ReclassifyFileToFixture(node *graph.Node) bool {
	if node == nil || node.Kind != graph.KindFile {
		return false
	}
	if !IsFixturePath(node.FilePath) {
		return false
	}
	node.Kind = graph.KindFixture
	if node.Meta == nil {
		node.Meta = map[string]any{}
	}
	node.Meta["fixture"] = true
	return true
}
