// Package project 承载 Project / Requirement / Issue 三个 PM 聚合根的领域类型与
// Service 接口契约。本文件只做类型与签名声明，业务实现在 *_service.go。
//
// 三个聚合根共享同一个 Go 包是有意为之：它们在 schema 上紧耦合（Issue 引用
// Requirement，Requirement 属于 Project），且对外只暴露一组 chi route，拆包
// 反而要互相 import。
package project

import (
	"context"
	"time"

	"github.com/google/uuid"
)

// Phase 是 Requirement 的六阶段枚举。phase 不强制单向流动，可任意跳跃/回退。
// 字符串常量保持与 DB CHECK 约束一致。
type Phase string

// 六阶段枚举值，与最新 phase CHECK 约束一致（prototyping 由 migrations/0011 加入）。
const (
	PhasePlanning     Phase = "planning"
	PhasePrototyping  Phase = "prototyping"
	PhaseAuditing     Phase = "auditing"
	PhaseImplementing Phase = "implementing"
	PhaseReviewing    Phase = "reviewing"
	PhaseDone         Phase = "done"
)

// AllPhases 用于校验 phase 字符串合法性，顺序也对应前端看板/流转条的列序。
var AllPhases = []Phase{PhasePlanning, PhasePrototyping, PhaseAuditing, PhaseImplementing, PhaseReviewing, PhaseDone}

// IsValid 报告 p 是否在枚举集合内。
func (p Phase) IsValid() bool {
	for _, x := range AllPhases {
		if p == x {
			return true
		}
	}
	return false
}

// ArtifactPhases 是允许产出文档型产物的五个阶段（与 requirement_artifacts.phase
// CHECK 约束一致）：implementing/reviewing 的产出是审计报告 / 评审记录（也是文档型
// 产物），done 阶段是汇总视图、不单独建产物，故只留前五阶段。
var ArtifactPhases = []Phase{PhasePlanning, PhasePrototyping, PhaseAuditing, PhaseImplementing, PhaseReviewing}

// IsArtifactPhase 报告 p 是否允许创建产物版本。
func (p Phase) IsArtifactPhase() bool {
	for _, x := range ArtifactPhases {
		if p == x {
			return true
		}
	}
	return false
}

// Verdict 是单个产物版本的评审结论，与 requirement_artifacts.verdict CHECK 一致。
// 'none' 为默认（未评审）；'pass' 表示通过、可作为阶段网关的"已批准产物"；'fail'
// 表示驳回。仅 'pass' 会被 ArtifactService.Approve 写入阶段指针。
type Verdict string

// 评审结论枚举值。
const (
	VerdictNone Verdict = "none"
	VerdictPass Verdict = "pass"
	VerdictFail Verdict = "fail"
)

// IsValid 报告 v 是否在枚举集合内。
func (v Verdict) IsValid() bool {
	return v == VerdictNone || v == VerdictPass || v == VerdictFail
}

// Status 是 Requirement / Issue 通用的开/关状态。
type Status string

// 开/关状态枚举值，与 DB CHECK 约束一致。
const (
	StatusOpen   Status = "open"
	StatusClosed Status = "closed"
)

// Visibility 控制 Project 的可见域：private 仅 owner+admin 可读，
// internal 所有登录用户可读；写权限两者都仅限 owner+admin。
type Visibility string

// 可见性枚举值，与 DB CHECK 约束一致。
const (
	VisibilityPrivate  Visibility = "private"
	VisibilityInternal Visibility = "internal"
)

// IsValid 报告 v 是否在枚举集合内。
func (v Visibility) IsValid() bool {
	return v == VisibilityPrivate || v == VisibilityInternal
}

