package main

import (
	"errors"
	"fmt"
	"os/exec"
	"runtime"
	"strings"
)

// commandDefaults holds the per-OS open-command templates used when the user
// hasn't overridden them in the config.
type commandDefaults struct {
	OpenFile   string
	OpenFolder string
}

func defaultCommands() commandDefaults {
	if runtime.GOOS == "windows" {
		return commandDefaults{
			OpenFile:   `cmd /c start "" "{path}"`,
			OpenFolder: `explorer.exe "{path}"`,
		}
	}
	return commandDefaults{
		OpenFile:   `xdg-open "{path}"`,
		OpenFolder: `xdg-open "{path}"`,
	}
}

// resolveOpenCommand returns the user-configured command if non-blank, else
// the platform default.
func resolveOpenCommand(configured, fallback string) string {
	if strings.TrimSpace(configured) != "" {
		return configured
	}
	return fallback
}

// runOpenCommand tokenizes template, substitutes {path} with the resolved
// path (appending it if the placeholder is missing), and starts the command.
func runOpenCommand(template, path string) error {
	tokens, err := tokenizeCommand(template)
	if err != nil {
		return fmt.Errorf("parse command: %w", err)
	}
	if len(tokens) == 0 {
		return errors.New("command is empty")
	}
	sawPath := false
	for i, t := range tokens {
		if strings.Contains(t, "{path}") {
			tokens[i] = strings.ReplaceAll(t, "{path}", path)
			sawPath = true
		}
	}
	if !sawPath {
		tokens = append(tokens, path)
	}
	return exec.Command(tokens[0], tokens[1:]...).Start()
}

// tokenizeCommand splits a command-line string into argv tokens, honouring
// double-quoted segments. An empty pair "" yields an empty argument — needed
// for Windows `cmd /c start "" file`, where the empty quotes are the title.
func tokenizeCommand(s string) ([]string, error) {
	var tokens []string
	var cur strings.Builder
	inQuote := false
	inToken := false
	for i := 0; i < len(s); i++ {
		c := s[i]
		if c == '"' {
			inQuote = !inQuote
			inToken = true
			continue
		}
		if !inQuote && (c == ' ' || c == '\t') {
			if inToken {
				tokens = append(tokens, cur.String())
				cur.Reset()
				inToken = false
			}
			continue
		}
		cur.WriteByte(c)
		inToken = true
	}
	if inQuote {
		return nil, errors.New("unclosed quote")
	}
	if inToken {
		tokens = append(tokens, cur.String())
	}
	return tokens, nil
}
