package knowledge

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

const (
	inceptionStateFile  = "inception/state.json"
	ideaDefaultConf     = 0.3
	visionDefaultConf   = 0.6
	constDefaultConf    = 0.9
	reqDefaultConf      = 0.6
	constraintDefaultConf = 0.7
	stakeholderDefaultConf = 0.7
	acceptanceDefaultConf  = 0.6
	brownfieldConfBoost = 0.15

	maxSlugLen = 30
)

// InceptionEngine manages the Level 1 ideation workflow — from raw idea to
// structured KB facts and scaffold output. Supports both greenfield (new
// project) and brownfield (existing repo) modes.
type InceptionEngine struct {
	dataDir string
	api     *KnowledgeAPI
	mu      sync.Mutex
	state   *InceptionState
	logger  *slog.Logger
}

// NewInceptionEngine creates an engine that persists state under dataDir.
func NewInceptionEngine(dataDir string, api *KnowledgeAPI, logger *slog.Logger) *InceptionEngine {
	if logger == nil {
		logger = slog.New(slog.NewTextHandler(io.Discard, nil))
	}
	e := &InceptionEngine{
		dataDir: dataDir,
		api:     api,
		logger:  logger,
	}
	e.loadState()
	e.connectExistingVault()
	return e
}

// Start begins a greenfield inception — the user has an idea but no repo.
func (e *InceptionEngine) Start(rawIdea string) (*InceptionState, error) {
	e.mu.Lock()
	defer e.mu.Unlock()

	rawIdea = strings.TrimSpace(rawIdea)
	if rawIdea == "" {
		return nil, fmt.Errorf("idea text is required")
	}
	const maxIdeaLen = 4000
	if len(rawIdea) > maxIdeaLen {
		return nil, fmt.Errorf("idea text must be at most %d characters", maxIdeaLen)
	}

	if e.state != nil && e.state.Phase != PhaseComplete {
		return nil, fmt.Errorf("inception already in progress (phase: %s) — reset first", e.state.Phase)
	}

	slug := slugify("idea-" + truncateSlug(rawIdea))
	if len(slug) < 8 {
		slug = slug + "-" + fmt.Sprintf("%d", time.Now().UnixMilli()%100000)
	}

	if e.api != nil {
		ctx := context.Background()
		err := e.api.CreateFact(ctx, CreateFactRequest{
			Title:      rawIdea,
			Body:       rawIdea,
			Type:       string(FactIdea),
			Tags:       []string{"inception", "seed"},
			Layer:      string(LayerProject),
			Confidence: ideaDefaultConf,
		})
		if err != nil {
			e.logger.Warn("could not persist idea fact to KB layer (knowledge may not be configured)", "error", err)
		}
	}

	e.clearWikiVault()

	wikiName := slugify(truncateSlug(rawIdea)) + "-wiki"

	e.state = &InceptionState{
		Phase:     PhaseCapture,
		Mode:      InceptionGreenfield,
		IdeaText:  rawIdea,
		IdeaSlug:  slug,
		WikiName:  wikiName,
		FactSlugs: []string{slug},
		Questions: []Question{},
		Answers:   make(map[string]string),
		StartedAt: time.Now(),
	}

	if err := e.saveState(); err != nil {
		return nil, fmt.Errorf("persisting state: %w", err)
	}

	e.logger.Info("inception started",
		"mode", "greenfield",
		"slug", slug,
	)

	return e.state, nil
}

// StartBrownfield begins an inception against an existing repo. The guide
// agent will scan the repo and extract initial facts.
func (e *InceptionEngine) StartBrownfield(repoURL string) (*InceptionState, error) {
	e.mu.Lock()
	defer e.mu.Unlock()

	repoURL = strings.TrimSpace(repoURL)
	if repoURL == "" {
		return nil, fmt.Errorf("repo URL is required")
	}
	const maxRepoURLLen = 2000
	if len(repoURL) > maxRepoURLLen {
		return nil, fmt.Errorf("repo URL must be at most %d characters", maxRepoURLLen)
	}
	if !strings.HasPrefix(repoURL, "https://") && !strings.HasPrefix(repoURL, "http://") {
		return nil, fmt.Errorf("repo URL must start with https:// or http://")
	}

	if e.state != nil && e.state.Phase != PhaseComplete {
		return nil, fmt.Errorf("inception already in progress (phase: %s) — reset first", e.state.Phase)
	}

	slug := slugify("scan-" + repoBaseName(repoURL))

	e.clearWikiVault()

	wikiName := slugify(repoBaseName(repoURL)) + "-wiki"

	e.state = &InceptionState{
		Phase:     PhaseCapture,
		Mode:      InceptionBrownfield,
		IdeaText:  "Brownfield scan: " + repoURL,
		IdeaSlug:  slug,
		RepoURL:   repoURL,
		WikiName:  wikiName,
		FactSlugs: []string{},
		Questions: []Question{},
		Answers:   make(map[string]string),
		StartedAt: time.Now(),
	}

	if err := e.saveState(); err != nil {
		return nil, fmt.Errorf("persisting state: %w", err)
	}

	e.logger.Info("inception started",
		"mode", "brownfield",
		"repo", repoURL,
		"slug", slug,
	)

	return e.state, nil
}

// SetQuestions records the clarification questions generated by the guide agent
// and advances the state to the clarify phase.
func (e *InceptionEngine) SetQuestions(questions []Question) error {
	e.mu.Lock()
	defer e.mu.Unlock()

	if e.state == nil {
		return fmt.Errorf("no inception in progress")
	}
	if e.state.Phase != PhaseCapture {
		return fmt.Errorf("cannot set questions in phase %s — must be in capture phase", e.state.Phase)
	}

	if len(questions) == 0 {
		return fmt.Errorf("at least one question is required")
	}

	seen := make(map[string]bool, len(questions))
	for _, q := range questions {
		if q.ID == "" {
			return fmt.Errorf("question ID is required")
		}
		if q.Text == "" {
			return fmt.Errorf("question text is required for %q", q.ID)
		}
		if seen[q.ID] {
			return fmt.Errorf("duplicate question ID: %q", q.ID)
		}
		seen[q.ID] = true
	}

	e.state.Questions = questions
	e.state.Phase = PhaseClarify
	now := time.Now()
	e.state.PhaseChangedAt = &now

	return e.saveState()
}

// SubmitAnswers records user responses to clarification questions and advances
// to the structure phase. The scheduler will re-kick the guide agent with
// the answers injected.
func (e *InceptionEngine) SubmitAnswers(answers map[string]string) (*InceptionState, error) {
	e.mu.Lock()
	defer e.mu.Unlock()

	if e.state == nil {
		return nil, fmt.Errorf("no inception in progress")
	}
	// Accept answers in clarify or structure phase — the brainstorm agent
	// can advance past clarify before the user submits, but the answers
	// are still valuable context for the next kick.
	if e.state.Phase != PhaseClarify && e.state.Phase != PhaseStructure {
		return nil, fmt.Errorf("cannot submit answers in phase %s", e.state.Phase)
	}
	if len(answers) == 0 {
		return nil, fmt.Errorf("at least one answer is required")
	}

	// Validate answer keys match known question IDs
	if len(e.state.Questions) > 0 {
		qIDs := make(map[string]bool, len(e.state.Questions))
		for _, q := range e.state.Questions {
			qIDs[q.ID] = true
		}
		for id, val := range answers {
			if !qIDs[id] {
				return nil, fmt.Errorf("answer key %q does not match any question ID", id)
			}
			if strings.TrimSpace(val) == "" {
				return nil, fmt.Errorf("answer for %q is empty", id)
			}
		}
	}

	for k, v := range answers {
		e.state.Answers[k] = v
	}
	if e.state.Phase == PhaseClarify {
		e.state.Phase = PhaseStructure
		now := time.Now()
		e.state.PhaseChangedAt = &now
	}

	if err := e.saveState(); err != nil {
		return nil, fmt.Errorf("persisting state: %w", err)
	}

	e.logger.Info("inception answers submitted",
		"answer_count", len(answers),
		"mode", e.state.Mode,
	)

	return e.state, nil
}

// RecordFacts records structured facts produced by the guide agent during the
// structure phase. Each fact is ingested into the project KB layer.
func (e *InceptionEngine) RecordFacts(ctx context.Context, facts []IdeationFact) error {
	e.mu.Lock()
	defer e.mu.Unlock()

	if e.state == nil {
		return fmt.Errorf("no inception in progress")
	}
	if e.state.Phase != PhaseStructure {
		return fmt.Errorf("facts already recorded (phase: %s)", e.state.Phase)
	}

	if len(facts) == 0 {
		return fmt.Errorf("at least one fact is required")
	}

	for _, f := range facts {
		if !f.Type.IsIdeation() {
			return fmt.Errorf("invalid ideation fact type: %q", f.Type)
		}
		if f.Title == "" {
			return fmt.Errorf("fact title is required")
		}
		if f.Body == "" {
			return fmt.Errorf("fact body is required for type %q", f.Type)
		}
		conf := defaultConfidence(f.Type)
		if e.state.Mode == InceptionBrownfield {
			conf += brownfieldConfBoost
			if conf > 1.0 {
				conf = 1.0
			}
		}

		if e.api == nil {
			slug := slugify(string(f.Type) + "-" + truncateSlug(f.Title))
			e.state.FactSlugs = append(e.state.FactSlugs, slug)
			continue
		}

		tags := make([]string, len(f.Tags)+1)
		copy(tags, f.Tags)
		tags[len(f.Tags)] = "inception"
		err := e.api.CreateFact(ctx, CreateFactRequest{
			Title:      f.Title,
			Body:       f.Body,
			Type:       string(f.Type),
			Tags:       tags,
			Layer:      string(LayerProject),
			Confidence: conf,
		})
		if err != nil {
			e.logger.Warn("failed to record ideation fact",
				"title", f.Title,
				"type", f.Type,
				"error", err,
			)
		}

		slug := slugify(string(f.Type) + "-" + truncateSlug(f.Title))
		e.state.FactSlugs = append(e.state.FactSlugs, slug)
	}

	e.state.Phase = PhaseScaffold
	now := time.Now()
	e.state.PhaseChangedAt = &now

	e.writeFactsToVault(facts)

	return e.saveState()
}

