/*
Copyright © 2025 Benny Powers

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package generate

import (
	"context"
	"fmt"
	"sync"

	M "bennypowers.dev/cem/manifest"
	"bennypowers.dev/cem/types"
)

// GenerateSession holds reusable state for efficient generation cycles.
// Uses the GenerateSetupContext wrapper for clean setup object management.
//
// Callsites:
// - cmd/generate.go: NewGenerateSession() for watch mode initialization
// - generate/generate.go: NewGenerateSession() for single generation runs
// - generate/session_watch.go: Used throughout watch session lifecycle
//
// Thread Safety: sync.RWMutex protects inMemoryManifest and moduleIndex fields for concurrent access
type GenerateSession struct {
	setupCtx         *GenerateContext
	inMemoryManifest *M.Package           // protected by mu
	moduleIndex      map[string]*M.Module // path -> module for O(1) lookups, protected by mu
	mu               sync.RWMutex         // protects inMemoryManifest and moduleIndex
}

// NewGenerateSession creates a new session with initialized setup context.
// This is expensive (tree-sitter query loading) and should be done once
// per watch session or single generation run.
//
// Callsites:
// - cmd/generate.go:123 (watch mode initialization)
// - generate/generate.go:234 (single generation)
//
// Performance: Expensive operation (~10-50ms) due to tree-sitter query compilation
func NewGenerateSession(ctx types.WorkspaceContext) (*GenerateSession, error) {
	setupCtx, err := NewGenerateContext(ctx)
	if err != nil {
		return nil, fmt.Errorf("initialize setup context: %w", err)
	}

	return &GenerateSession{
		setupCtx:    setupCtx,
		moduleIndex: make(map[string]*M.Module),
	}, nil
}

// Close releases resources held by the session
func (gs *GenerateSession) Close() {
	if gs.setupCtx != nil {
		gs.setupCtx.Close()
	}
}

// GenerateFullManifest performs a complete generation using the existing logic.
// This is used for the initial generation in watch mode and for regular generate command.
func (gs *GenerateSession) GenerateFullManifest(ctx context.Context) (*M.Package, error) {
	// Check for cancellation
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}

	// Use existing generation logic with our reusable QueryManager
	result, err := gs.preprocessWithContext(ctx)
	if err != nil {
		return nil, WrapPreprocessError(err)
	}

	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}

	modules, logs, aliases, err := gs.processWithContext(ctx, result)
	if err != nil {
		return nil, WrapProcessError(err)
	}

	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}

	pkg, err := gs.postprocessWithContext(ctx, result, aliases, modules)
	if err != nil {
		return nil, WrapPostprocessError(err)
	}

	// Update in-memory manifest and rebuild index
	gs.mu.Lock()
	gs.inMemoryManifest = &pkg
	gs.rebuildModuleIndex()
	gs.mu.Unlock()

	cfg, _ := gs.setupCtx.Config()
	if cfg != nil && cfg.Verbose {
		RenderBarChart(logs)
	}

	return &pkg, nil
}

// InMemoryManifest returns a copy of the current in-memory manifest.
// For performance, this returns a shallow copy by default. Use InMemoryManifestDeep()
// when true isolation is needed (e.g., for LSP integration).
//
// Callsites:
// - session_incremental.go:58,62,100,279 (incremental processing)
// - cmd/generate_watch.go (watch mode manifest access)
//
// Performance: Optimized shallow copy (~microseconds) vs deep copy (~1-5ms)
func (gs *GenerateSession) InMemoryManifest() *M.Package {
	gs.mu.RLock()
	defer gs.mu.RUnlock()

	if gs.inMemoryManifest == nil {
		return nil
	}

	// Return shallow copy for performance - sufficient for most use cases
	manifest := *gs.inMemoryManifest
	return &manifest
}

// InMemoryManifestDeep returns a deep copy of the current in-memory manifest.
// This is safe for concurrent access and intended for LSP integration.
// Uses efficient Clone methods instead of JSON serialization (~5-10x faster).
//
// Callsites:
// - Future LSP integration (when implemented)
// - Concurrent processing requiring full data isolation
//
// Performance Note: Optimized deep copying (~200μs-1ms) vs JSON (~1-5ms).
// Only use this when true isolation is required.
func (gs *GenerateSession) InMemoryManifestDeep() *M.Package {
	gs.mu.RLock()
	defer gs.mu.RUnlock()

	if gs.inMemoryManifest == nil {
		return nil
	}

	// Use efficient Clone method instead of JSON serialization
	return gs.inMemoryManifest.Clone()
}

// rebuildModuleIndex rebuilds the module index from the current manifest.
// Must be called with gs.mu locked for writing.
func (gs *GenerateSession) rebuildModuleIndex() {
	gs.moduleIndex = make(map[string]*M.Module)
	if gs.inMemoryManifest != nil {
		for i := range gs.inMemoryManifest.Modules {
			module := &gs.inMemoryManifest.Modules[i]
			gs.moduleIndex[module.Path] = module
		}
	}
}

// ModuleByPath returns a module by its path using O(1) lookup.
// Returns nil if the module is not found.
// This is safe for concurrent access.
func (gs *GenerateSession) ModuleByPath(path string) *M.Module {
	gs.mu.RLock()
	defer gs.mu.RUnlock()

	return gs.moduleIndex[path]
}

// CssCache returns the CSS cache for this session.
// This provides controlled access to the cache for dependency injection.
func (gs *GenerateSession) CssCache() CssCache {
	return gs.setupCtx.CssCache()
}

// WorkerCount returns the number of workers that would be used for parallel processing.
// This creates a temporary processor to get the actual configured worker count.
func (gs *GenerateSession) WorkerCount() int {
	processor := NewModuleBatchProcessor(gs.setupCtx.QueryManager(), gs.setupCtx.DependencyTracker(), gs.setupCtx.CssCache())
	return processor.WorkerCount()
}

// preprocessWithContext is the existing preprocess logic with cancellation support
func (gs *GenerateSession) preprocessWithContext(ctx context.Context) (preprocessResult, error) {
	select {
	case <-ctx.Done():
		return preprocessResult{}, ctx.Err()
	default:
	}

	return preprocess(gs.setupCtx.WorkspaceContext)
}

// processWithContext is the existing process logic with cancellation support
func (gs *GenerateSession) processWithContext(ctx context.Context, result preprocessResult) ([]M.Module, []*LogCtx, map[string]string, error) {
	select {
	case <-ctx.Done():
		return nil, nil, nil, ctx.Err()
	default:
	}

	// Use the dependency-tracking version to collect file dependencies
	return gs.processWithDeps(ctx, result)
}

// processWithDeps processes files while tracking dependencies for incremental rebuilds
func (gs *GenerateSession) processWithDeps(ctx context.Context, result preprocessResult) ([]M.Module, []*LogCtx, map[string]string, error) {
	// Create jobs for all included files
	jobs := make([]processJob, 0, len(result.includedFiles))
	for _, file := range result.includedFiles {
		jobs = append(jobs, processJob{file: file, ctx: gs.setupCtx.WorkspaceContext})
	}

	// Use parallel processor with dependency tracking
	processor := NewModuleBatchProcessor(gs.setupCtx.QueryManager(), gs.setupCtx.DependencyTracker(), gs.setupCtx.CssCache())
	processingResult := processor.ProcessModules(ctx, jobs, ModuleProcessorFunc(processModule))

	return processingResult.Modules, processingResult.Logs, processingResult.Aliases, processingResult.Errors
}

// postprocessWithContext is the existing postprocess logic with cancellation support
func (gs *GenerateSession) postprocessWithContext(ctx context.Context, result preprocessResult, aliases map[string]string, modules []M.Module) (M.Package, error) {
	select {
	case <-ctx.Done():
		return M.Package{}, ctx.Err()
	default:
	}

	// TODO: Add cancellation points within the postprocess function
	// For now, we'll use the existing postprocess function
	return postprocess(gs.setupCtx.WorkspaceContext, result, aliases, gs.setupCtx.QueryManager(), modules)
}
