//go:build !windows

package zenutil

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os"
	"os/exec"
	"runtime"
	"strconv"
	"strings"
	"sync/atomic"
	"time"
)

type progressDialog struct {
	ctx     context.Context
	cmd     *exec.Cmd
	max     int
	close   bool
	percent bool
	closed  int32
	lines   chan string
	done    chan struct{}
	err     error
}

func (d *progressDialog) send(line string) error {
	select {
	case d.lines <- line:
		return nil
	case <-d.done:
		return d.err
	}
}

func (d *progressDialog) Text(text string) error {
	return d.send("#" + text)
}

func (d *progressDialog) Value(value int) error {
	if value >= d.max && d.close {
		return d.Close()
	}
	if d.percent {
		return d.send(strconv.FormatFloat(100*float64(value)/float64(d.max), 'f', -1, 64))
	} else {
		return d.send(strconv.Itoa(value))
	}
}

func (d *progressDialog) MaxValue() int {
	return d.max
}

func (d *progressDialog) Done() <-chan struct{} {
	return d.done
}

func (d *progressDialog) Complete() error {
	err := d.Value(d.max)
	close(d.lines)
	return err
}

func (d *progressDialog) Close() error {
	atomic.StoreInt32(&d.closed, 1)
	d.cmd.Process.Signal(os.Interrupt)
	<-d.done
	return d.err
}

func (d *progressDialog) wait(extra *string, out *bytes.Buffer) {
	err := d.cmd.Wait()
	if cerr := d.ctx.Err(); cerr != nil {
		err = cerr
	}
	if eerr, ok := err.(*exec.ExitError); ok {
		switch {
		case eerr.ExitCode() == -1 && atomic.LoadInt32(&d.closed) != 0:
			err = nil
		case eerr.ExitCode() == 1:
			if extra != nil && *extra == strings.TrimSuffix(out.String(), "\n") {
				err = ErrExtraButton
			} else {
				err = ErrCanceled
			}
		default:
			err = fmt.Errorf("%w: %s", eerr, eerr.Stderr)
		}
	}
	d.err = err
	close(d.done)
}

func (d *progressDialog) pipe(w io.WriteCloser) {
	defer w.Close()
	var timeout = time.Second
	if runtime.GOOS == "darwin" {
		timeout = 40 * time.Millisecond
	}
	for {
		var line string
		select {
		case s, ok := <-d.lines:
			if !ok {
				return
			}
			line = s
		case <-d.ctx.Done():
			return
		case <-d.done:
			return
		case <-time.After(timeout):
			// line = ""
		}
		if _, err := w.Write([]byte(line + "\n")); err != nil {
			return
		}
	}
}