// Project 是顶层聚合根。ArchivedAt 为 nil 表示未归档。
type Project struct {
	ID          uuid.UUID
	Slug        string
	Name        string
	Description string
	Visibility  Visibility
	OwnerID     uuid.UUID
	ArchivedAt  *time.Time
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

// Requirement 表示一个项目内的需求。Number 在 (project_id) 内自增。
// WorkspaceID 为 nil 表示未关联任何 workspace；非 nil 时由 Service 层保证
// 该 workspace 与本 requirement 同 project（Plan 3 引入）。
type Requirement struct {
	ID          uuid.UUID
	ProjectID   uuid.UUID
	WorkspaceID *uuid.UUID
	Number      int
	Title       string
	Description string
	Phase       Phase
	Status      Status
	OwnerID     uuid.UUID
	ClosedAt    *time.Time
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

// Issue 可挂载到某个 Requirement，也可游离（RequirementID == nil）。
// AssigneeID 为 nil 表示未指派。CreatedBy 永远存在（DELETE RESTRICT）。
// WorkspaceID 为 nil 表示未关联 workspace；非 nil 时 DB 通过复合外键
// (project_id, workspace_id) 拒绝跨 project 关联（Plan 3 引入）。
type Issue struct {
	ID            uuid.UUID
	ProjectID     uuid.UUID
	RequirementID *uuid.UUID
	WorkspaceID   *uuid.UUID
	Number        int
	Title         string
	Description   string
	Status        Status
	AssigneeID    *uuid.UUID
	CreatedBy     uuid.UUID
	ClosedAt      *time.Time
	CreatedAt     time.Time
	UpdatedAt     time.Time
	// ParentID 非 nil 表示本 issue 是某 issue 的子任务（任务分解）。父删除时
	// ON DELETE SET NULL 置空（orphan，不级联删子）。
	ParentID *uuid.UUID
	// Priority 0..3，越大越早被调度器选取。默认 0（normal）。
	Priority int
}

// Dependency 表示一条 issue 依赖边：BlockedID 被 BlockerID 阻塞（blocked-by）。
// BlockedID 仅在 BlockerID 关闭后才"就绪"。ProjectID 冗余存储用于作用域校验。
type Dependency struct {
	ID        uuid.UUID
	ProjectID uuid.UUID
	BlockedID uuid.UUID
	BlockerID uuid.UUID
	CreatedBy uuid.UUID
	CreatedAt time.Time
}

// Artifact 是某条 Requirement 在某阶段的一个产物文档版本快照。Version 在
// (RequirementID, Phase) 内自增（max+1），CreatedBy 永远存在（DELETE RESTRICT）。
// SourceMessageID 非 nil 时指向该产物派生自的 chat assistant 消息（仅追溯用，
// schema 层 FK ON DELETE SET NULL，代码层不依赖 chat 模块）。
type Artifact struct {
	ID              uuid.UUID
	RequirementID   uuid.UUID
	Phase           Phase
	Version         int
	Content         string
	Note            string
	SourceMessageID *int64
	CreatedBy       uuid.UUID
	CreatedAt       time.Time
	// Verdict 是该版本的评审结论（none/pass/fail）。新建产物默认 none。
	Verdict Verdict
}

// PhasePointer 是某条 Requirement 在某阶段的"已批准/当前"产物指针。仅在首次
// Approve(verdict=pass) 后才存在一行；ApprovedArtifactID 在被指向的产物删除时
// 由 DB（ON DELETE SET NULL）置空。Done 等阶段网关据此判定前置产物是否就绪。
type PhasePointer struct {
	RequirementID      uuid.UUID
	Phase              Phase
	ApprovedArtifactID *uuid.UUID
	ApprovedBy         *uuid.UUID
	ApprovedAt         *time.Time
}

// Caller 表示发起请求的用户上下文。Service 层据此判定鉴权。
type Caller struct {
	UserID  uuid.UUID
	IsAdmin bool
}

// Role 是 project_members.role 的角色枚举（与 0026 迁移的 CHECK 约束一致）。
// 语义（在 owner+global-admin+internal-visibility 既有规则之上"叠加"授权，
// 只增不减）：
//
//	admin   读 + 写 + 成员管理（owner 经迁移回填为本角色）
//	member  读 + 写（需求/issue 等）
//	viewer  只读
//
// RoleNone 是非成员的零值，仅在代码内部表示"无成员关系"，不写入 DB。
type Role string

// 角色枚举值。注意 DB CHECK 仅允许 viewer/member/admin；RoleNone 不入库。
const (
	RoleNone   Role = ""
	RoleViewer Role = "viewer"
	RoleMember Role = "member"
	RoleAdmin  Role = "admin"
)

// IsValid 报告 r 是否为可写入 DB 的合法角色（不含 RoleNone）。
func (r Role) IsValid() bool {
	return r == RoleViewer || r == RoleMember || r == RoleAdmin
}

// CanWrite 报告该角色是否授予写权限（member/admin）。
func (r Role) CanWrite() bool { return r == RoleMember || r == RoleAdmin }

// CanRead 报告该角色是否授予读权限（任何已建立的成员关系都可读）。
func (r Role) CanRead() bool { return r == RoleViewer || r == RoleMember || r == RoleAdmin }

// CanManageMembers 报告该角色是否可管理项目成员（仅 admin）。
func (r Role) CanManageMembers() bool { return r == RoleAdmin }

// Member 是一条 project_members 关系：某用户在某 project 的角色。
type Member struct {
	ProjectID uuid.UUID
	UserID    uuid.UUID
	Role      Role
	AddedBy   *uuid.UUID
	CreatedAt time.Time
}

// ProjectService 暴露 Project 聚合根的应用服务方法。包内有三个并列服务
// （Project/Requirement/Issue），这里保留聚合名前缀以避免歧义。
//
//nolint:revive // 名字带聚合根前缀比 "Service" 更准确：包里有 3 个并列服务接口
type ProjectService interface {
	Create(ctx context.Context, c Caller, in CreateProjectInput) (*Project, error)
	Get(ctx context.Context, c Caller, slug string) (*Project, error)
	GetByID(ctx context.Context, c Caller, id uuid.UUID) (*Project, error)
	List(ctx context.Context, c Caller, includeArchived bool) ([]*Project, error)
	Update(ctx context.Context, c Caller, slug string, in UpdateProjectInput) (*Project, error)
	Archive(ctx context.Context, c Caller, slug string) error
	Unarchive(ctx context.Context, c Caller, slug string) error

	// ===== 成员/角色 ACL（autonomy roadmap §11）=====

	// ListMembers 返回 slug 项目的全部成员。要求 caller 对项目可读。
	ListMembers(ctx context.Context, c Caller, slug string) ([]*Member, error)
	// AddMember 新增或更新一名成员的角色。仅 project-admin 或 global-admin 可调用。
	AddMember(ctx context.Context, c Caller, slug string, in AddMemberInput) (*Member, error)
	// RemoveMember 移除一名成员。仅 project-admin 或 global-admin 可调用。
	RemoveMember(ctx context.Context, c Caller, slug string, userID uuid.UUID) error
	// MemberRole 返回 userID 在 projectID 的成员角色（非成员返回 RoleNone）。
	// 供 app 层 projectACL 桥接成员感知鉴权；不做可见性校验（调用方已校验）。
	MemberRole(ctx context.Context, projectID, userID uuid.UUID) (Role, error)
}

// RequirementService 暴露 Requirement 聚合根的应用服务方法。
type RequirementService interface {
	Create(ctx context.Context, c Caller, projectSlug string, in CreateRequirementInput) (*Requirement, error)
	Get(ctx context.Context, c Caller, projectSlug string, number int) (*Requirement, error)
	List(ctx context.Context, c Caller, projectSlug string, filter RequirementFilter) ([]*Requirement, error)
	Update(ctx context.Context, c Caller, projectSlug string, number int, in UpdateRequirementInput) (*Requirement, error)
	ChangePhase(ctx context.Context, c Caller, projectSlug string, number int, to Phase) (*Requirement, error)
	Close(ctx context.Context, c Caller, projectSlug string, number int) error
	Reopen(ctx context.Context, c Caller, projectSlug string, number int) error
}

// IssueService 暴露 Issue 聚合根的应用服务方法。
type IssueService interface {
	Create(ctx context.Context, c Caller, projectSlug string, in CreateIssueInput) (*Issue, error)
	Get(ctx context.Context, c Caller, projectSlug string, number int) (*Issue, error)
	List(ctx context.Context, c Caller, projectSlug string, filter IssueFilter) ([]*Issue, error)
	Update(ctx context.Context, c Caller, projectSlug string, number int, in UpdateIssueInput) (*Issue, error)
	Assign(ctx context.Context, c Caller, projectSlug string, number int, assignee *uuid.UUID) (*Issue, error)
	Close(ctx context.Context, c Caller, projectSlug string, number int) error
	Reopen(ctx context.Context, c Caller, projectSlug string, number int) error

	// ===== 任务分解 / 依赖图（autonomy roadmap §10）=====

	// CreateSubtask 在 parentNumber 指向的 issue 下创建一个子任务（设置 parent_id +
	// priority），继承 project。父 issue 须与目标 project 同属。
	CreateSubtask(ctx context.Context, c Caller, projectSlug string, parentNumber int, in CreateIssueInput) (*Issue, error)
	// AddDependency 新增一条依赖边：blocked 被 blocker 阻塞。拒绝自依赖、跨 project、
	// 重复、以及会形成环的边（沿 blocked-by 边做可达性检查）。
	AddDependency(ctx context.Context, c Caller, projectSlug string, in AddDependencyInput) (*Dependency, error)
	// RemoveDependency 删除一条依赖边（plan 修正用）。边不存在返回 NotFound。
	RemoveDependency(ctx context.Context, c Caller, projectSlug string, blockedNumber, blockerNumber int) error
	// ListDependencies 返回 number 指向 issue 的 (被谁阻塞 blockedBy, 阻塞了谁 blocks)。
	ListDependencies(ctx context.Context, c Caller, projectSlug string, number int) (blockedBy []*Issue, blocks []*Issue, err error)
}

// ArtifactService 暴露 Requirement 阶段产物版本的应用服务方法。
type ArtifactService interface {
	Create(ctx context.Context, c Caller, projectSlug string, reqNumber int, in CreateArtifactInput) (*Artifact, error)
	// List 的 phase 传零值（""）表示返回全部阶段产物（done 阶段汇总视图用）。
	List(ctx context.Context, c Caller, projectSlug string, reqNumber int, phase Phase) ([]*Artifact, error)
	GetByVersion(ctx context.Context, c Caller, projectSlug string, reqNumber int, phase Phase, version int) (*Artifact, error)
	// Approve 给指定 (phase, version) 产物打 verdict（pass/fail）。verdict==pass 时
	// 同时把该阶段指针指向此产物（阶段网关据此放行后续 phase）。verdict==fail 仅
	// 落 verdict、不动指针。已关闭 requirement 拒绝批准。
	Approve(ctx context.Context, c Caller, projectSlug string, reqNumber int, phase Phase, version int, verdict Verdict) (*Artifact, error)
}

// PhaseChangeEvent 是一次 requirement phase 流转的领域事件载荷。下游自动化
// （如 webhook、触发 ACP run）通过 PhaseEventEmitter 订阅。
type PhaseChangeEvent struct {
	ProjectID         uuid.UUID
	ProjectSlug       string
	RequirementID     uuid.UUID
	RequirementNumber int
	From              Phase
	To                Phase
	ActorID           uuid.UUID
	// OwnerID 是该需求的 owner——phase 变更通知的主要接收人（owner 关心进度，而非
	// 发起变更的 actor，后者可能是编排器/其它协作者）。
	OwnerID uuid.UUID
}

// PhaseEventEmitter 把 phase 流转事件投递到通知/自动化总线。project 是领域模块，
// 不直接 import infra/notify；由 app 层用 notify.Dispatcher 实现该窄接口。
// 实现必须是 fire-and-forget 语义：投递失败不得阻塞 phase 流转。
type PhaseEventEmitter interface {
	EmitPhaseChange(ctx context.Context, e PhaseChangeEvent)
}

// WorkspaceStateLookup 是 Done 网关查询 workspace PR/合并状态的窄接口。由 app
// 层用 changerequest/workspace 服务实现，避免 project 直接依赖那些模块。
type WorkspaceStateLookup interface {
	// PullRequestMerged 报告该 requirement 是否已有合入主干的 PR（merged）。按
	// requirementID 过滤而非整个 workspace，避免同一 workspace 下其它需求的已合 PR 误判。
	PullRequestMerged(ctx context.Context, workspaceID, requirementID uuid.UUID) (bool, error)
}

// GateRunner 在 ChangePhase 写库前运行目标 phase 的所有 entry 网关，全部通过才
// 返回 nil；任一失败返回 CodePhaseGateFailed。实现见 phase_gate.go。
type GateRunner interface {
	Check(ctx context.Context, r *Requirement, from, to Phase) error
}

// ===== 输入 DTO =====
//
// 约定（适用于全部 Update*Input）：指针字段为 nil 表示"未提供，保持原值"；
// 非 nil 表示"用解引用值替换"。Filter 类型使用零值（""、nil）表示"不过滤"。

// CreateProjectInput 携带创建 Project 必需的字段。Visibility 留空时由 Service 默认 private。
type CreateProjectInput struct {
	Slug        string
	Name        string
	Description string
	Visibility  Visibility
}

// UpdateProjectInput 是 Project 的 patch 输入：每个非 nil 字段表示要替换的值；
// nil 字段保持现有值不变。Slug / Owner / ArchivedAt 不在此处修改。
type UpdateProjectInput struct {
	Name        *string
	Description *string
	Visibility  *Visibility
}

// AddMemberInput 携带新增/更新一名 project 成员所需字段。Role 必须是合法角色
// （viewer/member/admin）。
type AddMemberInput struct {
	UserID uuid.UUID
	Role   Role
}

// CreateRequirementInput 创建一条需求。OwnerID 为 nil 时由 Service 默认为调用者。
// WorkspaceID 为 nil 表示不关联任何 workspace；非 nil 时直接落库（FK 由 PG 校验）。
type CreateRequirementInput struct {
	Title       string
	Description string
	OwnerID     *uuid.UUID
	WorkspaceID *uuid.UUID
}

// UpdateRequirementInput 同 UpdateProjectInput 的 patch 语义。Phase / Status 不在此处修改，
// 走专用方法 ChangePhase / Close / Reopen 以便 audit 区分。
//
// WorkspaceID 走"uuid.Nil sentinel"三态语义（与 RequirementNumber 的 **int 不同）：
//
//	nil           —— 未提供（保持原 workspace_id 不变）
//	&uuid.Nil     —— 已提供，要求"解绑"（workspace_id 置 NULL）
//	&id (非 Nil)  —— 已提供，要求关联到指定 workspace
type UpdateRequirementInput struct {
	Title       *string
	Description *string
	OwnerID     *uuid.UUID
	WorkspaceID *uuid.UUID
}

// RequirementFilter 控制 List 时的过滤维度。Phase / Status 留零值（"" / Status("")）表示
// 不限制；非零值必须落在对应枚举集合内（IsValid 校验在 Service 层做）。
type RequirementFilter struct {
	Phase  Phase
	Status Status
}

// CreateIssueInput 创建 Issue。RequirementNumber == nil 表示游离 Issue（不挂载到任何
// Requirement）；非 nil 时 Service 会校验 1) 该 Requirement 与目标 Project 同属，
// 2) 未关闭。
// WorkspaceID 为 nil 表示不关联 workspace；非 nil 时 DB 复合 FK 保证同 project。
type CreateIssueInput struct {
	Title             string
	Description       string
	RequirementNumber *int
	AssigneeID        *uuid.UUID
	WorkspaceID       *uuid.UUID
	// ParentNumber 非 nil 表示创建为该编号 issue 的子任务（任务分解）。Service 校验
	// 父 issue 与目标 project 同属。
	ParentNumber *int
	// Priority 非 nil 时设置优先级（0..3）；nil 时默认 0。
	Priority *int
}

// AddDependencyInput 携带新增依赖边的两端 issue 编号。语义：BlockedNumber 被
// BlockerNumber 阻塞。两端须在同一 project（由 Service 在同 project 上下文解析保证）。
type AddDependencyInput struct {
	BlockedNumber int
	BlockerNumber int
}

// CreateArtifactInput 创建一个阶段产物版本。Version 由 Service 自动计算
// （(requirement, phase) 内 max+1），不在此处提供。Phase 必须是 ArtifactPhases
// 之一；Note 可为空；SourceMessageID 仅在产物派生自 chat 消息时提供。
type CreateArtifactInput struct {
	Phase           Phase
	Content         string
	Note            string
	SourceMessageID *int64
}

// UpdateIssueInput 是 Issue 的 patch 输入。RequirementNumber 为指针的指针，承载三种状态：
//
//	nil       —— 未提供（不修改 issue.requirement_id）
//	*x == nil —— 已提供，但要求"取消挂载"（issue.requirement_id 置 NULL）
//	*x != nil —— 已提供，要求"挂载到本项目下 number=**x 的 Requirement"
//
// HTTP 层用 json.RawMessage 区分 "字段缺省 vs 显式 null vs 数字"，再翻译为本三态。
//
// WorkspaceID 走"uuid.Nil sentinel"三态语义（不再嵌一层指针）：
//
//	nil           —— 未提供（保持原 workspace_id 不变）
//	&uuid.Nil     —— 已提供，要求"解绑"（workspace_id 置 NULL）
//	&id (非 Nil)  —— 已提供，要求关联到指定 workspace（DB FK 校验同 project）
type UpdateIssueInput struct {
	Title             *string
	Description       *string
	RequirementNumber **int
	WorkspaceID       *uuid.UUID
}

// IssueFilter 控制 List 时的过滤。Requirement 字段三态（与 sqlc requirement_filter
// narg 对应）：
//
//	nil               —— 不按 requirement 过滤
//	*Requirement == 0 —— 仅游离（requirement_id IS NULL）—— 0 是 sentinel，因为
//	                     Issue.Number 自增从 1 起
//	*Requirement >  0 —— 仅匹配 number == *Requirement 的 Requirement
//
// AssigneeID nil 表示不按 assignee 过滤；Status 留零值表示不按状态过滤。
type IssueFilter struct {
	Status      Status
	Requirement *int
	AssigneeID  *uuid.UUID
}
