package fbdraw

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

// Screen represents a single screen in the carousel
type Screen struct {
	Name      string
	Generator FrameGenerator
	ticker    *time.Ticker
	stop      chan struct{}
}

// Carousel manages rotating between multiple screens
type Carousel struct {
	display          FramebufferDisplay
	screens          []*Screen
	currentIndex     int
	rotationInterval time.Duration

	mu     sync.Mutex
	ctx    context.Context
	cancel context.CancelFunc
	wg     sync.WaitGroup
}

// NewCarousel creates a new carousel
func NewCarousel(display FramebufferDisplay, rotationInterval time.Duration) *Carousel {
	ctx, cancel := context.WithCancel(context.Background())

	return &Carousel{
		display:          display,
		screens:          make([]*Screen, 0),
		currentIndex:     0,
		rotationInterval: rotationInterval,
		ctx:              ctx,
		cancel:           cancel,
	}
}

// AddScreen adds a new screen to the carousel
func (c *Carousel) AddScreen(name string, generator FrameGenerator) {
	c.mu.Lock()
	defer c.mu.Unlock()

	screen := &Screen{
		Name:      name,
		Generator: generator,
		stop:      make(chan struct{}),
	}

	c.screens = append(c.screens, screen)
}

// Run starts the carousel
func (c *Carousel) Run() error {
	if len(c.screens) == 0 {
		return fmt.Errorf("no screens added to carousel")
	}

	// Start rotation timer
	rotationTicker := time.NewTicker(c.rotationInterval)
	defer rotationTicker.Stop()

	// Start with first screen
	if err := c.activateScreen(0); err != nil {
		return err
	}

	// Main loop
	for {
		select {
		case <-c.ctx.Done():
			return c.ctx.Err()

		case <-rotationTicker.C:
			// Move to next screen
			c.mu.Lock()
			nextIndex := (c.currentIndex + 1) % len(c.screens)
			c.mu.Unlock()

			if err := c.activateScreen(nextIndex); err != nil {
				return err
			}
		}
	}
}

// Stop stops the carousel
func (c *Carousel) Stop() {
	c.cancel()
	c.wg.Wait()
}

// activateScreen switches to the specified screen
func (c *Carousel) activateScreen(index int) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	// Stop current screen if any
	if c.currentIndex >= 0 && c.currentIndex < len(c.screens) {
		close(c.screens[c.currentIndex].stop)
		if c.screens[c.currentIndex].ticker != nil {
			c.screens[c.currentIndex].ticker.Stop()
		}
	}

	// Wait for current screen to stop
	c.wg.Wait()

	// Start new screen
	c.currentIndex = index
	screen := c.screens[index]

	// Calculate frame interval
	fps := screen.Generator.FramesPerSecond()
	if fps <= 0 {
		fps = 1 // Default to 1 FPS if invalid
	}
	frameInterval := time.Duration(float64(time.Second) / fps)

	// Create new stop channel and ticker
	screen.stop = make(chan struct{})
	screen.ticker = time.NewTicker(frameInterval)

	// Start frame generation goroutine
	c.wg.Add(1)
	go c.runScreen(screen)

	return nil
}

// runScreen runs a single screen's frame generation loop
func (c *Carousel) runScreen(screen *Screen) {
	defer c.wg.Done()

	// Get display size
	width, height := c.display.Size()
	grid := NewCharGrid(width, height)

	// Generate first frame immediately
	if err := screen.Generator.GenerateFrame(grid); err == nil {
		_ = c.display.Write(grid)
	}

	// Frame generation loop
	for {
		select {
		case <-screen.stop:
			return

		case <-screen.ticker.C:
			if err := screen.Generator.GenerateFrame(grid); err == nil {
				_ = c.display.Write(grid)
			}
		}
	}
}