const inceptionWikiDir = "inception-wiki"

func (e *InceptionEngine) clearWikiVault() {
	vaultDir := filepath.Join(e.dataDir, inceptionWikiDir)
	entries, err := os.ReadDir(vaultDir)
	if err != nil {
		return
	}
	for _, entry := range entries {
		os.Remove(filepath.Join(vaultDir, entry.Name()))
	}
	if len(entries) > 0 {
		e.logger.Info("inception wiki cleared on new start", "files", len(entries))
		if e.api != nil {
			if err := e.api.ReindexVault(vaultDir); err != nil {
				e.logger.Debug("reindex after wiki clear", "error", err)
			}
		}
	}
}

// writeFactsToVault writes each fact as a markdown file with YAML frontmatter
// into a local directory, then connects it as a KB vault so facts are available
// to all agents via ${KNOWLEDGE} priming.
func (e *InceptionEngine) writeFactsToVault(facts []IdeationFact) {
	vaultDir := filepath.Join(e.dataDir, inceptionWikiDir)
	if err := os.MkdirAll(vaultDir, 0o755); err != nil {
		e.logger.Warn("failed to create inception wiki dir", "error", err)
		return
	}

	// Remove stale wiki files from previous inception before writing new ones.
	if len(facts) > 0 {
		if entries, err := os.ReadDir(vaultDir); err == nil && len(entries) > 0 {
			for _, entry := range entries {
				os.Remove(filepath.Join(vaultDir, entry.Name()))
			}
			e.logger.Info("inception wiki cleared before write", "files", len(entries), "new_facts", len(facts))
		}
	}

	usedSlugs := make(map[string]int)
	for _, f := range facts {
		slug := slugify(string(f.Type) + "-" + truncateSlug(f.Title))
		usedSlugs[slug]++
		if usedSlugs[slug] > 1 {
			slug = fmt.Sprintf("%s-%d", slug, usedSlugs[slug])
		}
		filename := slug + ".md"

		conf := defaultConfidence(f.Type)
		tags := make([]string, len(f.Tags)+2)
		copy(tags, f.Tags)
		tags[len(f.Tags)] = "inception"
		tags[len(f.Tags)+1] = string(f.Type)

		var content strings.Builder
		content.WriteString("---\n")
		fmt.Fprintf(&content, "title: \"%s\"\n", strings.ReplaceAll(singleLine(f.Title), `"`, `\"`))
		fmt.Fprintf(&content, "type: %s\n", string(f.Type))
		fmt.Fprintf(&content, "confidence: %.2f\n", conf)
		if len(tags) > 0 {
			quoted := make([]string, len(tags))
			for i, t := range tags {
				quoted[i] = fmt.Sprintf("%q", t)
			}
			fmt.Fprintf(&content, "tags: [%s]\n", strings.Join(quoted, ", "))
		}
		content.WriteString("---\n\n")
		content.WriteString(f.Body)

		path := filepath.Join(vaultDir, filename)
		tmpWiki := path + ".tmp"
		if err := os.WriteFile(tmpWiki, []byte(content.String()), 0o644); err != nil {
			e.logger.Warn("failed to write inception fact file", "path", path, "error", err)
			continue
		}
		if err := os.Rename(tmpWiki, path); err != nil {
			e.logger.Warn("failed to rename inception fact file", "path", path, "error", err)
		}
	}

	if e.api != nil {
		if err := e.api.ConnectVault(vaultDir, "inception-wiki"); err != nil {
			if strings.Contains(err.Error(), "already connected") {
				// Vault exists — reindex to pick up new files
				if err := e.api.ReindexVault(vaultDir); err != nil {
					e.logger.Warn("failed to reindex inception wiki", "error", err)
				} else {
					e.logger.Info("inception wiki reindexed", "dir", vaultDir, "facts", len(facts))
				}
			} else {
				e.logger.Warn("failed to connect inception wiki", "error", err)
			}
		} else {
			e.logger.Info("inception wiki connected as knowledge source",
				"dir", vaultDir,
				"facts", len(facts),
			)
		}
	}
}

// connectExistingVault checks if inception facts were previously written to disk
// and reconnects the vault on startup (survives container restarts).
func (e *InceptionEngine) connectExistingVault() {
	if e.api == nil {
		return
	}
	vaultDir := filepath.Join(e.dataDir, inceptionWikiDir)
	entries, err := os.ReadDir(vaultDir)
	if err != nil || len(entries) == 0 {
		return
	}
	wikiName := "inception-wiki"
	if e.state != nil && e.state.WikiName != "" {
		wikiName = e.state.WikiName
	}
	if err := e.api.ConnectVault(vaultDir, wikiName); err != nil {
		if !strings.Contains(err.Error(), "already connected") {
			e.logger.Warn("failed to reconnect inception wiki on startup", "error", err)
		}
	} else {
		e.logger.Info("inception wiki reconnected on startup",
			"dir", vaultDir,
			"name", wikiName,
			"files", len(entries),
		)
	}
}

// IdeationFact is the input for recording a new ideation fact from the guide agent.
type IdeationFact struct {
	Title string   `json:"title"`
	Body  string   `json:"body"`
	Type  FactType `json:"type"`
	Tags  []string `json:"tags"`
}

