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

package project

import (
	"context"

	project_model "code.gitea.io/gitea/models/project"
	"code.gitea.io/gitea/services/convert"
	"code.gitea.io/gitea/services/project_events"
)

// CreateColumn inserts a new column into a project and publishes a
// ColumnCreated event. Routers should call this instead of
// project_model.NewColumn so the SSE side-effect fires uniformly across
// repo, user, and org scopes.
func CreateColumn(ctx context.Context, column *project_model.Column) error {
	if err := project_model.NewColumn(ctx, column); err != nil {
		return err
	}
	project_events.PublishColumnCreated(ctx, project_events.ColumnCreated{
		ProjectID: column.ProjectID,
		ColumnID:  column.ID,
		Title:     column.Title,
		Color:     column.Color,
		Sorting:   int64(column.Sorting),
		IsDefault: column.Default,
	})
	return nil
}

// EditColumn updates a column and publishes a ColumnUpdated event.
func EditColumn(ctx context.Context, column *project_model.Column) error {
	if err := project_model.UpdateColumn(ctx, column); err != nil {
		return err
	}
	project_events.PublishColumnUpdated(ctx, project_events.ColumnUpdated{
		ProjectID: column.ProjectID,
		ColumnID:  column.ID,
		Title:     column.Title,
		Color:     column.Color,
		Sorting:   int64(column.Sorting),
	})
	return nil
}

// DeleteColumn removes a column from a project and publishes the
// matching ColumnDeleted event. The model layer also moves the
// column's issues to the project's default column; we publish those
// individual moves so receiving tabs can patch the DOM without a full
// reload. We snapshot affected issues *before* the delete so we have
// their ids; the destination column id is resolved after.
func DeleteColumn(ctx context.Context, columnID int64) error {
	// Snapshot the column + its issues so we know what to publish
	// after the delete commits. Errors here are non-fatal: we still
	// run the delete, and just skip per-issue events.
	col, snapErr := project_model.GetColumn(ctx, columnID)
	var (
		projectID   int64
		movedIssues []int64
	)
	if snapErr == nil {
		projectID = col.ProjectID
		issues, err := col.GetIssues(ctx)
		if err == nil {
			movedIssues = make([]int64, 0, len(issues))
			for _, pi := range issues {
				movedIssues = append(movedIssues, pi.IssueID)
			}
		}
	}

	if err := project_model.DeleteColumnByID(ctx, columnID); err != nil {
		return err
	}

	if snapErr != nil || projectID == 0 {
		return nil
	}
	project_events.PublishColumnDeleted(ctx, project_events.ColumnDeleted{
		ProjectID: projectID,
		ColumnID:  columnID,
	})

	// Resolve the new (default) column to attach to the per-issue
	// CardMoved events. Failures here are tolerated; the frontend
	// already knows the column is gone and will simply render its
	// next refresh as authoritative.
	project, err := project_model.GetProjectByID(ctx, projectID)
	if err != nil {
		return nil
	}
	defaultCol, err := project.MustDefaultColumn(ctx)
	if err != nil {
		return nil
	}
	for _, issueID := range movedIssues {
		project_events.PublishCardMoved(ctx, project_events.CardMoved{
			ProjectID:    projectID,
			IssueID:      issueID,
			FromColumnID: columnID,
			ToColumnID:   defaultCol.ID,
		})
	}
	return nil
}

// ReorderColumns persists a new sort order for project columns and
// publishes a ColumnReordered batch event.
func ReorderColumns(ctx context.Context, project *project_model.Project, sortedColumnIDs map[int64]int64) error {
	if err := project_model.MoveColumnsOnProject(ctx, project, sortedColumnIDs); err != nil {
		return err
	}
	cols := make([]project_events.ColumnSort, 0, len(sortedColumnIDs))
	for sorting, columnID := range sortedColumnIDs {
		cols = append(cols, project_events.ColumnSort{
			ColumnID: columnID,
			Sorting:  sorting,
		})
	}
	project_events.PublishColumnReordered(ctx, project_events.ColumnReordered{
		ProjectID: project.ID,
		Columns:   cols,
	})
	return nil
}

// DeleteProject deletes a project and publishes a ProjectDeleted event.
func DeleteProject(ctx context.Context, projectID int64) error {
	if err := project_model.DeleteProjectByID(ctx, projectID); err != nil {
		return err
	}
	project_events.PublishProjectDeleted(ctx, project_events.ProjectDeleted{
		ProjectID: projectID,
	})
	return nil
}

// publishProjectUpdated emits a ProjectUpdated event for the current
// state of the given project. It is exported via UpdateProject in
// project.go after the txn commits.
func publishProjectUpdated(ctx context.Context, project *project_model.Project) {
	project_events.PublishProjectUpdated(ctx, project_events.ProjectUpdated{
		ProjectID:   project.ID,
		Title:       project.Title,
		Description: project.Description,
		CardType:    convert.ProjectCardTypeToString(project.CardType),
		IsClosed:    project.IsClosed,
	})
}
