package nix

import (
	"bufio"
	"io"
	"iter"
	"slices"
	"strings"
)

const (
	// CommandBuild build a derivation or fetch a store path
	CommandBuild = "build"
	// CommandDerivation work with derivations, Nix's notion of a build plan.
	CommandDerivation = "derivation"
	// CommandDerivationAdd add a store derivation
	CommandDerivationAdd = "add"
	// CommandDerivationShow show the contents of a store derivation
	CommandDerivationShow = "show"
	// CommandStore manipulate a Nix store.
	CommandStore = "store"
	// CommandStoreSign sign store paths with a local key
	CommandStoreSign = "sign"
	// CommandCopy copy paths between Nix stores
	CommandCopy = "copy"

	// FlagAll apply the operation to every store path.
	FlagAll = "--all"
	// FlagDerivation operate on the store derivation rather than its outputs.
	FlagDerivation = "--derivation"
	// FlagExpr interpret installables as attribute paths relative to the Nix expression expr.
	FlagExpr = "--expr"
	// FlagFile interpret installables as attribute paths relative to the Nix expression stored in file.
	// If file is the character -, then a Nix expression will be read from standard input. Implies --impure.
	FlagFile = "--file"
	// FlagRecursive apply operation to closure of the specified paths.
	FlagRecursive = "--recursive"

	// FlagKeyFile file containing the secret signing key.
	FlagKeyFile = "--key-file"
	// FlagTo URL of the destination Nix store.
	FlagTo = "--to"

	// FlagDryRun show what this command would do without doing it.
	FlagDryRun = "--dry-run"
	// FlagJson produce output in JSON format, suitable for consumption by another program.
	FlagJson = "--json"
	// FlagNoLink do not create symlinks to the build results.
	FlagNoLink = "--no-link"
	// FlagOutLink use path as prefix for the symlinks to the build results. It defaults to result.
	FlagOutLink = "--out-link"
	// FlagPrintOutPaths print the resulting output paths
	FlagPrintOutPaths = "--print-out-paths"
	// FlagProfile the profile to operate on.
	FlagProfile = "--profile"
	// FlagRebuild rebuild an already built package and compare the result to the existing store paths.
	FlagRebuild = "--rebuild"
	// FlagStdin read installables from the standard input. No default installable applied.
	FlagStdin = "--stdin"

	// FlagDebug set the logging verbosity level to 'debug'.
	FlagDebug = "--debug"
	// FlagLogFormat set the format of log output; one of raw, internal-json, bar or bar-with-logs.
	FlagLogFormat = "--log-format"
	// FlagPrintBuildLogs print full build logs on standard error.
	FlagPrintBuildLogs = "--print-build-logs"
	// FlagQuiet decrease the logging verbosity level.
	FlagQuiet = "--quiet"
	// FlagVerbose increase the logging verbosity level.
	FlagVerbose = "--verbose"

	// FlagOffline disable substituters and consider all previously downloaded files up-to-date.
	FlagOffline = "--offline"
	// FlagOption set the Nix configuration setting name to value (overriding nix.conf).
	FlagOption = "--option"
	// FlagRefresh consider all previously downloaded files out-of-date.
	FlagRefresh = "--refresh"
	// FlagRepair during evaluation, rewrite missing or corrupted files in the Nix store. During building, rebuild missing or corrupted store paths.
	FlagRepair = "--repair"
	// FlagVersion show version information.
	FlagVersion = "--version"

	// FlagKeepGoing keep going in case of failed builds, to the greatest extent possible.
	// That is, if building an input of some derivation fails, Nix will still build the other inputs,
	// but not the derivation itself.
	// Without this option, Nix stops if any build fails (except for builds of substitutes),
	// possibly killing builds in progress (in case of parallel or distributed builds).
	FlagKeepGoing = "--keep-going"

	// OptionEvalCache whether to use the flake evaluation cache.
	// Certain commands won’t have to evaluate when invoked for the second time with a particular version of a flake.
	// Intermediate results are not cached.
	OptionEvalCache = "eval-cache"
	// OptionBuildUseSubstitutes if set to true (default), Nix will use binary substitutes if available.
	// This option can be disabled to force building from source.
	OptionBuildUseSubstitutes = "build-use-substitutes"
	// OptionSubstituters a list of URLs of Nix stores to be used as substituters, separated by whitespace.
	// A substituter is an additional store from which Nix can obtain store objects instead of building them.
	OptionSubstituters = "substituters"
	// OptionTrustedSubstituters a list of Nix store URLs, separated by whitespace.
	//These are not used by default, but users of the Nix daemon can enable them by specifying substituters.
	OptionTrustedSubstituters = "trusted-substituters"
	// OptionTrustedPublicKeys a whitespace-separated list of public keys.
	OptionTrustedPublicKeys = "trusted-public-keys"

	ValueTrue  = "true"
	ValueFalse = "false"
)

const (
	nixosSuffix0 = "#nixosConfigurations."
	nixosSuffix1 = ".config.system.build.toplevel"
)

// InstallableNixOS returns the nixos installable for a given flake and host.
func InstallableNixOS(flake, host string) string { return flake + nixosSuffix0 + host + nixosSuffix1 }

// WriteStdin writes installables for a nix process running with [FlagStdin].
func WriteStdin(w io.Writer, installables iter.Seq[string]) (int, error) {
	var count int
	for drv := range installables {
		if strings.HasSuffix(drv, ".drv") {
			// this is just what nix requires now :c
			drv += "^*"
		}
		n, err := w.Write([]byte(drv + "\n"))
		count += n
		if err != nil {
			return count, err
		}
	}
	return count, nil
}

const readStdinInitialCap = 1 << 10

// ReadStdin is the inverse of [WriteStdin].
func ReadStdin(r io.Reader) ([]string, error) {
	collective := make([]string, 0, readStdinInitialCap)
	scanner := bufio.NewScanner(r)
	for scanner.Scan() {
		if len(collective) == cap(collective) {
			// grow the buffer exponentially to minimise copies
			collective = slices.Grow(collective, cap(collective))
		}
		p := scanner.Text()
		if strings.HasSuffix(p, ".drv^*") {
			p = p[:len(p)-2]
		}
		collective = append(collective, p)
	}
	return collective, scanner.Err()
}
