// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package config

import (
	"context"
	"time"

	"github.com/hashicorp/go-hclog"
)

type rateLimitedFileWatcher struct {
	watcher          Watcher
	eventCh          chan *FileWatcherEvent
	coalesceInterval time.Duration
}

func (r *rateLimitedFileWatcher) Start(ctx context.Context) {
	r.watcher.Start(ctx)
	r.coalesceTimer(ctx, r.watcher.EventsCh(), r.coalesceInterval)
}

func (r rateLimitedFileWatcher) Stop() error {
	return r.watcher.Stop()
}

func (r rateLimitedFileWatcher) Add(filename string) error {
	return r.watcher.Add(filename)
}

func (r rateLimitedFileWatcher) Remove(filename string) {
	r.watcher.Remove(filename)
}

func (r rateLimitedFileWatcher) Replace(oldFile, newFile string) error {
	return r.watcher.Replace(oldFile, newFile)
}

func (r rateLimitedFileWatcher) EventsCh() chan *FileWatcherEvent {
	return r.eventCh
}

func NewRateLimitedFileWatcher(configFiles []string, logger hclog.Logger, coalesceInterval time.Duration) (Watcher, error) {

	watcher, err := NewFileWatcher(configFiles, logger)
	if err != nil {
		return nil, err
	}
	return &rateLimitedFileWatcher{
		watcher:          watcher,
		coalesceInterval: coalesceInterval,
		eventCh:          make(chan *FileWatcherEvent),
	}, nil
}

func (r rateLimitedFileWatcher) coalesceTimer(ctx context.Context, inputCh chan *FileWatcherEvent, coalesceDuration time.Duration) {
	var (
		coalesceTimer     *time.Timer
		sendCh            = make(chan struct{})
		fileWatcherEvents []string
	)

	go func() {
		for {
			select {
			case event, ok := <-inputCh:
				if !ok {
					if len(fileWatcherEvents) > 0 {
						r.eventCh <- &FileWatcherEvent{Filenames: fileWatcherEvents}
					}
					close(r.eventCh)
					return
				}
				fileWatcherEvents = append(fileWatcherEvents, event.Filenames...)
				if coalesceTimer == nil {
					coalesceTimer = time.AfterFunc(coalesceDuration, func() {
						// This runs in another goroutine so we can't just do the send
						// directly here as access to fileWatcherEvents is racy. Instead,
						// signal the main loop above.
						sendCh <- struct{}{}
					})
				}
			case <-sendCh:
				coalesceTimer = nil
				r.eventCh <- &FileWatcherEvent{Filenames: fileWatcherEvents}
				fileWatcherEvents = make([]string, 0)
			case <-ctx.Done():
				return
			}
		}
	}()
}
