package helpers

import (
	"errors"
	"io/fs"
	"path/filepath"
	"sync"

	"github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/logging"
	"github.com/fsnotify/fsnotify"
)

var NotInitializedError = errors.New("FileWatcher not initialized")
var NoWatchFunctionError = errors.New("No watch function provided")

type IFileWatcher interface {
	RecursiveDir(path string) error
	Dir(path string) error
	Append(fn func(string))
	Prepend(fn func(string))
	Watch() error
	Close()
	Restart()
}

type FileWatcher struct {
	mu      sync.Mutex
	wf      []func(string)
	paths   []string
	watcher *fsnotify.Watcher
}

func NewFileWatcher() (*FileWatcher, error) {
	fw := &FileWatcher{mu: sync.Mutex{}}
	fw.Watch()
	return fw, nil
}

func (fw *FileWatcher) RecursiveDir(path string) error {
	err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
		if d.IsDir() {
			err := fw.Dir(path)
			if err != nil {
				return err
			}
		}
		return nil
	})

	return err
}

func (fw *FileWatcher) Dir(path string) error {
	fw.mu.Lock()
	defer fw.mu.Unlock()
	if fw.watcher != nil {
		err := fw.watcher.Add(path)
		if err != nil {
			return err
		}
	}

	fw.paths = append(fw.paths, path)

	return nil
}

func (fw *FileWatcher) Append(fn func(string)) {
	fw.mu.Lock()
	defer fw.mu.Unlock()
	fw.wf = append(fw.wf, fn)
}

func (fw *FileWatcher) Prepend(fn func(string)) {
	fw.mu.Lock()
	defer fw.mu.Unlock()
	fw.wf = append([]func(string){fn}, fw.wf...)
}

func (fw *FileWatcher) Watch() error {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}

	fw.mu.Lock()
	fw.watcher = watcher
	fw.mu.Unlock()

	// Start listening for events.
	go func() {
		for {
			select {
			case event, ok := <-watcher.Events:
				if !ok {
					return
				}
				if !event.Has(fsnotify.Chmod) {
					fw.mu.Lock()
					for _, wf := range fw.wf {
						wf(event.Name)
					}
					fw.mu.Unlock()
				}
			case err, ok := <-watcher.Errors:
				if !ok {
					return
				}
				logging.Error(err)
			}
		}
	}()

	return nil
}

// INFO: After closing the watcher, you can't use it anymore.
// Also, after a restart, you need to re add the paths
func (fw *FileWatcher) Close() {
	fw.mu.Lock()
	defer fw.mu.Unlock()

	if fw.watcher != nil {
		fw.watcher.Close()
	}
	fw.watcher = nil
	fw.paths = nil
}

func (fw *FileWatcher) Restart() {
	fw.Close()
	fw.Watch()
}
