//go:build !windows

package main

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

	"github.com/fsnotify/fsnotify"
)

// watchForTests uses inotify directly for integration testing.
// This is only used in tests - production uses socket-based events from Windows watcher.
func watchForTests(ctx context.Context, hostDir, dataDir string, manifest *Manifest, stack *GitignoreStack, dryRun bool) error {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}
	defer watcher.Close()

	var mu sync.Mutex
	debounceTimers := make(map[string]*time.Timer)
	debounceOps := make(map[string]fsnotify.Op)

	if err := addWatchersForTests(watcher, hostDir, stack); err != nil {
		return err
	}

	for {
		select {
		case <-ctx.Done():
			return nil

		case event, ok := <-watcher.Events:
			if !ok {
				return nil
			}

			relPath, err := filepath.Rel(hostDir, event.Name)
			if err != nil {
				continue
			}

			if isGitDir(relPath) {
				continue
			}

			if stack.IsIgnored(relPath) {
				continue
			}

			if event.Op&fsnotify.Create != 0 {
				srcPath := filepath.Join(hostDir, relPath)
				info, err := os.Lstat(srcPath)
				if err == nil && info.IsDir() {
					if hasGitignore(srcPath) {
						stack.Push(srcPath)
					}
					watcher.Add(srcPath)
					filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
						if err != nil || !info.IsDir() || path == srcPath {
							return nil
						}
						watcher.Add(path)
						return nil
					})
				}
			}

			mu.Lock()
			if existing, exists := debounceOps[relPath]; exists {
				debounceTimers[relPath].Stop()
				debounceOps[relPath] = existing | event.Op
			} else {
				debounceOps[relPath] = event.Op
			}
			path := relPath
			debounceTimers[relPath] = time.AfterFunc(200*time.Millisecond, func() {
				mu.Lock()
				op := debounceOps[path]
				delete(debounceOps, path)
				delete(debounceTimers, path)
				mu.Unlock()

				destPath := filepath.Join(dataDir, path)
				srcPath := filepath.Join(hostDir, path)

				switch {
				case op&fsnotify.Create != 0:
					syncCreate(srcPath, destPath, path, manifest, stack, dryRun, watcher)
				case op&fsnotify.Write != 0:
					syncWrite(srcPath, destPath, path, manifest, dryRun)
				case op&fsnotify.Remove != 0:
					syncRemove(destPath, path, manifest, dryRun)
				case op&fsnotify.Rename != 0:
					syncRemove(destPath, path, manifest, dryRun)
				}
			})
			mu.Unlock()

		case err, ok := <-watcher.Errors:
			if !ok {
				return nil
			}
			_ = err
		}
	}
}

func addWatchersForTests(watcher *fsnotify.Watcher, root string, stack *GitignoreStack) error {
	return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		relPath, err := filepath.Rel(root, path)
		if err != nil {
			return err
		}

		if isGitDir(relPath) {
			if info.IsDir() {
				return filepath.SkipDir
			}
			return nil
		}

		if info.IsDir() {
			if hasGitignore(path) {
				stack.Push(path)
			}
			if !stack.IsIgnored(relPath) {
				return watcher.Add(path)
			}
		}

		return nil
	})
}
