package nix

import (
	"context"
	"errors"
	"io"
	"iter"
	"os"
	"os/exec"
	"time"
)

const (
	defaultWaitDelay = 30 * time.Second
)

// Nix is the name of the nix program.
var Nix = "nix"

type nix struct {
	name  string
	ctx   context.Context
	extra []string

	stdout, stderr io.Writer
}

func (n *nix) Unwrap() context.Context             { return n.ctx }
func (n *nix) Streams() (stdout, stderr io.Writer) { return n.stdout, n.stderr }

const (
	ExtraExperimentalFeatures  = "--extra-experimental-features"
	ExperimentalFeaturesFlakes = "nix-command flakes"
)

/*
New returns a new [Context].

A non-nil stderr implies verbose.

Streams will not be connected for commands outputting JSON.
*/
func New(ctx context.Context, extraArgs []string, stdout, stderr io.Writer) Context {
	return &nix{
		name: Nix,
		ctx:  ctx,
		// since flakes are supposedly experimental
		extra: append(extraArgs, ExtraExperimentalFeatures, ExperimentalFeaturesFlakes),

		stdout: stdout,
		stderr: stderr,
	}
}

func (n *nix) Nix(ctx context.Context, arg ...string) *exec.Cmd {
	cmd := exec.CommandContext(ctx, n.name, append(n.extra, arg...)...)
	cmd.Cancel = func() error { return cmd.Process.Signal(os.Interrupt) }
	cmd.WaitDelay = defaultWaitDelay
	return cmd
}

func (n *nix) WriteStdin(cmd *exec.Cmd, installables iter.Seq[string], f func() error) (int, error) {
	w, err := cmd.StdinPipe()
	if err != nil {
		return 0, err
	}
	if err = cmd.Start(); err != nil {
		return 0, err
	}
	count, writeErr := WriteStdin(w, installables)
	closeErr := w.Close()
	var fErr error
	if f != nil && writeErr == nil && closeErr == nil {
		fErr = f()
	}
	return count, errors.Join(writeErr, closeErr, fErr, cmd.Wait())
}
