package input

import (
	"context"
	"fmt"
	"strings"

	"gitoa.ru/go-4devs/console/input/errs"
	"gitoa.ru/go-4devs/console/input/option"
	"gitoa.ru/go-4devs/console/input/value"
)

const doubleDash = `--`

type Argv struct {
	Array
	Args      []string
	ErrHandle func(error) error
}

func (i *Argv) Bind(ctx context.Context, def *Definition) error {
	options := true

	for len(i.Args) > 0 {
		var err error

		arg := i.Args[0]
		i.Args = i.Args[1:]

		switch {
		case options && arg == doubleDash:
			options = false
		case options && len(arg) > 2 && arg[0:2] == doubleDash:
			err = i.parseLongOption(arg[2:], def)
		case options && arg[0:1] == "-":
			if len(arg) == 1 {
				return fmt.Errorf("%w: option name required given '-'", errs.ErrInvalidName)
			}

			err = i.parseShortOption(arg[1:], def)
		default:
			err = i.parseArgument(arg, def)
		}

		if err != nil && i.ErrHandle != nil {
			if herr := i.ErrHandle(err); herr != nil {
				return herr
			}
		}
	}

	return i.Array.Bind(ctx, def)
}

func (i *Argv) parseLongOption(arg string, def *Definition) error {
	var value *string

	name := arg

	if strings.Contains(arg, "=") {
		vals := strings.SplitN(arg, "=", 2)
		name = vals[0]
		value = &vals[1]
	}

	opt, err := def.Option(name)
	if err != nil {
		return errs.Option(name, err)
	}

	return i.appendOption(name, value, opt)
}

func (i *Argv) appendOption(name string, data *string, opt option.Option) error {
	v, ok := i.GetOption(name)

	if ok && !opt.IsArray() {
		return fmt.Errorf("%w: got: array, expect: %s", errs.ErrUnexpectedType, opt.Flag.Type())
	}

	var val string

	switch {
	case data != nil:
		val = *data
	case opt.IsBool():
		val = "true"
	case len(i.Args) > 0 && len(i.Args[0]) > 0 && i.Args[0][0:1] != "-":
		val = i.Args[0]
		i.Args = i.Args[1:]
	default:
		return errs.Option(name, errs.ErrRequired)
	}

	if !ok {
		v = value.ByFlag(opt.Flag)
		i.SetOption(name, v)
	}

	if err := v.Append(val); err != nil {
		return errs.Option(name, err)
	}

	return nil
}

func (i *Argv) parseShortOption(arg string, def *Definition) error {
	name := arg

	var value string

	if len(name) > 1 {
		name, value = arg[0:1], arg[1:]
	}

	opt, err := def.ShortOption(name)
	if err != nil {
		return err
	}

	if opt.IsBool() && value != "" {
		if err := i.parseShortOption(value, def); err != nil {
			return err
		}

		value = ""
	}

	if value == "" {
		return i.appendOption(opt.Name, nil, opt)
	}

	return i.appendOption(opt.Name, &value, opt)
}

func (i *Argv) parseArgument(arg string, def *Definition) error {
	opt, err := def.Argument(i.LenArguments())
	if err != nil {
		return err
	}

	v, ok := i.GetArgument(opt.Name)
	if !ok {
		v = value.ByFlag(opt.Flag)
		i.SetArgument(opt.Name, v)
	}

	if err := v.Append(arg); err != nil {
		return errs.Argument(opt.Name, err)
	}

	return nil
}