// ProduceScaffold generates bootstrap files from the structured facts. For
// greenfield, this is a full project skeleton. For brownfield, only missing
// files are generated.
func (e *InceptionEngine) ProduceScaffold(ctx context.Context) (*ScaffoldResult, error) {
	e.mu.Lock()
	defer e.mu.Unlock()

	if e.state == nil {
		return nil, fmt.Errorf("no inception in progress")
	}

	facts := e.gatherFacts(ctx)

	factTypes := make(map[string]int)
	for _, f := range facts {
		factTypes[string(f.Type)]++
	}
	e.logger.Info("ProduceScaffold called",
		"phase", e.state.Phase,
		"facts_gathered", len(facts),
		"fact_slugs", len(e.state.FactSlugs),
		"fact_types", factTypes,
		"idea", e.state.IdeaText[:min(40, len(e.state.IdeaText))],
	)

	result := &ScaffoldResult{}

	vision := findFactByType(facts, FactVision)
	constitution := findFactByType(facts, FactConstitution)
	requirements := filterFactsByType(facts, FactRequirement)
	constraints := filterFactsByType(facts, FactConstraint)
	acceptance := filterFactsByType(facts, FactAcceptance)
	stakeholders := filterFactsByType(facts, FactStakeholder)

	lang := inferLanguage(constitution)
	// Cross-check: if the idea text explicitly names a language and the
	// constitution disagrees, trust the idea text — the agent may have
	// produced a constitution that doesn't match the user's intent.
	if e.state != nil && e.state.IdeaText != "" {
		ideaLang := inferLanguageFromText(e.state.IdeaText)
		if ideaLang != "" && ideaLang != lang {
			e.logger.Info("scaffold language cross-check override", "idea_lang", ideaLang, "constitution_lang", lang, "idea", e.state.IdeaText[:min(50, len(e.state.IdeaText))])
			lang = ideaLang
		}
	}
	projectName := inferProjectName(vision, e.state)

	// Core documentation
	result.Files = append(result.Files, ScaffoldFile{
		Path:    "README.md",
		Content: buildReadme(projectName, lang, e.state.IdeaText, vision, constitution, requirements, constraints, stakeholders),
		Purpose: "readme",
		IsNew:   true,
	})

	result.Files = append(result.Files, ScaffoldFile{
		Path:    "AGENTS.md",
		Content: buildAgentsMD(constitution, constraints, requirements, vision),
		Purpose: "agents_md",
		IsNew:   true,
	})

	result.Files = append(result.Files, ScaffoldFile{
		Path:    "CONTRIBUTING.md",
		Content: buildContributing(projectName, lang, constitution),
		Purpose: "contributing",
		IsNew:   true,
	})

	result.Files = append(result.Files, ScaffoldFile{
		Path:    "CLAUDE.md",
		Content: buildClaudeMD(lang, constitution, constraints),
		Purpose: "claude_md",
		IsNew:   true,
	})

	// CI/CD — multi-tiered: PR checks, nightly full suite
	result.Files = append(result.Files, ScaffoldFile{
		Path:    ".github/workflows/ci.yml",
		Content: buildCIConfig(lang),
		Purpose: "ci",
		IsNew:   true,
	})
	result.Files = append(result.Files, ScaffoldFile{
		Path:    ".github/workflows/nightly.yml",
		Content: buildNightlyCI(lang),
		Purpose: "nightly_ci",
		IsNew:   true,
	})

	// Git
	result.Files = append(result.Files, ScaffoldFile{
		Path:    ".gitignore",
		Content: buildGitignore(lang),
		Purpose: "gitignore",
		IsNew:   true,
	})

	// Language-specific project files + code stubs
	switch lang {
	case "go":
		result.Files = append(result.Files,
			ScaffoldFile{Path: "go.mod", Content: buildGoMod(projectName), Purpose: "go_mod", IsNew: true},
			ScaffoldFile{Path: "main.go", Content: buildGoMain(projectName, vision), Purpose: "main", IsNew: true},
			ScaffoldFile{Path: "cmd/root.go", Content: buildGoCmdRoot(projectName, vision), Purpose: "cmd", IsNew: true},
		)
		if len(acceptance) > 0 {
			result.Files = append(result.Files, ScaffoldFile{
				Path: "main_test.go", Content: buildTestStubs(acceptance, lang), Purpose: "test_stub", IsNew: true,
			})
		}
	case "python":
		result.Files = append(result.Files,
			ScaffoldFile{Path: "pyproject.toml", Content: buildPyprojectToml(projectName, vision), Purpose: "pyproject", IsNew: true},
			ScaffoldFile{Path: "src/" + pyPackageName(projectName) + "/__init__.py", Content: buildPyInit(projectName, vision), Purpose: "main", IsNew: true},
			ScaffoldFile{Path: "src/" + pyPackageName(projectName) + "/cli.py", Content: buildPyCLI(projectName, vision), Purpose: "cmd", IsNew: true},
		)
		if len(acceptance) > 0 {
			result.Files = append(result.Files, ScaffoldFile{
				Path: "tests/test_acceptance.py", Content: buildTestStubs(acceptance, lang), Purpose: "test_stub", IsNew: true,
			})
		}
	case "typescript":
		result.Files = append(result.Files,
			ScaffoldFile{Path: "package.json", Content: buildPackageJSON(projectName, vision), Purpose: "package_json", IsNew: true},
			ScaffoldFile{Path: "tsconfig.json", Content: buildTSConfig(), Purpose: "tsconfig", IsNew: true},
			ScaffoldFile{Path: "src/index.ts", Content: buildTSIndex(projectName, vision), Purpose: "main", IsNew: true},
		)
		if len(acceptance) > 0 {
			result.Files = append(result.Files, ScaffoldFile{
				Path: "src/__tests__/acceptance.test.ts", Content: buildTestStubs(acceptance, lang), Purpose: "test_stub", IsNew: true,
			})
		}
	case "javascript":
		result.Files = append(result.Files,
			ScaffoldFile{Path: "package.json", Content: buildJSPackageJSON(projectName, vision), Purpose: "package_json", IsNew: true},
			ScaffoldFile{Path: "src/index.js", Content: buildJSIndex(projectName, vision), Purpose: "main", IsNew: true},
		)
		if len(acceptance) > 0 {
			result.Files = append(result.Files, ScaffoldFile{
				Path: "src/__tests__/acceptance.test.js", Content: buildTestStubs(acceptance, lang), Purpose: "test_stub", IsNew: true,
			})
		}
	case "rust":
		result.Files = append(result.Files,
			ScaffoldFile{Path: "Cargo.toml", Content: buildCargoToml(projectName, vision), Purpose: "cargo_toml", IsNew: true},
			ScaffoldFile{Path: "src/main.rs", Content: buildRustMain(projectName, vision), Purpose: "main", IsNew: true},
		)
		if len(acceptance) > 0 {
			result.Files = append(result.Files, ScaffoldFile{
				Path: "tests/acceptance.rs", Content: buildTestStubs(acceptance, lang), Purpose: "test_stub", IsNew: true,
			})
		}
	case "java":
		result.Files = append(result.Files,
			ScaffoldFile{Path: "pom.xml", Content: buildPomXml(projectName, vision), Purpose: "pom", IsNew: true},
			ScaffoldFile{Path: "src/main/java/App.java", Content: buildJavaMain(projectName, vision), Purpose: "main", IsNew: true},
		)
		if len(acceptance) > 0 {
			result.Files = append(result.Files, ScaffoldFile{
				Path: "src/test/java/AppTest.java", Content: buildTestStubs(acceptance, lang), Purpose: "test_stub", IsNew: true,
			})
		}
	case "shell":
		result.Files = append(result.Files,
			ScaffoldFile{Path: projectName + ".sh", Content: buildShellMain(projectName, vision), Purpose: "main", IsNew: true},
		)
		if len(acceptance) > 0 {
			result.Files = append(result.Files, ScaffoldFile{
				Path: "test.sh", Content: buildShellTestStubs(deduplicateFactTitles(acceptance)), Purpose: "test_stub", IsNew: true,
			})
		}
	}

	projectType := inferProjectType(constitution, requirements, vision)

	// Makefile
	result.Files = append(result.Files, ScaffoldFile{
		Path:    "Makefile",
		Content: buildMakefile(lang, projectName, projectType),
		Purpose: "makefile",
		IsNew:   true,
	})

	// Project-type-specific files
	if projectType == "container" || projectType == "kubernetes" {
		result.Files = append(result.Files, ScaffoldFile{
			Path: "Dockerfile", Content: buildDockerfile(lang, projectName), Purpose: "dockerfile", IsNew: true,
		})
	}

	if projectType == "ui" {
		i18nIdx, i18nPath := buildI18nIndexForLang(lang)
		result.Files = append(result.Files,
			ScaffoldFile{Path: "src/i18n/en.json", Content: buildI18nFile("en", projectName), Purpose: "i18n", IsNew: true},
			ScaffoldFile{Path: "src/i18n/es.json", Content: buildI18nFile("es", projectName), Purpose: "i18n", IsNew: true},
			ScaffoldFile{Path: i18nPath, Content: i18nIdx, Purpose: "i18n", IsNew: true},
		)
	}

	if projectType == "kubernetes" {
		result.Files = append(result.Files,
			ScaffoldFile{Path: "deploy/kustomization.yaml", Content: buildKustomization(), Purpose: "kustomize", IsNew: true},
			ScaffoldFile{Path: "deploy/base/deployment.yaml", Content: buildK8sDeployment(projectName), Purpose: "k8s_deployment", IsNew: true},
			ScaffoldFile{Path: "deploy/base/service.yaml", Content: buildK8sService(projectName), Purpose: "k8s_service", IsNew: true},
			ScaffoldFile{Path: "deploy/base/configmap.yaml", Content: buildK8sConfigMap(projectName), Purpose: "k8s_configmap", IsNew: true},
			ScaffoldFile{Path: "deploy/base/secret.yaml", Content: buildK8sSecret(projectName), Purpose: "k8s_secret", IsNew: true},
			ScaffoldFile{Path: "deploy/base/kustomization.yaml", Content: buildK8sBaseKustomization(), Purpose: "kustomize_base", IsNew: true},
			ScaffoldFile{Path: "deploy/overlays/dev/kustomization.yaml", Content: buildK8sOverlayKustomization(projectName, "dev"), Purpose: "kustomize_dev", IsNew: true},
			ScaffoldFile{Path: "deploy/overlays/prod/kustomization.yaml", Content: buildK8sOverlayKustomization(projectName, "prod"), Purpose: "kustomize_prod", IsNew: true},
		)
	}

	e.logger.Info("ProduceScaffold complete",
		"files", len(result.Files),
		"lang", lang,
		"project_type", inferProjectType(constitution, requirements, vision),
	)
	return result, nil
}

// AdvanceToComplete marks the inception as complete (scaffold approved).
func (e *InceptionEngine) AdvanceToComplete() error {
	e.mu.Lock()
	defer e.mu.Unlock()

	if e.state == nil {
		return fmt.Errorf("no inception in progress")
	}
	if e.state.Phase != PhaseScaffold && e.state.Phase != PhaseComplete {
		return fmt.Errorf("cannot approve in phase %s — must be in scaffold phase", e.state.Phase)
	}

	e.state.Phase = PhaseComplete
	now := time.Now()
	e.state.PhaseChangedAt = &now
	e.logger.Info("inception complete",
		"mode", e.state.Mode,
		"facts", len(e.state.FactSlugs),
	)
	return e.saveState()
}

// GetState returns the current inception state, or nil if none is in progress.
func (e *InceptionEngine) GetState() *InceptionState {
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.state == nil {
		return nil
	}
	cp := *e.state
	if e.state.Questions != nil {
		cp.Questions = append([]Question{}, e.state.Questions...)
	}
	if e.state.FactSlugs != nil {
		cp.FactSlugs = append([]string{}, e.state.FactSlugs...)
	} else {
		cp.FactSlugs = []string{}
	}
	cp.Answers = make(map[string]string, len(e.state.Answers))
	for k, v := range e.state.Answers {
		cp.Answers[k] = v
	}
	if e.state.PhaseChangedAt != nil {
		t := *e.state.PhaseChangedAt
		cp.PhaseChangedAt = &t
	}
	return &cp
}

// ReadIdeaFact returns the seed idea fact for the current inception.
func (e *InceptionEngine) ReadIdeaFact(ctx context.Context) (*Fact, error) {
	e.mu.Lock()
	defer e.mu.Unlock()

	if e.state == nil {
		return nil, fmt.Errorf("no inception in progress")
	}
	if e.api == nil {
		return nil, nil
	}
	return e.api.ReadFact(ctx, e.state.IdeaSlug)
}

// SetWikiName persists a vanity name for the inception wiki.
func (e *InceptionEngine) SetWikiName(name string) {
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.state != nil {
		e.state.WikiName = name
		if err := e.saveState(); err != nil {
			e.logger.Warn("failed to persist wiki name", "name", name, "error", err)
		}
	}
}

func (e *InceptionEngine) IncrementAutoFactCount(count int) {
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.state != nil {
		e.state.AutoFactCount += count
		if err := e.saveState(); err != nil {
			e.logger.Warn("failed to persist auto fact count", "count", count, "error", err)
		}
	}
}

func (e *InceptionEngine) IncrementAutoQuestionCount(count int) {
	e.mu.Lock()
	defer e.mu.Unlock()
	if e.state != nil {
		e.state.AutoQuestionCount += count
		if err := e.saveState(); err != nil {
			e.logger.Warn("failed to persist auto question count", "count", count, "error", err)
		}
	}
}

// HasWikiFiles returns true if the inception wiki has any files from a previous run.
func (e *InceptionEngine) WikiDir() string {
	return filepath.Join(e.dataDir, inceptionWikiDir)
}

func (e *InceptionEngine) HasWikiFiles() bool {
	e.mu.Lock()
	defer e.mu.Unlock()
	wikiDir := filepath.Join(e.dataDir, inceptionWikiDir)
	entries, err := os.ReadDir(wikiDir)
	if err != nil {
		return false
	}
	for _, entry := range entries {
		if strings.HasSuffix(entry.Name(), ".md") {
			return true
		}
	}
	return false
}

