package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

// section: basic
func main() {
	ctx, cancel := context.WithCancel(context.Background())

	// Register a function to be called when the context is done
	stop := context.AfterFunc(ctx, func() {
		fmt.Println("Context canceled - cleanup triggered!")
	})

	// The stop function can prevent the callback if called before cancellation
	_ = stop // We'll demonstrate stop() usage below

	// Cancel the context after a short delay
	time.Sleep(100 * time.Millisecond)
	cancel()

	// Give the AfterFunc goroutine time to execute
	time.Sleep(50 * time.Millisecond)
	fmt.Println("Main completed")
}

// section: output
/*
Context canceled - cleanup triggered!
Main completed
*/

// section: stop-example
func demonstrateStop() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Register a cleanup function
	stop := context.AfterFunc(ctx, func() {
		fmt.Println("This will NOT print because we call stop()")
	})

	// Stop the scheduled function before context cancellation
	// Returns true if the function was successfully stopped
	if stop() {
		fmt.Println("Successfully prevented the AfterFunc callback")
	}

	cancel() // Even though we cancel, the callback won't run
	time.Sleep(50 * time.Millisecond)
}

// section: resource-cleanup
// ResourceManager demonstrates using AfterFunc for automatic resource cleanup
type ResourceManager struct {
	mu        sync.Mutex
	resources map[string]bool
}

func NewResourceManager() *ResourceManager {
	return &ResourceManager{
		resources: make(map[string]bool),
	}
}

// AcquireResource acquires a resource and schedules automatic cleanup
// when the context is canceled
func (rm *ResourceManager) AcquireResource(ctx context.Context, name string) (release func()) {
	rm.mu.Lock()
	rm.resources[name] = true
	rm.mu.Unlock()
	fmt.Printf("Resource %q acquired\n", name)

	// Schedule automatic cleanup when context is done
	stop := context.AfterFunc(ctx, func() {
		rm.releaseResource(name)
		fmt.Printf("Resource %q auto-released via AfterFunc\n", name)
	})

	// Return a manual release function that also stops the auto-cleanup
	return func() {
		if stop() {
			// We stopped the AfterFunc, so we need to release manually
			rm.releaseResource(name)
			fmt.Printf("Resource %q manually released\n", name)
		}
		// If stop() returns false, AfterFunc already ran or is running
	}
}

func (rm *ResourceManager) releaseResource(name string) {
	rm.mu.Lock()
	defer rm.mu.Unlock()
	delete(rm.resources, name)
}

// section: idempotent
// IMPORTANT: AfterFunc callbacks should be idempotent (safe to call multiple times)
// because there's a potential race between manual cleanup and context cancellation

func safeCleanup() {
	var once sync.Once
	cleanup := func() {
		once.Do(func() {
			fmt.Println("Cleanup executed exactly once")
		})
	}

	ctx, cancel := context.WithCancel(context.Background())

	// Register the idempotent cleanup
	stop := context.AfterFunc(ctx, cleanup)

	// Simulate a race: both manual stop and cancel might happen
	go func() {
		time.Sleep(10 * time.Millisecond)
		if stop() {
			cleanup() // Manual cleanup
		}
	}()

	go func() {
		time.Sleep(10 * time.Millisecond)
		cancel() // This might trigger AfterFunc
	}()

	time.Sleep(50 * time.Millisecond)
}
