package main

import (
	"os"
	"path/filepath"
)

func syncCreate(srcPath, destPath, relPath string, manifest *Manifest, stack *GitignoreStack, dryRun bool, watcher Watcher) {
	info, err := os.Lstat(srcPath)
	if err != nil {
		if os.IsNotExist(err) {
			return
		}
		logErrorf("stat %s: %v", relPath, err)
		return
	}

	if info.IsDir() {
		if hasGitignore(srcPath) {
			stack.Push(srcPath)
		}
		if watcher != nil {
			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
			})
		}
		if dryRun {
			logInfo("[dry-run] would create dir: %s", relPath)
			return
		}
		if err := os.MkdirAll(destPath, info.Mode()); err != nil {
			logErrorf("mkdir %s: %v", relPath, err)
		}
		return
	}

	linkTarget := ""
	if info.Mode()&os.ModeSymlink != 0 {
		linkTarget, err = os.Readlink(srcPath)
		if err != nil {
			logErrorf("readlink %s: %v", relPath, err)
			return
		}
	}

	meta := FileMeta{
		Size:    info.Size(),
		Mode:    info.Mode(),
		ModTime: info.ModTime(),
		Symlink: linkTarget,
	}
	manifest.Files[relPath] = meta

	if dryRun {
		logInfo("[dry-run] would sync: %s", relPath)
		return
	}

	os.MkdirAll(filepath.Dir(destPath), 0755)
	os.Remove(destPath)

	if linkTarget != "" {
		if err := os.Symlink(linkTarget, destPath); err != nil {
			logErrorf("symlink %s: %v", relPath, err)
		}
		logInfo("sync: %s -> %s (symlink)", relPath, linkTarget)
		return
	}

	if err := copyFileWithRetry(srcPath, destPath, info.Mode()); err != nil {
		logErrorf("copy %s: %v", relPath, err)
	} else {
		logInfo("sync: %s", relPath)
	}
}

func syncWrite(srcPath, destPath, relPath string, manifest *Manifest, dryRun bool) {
	info, err := os.Lstat(srcPath)
	if err != nil {
		if os.IsNotExist(err) {
			return
		}
		logErrorf("stat %s: %v", relPath, err)
		return
	}

	meta, exists := manifest.Files[relPath]
	if exists && meta.Size == info.Size() && meta.Mode == info.Mode() && meta.ModTime.Equal(info.ModTime()) {
		return
	}

	linkTarget := ""
	if info.Mode()&os.ModeSymlink != 0 {
		linkTarget, err = os.Readlink(srcPath)
		if err != nil {
			logErrorf("readlink %s: %v", relPath, err)
			return
		}
	}

	manifest.Files[relPath] = FileMeta{
		Size:    info.Size(),
		Mode:    info.Mode(),
		ModTime: info.ModTime(),
		Symlink: linkTarget,
	}

	if dryRun {
		logInfo("[dry-run] would update: %s", relPath)
		return
	}

	os.Remove(destPath)

	if linkTarget != "" {
		if err := os.Symlink(linkTarget, destPath); err != nil {
			logErrorf("symlink %s: %v", relPath, err)
		}
		logInfo("sync: %s -> %s (symlink)", relPath, linkTarget)
		return
	}

	if err := copyFileWithRetry(srcPath, destPath, info.Mode()); err != nil {
		logErrorf("copy %s: %v", relPath, err)
	} else {
		logInfo("sync: %s", relPath)
	}
}

func syncRemove(destPath, relPath string, manifest *Manifest, dryRun bool) {
	delete(manifest.Files, relPath)

	if dryRun {
		logInfo("[dry-run] would remove: %s", relPath)
		return
	}

	if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) {
		logErrorf("remove %s: %v", relPath, err)
	} else {
		logInfo("remove: %s", relPath)
	}
}

type Watcher interface {
	Add(path string) error
}