func (e *InceptionEngine) Reset() error {
	e.mu.Lock()
	defer e.mu.Unlock()

	e.state = nil
	path := filepath.Join(e.dataDir, inceptionStateFile)
	if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("removing state file: %w", err)
	}

	// Don't clear wiki on reset — keep last inception's facts visible
	// in the KB until a new inception writes new facts (writeFactsToVault
	// clears before writing).

	e.logger.Info("inception reset")
	return nil
}

// FormatAnswersForPrompt renders user answers as markdown for injection into
// the guide agent's kick template via ${INCEPTION_ANSWERS}.
func (e *InceptionEngine) FormatAnswersForPrompt() string {
	e.mu.Lock()
	defer e.mu.Unlock()

	if e.state == nil || len(e.state.Answers) == 0 {
		return ""
	}

	var b strings.Builder
	b.WriteString("# User Answers\n\n")
	for _, q := range e.state.Questions {
		answer, ok := e.state.Answers[q.ID]
		if !ok {
			continue
		}
		fmt.Fprintf(&b, "**%s**\n%s\n\n", q.Text, answer)
	}
	return b.String()
}

// --- persistence ---

func (e *InceptionEngine) loadState() {
	path := filepath.Join(e.dataDir, inceptionStateFile)
	data, err := os.ReadFile(path)
	if err != nil {
		return
	}
	var state InceptionState
	if err := json.Unmarshal(data, &state); err != nil {
		e.logger.Warn("corrupt inception state", "path", path, "error", err)
		return
	}
	if state.Answers == nil {
		state.Answers = make(map[string]string)
	}
	if state.FactSlugs == nil {
		state.FactSlugs = []string{}
	}
	e.state = &state
	e.logger.Info("inception state loaded",
		"phase", state.Phase,
		"mode", state.Mode,
	)
}

func (e *InceptionEngine) saveState() error {
	path := filepath.Join(e.dataDir, inceptionStateFile)
	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0o755); err != nil {
		return fmt.Errorf("creating state dir: %w", err)
	}

	data, err := json.MarshalIndent(e.state, "", "  ")
	if err != nil {
		return fmt.Errorf("marshaling state: %w", err)
	}

	tmpPath := path + ".tmp"
	if err := os.WriteFile(tmpPath, data, 0o644); err != nil {
		return fmt.Errorf("writing state: %w", err)
	}
	return os.Rename(tmpPath, path)
}

// --- fact helpers ---

// GatherFactsPublic exposes gatherFacts for the API layer when the KB project
// layer has no ideation facts (e.g. api was nil during RecordFacts).
func (e *InceptionEngine) GatherFactsPublic(ctx context.Context) []Fact {
	e.mu.Lock()
	defer e.mu.Unlock()
	return e.gatherFacts(ctx)
}

func (e *InceptionEngine) gatherFacts(ctx context.Context) []Fact {
	// Read facts from the inception wiki vault files directly
	wikiDir := filepath.Join(e.dataDir, inceptionWikiDir)
	entries, err := os.ReadDir(wikiDir)
	if err != nil || len(entries) == 0 {
		// Fallback to KB layer if no wiki files
		if e.api != nil {
			return e.api.LayerFacts(ctx, LayerProject, "")
		}
		return nil
	}

	var facts []Fact
	for _, entry := range entries {
		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".md") {
			continue
		}
		data, err := os.ReadFile(filepath.Join(wikiDir, entry.Name()))
		if err != nil {
			continue
		}
		content := string(data)

		// Parse YAML frontmatter
		title, body, factType, conf := parseInceptionFactFile(content, entry.Name())
		facts = append(facts, Fact{
			Slug:       strings.TrimSuffix(entry.Name(), ".md"),
			Title:      title,
			Type:       FactType(factType),
			Body:       body,
			Confidence: conf,
			Layer:      LayerProject,
		})
	}
	return facts
}

func parseInceptionFactFile(content, filename string) (title, body, factType string, confidence float64) {
	confidence = 0.6
	title = strings.TrimSuffix(filename, ".md")

	if !strings.HasPrefix(content, "---\n") {
		body = content
		return
	}

	endIdx := strings.Index(content[4:], "\n---")
	if endIdx < 0 {
		body = content
		return
	}

	frontmatter := content[4 : 4+endIdx]
	body = strings.TrimSpace(content[4+endIdx+4:])

	for _, line := range strings.Split(frontmatter, "\n") {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "title:") {
			title = strings.TrimSpace(strings.TrimPrefix(line, "title:"))
			title = strings.Trim(title, `"`)
			title = strings.ReplaceAll(title, `\"`, `"`)
		}
		if strings.HasPrefix(line, "type:") {
			factType = strings.TrimSpace(strings.TrimPrefix(line, "type:"))
		}
		if strings.HasPrefix(line, "confidence:") {
			fmt.Sscanf(strings.TrimPrefix(line, "confidence:"), "%f", &confidence)
		}
	}
	return
}

func findFactByType(facts []Fact, ft FactType) *Fact {
	for _, f := range facts {
		if f.Type == ft {
			return &f
		}
	}
	return nil
}

func filterFactsByType(facts []Fact, ft FactType) []Fact {
	var result []Fact
	for _, f := range facts {
		if f.Type == ft {
			result = append(result, f)
		}
	}
	return result
}

func defaultConfidence(ft FactType) float64 {
	switch ft {
	case FactIdea:
		return ideaDefaultConf
	case FactVision:
		return visionDefaultConf
	case FactConstitution:
		return constDefaultConf
	case FactRequirement:
		return reqDefaultConf
	case FactConstraint:
		return constraintDefaultConf
	case FactStakeholder:
		return stakeholderDefaultConf
	case FactAcceptance:
		return acceptanceDefaultConf
	default:
		return 0.5
	}
}

// --- scaffold builders ---

func buildReadme(projectName, lang, ideaText string, vision, constitution *Fact, reqs, constraints, stakeholders []Fact) string {
	var b strings.Builder

	title := "Project"
	if vision != nil {
		title = vision.Title
	}

	fmt.Fprintf(&b, "# %s\n\n", title)

	if vision != nil {
		b.WriteString("## Overview\n\n")
		b.WriteString(vision.Body)
		b.WriteString("\n\n")
	}

	if ideaText != "" && (vision == nil || len(vision.Body) < 100) {
		b.WriteString("## About This Project\n\n")
		fmt.Fprintf(&b, "This project was conceived from the idea: *%s*\n\n", ideaText)
	}

	if constitution != nil {
		b.WriteString("## Architecture & Principles\n\n")
		b.WriteString(constitution.Body)
		b.WriteString("\n\n")
	}

	if len(stakeholders) > 0 {
		b.WriteString("## Who Is This For\n\n")
		for _, s := range stakeholders {
			fmt.Fprintf(&b, "- **%s** — %s\n", s.Title, s.Body)
		}
		b.WriteString("\n")
	}

	if len(reqs) > 0 {
		b.WriteString("## Features\n\n")
		for _, r := range reqs {
			if r.Body != "" && r.Body != r.Title {
				fmt.Fprintf(&b, "- **%s** — %s\n", r.Title, r.Body)
			} else {
				fmt.Fprintf(&b, "- %s\n", r.Title)
			}
		}
		b.WriteString("\n")
	}

	if len(constraints) > 0 {
		b.WriteString("## Constraints\n\n")
		for _, c := range constraints {
			if c.Body != "" && c.Body != c.Title {
				fmt.Fprintf(&b, "- **%s** — %s\n", c.Title, c.Body)
			} else {
				fmt.Fprintf(&b, "- %s\n", c.Title)
			}
		}
		b.WriteString("\n")
	}

	b.WriteString("## Getting Started\n\n")
	if constitution != nil {
		switch lang {
		case "go":
			fmt.Fprintf(&b, "```bash\ngo build -o bin/ .\n./bin/%s\n```\n\n", projectName)
		case "python":
			fmt.Fprintf(&b, "```bash\npip install -e .\npython -m %s\n```\n\n", pyPackageName(projectName))
		case "typescript":
			b.WriteString("```bash\nnpm install\nnpm run dev\n```\n\n")
		case "javascript":
			b.WriteString("```bash\nnpm install\nnpm start\n```\n\n")
		case "rust":
			fmt.Fprintf(&b, "```bash\ncargo build --release\n./target/release/%s\n```\n\n", projectName)
		case "java":
			fmt.Fprintf(&b, "```bash\nmvn package\njava -jar target/%s-0.1.0.jar\n```\n\n", projectName)
		case "shell":
			fmt.Fprintf(&b, "```bash\nchmod +x *.sh\n./%s.sh\n```\n\n", projectName)
		default:
			b.WriteString("TODO: Add setup instructions.\n\n")
		}
	} else {
		b.WriteString("TODO: Add setup instructions.\n\n")
	}

	b.WriteString("## License\n\n")
	b.WriteString("TODO: Choose a license.\n")

	return b.String()
}

func buildClaudeMD(lang string, constitution *Fact, constraints []Fact) string {
	var b strings.Builder

	b.WriteString("# Project Guidelines\n\n")
	b.WriteString("## Build & Test\n\n")
	switch lang {
	case "go":
		b.WriteString("```bash\ngo build ./...\ngo test ./...\ngo vet ./...\n```\n\n")
	case "python":
		b.WriteString("```bash\npip install -e '.[dev]'\npytest\nruff check .\n```\n\n")
	case "typescript":
		b.WriteString("```bash\nnpm install\nnpm run build\nnpm test\nnpm run lint\n```\n\n")
	case "javascript":
		b.WriteString("```bash\nnpm install\nnpm test\nnpm run lint\n```\n\n")
	case "rust":
		b.WriteString("```bash\ncargo build\ncargo test\ncargo clippy -- -D warnings\n```\n\n")
	case "java":
		b.WriteString("```bash\nmvn compile\nmvn test\n```\n\n")
	case "shell":
		b.WriteString("```bash\nbash test.sh\nshellcheck *.sh\n```\n\n")
	}

	if constitution != nil {
		b.WriteString("## Principles\n\n")
		b.WriteString(constitution.Body)
		b.WriteString("\n\n")
	}

	if len(constraints) > 0 {
		b.WriteString("## Constraints\n\n")
		for _, c := range constraints {
			fmt.Fprintf(&b, "- %s\n", c.Title)
		}
		b.WriteString("\n")
	}

	b.WriteString("## Code Quality\n\n")
	b.WriteString("- No magic numbers — use named constants\n")
	b.WriteString("- All commits must be signed with DCO\n")
	b.WriteString("- Run tests before committing\n")

	return b.String()
}

