package githubsearch

import (
	"fmt"
	"strings"
)

type EntityType string
type State string
type RepoVisibility string
type SortField string
type SortDirection int

const (
	Asc SortDirection = iota
	Desc

	UpdatedAt SortField = "updated"
	CreatedAt SortField = "created"

	Issue       EntityType = "issue"
	PullRequest EntityType = "pr"

	Open   State = "open"
	Closed State = "closed"
	Merged State = "merged"

	Public  RepoVisibility = "public"
	Private RepoVisibility = "private"
)

func NewQuery() *Query {
	return &Query{}
}

type Query struct {
	repo  string
	owner string
	sort  string
	query string

	entity     string
	state      string
	baseBranch string
	headBranch string
	labels     []string
	assignee   string
	author     string
	mentions   string
	milestone  string

	language   string
	topic      string
	forkState  string
	visibility string
	isArchived *bool
	draft      string
}

func (q *Query) InRepository(nameWithOwner string) {
	q.repo = nameWithOwner
}

func (q *Query) OwnedBy(owner string) {
	q.owner = owner
}

func (q *Query) SortBy(field SortField, direction SortDirection) {
	var dir string
	switch direction {
	case Asc:
		dir = "asc"
	case Desc:
		dir = "desc"
	}
	q.sort = fmt.Sprintf("%s-%s", field, dir)
}

func (q *Query) AddQuery(query string) {
	q.query = query
}

func (q *Query) SetType(t EntityType) {
	q.entity = string(t)
}

func (q *Query) SetState(s State) {
	q.state = string(s)
}

func (q *Query) SetBaseBranch(name string) {
	q.baseBranch = name
}

func (q *Query) SetHeadBranch(name string) {
	q.headBranch = name
}

func (q *Query) AssignedTo(user string) {
	q.assignee = user
}

func (q *Query) AuthoredBy(user string) {
	q.author = user
}

func (q *Query) Mentions(handle string) {
	q.mentions = handle
}

func (q *Query) InMilestone(title string) {
	q.milestone = title
}

func (q *Query) AddLabel(name string) {
	q.labels = append(q.labels, name)
}

func (q *Query) SetLanguage(name string) {
	q.language = name
}

func (q *Query) SetTopic(name string) {
	q.topic = name
}

func (q *Query) SetVisibility(visibility RepoVisibility) {
	q.visibility = string(visibility)
}

func (q *Query) OnlyForks() {
	q.forkState = "only"
}

func (q *Query) IncludeForks(include bool) {
	q.forkState = fmt.Sprintf("%v", include)
}

func (q *Query) SetArchived(isArchived bool) {
	q.isArchived = &isArchived
}

func (q *Query) SetDraft(draft string) {
	q.draft = draft
}

func (q *Query) String() string {
	var qs string

	// context
	if q.repo != "" {
		qs += fmt.Sprintf("repo:%s ", q.repo)
	} else if q.owner != "" {
		qs += fmt.Sprintf("user:%s ", q.owner)
	}

	// type
	if q.entity != "" {
		qs += fmt.Sprintf("is:%s ", q.entity)
	}
	if q.state != "" {
		qs += fmt.Sprintf("is:%s ", q.state)
	}

	// repositories
	if q.visibility != "" {
		qs += fmt.Sprintf("is:%s ", q.visibility)
	}
	if q.language != "" {
		qs += fmt.Sprintf("language:%s ", quote(q.language))
	}
	if q.topic != "" {
		qs += fmt.Sprintf("topic:%s ", quote(q.topic))
	}
	if q.forkState != "" {
		qs += fmt.Sprintf("fork:%s ", q.forkState)
	}
	if q.isArchived != nil {
		qs += fmt.Sprintf("archived:%v ", *q.isArchived)
	}

	// issues
	if q.assignee != "" {
		qs += fmt.Sprintf("assignee:%s ", q.assignee)
	}
	for _, label := range q.labels {
		qs += fmt.Sprintf("label:%s ", quote(label))
	}
	if q.author != "" {
		qs += fmt.Sprintf("author:%s ", q.author)
	}
	if q.mentions != "" {
		qs += fmt.Sprintf("mentions:%s ", q.mentions)
	}
	if q.milestone != "" {
		qs += fmt.Sprintf("milestone:%s ", quote(q.milestone))
	}

	// pull requests
	if q.baseBranch != "" {
		qs += fmt.Sprintf("base:%s ", quote(q.baseBranch))
	}
	if q.headBranch != "" {
		qs += fmt.Sprintf("head:%s ", quote(q.headBranch))
	}
	if q.draft != "" {
		qs += fmt.Sprintf("draft:%v ", q.draft)
	}

	if q.sort != "" {
		qs += fmt.Sprintf("sort:%s ", q.sort)
	}
	return strings.TrimRight(qs+q.query, " ")
}

func quote(v string) string {
	if strings.ContainsAny(v, " \"\t\r\n") {
		return fmt.Sprintf("%q", v)
	}
	return v
}
