package playwright

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"
)

func convertRegexp(reg *regexp.Regexp) (pattern, flags string) {
	matches := regexp.MustCompile(`\(\?([imsU]+)\)(.+)`).FindStringSubmatch(reg.String())

	if len(matches) == 3 {
		pattern = matches[2]
		flags = matches[1]
	} else {
		pattern = reg.String()
	}
	return
}

func escapeForAttributeSelector(text interface{}, exact bool) string {
	switch text := text.(type) {
	case *regexp.Regexp:
		return escapeRegexForSelector(text)
	default:
		suffix := "i"
		if exact {
			suffix = "s"
		}
		return fmt.Sprintf(`"%s"%s`, strings.Replace(strings.Replace(text.(string), `\`, `\\`, -1), `"`, `\"`, -1), suffix)
	}
}

func escapeForTextSelector(text interface{}, exact bool) string {
	switch text := text.(type) {
	case *regexp.Regexp:
		return escapeRegexForSelector(text)
	default:
		if exact {
			return fmt.Sprintf(`%ss`, escapeText(text.(string)))
		}
		return fmt.Sprintf(`%si`, escapeText(text.(string)))
	}
}

func escapeRegexForSelector(re *regexp.Regexp) string {
	pattern, flag := convertRegexp(re)
	return fmt.Sprintf(`/%s/%s`, strings.ReplaceAll(pattern, `>>`, `\>\>`), flag)
}

func escapeText(s string) string {
	builder := &strings.Builder{}
	encoder := json.NewEncoder(builder)
	encoder.SetEscapeHTML(false)
	_ = encoder.Encode(s)
	return strings.TrimSpace(builder.String())
}

func getByAltTextSelector(text interface{}, exact bool) string {
	return getByAttributeTextSelector("alt", text, exact)
}

func getByAttributeTextSelector(attrName string, text interface{}, exact bool) string {
	return fmt.Sprintf(`internal:attr=[%s=%s]`, attrName, escapeForAttributeSelector(text, exact))
}

func getByLabelSelector(text interface{}, exact bool) string {
	return fmt.Sprintf(`internal:label=%s`, escapeForTextSelector(text, exact))
}

func getByPlaceholderSelector(text interface{}, exact bool) string {
	return getByAttributeTextSelector("placeholder", text, exact)
}

func getByRoleSelector(role AriaRole, options ...LocatorGetByRoleOptions) string {
	props := make(map[string]string)
	if len(options) == 1 {
		if options[0].Checked != nil {
			props["checked"] = fmt.Sprintf("%t", *options[0].Checked)
		}
		if options[0].Disabled != nil {
			props["disabled"] = fmt.Sprintf("%t", *options[0].Disabled)
		}
		if options[0].Selected != nil {
			props["selected"] = fmt.Sprintf("%t", *options[0].Selected)
		}
		if options[0].Expanded != nil {
			props["expanded"] = fmt.Sprintf("%t", *options[0].Expanded)
		}
		if options[0].IncludeHidden != nil {
			props["include-hidden"] = fmt.Sprintf("%t", *options[0].IncludeHidden)
		}
		if options[0].Level != nil {
			props["level"] = fmt.Sprintf("%d", *options[0].Level)
		}
		if options[0].Name != nil {
			exact := false
			if options[0].Exact != nil {
				exact = *options[0].Exact
			}
			props["name"] = escapeForAttributeSelector(options[0].Name, exact)
		}
		if options[0].Pressed != nil {
			props["pressed"] = fmt.Sprintf("%t", *options[0].Pressed)
		}
	}
	propsStr := ""
	for k, v := range props {
		propsStr += "[" + k + "=" + v + "]"
	}
	return fmt.Sprintf("internal:role=%s%s", role, propsStr)
}

func getByTextSelector(text interface{}, exact bool) string {
	return fmt.Sprintf(`internal:text=%s`, escapeForTextSelector(text, exact))
}

func getByTestIdSelector(testIdAttributeName string, testId interface{}) string {
	return fmt.Sprintf(`internal:testid=[%s=%s]`, testIdAttributeName, escapeForAttributeSelector(testId, true))
}

func getByTitleSelector(text interface{}, exact bool) string {
	return getByAttributeTextSelector("title", text, exact)
}

func getTestIdAttributeName() string {
	return testIdAttributeName
}

func setTestIdAttributeName(name string) {
	testIdAttributeName = name
}
