package transformers

import (
	"fmt"
	"os"
	"regexp"
	"strings"

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

const verbNameSub = "sub"
const verbNameGsub = "gsub"
const verbNameSsub = "ssub"

var subOptions = []OptionSpec{
	{Flag: "-f", Arg: "{a,b,c}", Type: "csv-list", Desc: "Field names to apply substitution to."},
	{Flag: "-r", Arg: "{regex}", Type: "regex", Desc: "Regular expression for field names to apply substitution to."},
	{Flag: "-a", Type: "bool", Desc: "Apply substitution to all fields."},
}

var SubSetup = TransformerSetup{
	Verb:         verbNameSub,
	UsageFunc:    transformerSubUsage,
	ParseCLIFunc: transformerSubParseCLI,
	IgnoresInput: false,
	Options:      subOptions,
}

var gsubOptions = []OptionSpec{
	{Flag: "-f", Arg: "{a,b,c}", Type: "csv-list", Desc: "Field names to apply substitution to."},
	{Flag: "-r", Arg: "{regex}", Type: "regex", Desc: "Regular expression for field names to apply substitution to."},
	{Flag: "-a", Type: "bool", Desc: "Apply substitution to all fields."},
}

var GsubSetup = TransformerSetup{
	Verb:         verbNameGsub,
	UsageFunc:    transformerGsubUsage,
	ParseCLIFunc: transformerGsubParseCLI,
	IgnoresInput: false,
	Options:      gsubOptions,
}

var ssubOptions = []OptionSpec{
	{Flag: "-f", Arg: "{a,b,c}", Type: "csv-list", Desc: "Field names to apply substitution to."},
	{Flag: "-r", Arg: "{regex}", Type: "regex", Desc: "Regular expression for field names to apply substitution to."},
	{Flag: "-a", Type: "bool", Desc: "Apply substitution to all fields."},
}

var SsubSetup = TransformerSetup{
	Verb:         verbNameSsub,
	UsageFunc:    transformerSsubUsage,
	ParseCLIFunc: transformerSsubParseCLI,
	IgnoresInput: false,
	Options:      ssubOptions,
}

func transformerSubUsage(
	o *os.File,
) {
	fmt.Fprintf(o, "Usage: %s %s [options]\n", "mlr", verbNameSub)
	fmt.Fprintf(o, "Replaces old string with new string in specified field(s), with regex support\n")
	fmt.Fprintf(o, "for the old string and not handling multiple matches, like the `sub` DSL function.\n")
	fmt.Fprintf(o, "The replacement string supports C-style backslash escapes such as \\n, \\t,\n")
	fmt.Fprintf(o, "and \\x1f. Write \\\\ to get a literal backslash.\n")
	fmt.Fprintf(o, "See also the `gsub` and `ssub` verbs.\n")
	WriteVerbOptions(o, subOptions)
}

func transformerGsubUsage(
	o *os.File,
) {
	fmt.Fprintf(o, "Usage: %s %s [options]\n", "mlr", verbNameGsub)
	fmt.Fprintf(o, "Replaces old string with new string in specified field(s), with regex support\n")
	fmt.Fprintf(o, "for the old string and handling multiple matches, like the `gsub` DSL function.\n")
	fmt.Fprintf(o, "The replacement string supports C-style backslash escapes such as \\n, \\t,\n")
	fmt.Fprintf(o, "and \\x1f. Write \\\\ to get a literal backslash.\n")
	fmt.Fprintf(o, "See also the `sub` and `ssub` verbs.\n")
	WriteVerbOptions(o, gsubOptions)
}

func transformerSsubUsage(
	o *os.File,
) {
	fmt.Fprintf(o, "Usage: %s %s [options]\n", "mlr", verbNameSsub)
	fmt.Fprintf(o, "Replaces old string with new string in specified field(s), without regex support for\n")
	fmt.Fprintf(o, "the old string, like the `ssub` DSL function.\n")
	fmt.Fprintf(o, "Both the search and replacement strings support C-style backslash escapes such\n")
	fmt.Fprintf(o, "as \\n, \\t, and \\x1f. Write \\\\ to get a literal backslash.\n")
	fmt.Fprintf(o, "See also the `gsub` and `sub` verbs.\n")
	WriteVerbOptions(o, ssubOptions)
}

type subConstructorFunc func(
	fieldNames []string,
	doAllFieldNames bool,
	doRegexes bool,
	oldText string,
	newText string,
) (RecordTransformer, error)

type fieldAcceptorFunc func(
	fieldName string,
) bool

func transformerSubParseCLI(
	pargi *int,
	argc int,
	args []string,
	opts *cli.TOptions,
	doConstruct bool, // false for first pass of CLI-parse, true for second pass
) (RecordTransformer, error) {
	return transformerSubsParseCLI(pargi, argc, args, opts, doConstruct, transformerSubUsage, NewTransformerSub, false)
}

func transformerGsubParseCLI(
	pargi *int,
	argc int,
	args []string,
	opts *cli.TOptions,
	doConstruct bool, // false for first pass of CLI-parse, true for second pass
) (RecordTransformer, error) {
	return transformerSubsParseCLI(pargi, argc, args, opts, doConstruct, transformerGsubUsage, NewTransformerGsub, false)
}

func transformerSsubParseCLI(
	pargi *int,
	argc int,
	args []string,
	opts *cli.TOptions,
	doConstruct bool, // false for first pass of CLI-parse, true for second pass
) (RecordTransformer, error) {
	return transformerSubsParseCLI(pargi, argc, args, opts, doConstruct, transformerSsubUsage, NewTransformerSsub, true)
}

// transformerSubsParseCLI is a shared CLI-parser for the sub, gsub, and ssub verbs.
// When unbackslashOldText is true (ssub only), the search string is also unescaped;
// for sub/gsub the search string is a regex and Go's regexp engine handles \n/\t/etc.
func transformerSubsParseCLI(
	pargi *int,
	argc int,
	args []string,
	_ *cli.TOptions,
	doConstruct bool, // false for first pass of CLI-parse, true for second pass
	usageFunc TransformerUsageFunc,
	constructorFunc subConstructorFunc,
	unbackslashOldText bool,
) (RecordTransformer, error) {

	// Skip the verb name from the current spot in the mlr command line
	argi := *pargi
	verb := args[argi]
	argi++

	// Parse local flags
	var fieldNames []string = nil
	doAllFieldNames := false
	doRegexes := false
	var oldText string
	var newText string

	var err error
	for argi < argc /* variable increment: 1 or 2 depending on flag */ {
		opt := args[argi]
		if !strings.HasPrefix(opt, "-") {
			break // No more flag options to process
		}
		if args[argi] == "--" {
			break // All transformers must do this so main-flags can follow verb-flags
		}
		argi++

		switch opt {
		case "-h", "--help":
			usageFunc(os.Stdout)
			return nil, cli.ErrHelpRequested

		case "-a":
			doAllFieldNames = true
			doRegexes = false
			fieldNames = nil

		case "-r":
			doRegexes = true

		case "-f":
			fieldNames, err = cli.VerbGetStringArrayArg(verb, opt, args, &argi, argc)
			if err != nil {
				return nil, err
			}
			doAllFieldNames = false
		default:
			usageFunc(os.Stderr)
			return nil, cli.ErrUsagePrinted
		}
	}

	if fieldNames == nil && !doAllFieldNames {
		usageFunc(os.Stderr)
		return nil, cli.ErrUsagePrinted
	}

	// Get the old and new text from the command line
	if (argc - argi) < 2 {
		usageFunc(os.Stderr)
		return nil, cli.ErrUsagePrinted
	}
	oldText = args[argi]
	newText = args[argi+1]

	// Interpret C-style backslash escapes ("\n", "\t", "\x1f", etc.) in the
	// replacement string the same way the DSL string-literal parser does, so
	// that e.g. `mlr sub -a r "\n"` matches `sub($x, "r", "\n")` in the DSL.
	// For sub/gsub the search string is a regex and Go's regexp engine already
	// handles \n/\r/\t inside patterns; pre-unescaping would corrupt user-
	// supplied regex metachars like \d or \s. For ssub the search string is a
	// literal, so we unescape it too.
	newText = lib.UnbackslashStringLiteral(newText)
	if unbackslashOldText {
		oldText = lib.UnbackslashStringLiteral(oldText)
	}

	argi += 2

	*pargi = argi
	if !doConstruct { // All transformers must do this for main command-line parsing
		return nil, nil
	}

	transformer, err := constructorFunc(
		fieldNames,
		doAllFieldNames,
		doRegexes,
		oldText,
		newText,
	)
	if err != nil {
		return nil, err
	}

	return transformer, nil
}

type TransformerSubs struct {
	fieldNamesSet map[string]bool  // for -f
	regexes       []*regexp.Regexp // for -r
	oldText       *mlrval.Mlrval
	newText       *mlrval.Mlrval
	fieldAcceptor fieldAcceptorFunc // for -f, -r, -a
	subber        bifs.TernaryFunc  // for sub, gsub, ssub
}

func NewTransformerSub(
	fieldNames []string,
	doAllFieldNames bool,
	doRegexes bool,
	oldText string,
	newText string,
) (RecordTransformer, error) {
	return NewTransformerSubs(fieldNames, doAllFieldNames, doRegexes, oldText, newText, safe_sub)
}

func NewTransformerGsub(
	fieldNames []string,
	doAllFieldNames bool,
	doRegexes bool,
	oldText string,
	newText string,
) (RecordTransformer, error) {
	return NewTransformerSubs(fieldNames, doAllFieldNames, doRegexes, oldText, newText, safe_gsub)
}

func NewTransformerSsub(
	fieldNames []string,
	doAllFieldNames bool,
	doRegexes bool,
	oldText string,
	newText string,
) (RecordTransformer, error) {
	return NewTransformerSubs(fieldNames, doAllFieldNames, doRegexes, oldText, newText, safe_ssub)
}

func NewTransformerSubs(
	fieldNames []string,
	doAllFieldNames bool,
	doRegexes bool,
	oldText string,
	newText string,
	subber bifs.TernaryFunc,
) (RecordTransformer, error) {
	tr := &TransformerSubs{
		fieldNamesSet: lib.StringListToSet(fieldNames),
		oldText:       mlrval.FromString(oldText),
		newText:       mlrval.FromString(newText),
		subber:        subber,
	}
	if doAllFieldNames {
		tr.fieldAcceptor = tr.fieldAcceptorAll
	} else if doRegexes {
		tr.fieldAcceptor = tr.fieldAcceptorByRegexes

		tr.regexes = make([]*regexp.Regexp, len(fieldNames))
		for i, regexString := range fieldNames {
			// Handles "a.*b"i Miller case-insensitive-regex specification
			regex, err := lib.CompileMillerRegex(regexString)
			if err != nil {
				return nil, cli.VerbErrorf("sub", "invalid regex \"%s\": %w", regexString, err)
			}
			tr.regexes[i] = regex
		}
	} else {
		tr.fieldAcceptor = tr.fieldAcceptorByNames
	}
	return tr, nil
}

func (tr *TransformerSubs) Transform(
	inrecAndContext *types.RecordAndContext,
	outputRecordsAndContexts *[]*types.RecordAndContext, // list of *types.RecordAndContext
	inputDownstreamDoneChannel <-chan bool,
	outputDownstreamDoneChannel chan<- bool,
) error {
	HandleDefaultDownstreamDone(inputDownstreamDoneChannel, outputDownstreamDoneChannel)

	if !inrecAndContext.EndOfStream {
		inrec := inrecAndContext.Record
		// Run sub, gsub, or ssub on the user-specified field names
		for pe := inrec.Head; pe != nil; pe = pe.Next {
			if tr.fieldAcceptor(pe.Key) {
				pe.Value = tr.subber(pe.Value, tr.oldText, tr.newText)
			}
		}
	}
	// Including emit of end-of-stream marker
	*outputRecordsAndContexts = append(*outputRecordsAndContexts, inrecAndContext)
	return nil
}

// fieldAcceptorByNames implements -f
func (tr *TransformerSubs) fieldAcceptorByNames(
	fieldName string,
) bool {
	return tr.fieldNamesSet[fieldName]
}

// fieldAcceptorByRegexes implements -r
func (tr *TransformerSubs) fieldAcceptorByRegexes(
	fieldName string,
) bool {
	for _, regex := range tr.regexes {
		if regex.MatchString(fieldName) {
			return true
		}
	}
	return false
}

// fieldAcceptorAll implements -a
func (tr *TransformerSubs) fieldAcceptorAll(
	fieldName string,
) bool {
	return true
}

// safe_sub implements sub, but doesn't produce error-type on non-string input.
func safe_sub(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsString() {
		return bifs.BIF_sub(input1, input2, input3)
	}
	return input1
}

// safe_gsub implements gsub, but doesn't produce error-type on non-string input.
func safe_gsub(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsString() {
		return bifs.BIF_gsub(input1, input2, input3)
	}
	return input1
}

// safe_ssub implements ssub, but doesn't produce error-type on non-string input.
func safe_ssub(input1, input2, input3 *mlrval.Mlrval) *mlrval.Mlrval {
	if input1.IsString() {
		return bifs.BIF_ssub(input1, input2, input3)
	}
	return input1
}