func buildTestStubs(acceptance []Fact, lang string) string {
	acceptance = deduplicateFactTitles(acceptance)

	switch lang {
	case "go":
		return buildGoTestStubs(acceptance)
	case "typescript", "javascript":
		return buildTSTestStubs(acceptance)
	case "python":
		return buildPythonTestStubs(acceptance)
	case "rust":
		return buildRustTestStubs(acceptance)
	case "java":
		return buildJavaTestStubs(acceptance)
	default:
		return buildGoTestStubs(acceptance)
	}
}

func deduplicateFactTitles(facts []Fact) []Fact {
	seen := make(map[string]int)
	var result []Fact
	for _, f := range facts {
		key := toSnakeCase(f.Title)
		seen[key]++
		if seen[key] > 1 {
			f.Title = fmt.Sprintf("%s (%d)", f.Title, seen[key])
		}
		result = append(result, f)
	}
	return result
}

func buildGoTestStubs(acceptance []Fact) string {
	var b strings.Builder
	b.WriteString("package main\n\nimport \"testing\"\n\n")
	for _, a := range acceptance {
		title := singleLine(a.Title)
		funcName := "Test" + toPascalCase(title)
		fmt.Fprintf(&b, "func %s(t *testing.T) {\n", funcName)
		escaped := strings.ReplaceAll(title, `\`, `\\`)
		escaped = strings.ReplaceAll(escaped, `"`, `\"`)
		fmt.Fprintf(&b, "\tt.Skip(\"TODO: %s\")\n", escaped)
		b.WriteString("}\n\n")
	}
	return b.String()
}

func buildTSTestStubs(acceptance []Fact) string {
	var b strings.Builder
	b.WriteString("import { describe, it } from 'vitest';\n\n")
	b.WriteString("describe('acceptance criteria', () => {\n")
	for _, a := range acceptance {
		tsEsc := strings.ReplaceAll(singleLine(a.Title), `\`, `\\`)
		tsEsc = strings.ReplaceAll(tsEsc, "'", "\\'")
		fmt.Fprintf(&b, "  it.todo('%s');\n", tsEsc)
	}
	b.WriteString("});\n")
	return b.String()
}

func buildPythonTestStubs(acceptance []Fact) string {
	var b strings.Builder
	b.WriteString("import pytest\n\n")
	for _, a := range acceptance {
		title := singleLine(a.Title)
		funcName := "test_" + toSnakeCase(title)
		pyEsc := strings.ReplaceAll(title, `\`, `\\`)
		pyEsc = strings.ReplaceAll(pyEsc, `"`, `\"`)
		fmt.Fprintf(&b, "@pytest.mark.skip(reason=\"TODO: %s\")\n", pyEsc)
		fmt.Fprintf(&b, "def %s():\n    pass\n\n", funcName)
	}
	return b.String()
}

func buildRustTestStubs(acceptance []Fact) string {
	var b strings.Builder
	b.WriteString("#[cfg(test)]\nmod tests {\n\n")
	for _, a := range acceptance {
		title := singleLine(a.Title)
		funcName := toSnakeCase(title)
		rsEsc := strings.ReplaceAll(title, `\`, `\\`)
		rsEsc = strings.ReplaceAll(rsEsc, `"`, `\"`)
		fmt.Fprintf(&b, "    #[test]\n    #[ignore = \"TODO: %s\"]\n", rsEsc)
		fmt.Fprintf(&b, "    fn %s() {\n        todo!()\n    }\n\n", funcName)
	}
	b.WriteString("}\n")
	return b.String()
}

func buildJavaTestStubs(acceptance []Fact) string {
	var b strings.Builder
	b.WriteString("import org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\n")
	b.WriteString("class AppTest {\n\n")
	for _, a := range acceptance {
		title := singleLine(a.Title)
		funcName := "test" + toPascalCase(title)
		jvEsc := strings.ReplaceAll(title, `\`, `\\`)
		jvEsc = strings.ReplaceAll(jvEsc, `"`, `\"`)
		fmt.Fprintf(&b, "    @Test\n    @Disabled(\"TODO: %s\")\n", jvEsc)
		fmt.Fprintf(&b, "    void %s() {\n        // TODO: implement\n    }\n\n", funcName)
	}
	b.WriteString("}\n")
	return b.String()
}

func buildShellMain(name string, vision *Fact) string {
	desc := singleLine(name)
	if vision != nil && vision.Title != "" {
		desc = singleLine(vision.Title)
	}
	return fmt.Sprintf(`#!/usr/bin/env bash
set -euo pipefail

# %s

main() {
    echo "Starting %s..."
    # TODO: implement
}

main "$@"
`, desc, name)
}

func buildShellTestStubs(acceptance []Fact) string {
	var b strings.Builder
	b.WriteString("#!/usr/bin/env bash\nset -euo pipefail\n\n")
	b.WriteString("PASS=0\nFAIL=0\n\n")
	for _, a := range acceptance {
		title := singleLine(a.Title)
		funcName := toSnakeCase(title)
		fmt.Fprintf(&b, "test_%s() {\n", funcName)
		safeTitle := strings.NewReplacer(`\`, `\\`, `"`, `\"`, `$`, `\$`, "`", "\\`").Replace(title)
		fmt.Fprintf(&b, "    echo \"SKIP: %s\"\n", safeTitle)
		b.WriteString("    PASS=$((PASS+1))\n}\n\n")
	}
	b.WriteString("# Run all tests\n")
	for _, a := range acceptance {
		fmt.Fprintf(&b, "test_%s\n", toSnakeCase(singleLine(a.Title)))
	}
	b.WriteString("\necho \"$PASS passed, $FAIL failed\"\n")
	return b.String()
}

func buildContributing(projectName, lang string, constitution *Fact) string {
	var b strings.Builder
	b.WriteString("# Contributing\n\n")
	b.WriteString("Thank you for your interest in contributing!\n\n")
	b.WriteString("## Development Setup\n\n")
	if constitution != nil {
		switch lang {
		case "go":
			fmt.Fprintf(&b, "```bash\ngit clone <repo-url>\ncd %s\ngo mod download\ngo build ./...\ngo test ./...\n```\n\n", projectName)
		case "python":
			fmt.Fprintf(&b, "```bash\ngit clone <repo-url>\ncd %s\npython -m venv .venv && source .venv/bin/activate\npip install -e '.[dev]'\npytest\n```\n\n", projectName)
		case "typescript", "javascript":
			fmt.Fprintf(&b, "```bash\ngit clone <repo-url>\ncd %s\nnpm install\nnpm test\n```\n\n", projectName)
		case "rust":
			fmt.Fprintf(&b, "```bash\ngit clone <repo-url>\ncd %s\ncargo build\ncargo test\n```\n\n", projectName)
		case "java":
			fmt.Fprintf(&b, "```bash\ngit clone <repo-url>\ncd %s\nmvn compile\nmvn test\n```\n\n", projectName)
		case "shell":
			fmt.Fprintf(&b, "```bash\ngit clone <repo-url>\ncd %s\nchmod +x *.sh\nbash test.sh\n```\n\n", projectName)
		default:
			b.WriteString("TODO: Add development setup instructions.\n\n")
		}
	} else {
		b.WriteString("TODO: Add development setup instructions.\n\n")
	}
	b.WriteString("## Pull Request Process\n\n")
	b.WriteString("1. Fork the repository and create your branch from `main`\n")
	b.WriteString("2. Ensure tests pass before submitting\n")
	b.WriteString("3. Sign all commits with DCO (`git commit -s`)\n")
	b.WriteString("4. Open a PR with a clear description of the change\n\n")

	if constitution != nil {
		b.WriteString("## Code Style\n\n")
		b.WriteString(constitution.Body)
		b.WriteString("\n")
	}

	return b.String()
}

func buildCIConfig(lang string) string {
	switch lang {
	case "go":
		return goCI()
	case "typescript":
		return tsCI()
	case "javascript":
		return jsCI()
	case "python":
		return pythonCI()
	case "rust":
		return rustCI()
	case "java":
		return javaCI()
	case "shell":
		return shellCI()
	default:
		return goCI()
	}
}

func goCI() string {
	return `name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: stable
      - run: go build ./...
      - run: go test ./...
      - run: go vet ./...
`
}

func tsCI() string {
	return `name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npm run build
      - run: npm run lint
      - run: npm test
`
}

func jsCI() string {
	return `name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npm run lint
      - run: npm test
`
}

func pythonCI() string {
	return `name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -e ".[dev]"
      - run: ruff check .
      - run: pytest
`
}

func rustCI() string {
	return `name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - run: cargo build --release
      - run: cargo test
      - run: cargo clippy -- -D warnings
`
}

func javaCI() string {
	return `name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '21'
      - run: mvn compile
      - run: mvn test
`
}

func shellCI() string {
	return `name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install shellcheck
        run: sudo apt-get install -y shellcheck
      - run: shellcheck *.sh
      - run: bash test.sh
`
}

func inferProjectName(vision *Fact, state *InceptionState) string {
	if vision != nil {
		words := strings.Fields(strings.ToLower(vision.Title))
		var name []string
		for _, w := range words {
			w = strings.Map(func(r rune) rune {
				if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
					return r
				}
				return -1
			}, w)
			if w != "" && w != "a" && w != "the" && w != "for" && w != "and" && w != "that" {
				name = append(name, w)
				if len(name) >= 3 {
					break
				}
			}
		}
		if len(name) > 0 {
			return strings.Join(name, "-")
		}
	}
	if state != nil && state.IdeaSlug != "" {
		slug := strings.TrimPrefix(state.IdeaSlug, "idea-")
		if len(slug) > maxSlugLen {
			slug = slug[:maxSlugLen]
		}
		return slug
	}
	return "myproject"
}

func buildAgentsMD(constitution *Fact, constraints []Fact, requirements []Fact, vision *Fact) string {
	var b strings.Builder
	b.WriteString("# AI Agent Instructions\n\n")
	b.WriteString("This file provides instructions for AI coding agents (Claude, Copilot, Cursor, etc.) working on this project.\n\n")

	if vision != nil {
		b.WriteString("## Project Purpose\n\n")
		b.WriteString(vision.Body)
		b.WriteString("\n\n")
	}

	if constitution != nil {
		b.WriteString("## Architecture & Principles\n\n")
		b.WriteString(constitution.Body)
		b.WriteString("\n\n")
	}

	if len(requirements) > 0 {
		b.WriteString("## Key Requirements\n\n")
		for _, r := range requirements {
			fmt.Fprintf(&b, "- **%s**: %s\n", r.Title, r.Body)
		}
		b.WriteString("\n")
	}

	if len(constraints) > 0 {
		b.WriteString("## Constraints\n\n")
		for _, c := range constraints {
			fmt.Fprintf(&b, "- %s\n", c.Title)
			if c.Body != c.Title {
				fmt.Fprintf(&b, "  %s\n", c.Body)
			}
		}
		b.WriteString("\n")
	}

	b.WriteString("## Code Quality Rules\n\n")
	b.WriteString("- No magic numbers — use named constants\n")
	b.WriteString("- All commits must be signed with DCO (`git commit -s`)\n")
	b.WriteString("- Run tests before committing\n")
	b.WriteString("- Prefer editing existing files over creating new ones\n")
	b.WriteString("- No comments unless the WHY is non-obvious\n")

	return b.String()
}

func buildGitignore(lang string) string {
	common := "# OS\n.DS_Store\nThumbs.db\n\n# IDE\n.idea/\n.vscode/\n*.swp\n*.swo\n\n# Secrets\n.env\n.env.*\n*.pem\n*.key\n\n"
	switch lang {
	case "go":
		return common + "# Go\n/bin/\n*.exe\ncoverage.out\n"
	case "python":
		return common + "# Python\n__pycache__/\n*.pyc\n*.egg-info/\ndist/\nbuild/\n.venv/\nvenv/\n.pytest_cache/\n.ruff_cache/\n"
	case "typescript":
		return common + "# Node\nnode_modules/\ndist/\ncoverage/\n*.tsbuildinfo\n"
	case "javascript":
		return common + "# Node\nnode_modules/\ncoverage/\n"
	case "rust":
		return common + "# Rust\n/target/\n"
	case "java":
		return common + "# Java\ntarget/\n*.class\n*.jar\n.gradle/\nbuild/\n"
	case "shell":
		return common + "# Shell\n*.log\n"
	default:
		return common
	}
}

func buildGoMod(name string) string {
	return fmt.Sprintf(`module %s

go 1.23

require github.com/spf13/cobra v1.8.1
`, name)
}

func buildGoMain(name string, vision *Fact) string {
	desc := singleLine(name)
	if vision != nil {
		desc = singleLine(vision.Title)
	}
	return fmt.Sprintf(`package main

import "%s/cmd"

// %s
func main() {
	cmd.Execute()
}
`, name, desc)
}

func buildGoCmdRoot(name string, vision *Fact) string {
	desc := singleLine(name)
	if vision != nil {
		desc = singleLine(vision.Title)
	}
	escapedDesc := strings.ReplaceAll(desc, `"`, `\"`)
	return fmt.Sprintf(`package cmd

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	Use:   "%s",
	Short: "%s",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("TODO: implement main logic")
	},
}

func Execute() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
`, name, escapedDesc)
}

func buildCargoToml(name string, vision *Fact) string {
	desc := name
	if vision != nil {
		desc = singleLine(vision.Title)
	}
	return fmt.Sprintf(`[package]
name = "%s"
version = "0.1.0"
edition = "2021"
description = "%s"

[dependencies]
`, tomlEscape(name), tomlEscape(desc))
}

func buildRustMain(name string, vision *Fact) string {
	desc := singleLine(name)
	if vision != nil && vision.Title != "" {
		desc = singleLine(vision.Title)
	}
	return fmt.Sprintf(`// %s
fn main() {
    println!("Starting %s...");
}
`, desc, name)
}

func buildPomXml(name string, vision *Fact) string {
	desc := name
	if vision != nil {
		desc = singleLine(vision.Title)
	}
	return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>%s</artifactId>
    <version>0.1.0</version>
    <packaging>jar</packaging>
    <name>%s</name>
    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>App</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
`, xmlEscape(name), xmlEscape(desc))
}

func buildJavaMain(name string, vision *Fact) string {
	desc := singleLine(name)
	if vision != nil && vision.Title != "" {
		desc = singleLine(vision.Title)
	}
	return fmt.Sprintf(`// %s
public class App {
    public static void main(String[] args) {
        System.out.println("Starting %s...");
    }
}
`, desc, name)
}

func buildPyprojectToml(name string, vision *Fact) string {
	desc := name
	if vision != nil {
		desc = singleLine(vision.Title)
	}
	return fmt.Sprintf(`[project]
name = "%s"
version = "0.1.0"
description = "%s"
requires-python = ">=3.11"
dependencies = [
    "click>=8.0",
]

[project.scripts]
%s = "%s.cli:main"

[project.optional-dependencies]
dev = ["pytest", "ruff"]

[build-system]
requires = ["setuptools>=68.0"]
build-backend = "setuptools.build_meta"
`, tomlEscape(name), tomlEscape(desc), tomlEscape(name), pyPackageName(name))
}

func buildPyInit(name string, vision *Fact) string {
	desc := name
	if vision != nil {
		desc = singleLine(vision.Title)
	}
	safeDesc := strings.ReplaceAll(singleLine(desc), `"""`, `\"\"\"`)
	return fmt.Sprintf("\"\"\"%s\"\"\"\n\n__version__ = \"0.1.0\"\n", safeDesc)
}

func buildPyCLI(name string, vision *Fact) string {
	desc := name
	if vision != nil {
		desc = singleLine(vision.Title)
	}
	safeDesc := strings.ReplaceAll(singleLine(desc), `"""`, `\"\"\"`)
	return fmt.Sprintf(`"""CLI entry point for %s."""

import click

from . import __version__


@click.group()
@click.version_option(version=__version__)
def main():
    """%s"""
    pass


@main.command()
@click.argument("path", type=click.Path(exists=True))
def run(path: str):
    """Run the main operation on PATH."""
    click.echo(f"TODO: implement main logic for {path}")


if __name__ == "__main__":
    main()
`, safeDesc, safeDesc)
}

func buildPackageJSON(name string, vision *Fact) string {
	desc := name
	if vision != nil {
		desc = singleLine(vision.Title)
	}
	return fmt.Sprintf(`{
  "name": "%s",
  "version": "0.1.0",
  "description": "%s",
  "type": "module",
  "main": "dist/index.js",
  "scripts": {
    "build": "tsc",
    "dev": "tsx src/index.ts",
    "test": "vitest run",
    "lint": "eslint src/"
  },
  "devDependencies": {
    "typescript": "^5.0.0",
    "tsx": "^4.0.0",
    "vitest": "^2.0.0",
    "eslint": "^9.0.0"
  }
}
`, jsonEscape(name), jsonEscape(desc))
}

func buildJSPackageJSON(name string, vision *Fact) string {
	desc := name
	if vision != nil {
		desc = singleLine(vision.Title)
	}
	return fmt.Sprintf(`{
  "name": "%s",
  "version": "0.1.0",
  "description": "%s",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "test": "vitest run",
    "lint": "eslint src/"
  },
  "devDependencies": {
    "vitest": "^2.0.0",
    "eslint": "^9.0.0"
  }
}
`, jsonEscape(name), jsonEscape(desc))
}

func buildTSConfig() string {
	return `{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "outDir": "dist",
    "rootDir": "src",
    "declaration": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}
`
}

func buildTSIndex(name string, vision *Fact) string {
	desc := name
	if vision != nil {
		desc = singleLine(vision.Title)
	}
	desc = strings.ReplaceAll(desc, "*/", "* /")
	return fmt.Sprintf(`/**
 * %s
 */

export function main(): void {
  console.log("TODO: implement main logic");
}

main();
`, desc)
}

func buildJSIndex(name string, vision *Fact) string {
	desc := name
	if vision != nil {
		desc = singleLine(vision.Title)
	}
	desc = strings.ReplaceAll(desc, "*/", "* /")
	return fmt.Sprintf(`/**
 * %s
 */

function main() {
  console.log("TODO: implement main logic");
}

main();
`, desc)
}

func inferProjectType(constitution *Fact, requirements []Fact, vision *Fact) string {
	all := ""
	if constitution != nil {
		all += strings.ToLower(constitution.Body)
	}
	if vision != nil {
		all += " " + strings.ToLower(vision.Body) + " " + strings.ToLower(vision.Title)
	}
	for _, r := range requirements {
		all += " " + strings.ToLower(r.Title) + " " + strings.ToLower(r.Body)
	}

	switch {
	case strings.Contains(all, "operator") || strings.Contains(all, "controller") ||
		strings.Contains(all, "kubernetes") || strings.Contains(all, "k8s") ||
		strings.Contains(all, "helm") || strings.Contains(all, "crd") ||
		strings.Contains(all, "kustomize") || strings.Contains(all, "namespace"):
		return "kubernetes"
	case strings.Contains(all, "dashboard") || strings.Contains(all, "frontend") ||
		strings.Contains(all, "react") || strings.Contains(all, "vue") ||
		strings.Contains(all, "web app") || strings.Contains(all, "ui component") ||
		strings.Contains(all, "next.js") || containsWord(all, "vite"):
		return "ui"
	case strings.Contains(all, "docker") || strings.Contains(all, "container") ||
		strings.Contains(all, "deploy") || strings.Contains(all, "service"):
		return "container"
	case strings.Contains(all, "library") || strings.Contains(all, "package") || strings.Contains(all, "sdk"):
		return "library"
	case strings.Contains(all, "api") || strings.Contains(all, "server") || strings.Contains(all, "endpoint"):
		return "api"
	default:
		return "cli"
	}
}

func buildDockerfile(lang, name string) string {
	switch lang {
	case "go":
		return fmt.Sprintf(`FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod ./
COPY go.sum* ./
RUN go mod download 2>/dev/null || true
COPY . .
RUN go mod tidy
RUN CGO_ENABLED=0 go build -o /bin/%s .

FROM alpine:3.21
RUN apk add --no-cache ca-certificates
COPY --from=builder /bin/%s /usr/local/bin/%s
ENTRYPOINT ["%s"]
`, name, name, name, name)
	case "python":
		return fmt.Sprintf(`FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml .
COPY src/ src/
RUN pip install --no-cache-dir .
ENTRYPOINT ["%s"]
`, name)
	case "rust":
		return fmt.Sprintf(`FROM rust:1.82-slim AS builder
WORKDIR /app
COPY Cargo.toml ./
COPY Cargo.lock* ./
COPY src/ src/
RUN cargo build --release

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/%s /usr/local/bin/%s
ENTRYPOINT ["%s"]
`, name, name, name)
	case "java":
		return fmt.Sprintf(`FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src/ src/
RUN mvn package -DskipTests

FROM eclipse-temurin:21-jre
COPY --from=builder /app/target/%s-*.jar /app/%s.jar
ENTRYPOINT ["java", "-jar", "/app/%s.jar"]
`, name, name, name)
	case "javascript":
		return `FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY src/ src/
CMD ["node", "src/index.js"]
`
	default:
		return `FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json .
CMD ["node", "dist/index.js"]
`
	}
}

func buildKustomization() string {
	return `apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - base

# Use overlays for environment-specific config:
#   kubectl apply -k deploy/overlays/dev
#   kubectl apply -k deploy/overlays/prod
`
}

func buildK8sDeployment(name string) string {
	return fmt.Sprintf(`apiVersion: apps/v1
kind: Deployment
metadata:
  name: %s
  labels:
    app: %s
spec:
  replicas: 1
  selector:
    matchLabels:
      app: %s
  template:
    metadata:
      labels:
        app: %s
    spec:
      containers:
        - name: %s
          image: %s:latest
          ports:
            - containerPort: 8080
          envFrom:
            - configMapRef:
                name: %s-config
            - secretRef:
                name: %s-secrets
          resources:
            requests:
              memory: "64Mi"
              cpu: "100m"
            limits:
              memory: "256Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
          readinessProbe:
            httpGet:
              path: /readyz
              port: 8080
            initialDelaySeconds: 3
`, name, name, name, name, name, name, name, name)
}

func buildK8sService(name string) string {
	return fmt.Sprintf(`apiVersion: v1
kind: Service
metadata:
  name: %s
  labels:
    app: %s
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
  selector:
    app: %s
`, name, name, name)
}

func buildK8sConfigMap(name string) string {
	return fmt.Sprintf(`apiVersion: v1
kind: ConfigMap
metadata:
  name: %s-config
data:
  LOG_LEVEL: "info"
  PORT: "8080"
  # Add application-specific config here
`, name)
}

func buildK8sSecret(name string) string {
	return fmt.Sprintf(`apiVersion: v1
kind: Secret
metadata:
  name: %s-secrets
type: Opaque
data: {}
  # Populate from sealed-secrets, external-secrets-operator, or
  # kubectl create secret generic %s-secrets --from-literal=API_KEY=<value>
  # Do NOT add plaintext secrets to this file.
`, name, name)
}

func buildK8sBaseKustomization() string {
	return `apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml
  - configmap.yaml
  - secret.yaml
`
}

func buildK8sOverlayKustomization(name, env string) string {
	replicas := 1
	if env == "prod" {
		replicas = 3
	}
	return fmt.Sprintf(`apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

namePrefix: %s-

patches:
  - target:
      kind: Deployment
      name: %s
    patch: |
      - op: replace
        path: /spec/replicas
        value: %d
`, env, name, replicas)
}

func buildNightlyCI(lang string) string {
	var setupStep, testCmd string
	switch lang {
	case "go":
		setupStep = "      - uses: actions/setup-go@v5\n        with:\n          go-version: stable\n"
		testCmd = "go test -race -cover -count=3 ./..."
	case "python":
		setupStep = "      - uses: actions/setup-python@v5\n        with:\n          python-version: '3.12'\n      - run: pip install -e '.[dev]'\n"
		testCmd = "pytest --tb=long -v"
	case "rust":
		setupStep = "      - uses: dtolnay/rust-toolchain@stable\n"
		testCmd = "cargo test -- --nocapture"
	case "java":
		setupStep = "      - uses: actions/setup-java@v4\n        with:\n          distribution: temurin\n          java-version: '21'\n"
		testCmd = "mvn verify -P integration-test"
	case "shell":
		setupStep = ""
		testCmd = "bash test.sh"
	default:
		setupStep = "      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n      - run: npm ci\n"
		testCmd = "npm test -- --reporter=verbose"
	}
	return fmt.Sprintf(`name: Nightly
on:
  schedule:
    - cron: '0 4 * * *'  # 4am UTC daily
  workflow_dispatch: {}

jobs:
  full-suite:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
%s      - name: Full test suite
        run: %s
      - name: Upload results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: nightly-results
          path: coverage*
`, setupStep, testCmd)
}

func buildI18nFile(locale, name string) string {
	escaped := jsonEscape(name)
	switch locale {
	case "es":
		return fmt.Sprintf(`{
  "app.title": "%s",
  "app.description": "Descripción del proyecto",
  "common.save": "Guardar",
  "common.cancel": "Cancelar",
  "common.delete": "Eliminar",
  "common.edit": "Editar",
  "common.loading": "Cargando...",
  "common.error": "Error",
  "common.success": "Éxito",
  "common.search": "Buscar"
}
`, escaped)
	default:
		return fmt.Sprintf(`{
  "app.title": "%s",
  "app.description": "Project description",
  "common.save": "Save",
  "common.cancel": "Cancel",
  "common.delete": "Delete",
  "common.edit": "Edit",
  "common.loading": "Loading...",
  "common.error": "Error",
  "common.success": "Success",
  "common.search": "Search"
}
`, escaped)
	}
}

func buildI18nIndex() string {
	content, _ := buildI18nIndexForLang("typescript")
	return content
}

func buildI18nIndexForLang(lang string) (content string, path string) {
	switch lang {
	case "python":
		return `import json
from pathlib import Path

_dir = Path(__file__).parent
_messages = {
    "en": json.loads((_dir / "en.json").read_text()),
    "es": json.loads((_dir / "es.json").read_text()),
}
_locale = "en"


def set_locale(locale: str) -> None:
    global _locale
    _locale = locale


def t(key: str) -> str:
    return _messages.get(_locale, {}).get(key, key)
`, "src/i18n/__init__.py"
	case "go":
		return `package i18n

import (
	"embed"
	"encoding/json"
)

//go:embed en.json es.json
var localeFS embed.FS

var messages = map[string]map[string]string{}
var currentLocale = "en"

func init() {
	for _, loc := range []string{"en", "es"} {
		data, _ := localeFS.ReadFile(loc + ".json")
		m := map[string]string{}
		_ = json.Unmarshal(data, &m)
		messages[loc] = m
	}
}

func SetLocale(locale string) { currentLocale = locale }

func T(key string) string {
	if m, ok := messages[currentLocale]; ok {
		if v, ok := m[key]; ok {
			return v
		}
	}
	return key
}
`, "src/i18n/i18n.go"
	case "rust":
		return `use std::collections::HashMap;
use std::sync::Mutex;

static LOCALE: Mutex<String> = Mutex::new(String::new());

pub fn set_locale(locale: &str) {
    *LOCALE.lock().unwrap() = locale.to_string();
}

pub fn t(key: &str) -> String {
    let locale = LOCALE.lock().unwrap();
    let loc = if locale.is_empty() { "en" } else { &locale };
    let messages: HashMap<&str, &str> = match loc {
        "es" => include!("es_messages.rs"),
        _ => include!("en_messages.rs"),
    };
    messages.get(key).map(|s| s.to_string()).unwrap_or_else(|| key.to_string())
}
`, "src/i18n/mod.rs"
	case "java":
		return `package i18n;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

public class I18n {
    private static String locale = "en";
    private static final Map<String, Map<String, String>> messages = new HashMap<>();

    public static void setLocale(String loc) { locale = loc; }

    public static String t(String key) {
        Map<String, String> m = messages.getOrDefault(locale, Map.of());
        return m.getOrDefault(key, key);
    }
}
`, "src/i18n/I18n.java"
	case "javascript":
		return `import en from './en.json' assert { type: 'json' };
import es from './es.json' assert { type: 'json' };

const messages = { en, es };
let currentLocale = 'en';

export function setLocale(locale) {
  currentLocale = locale;
}

export function t(key) {
  return messages[currentLocale]?.[key] ?? key;
}
`, "src/i18n/index.js"
	case "shell":
		return `#!/usr/bin/env bash
LOCALE="${LOCALE:-en}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

t() {
  local key="$1"
  local file="$SCRIPT_DIR/${LOCALE}.json"
  if command -v jq &>/dev/null && [ -f "$file" ]; then
    jq -r --arg k "$key" '.[$k] // $k' "$file"
  else
    echo "$key"
  fi
}
`, "src/i18n/i18n.sh"
	default:
		return `import en from './en.json';
import es from './es.json';

export type Locale = 'en' | 'es';

const messages: Record<Locale, Record<string, string>> = { en, es };

let currentLocale: Locale = 'en';

export function setLocale(locale: Locale): void {
  currentLocale = locale;
}

export function t(key: string): string {
  return messages[currentLocale]?.[key] ?? key;
}
`, "src/i18n/index.ts"
	}
}

func buildMakefile(lang, name, projectType string) string {
	var b strings.Builder
	var targets []string
	if lang != "javascript" && lang != "shell" {
		targets = append(targets, "build")
	}
	targets = append(targets, "test", "lint")
	if lang != "shell" {
		targets = append(targets, "clean")
	}
	if projectType == "container" || projectType == "kubernetes" {
		targets = append(targets, "docker-build", "docker-push")
	}
	if projectType == "kubernetes" {
		targets = append(targets, "deploy", "deploy-prod")
	}
	fmt.Fprintf(&b, ".PHONY: %s\n\n", strings.Join(targets, " "))
	switch lang {
	case "go":
		fmt.Fprintf(&b, "build:\n\tgo build -o bin/%s .\n\n", name)
		b.WriteString("test:\n\tgo test -race -cover ./...\n\n")
		b.WriteString("lint:\n\tgolangci-lint run ./...\n\n")
		b.WriteString("clean:\n\trm -rf bin/\n\n")
	case "python":
		b.WriteString("build:\n\tpip install -e .\n\n")
		b.WriteString("test:\n\tpytest\n\n")
		b.WriteString("lint:\n\truff check .\n\n")
		b.WriteString("clean:\n\trm -rf dist/ build/ *.egg-info/\n\n")
	case "typescript":
		b.WriteString("build:\n\tnpm run build\n\n")
		b.WriteString("test:\n\tnpm test\n\n")
		b.WriteString("lint:\n\tnpm run lint\n\n")
		b.WriteString("clean:\n\trm -rf dist/ node_modules/\n\n")
	case "javascript":
		b.WriteString("test:\n\tnpm test\n\n")
		b.WriteString("lint:\n\tnpm run lint\n\n")
		b.WriteString("clean:\n\trm -rf node_modules/\n\n")
	case "rust":
		b.WriteString("build:\n\tcargo build --release\n\n")
		b.WriteString("test:\n\tcargo test\n\n")
		b.WriteString("lint:\n\tcargo clippy -- -D warnings\n\n")
		b.WriteString("clean:\n\tcargo clean\n\n")
	case "java":
		b.WriteString("build:\n\tmvn package\n\n")
		b.WriteString("test:\n\tmvn test\n\n")
		b.WriteString("lint:\n\tmvn checkstyle:check\n\n")
		b.WriteString("clean:\n\tmvn clean\n\n")
	case "shell":
		b.WriteString("test:\n\tbash test.sh\n\n")
		b.WriteString("lint:\n\tshellcheck *.sh\n\n")
	}
	if projectType == "container" || projectType == "kubernetes" {
		fmt.Fprintf(&b, "docker-build:\n\tdocker build -t %s:latest .\n\n", name)
		fmt.Fprintf(&b, "docker-push:\n\tdocker push %s:latest\n\n", name)
	}
	if projectType == "kubernetes" {
		b.WriteString("deploy:\n\tkubectl apply -k deploy/overlays/dev\n\n")
		b.WriteString("deploy-prod:\n\tkubectl apply -k deploy/overlays/prod\n")
	}
	return b.String()
}

// --- string helpers ---

func jsonEscape(s string) string {
	s = strings.ReplaceAll(s, `\`, `\\`)
	s = strings.ReplaceAll(s, `"`, `\"`)
	s = strings.ReplaceAll(s, "\n", `\n`)
	s = strings.ReplaceAll(s, "\r", `\r`)
	s = strings.ReplaceAll(s, "\t", `\t`)
	return s
}

func xmlEscape(s string) string {
	s = strings.ReplaceAll(s, "&", "&amp;")
	s = strings.ReplaceAll(s, "<", "&lt;")
	s = strings.ReplaceAll(s, ">", "&gt;")
	s = strings.ReplaceAll(s, `"`, "&quot;")
	return s
}

func singleLine(s string) string {
	s = strings.ReplaceAll(s, "\n", " ")
	s = strings.ReplaceAll(s, "\r", "")
	return strings.TrimSpace(s)
}

func tomlEscape(s string) string {
	s = strings.ReplaceAll(s, `\`, `\\`)
	s = strings.ReplaceAll(s, `"`, `\"`)
	s = strings.ReplaceAll(s, "\n", `\n`)
	s = strings.ReplaceAll(s, "\r", `\r`)
	s = strings.ReplaceAll(s, "\t", `\t`)
	return s
}

func pyPackageName(name string) string {
	return strings.ReplaceAll(name, "-", "_")
}

func slugify(s string) string {
	s = strings.ToLower(s)
	s = strings.Map(func(r rune) rune {
		if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
			return r
		}
		if r == ' ' || r == '_' || r == '\n' || r == '\r' || r == '\t' {
			return '-'
		}
		return -1
	}, s)
	for strings.Contains(s, "--") {
		s = strings.ReplaceAll(s, "--", "-")
	}
	return strings.Trim(s, "-")
}

func truncateSlug(s string) string {
	const maxSlugInputLen = 60
	if len(s) <= maxSlugInputLen {
		return s
	}
	// Truncate at rune boundary to avoid splitting multi-byte UTF-8
	runes := []rune(s)
	if len(runes) > maxSlugInputLen {
		runes = runes[:maxSlugInputLen]
	}
	return string(runes)
}

func inferLanguageFromText(text string) string {
	lower := strings.ToLower(text)
	switch {
	case strings.Contains(lower, "python"):
		return "python"
	case strings.Contains(lower, "typescript"):
		return "typescript"
	case strings.Contains(lower, "javascript") && !strings.Contains(lower, "typescript"):
		return "javascript"
	case containsWord(lower, "rust"):
		return "rust"
	case containsWord(lower, "java") && !strings.Contains(lower, "javascript"):
		return "java"
	case containsWord(lower, "go") || strings.Contains(lower, "golang"):
		return "go"
	case containsWord(lower, "shell") || containsWord(lower, "bash"):
		return "shell"
	}
	return ""
}

func containsWord(text, word string) bool {
	idx := 0
	for {
		pos := strings.Index(text[idx:], word)
		if pos < 0 {
			return false
		}
		absPos := idx + pos
		endPos := absPos + len(word)
		atStart := absPos == 0 || !isAlpha(text[absPos-1])
		atEnd := endPos >= len(text) || !isAlpha(text[endPos])
		if atStart && atEnd {
			return true
		}
		idx = absPos + 1
	}
}

func isAlpha(b byte) bool {
	return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')
}

func repoBaseName(url string) string {
	url = strings.TrimSuffix(url, "/")
	url = strings.TrimSuffix(url, ".git")
	parts := strings.Split(url, "/")
	for i := len(parts) - 1; i >= 0; i-- {
		if parts[i] != "" {
			return parts[i]
		}
	}
	return "repo"
}

func inferLanguage(constitution *Fact) string {
	if constitution == nil {
		return "go"
	}
	body := strings.ToLower(constitution.Body + " " + constitution.Title)
	switch {
	case strings.Contains(body, "typescript") || strings.Contains(body, "react") || strings.Contains(body, "angular") ||
		strings.Contains(body, "vue") || strings.Contains(body, "next.js") || strings.Contains(body, "nest"):
		return "typescript"
	case strings.Contains(body, "python") || strings.Contains(body, "pytest") || containsWord(body, "pip") ||
		strings.Contains(body, "django") || strings.Contains(body, "flask") || strings.Contains(body, "fastapi") ||
		strings.Contains(body, "sqlalchemy") || strings.Contains(body, "poetry"):
		return "python"
	case strings.Contains(body, "javascript") || strings.Contains(body, "vitest") ||
		strings.Contains(body, "express") || strings.Contains(body, "webpack") || containsWord(body, "vite"):
		return "javascript"
	case containsWord(body, "rust") || strings.Contains(body, "cargo") || strings.Contains(body, "tokio") ||
		strings.Contains(body, "actix") || containsWord(body, "warp") || strings.Contains(body, "axum"):
		return "rust"
	case containsWord(body, "java") || strings.Contains(body, "maven") || strings.Contains(body, "gradle") ||
		strings.Contains(body, "spring") || strings.Contains(body, "kotlin") || strings.Contains(body, "quarkus"):
		return "java"
	case containsWord(body, "bash") || containsWord(body, "shell") || strings.Contains(body, "posix") ||
		strings.Contains(body, "zsh") || strings.Contains(body, "makefile") || strings.Contains(body, "shellcheck"):
		return "shell"
	default:
		return "go"
	}
}

func inferTestPath(constitution *Fact) string {
	lang := inferLanguage(constitution)
	switch lang {
	case "go":
		return "main_test.go"
	case "typescript":
		return "src/__tests__/acceptance.test.ts"
	case "javascript":
		return "src/__tests__/acceptance.test.js"
	case "python":
		return "tests/test_acceptance.py"
	default:
		return "main_test.go"
	}
}

func toPascalCase(s string) string {
	words := strings.Fields(s)
	var b strings.Builder
	for _, w := range words {
		w = strings.Map(func(r rune) rune {
			if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
				return r
			}
			return -1
		}, w)
		if w == "" {
			continue
		}
		b.WriteString(strings.ToUpper(w[:1]) + w[1:])
	}
	if b.Len() == 0 {
		return "Unnamed"
	}
	return b.String()
}

func toSnakeCase(s string) string {
	words := strings.Fields(strings.ToLower(s))
	var filtered []string
	for _, w := range words {
		w = strings.Map(func(r rune) rune {
			if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' {
				return r
			}
			return -1
		}, w)
		if w != "" {
			filtered = append(filtered, w)
		}
	}
	result := strings.Join(filtered, "_")
	if result == "" {
		return "unnamed"
	}
	return result
}
