package watcher

import (
	"context"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/fsnotify/fsnotify"

	"github.com/mathieu/project-notes/internal/indexer"
)

// Watcher observe les modifications dans le repertoire des notes et relance l indexation au besoin.
type Watcher struct {
	fs     *fsnotify.Watcher
	idx    *indexer.Indexer
	dir    string
	logger *log.Logger
	wg     sync.WaitGroup
}

// Start initialise le watcher et commence la surveillance dans une goroutine.
func Start(ctx context.Context, dir string, idx *indexer.Indexer, logger *log.Logger) (*Watcher, error) {
	fsWatcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}

	w := &Watcher{
		fs:     fsWatcher,
		idx:    idx,
		dir:    dir,
		logger: logger,
	}

	if err := w.addDirRecursive(dir); err != nil {
		fsWatcher.Close()
		return nil, err
	}

	w.wg.Add(1)
	go w.run(ctx)

	return w, nil
}

// Close arrete le watcher et attend la fin des goroutines.
func (w *Watcher) Close() error {
	err := w.fs.Close()
	w.wg.Wait()
	return err
}

func (w *Watcher) run(ctx context.Context) {
	defer w.wg.Done()

	debounce := time.NewTimer(0)
	if !debounce.Stop() {
		<-debounce.C
	}

	for {
		select {
		case <-ctx.Done():
			return
		case event, ok := <-w.fs.Events:
			if !ok {
				return
			}

			if event.Op&fsnotify.Create != 0 {
				if info, err := os.Stat(event.Name); err == nil && info.IsDir() {
					if err := w.addDirRecursive(event.Name); err != nil {
						w.logger.Printf("watcher: ajout repertoire %s impossible: %v", event.Name, err)
					}
				}
			}

			if w.shouldReindex(event.Name, event.Op) {
				if !debounce.Stop() {
					select {
					case <-debounce.C:
					default:
					}
				}
				debounce.Reset(200 * time.Millisecond)
			}
		case err, ok := <-w.fs.Errors:
			if !ok {
				return
			}
			w.logger.Printf("watcher: erreur: %v", err)
		case <-debounce.C:
			w.logger.Printf("watcher: reindexation suite a modification")
			if err := w.idx.Load(w.dir); err != nil {
				w.logger.Printf("watcher: echec reindexation: %v", err)
			}
		}
	}
}

func (w *Watcher) shouldReindex(path string, op fsnotify.Op) bool {
	ext := strings.ToLower(filepath.Ext(path))
	if ext != ".md" && op&fsnotify.Remove == 0 && op&fsnotify.Rename == 0 {
		return false
	}
	return true
}

func (w *Watcher) addDirRecursive(root string) error {
	return filepath.WalkDir(root, func(path string, entry os.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if entry.IsDir() {
			if err := w.fs.Add(path); err != nil {
				return err
			}
		}
		return nil
	})
}
