package apkbuild

import (
	"github.com/moznion/go-optional"
	"mvdan.cc/sh/v3/expand"
	"mvdan.cc/sh/v3/syntax"
)

type Variable struct {
	Assignment     *syntax.Assign
	Name           string
	Value          string
	EvaluatedValue string
	Exported       bool
	Local          bool
	CommandSubst   []*syntax.CmdSubst
}

// ParamExpressions extracts all the parameter expressions referenced in the
// variable assignment.
func (v *Variable) ParamExpressions() []*syntax.ParamExp {
	expressions := []*syntax.ParamExp{}

	if v.Assignment == nil || v.Assignment.Value == nil {
		return expressions
	}

	var iterate func(parts []syntax.WordPart)
	iterate = func(parts []syntax.WordPart) {
		for _, wordpart := range parts {
			switch part := wordpart.(type) {
			case *syntax.DblQuoted:
				iterate(part.Parts)
			case *syntax.ParamExp:
				expressions = append(expressions, part)
			}
		}
	}
	iterate(v.Assignment.Value.Parts)

	return expressions
}

type VariableScope []Variable

func (v VariableScope) Get(name string) optional.Option[Variable] {
	result := optional.None[Variable]()

	for _, current := range v {
		if current.Name == name {
			result = optional.Some(current)
		}
	}

	return result
}

func (v VariableScope) GetFirstAssignment(name string) optional.Option[Variable] {
	for _, current := range v {
		if current.Name == name {
			return optional.Some(current)
		}
	}

	return optional.None[Variable]()
}

func (v VariableScope) GetValue(name string) string {
	return optional.MapOr(v.Get(name), "", func(v Variable) string { return v.EvaluatedValue })
}

// Iter calls the provided function for each variable and provides the variable
// to the function. If the function returns false, the iteration will stop.
func (v VariableScope) Iter(f func(Variable)) {
	for _, v := range v {
		f(v)
	}
}

// IterVar calls the provided function for each variable where the name matches
// provided name and provides the variable to the function. If the function
// returns false, the iteration will stop.
func (v VariableScope) IterVar(name string, f func(Variable)) {
	v.Iter(func(v Variable) {
		if v.Name == name {
			f(v)
		}
	})
}

type Environ struct {
	VariableScope
}

var _ expand.Environ = (*Environ)(nil)

func (e Environ) Get(name string) (vr expand.Variable) {
	x := e.VariableScope.Get(name)
	x.IfSome(func(v Variable) {
		vr.Kind = expand.String
		vr.Str = v.EvaluatedValue
	})

	return
}

func (e Environ) Each(f func(name string, vr expand.Variable) bool) {
	for _, v := range e.VariableScope {
		r := f(v.Name, expand.Variable{
			Kind: expand.String,
			Str:  v.EvaluatedValue,
		})
		if !r {
			break
		}
	}
}
