package output

import (
	"bufio"
	"errors"
	"fmt"
	"os"

	"github.com/johnkerl/miller/v6/pkg/cli"
	"github.com/johnkerl/miller/v6/pkg/types"
)

func ChannelWriter(
	writerChannel <-chan []*types.RecordAndContext, // list of *types.RecordAndContext
	recordWriter IRecordWriter,
	writerOptions *cli.TWriterOptions,
	doneChannel chan<- bool,
	dataProcessingErrorChannel chan<- error,
	bufferedOutputStream *bufio.Writer,
	outputIsStdout bool,
) {

	for {
		recordsAndContexts := <-writerChannel
		done, errored := channelWriterHandleBatch(
			recordsAndContexts,
			recordWriter,
			writerOptions,
			bufferedOutputStream,
			outputIsStdout,
		)
		if errored {
			// Details have already been printed to stderr by
			// channelWriterHandleBatch. Non-blocking send: if a transformer
			// errored first, that error wins.
			select {
			case dataProcessingErrorChannel <- errors.New("exiting due to data error"):
			default:
			}
			doneChannel <- true
			break
		}
		if done {
			doneChannel <- true
			break
		}
	}
}

// channelWriterHandleBatch writes one batch of records. The first return
// value is true on end of record stream; the second is true on a data error
// (details already printed to stderr here).
func channelWriterHandleBatch(
	recordsAndContexts []*types.RecordAndContext,
	recordWriter IRecordWriter,
	writerOptions *cli.TWriterOptions,
	bufferedOutputStream *bufio.Writer,
	outputIsStdout bool,
) (done bool, errored bool) {
	for _, recordAndContext := range recordsAndContexts {
		// Three things can come through:
		// * End-of-stream marker
		// * Non-nil records to be printed
		// * Strings to be printed from put/filter DSL print/dump/etc
		//   statements. They are handled here rather than fmt.Println directly
		//   in the put/filter handlers since we want all print statements and
		//   record-output to be in the same goroutine, for deterministic
		//   output ordering.

		if !recordAndContext.EndOfStream {
			record := recordAndContext.Record
			context := &recordAndContext.Context

			// XXX more
			// XXX also make sure this results in exit 1 & goroutine cleanup
			if writerOptions.FailOnDataError && record != nil {
				ok := true
				for pe := record.Head; pe != nil; pe = pe.Next {
					if pe.Value.IsError() {
						context := recordAndContext.Context
						fmt.Fprintf(os.Stderr, "mlr: data error at NR=%d FNR=%d FILENAME=%s\n",
							context.NR, context.FNR, context.FILENAME,
						)
						is, err := pe.Value.GetError()
						if is {
							if err != nil {
								fmt.Fprintf(os.Stderr, "mlr: field %s: %v\n", pe.Key, err)
							} else {
								fmt.Fprintf(os.Stderr, "mlr: field %s\n", pe.Key)
							}
							ok = false
						}
					}
				}
				if !ok {
					return true, true
				}
			}

			if record != nil {
				err := recordWriter.Write(record, context, bufferedOutputStream, outputIsStdout)
				if err != nil {
					fmt.Fprintf(os.Stderr, "mlr: %v\n", err)
					return true, true
				}
			}

			outputString := recordAndContext.OutputString
			if outputString != "" {
				bufferedOutputStream.WriteString(outputString)
			}

			if writerOptions.FlushOnEveryRecord {
				// bufio.Writer errors are sticky; the final Flush in pkg/stream is checked
				_ = bufferedOutputStream.Flush()
			}

		} else {
			// Let the record-writers drain their output, if they have any
			// queued up. For example, PPRINT needs to see all same-schema
			// records before printing any, since it needs to compute max width
			// down columns.
			context := &recordAndContext.Context
			err := recordWriter.Write(nil, context, bufferedOutputStream, outputIsStdout)
			if err != nil {
				fmt.Fprintf(os.Stderr, "mlr: %v\n", err)
				return true, true
			}
			return true, false
		}
	}
	return false, false
}
