// Adapted from https://github.com/thought-machine/please
// Copyright Thought Machine, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cache

import (
	"sync"

	"github.com/vercel/turbo/cli/internal/turbopath"
)

// An asyncCache is a wrapper around a Cache interface that handles incoming
// store requests asynchronously and attempts to return immediately.
// The requests are handled on an internal queue, if that fills up then
// incoming requests will start to block again until it empties.
// Retrieval requests are still handled synchronously.
type asyncCache struct {
	requests  chan cacheRequest
	realCache Cache
	wg        sync.WaitGroup
}

// A cacheRequest models an incoming cache request on our queue.
type cacheRequest struct {
	anchor   turbopath.AbsoluteSystemPath
	key      string
	duration int
	files    []turbopath.AnchoredSystemPath
}

func newAsyncCache(realCache Cache, opts Opts) Cache {
	c := &asyncCache{
		requests:  make(chan cacheRequest),
		realCache: realCache,
	}
	c.wg.Add(opts.Workers)
	for i := 0; i < opts.Workers; i++ {
		go c.run()
	}
	return c
}

func (c *asyncCache) Put(anchor turbopath.AbsoluteSystemPath, key string, duration int, files []turbopath.AnchoredSystemPath) error {
	c.requests <- cacheRequest{
		anchor:   anchor,
		key:      key,
		files:    files,
		duration: duration,
	}
	return nil
}

func (c *asyncCache) Fetch(anchor turbopath.AbsoluteSystemPath, key string, files []string) (ItemStatus, []turbopath.AnchoredSystemPath, error) {
	return c.realCache.Fetch(anchor, key, files)
}

func (c *asyncCache) Exists(key string) ItemStatus {
	return c.realCache.Exists(key)
}

func (c *asyncCache) Clean(anchor turbopath.AbsoluteSystemPath) {
	c.realCache.Clean(anchor)
}

func (c *asyncCache) CleanAll() {
	c.realCache.CleanAll()
}

func (c *asyncCache) Shutdown() {
	// fmt.Println("Shutting down cache workers...")
	close(c.requests)
	c.wg.Wait()
	// fmt.Println("Shut down all cache workers")
}

// run implements the actual async logic.
func (c *asyncCache) run() {
	for r := range c.requests {
		_ = c.realCache.Put(r.anchor, r.key, r.duration, r.files)
	}
	c.wg.Done()
}
