package main

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

func repoPath() (string, error) {
	if override := os.Getenv("SIGIL_REPO"); override != "" {
		return filepath.Abs(expandHome(override))
	}
	return filepath.Abs(expandHome("~/.dotfiles"))
}

func expandHome(path string) string {
	if path == "~" {
		home, err := os.UserHomeDir()
		if err != nil {
			return path
		}
		return home
	}
	if strings.HasPrefix(path, "~/") {
		home, err := os.UserHomeDir()
		if err != nil {
			return path
		}
		return filepath.Join(home, path[2:])
	}
	return path
}

func compressHome(path string) string {
	home, err := os.UserHomeDir()
	if err != nil {
		return path
	}
	clean := filepath.Clean(path)
	homeClean := filepath.Clean(home)
	if clean == homeClean {
		return "~"
	}
	if strings.HasPrefix(clean, homeClean+string(os.PathSeparator)) {
		rel := strings.TrimPrefix(clean, homeClean+string(os.PathSeparator))
		return filepath.Join("~", rel)
	}
	return path
}

func splitPackageSpec(spec string) (string, string, error) {
	if spec == "" {
		return "", "", errors.New("missing package")
	}

	parts := strings.SplitN(spec, ":", 2)
	pkg := parts[0]
	rel := ""
	if len(parts) == 2 {
		rel = parts[1]
	}

	pkg = strings.Trim(pkg, "/")
	rel = strings.TrimPrefix(rel, "/")

	if pkg == "" {
		return "", "", errors.New("invalid package")
	}

	return pkg, rel, nil
}

func resolvePackageSpec(spec string) (string, string, error) {
	repo, err := repoPath()
	if err != nil {
		return "", "", err
	}

	repoAbs, err := filepath.Abs(repo)
	if err != nil {
		return "", "", err
	}

	spec = expandHome(spec)
	if filepath.IsAbs(spec) {
		return resolvePathSpec(spec, repoAbs)
	}

	if strings.Contains(spec, string(os.PathSeparator)) {
		return resolvePathSpec(spec, repoAbs)
	}

	clean := filepath.Clean(spec)
	if strings.HasPrefix(clean, ".") || strings.HasPrefix(clean, string(os.PathSeparator)) {
		return resolvePathSpec(clean, repoAbs)
	}

	return splitPackageSpec(spec)
}

func resolvePathSpec(pathSpec, repoAbs string) (string, string, error) {
	absPath, err := filepath.Abs(pathSpec)
	if err != nil {
		return "", "", err
	}

	if strings.HasPrefix(absPath, repoAbs+string(os.PathSeparator)) || absPath == repoAbs {
		rel, err := filepath.Rel(repoAbs, absPath)
		if err != nil {
			return "", "", err
		}
		parts := strings.Split(rel, string(os.PathSeparator))
		if len(parts) >= 1 {
			pkg := parts[0]
			if len(parts) >= 2 && parts[1] == filesDirName {
				relPath := filepath.Join(parts[2:]...)
				return pkg, relPath, nil
			}
			return pkg, filepath.Join(parts[1:]...), nil
		}
	}

	entries, err := os.ReadDir(repoAbs)
	if err != nil {
		return "", "", err
	}

	for _, entry := range entries {
		if !entry.IsDir() || strings.HasPrefix(entry.Name(), ".") {
			continue
		}
		pkgDir := filepath.Join(repoAbs, entry.Name())
		configPath := filepath.Join(pkgDir, configFileName)
		cfg, err := loadConfig(configPath)
		if err != nil {
			continue
		}
		targetRoot, err := selectTarget(cfg)
		if err != nil {
			if errors.Is(err, errTargetDisabled) {
				continue
			}
			continue
		}
		absTarget, err := filepath.Abs(expandHome(targetRoot))
		if err != nil {
			continue
		}
		if strings.HasPrefix(absPath, absTarget+string(os.PathSeparator)) || absPath == absTarget {
			rel, err := filepath.Rel(absTarget, absPath)
			if err != nil {
				return "", "", err
			}
			return entry.Name(), rel, nil
		}
	}

	return "", "", fmt.Errorf("could not resolve %s to a package", pathSpec)
}

func findPackageByTarget(repo, targetRoot string) (string, error) {
	repoEntries, err := os.ReadDir(repo)
	if err != nil {
		return "", err
	}

	absTarget, err := filepath.Abs(expandHome(targetRoot))
	if err != nil {
		return "", err
	}

	for _, entry := range repoEntries {
		if !entry.IsDir() || strings.HasPrefix(entry.Name(), ".") {
			continue
		}

		pkgDir := filepath.Join(repo, entry.Name())
		configPath := filepath.Join(pkgDir, configFileName)
		cfg, err := loadConfig(configPath)
		if err != nil {
			continue
		}
		target, err := selectTarget(cfg)
		if err != nil {
			if errors.Is(err, errTargetDisabled) {
				continue
			}
			continue
		}
		absPkgTarget, err := filepath.Abs(expandHome(target))
		if err != nil {
			continue
		}
		if absPkgTarget == absTarget {
			return entry.Name(), nil
		}
	}

	return "", nil
}

func repoPathForTarget(targetPath, repo string) (string, error) {
	info, err := os.Lstat(targetPath)
	if err != nil {
		return "", err
	}
	if info.Mode()&os.ModeSymlink == 0 {
		return "", nil
	}

	src, err := os.Readlink(targetPath)
	if err != nil {
		return "", err
	}
	if !filepath.IsAbs(src) {
		src = filepath.Join(filepath.Dir(targetPath), src)
	}
	src = filepath.Clean(src)

	repoAbs, err := filepath.Abs(repo)
	if err != nil {
		return "", err
	}

	rel, err := filepath.Rel(repoAbs, src)
	if err != nil {
		return "", err
	}
	if strings.HasPrefix(rel, "..") {
		return "", nil
	}

	parts := strings.Split(rel, string(os.PathSeparator))
	if len(parts) < 3 {
		return "", nil
	}
	if parts[1] != filesDirName {
		return "", nil
	}

	return filepath.Join(repoAbs, rel), nil
}
