// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package convert

import (
	"context"
	"fmt"

	project_model "code.gitea.io/gitea/models/project"
	user_model "code.gitea.io/gitea/models/user"
	"code.gitea.io/gitea/modules/container"
	api "code.gitea.io/gitea/modules/structs"
)

func ProjectTemplateTypeToString(t project_model.TemplateType) string {
	switch t {
	case project_model.TemplateTypeBasicKanban:
		return "basic_kanban"
	case project_model.TemplateTypeBugTriage:
		return "bug_triage"
	default:
		return "none"
	}
}

func ProjectTemplateTypeFromString(s string) (project_model.TemplateType, error) {
	switch s {
	case "", "none":
		return project_model.TemplateTypeNone, nil
	case "basic_kanban":
		return project_model.TemplateTypeBasicKanban, nil
	case "bug_triage":
		return project_model.TemplateTypeBugTriage, nil
	default:
		return 0, fmt.Errorf("invalid template_type %q (expected none, basic_kanban, bug_triage)", s)
	}
}

func ProjectCardTypeToString(t project_model.CardType) string {
	switch t {
	case project_model.CardTypeImagesAndText:
		return "images_and_text"
	default:
		return "text_only"
	}
}

func ProjectCardTypeFromString(s string) (project_model.CardType, error) {
	switch s {
	case "", "text_only":
		return project_model.CardTypeTextOnly, nil
	case "images_and_text":
		return project_model.CardTypeImagesAndText, nil
	default:
		return 0, fmt.Errorf("invalid card_type %q (expected text_only, images_and_text)", s)
	}
}

func ProjectTypeToString(t project_model.Type) string {
	switch t {
	case project_model.TypeIndividual:
		return "individual"
	case project_model.TypeRepository:
		return "repository"
	case project_model.TypeOrganization:
		return "organization"
	default:
		return ""
	}
}

// loadProjectCreators batch-fetches creators for the given projects + columns and
// returns a map keyed by user ID. Errors are surfaced; missing users are silently
// skipped (their creator field stays nil), matching the convention of other list
// converters that tolerate deleted users.
func loadProjectCreators(ctx context.Context, projects []*project_model.Project, columns []*project_model.Column) (map[int64]*user_model.User, error) {
	idSet := container.Set[int64]{}
	for _, p := range projects {
		if p.CreatorID > 0 {
			idSet.Add(p.CreatorID)
		}
	}
	for _, c := range columns {
		if c.CreatorID > 0 {
			idSet.Add(c.CreatorID)
		}
	}
	if len(idSet) == 0 {
		return map[int64]*user_model.User{}, nil
	}
	return user_model.GetUsersMapByIDs(ctx, idSet.Values())
}

// ToProject converts a project_model.Project to api.Project.
// Caller is expected to preload p.Repo / p.Owner to avoid N+1 lookups.
func ToProject(ctx context.Context, p *project_model.Project, doer *user_model.User) *api.Project {
	creators, _ := loadProjectCreators(ctx, []*project_model.Project{p}, nil)
	return toProject(ctx, p, doer, creators)
}

func toProject(ctx context.Context, p *project_model.Project, doer *user_model.User, creators map[int64]*user_model.User) *api.Project {
	state := api.StateOpen
	if p.IsClosed {
		state = api.StateClosed
	}

	project := &api.Project{
		ID:              p.ID,
		Title:           p.Title,
		Description:     p.Description,
		OwnerID:         p.OwnerID,
		RepoID:          p.RepoID,
		State:           state,
		TemplateType:    ProjectTemplateTypeToString(p.TemplateType),
		CardType:        ProjectCardTypeToString(p.CardType),
		Type:            ProjectTypeToString(p.Type),
		NumOpenIssues:   p.NumOpenIssues,
		NumClosedIssues: p.NumClosedIssues,
		NumIssues:       p.NumIssues,
		CreatedAt:       p.CreatedUnix.AsTime(),
		UpdatedAt:       p.UpdatedUnix.AsTime(),
	}

	if p.ClosedDateUnix > 0 {
		t := p.ClosedDateUnix.AsTime()
		project.ClosedAt = &t
	}

	if creator, ok := creators[p.CreatorID]; ok {
		project.Creator = ToUser(ctx, creator, doer)
	}

	if p.Type == project_model.TypeRepository && p.Repo != nil {
		project.HTMLURL = p.Repo.HTMLURL() + fmt.Sprintf("/projects/%d", p.ID)
	} else if p.Owner != nil {
		project.HTMLURL = p.Owner.HTMLURL(ctx) + fmt.Sprintf("/-/projects/%d", p.ID)
	}

	return project
}

func ToProjectColumn(ctx context.Context, column *project_model.Column, doer *user_model.User) *api.ProjectColumn {
	creators, _ := loadProjectCreators(ctx, nil, []*project_model.Column{column})
	return toProjectColumn(ctx, column, doer, creators)
}

func toProjectColumn(ctx context.Context, column *project_model.Column, doer *user_model.User, creators map[int64]*user_model.User) *api.ProjectColumn {
	apiColumn := &api.ProjectColumn{
		ID:              column.ID,
		Title:           column.Title,
		Default:         column.Default,
		Sorting:         int(column.Sorting),
		Color:           column.Color,
		ProjectID:       column.ProjectID,
		NumIssues:       column.NumIssues,
		NumOpenIssues:   column.NumOpenIssues,
		NumClosedIssues: column.NumClosedIssues,
		CreatedAt:       column.CreatedUnix.AsTime(),
		UpdatedAt:       column.UpdatedUnix.AsTime(),
	}
	if creator, ok := creators[column.CreatorID]; ok {
		apiColumn.Creator = ToUser(ctx, creator, doer)
	}
	return apiColumn
}

func ToProjectList(ctx context.Context, projects []*project_model.Project, doer *user_model.User) []*api.Project {
	creators, _ := loadProjectCreators(ctx, projects, nil)
	result := make([]*api.Project, len(projects))
	for i, p := range projects {
		result[i] = toProject(ctx, p, doer, creators)
	}
	return result
}

func ToProjectColumnList(ctx context.Context, columns []*project_model.Column, doer *user_model.User) []*api.ProjectColumn {
	creators, _ := loadProjectCreators(ctx, nil, columns)
	result := make([]*api.ProjectColumn, len(columns))
	for i, column := range columns {
		result[i] = toProjectColumn(ctx, column, doer, creators)
	}
	return result
}
