package store

import (
	"errors"
	"fmt"
	"sort"
	"sync"
	"time"

	"github.com/google/uuid"
)

type Workspace struct {
	ID        string    `json:"id"`
	Slug      string    `json:"slug"`
	Name      string    `json:"name"`
	Role      string    `json:"role"`
	CreatedAt time.Time `json:"createdAt"`
}

type Member struct {
	ID            string `json:"id"`
	WorkspaceSlug string `json:"workspaceSlug"`
	Name          string `json:"name"`
	Email         string `json:"email"`
	Role          string `json:"role"`
	Status        string `json:"status"`
}

type Invite struct {
	ID            string    `json:"id"`
	WorkspaceSlug string    `json:"workspaceSlug"`
	Email         string    `json:"email"`
	Role          string    `json:"role"`
	Token         string    `json:"token"`
	CreatedAt     time.Time `json:"createdAt"`
	Status        string    `json:"status"`
}

type ActivityEntry struct {
	ID            string    `json:"id"`
	WorkspaceSlug string    `json:"workspaceSlug"`
	Title         string    `json:"title"`
	Detail        string    `json:"detail"`
	CreatedAt     time.Time `json:"createdAt"`
}

type BoardGroup struct {
	ID            string `json:"id"`
	WorkspaceSlug string `json:"workspaceSlug"`
	Name          string `json:"name"`
	Color         string `json:"color"`
	Order         int    `json:"order"`
}

type Label struct {
	ID            string `json:"id"`
	WorkspaceSlug string `json:"workspaceSlug"`
	Name          string `json:"name"`
	Color         string `json:"color"`
}

type Attachment struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	MimeType string `json:"mimeType"`
	Size     int    `json:"size"`
	DataURL  string `json:"dataUrl,omitempty"`
}

type TaskComment struct {
	ID        string    `json:"id"`
	TaskID    string    `json:"taskId"`
	Author    string    `json:"author"`
	Content   string    `json:"content"`
	CreatedAt time.Time `json:"createdAt"`
}

type Task struct {
	ID             string        `json:"id"`
	WorkspaceSlug  string        `json:"workspaceSlug"`
	BoardGroupID   string        `json:"boardGroupId"`
	Title          string        `json:"title"`
	Description    string        `json:"description,omitempty"`
	Status         string        `json:"status"`
	Color          string        `json:"color"`
	DueAt          *time.Time    `json:"dueAt,omitempty"`
	ScheduledStart *time.Time    `json:"scheduledStart,omitempty"`
	ScheduledEnd   *time.Time    `json:"scheduledEnd,omitempty"`
	AssigneeID     *string       `json:"assigneeId,omitempty"`
	LabelIDs       []string      `json:"labelIds"`
	Attachments    []Attachment  `json:"attachments"`
	Comments       []TaskComment `json:"comments"`
	RecurrenceRule string        `json:"recurrenceRule,omitempty"`
	RecurrenceEnd  *time.Time    `json:"recurrenceEnd,omitempty"`
	Archived       bool          `json:"archived"`
	CreatedAt      time.Time     `json:"createdAt"`
	UpdatedAt      time.Time     `json:"updatedAt"`
}

type CalendarEvent struct {
	ID            string       `json:"id"`
	WorkspaceSlug string       `json:"workspaceSlug"`
	Title         string       `json:"title"`
	Description   string       `json:"description,omitempty"`
	StartsAt      time.Time    `json:"startsAt"`
	EndsAt        time.Time    `json:"endsAt"`
	Color         string       `json:"color"`
	LinkedTaskID  *string      `json:"linkedTaskId,omitempty"`
	Attachments   []Attachment `json:"attachments"`
}

type Note struct {
	ID            string    `json:"id"`
	WorkspaceSlug string    `json:"workspaceSlug"`
	Title         string    `json:"title"`
	Content       string    `json:"content"`
	UpdatedAt     time.Time `json:"updatedAt"`
}

type FocusSession struct {
	ID                 string     `json:"id"`
	WorkspaceSlug      string     `json:"workspaceSlug"`
	TaskID             *string    `json:"taskId,omitempty"`
	Mode               string     `json:"mode"`
	StartedAt          time.Time  `json:"startedAt"`
	CompletedAt        *time.Time `json:"completedAt,omitempty"`
	PausedAt           *time.Time `json:"pausedAt,omitempty"`
	PausedTotalSeconds int        `json:"pausedTotalSeconds"`
	DurationSeconds    int        `json:"durationSeconds"`
}

type CreateBoardGroupInput struct {
	WorkspaceSlug string `json:"workspaceSlug" binding:"required"`
	Name          string `json:"name" binding:"required"`
	Color         string `json:"color"`
}

type UpdateBoardGroupInput struct {
	Name  *string `json:"name"`
	Color *string `json:"color"`
	Order *int    `json:"order"`
}

type CreateInviteInput struct {
	WorkspaceSlug string `json:"workspaceSlug" binding:"required"`
	Email         string `json:"email" binding:"required"`
	Role          string `json:"role" binding:"required"`
}

type AcceptInviteInput struct {
	Name  string `json:"name" binding:"required"`
	Email string `json:"email" binding:"required"`
}

type UpdateMemberInput struct {
	Role   *string `json:"role"`
	Status *string `json:"status"`
}

type CreateLabelInput struct {
	WorkspaceSlug string `json:"workspaceSlug" binding:"required"`
	Name          string `json:"name" binding:"required"`
	Color         string `json:"color" binding:"required"`
}

type CreateTaskInput struct {
	WorkspaceSlug string     `json:"workspaceSlug" binding:"required"`
	BoardGroupID  string     `json:"boardGroupId" binding:"required"`
	Title         string     `json:"title" binding:"required"`
	Description   string     `json:"description"`
	DueAt         *time.Time `json:"dueAt"`
	Color         string     `json:"color"`
}

type UpdateTaskInput struct {
	Title          *string       `json:"title"`
	Description    *string       `json:"description"`
	Status         *string       `json:"status"`
	BoardGroupID   *string       `json:"boardGroupId"`
	Color          *string       `json:"color"`
	DueAt          *time.Time    `json:"dueAt"`
	ScheduledStart *time.Time    `json:"scheduledStart"`
	ScheduledEnd   *time.Time    `json:"scheduledEnd"`
	AssigneeID     *string       `json:"assigneeId"`
	LabelIDs       []string      `json:"labelIds"`
	Attachments    []Attachment  `json:"attachments"`
	Comments       []TaskComment `json:"comments"`
	RecurrenceRule *string       `json:"recurrenceRule"`
	RecurrenceEnd  *time.Time    `json:"recurrenceEnd"`
	Archived       *bool         `json:"archived"`
}

type CreateEventInput struct {
	WorkspaceSlug string       `json:"workspaceSlug" binding:"required"`
	Title         string       `json:"title" binding:"required"`
	Description   string       `json:"description"`
	StartsAt      string       `json:"startsAt" binding:"required"`
	EndsAt        string       `json:"endsAt" binding:"required"`
	LinkedTaskID  *string      `json:"linkedTaskId"`
	Color         string       `json:"color"`
	Attachments   []Attachment `json:"attachments"`
}

type UpdateEventInput struct {
	Title        *string      `json:"title"`
	Description  *string      `json:"description"`
	StartsAt     *time.Time   `json:"startsAt"`
	EndsAt       *time.Time   `json:"endsAt"`
	LinkedTaskID *string      `json:"linkedTaskId"`
	Color        *string      `json:"color"`
	Attachments  []Attachment `json:"attachments"`
}

type CreateNoteInput struct {
	WorkspaceSlug string `json:"workspaceSlug" binding:"required"`
	Title         string `json:"title" binding:"required"`
	Content       string `json:"content" binding:"required"`
}

type UpdateNoteInput struct {
	Title   *string `json:"title"`
	Content *string `json:"content"`
}

type CreateFocusSessionInput struct {
	WorkspaceSlug   string  `json:"workspaceSlug" binding:"required"`
	TaskID          *string `json:"taskId"`
	Mode            string  `json:"mode" binding:"required"`
	DurationSeconds int     `json:"durationSeconds" binding:"required"`
}

type UpdateFocusSessionInput struct {
	CompletedAt        *time.Time `json:"completedAt"`
	PausedAt           *time.Time `json:"pausedAt"`
	PausedTotalSeconds *int       `json:"pausedTotalSeconds"`
}

type State struct {
	mu            sync.RWMutex
	Mode          string
	Workspaces    []Workspace
	Members       []Member
	Invites       []Invite
	Activities    []ActivityEntry
	BoardGroups   []BoardGroup
	Labels        []Label
	Tasks         []Task
	Events        []CalendarEvent
	Notes         []Note
	FocusSessions []FocusSession
	Mailboxes     []Mailbox
	MailboxAuth   map[string]MailboxConnection
	MailMessages  []MailMessage
	OutgoingMails []OutgoingMail
}

func NewSeededState(mode string) *State {
	now := time.Now().UTC()
	workspace := Workspace{
		ID:        uuid.NewString(),
		Slug:      "personal",
		Name:      "Personal HQ",
		Role:      "owner",
		CreatedAt: now.Add(-72 * time.Hour),
	}

	boardGroups := []BoardGroup{
		{ID: "group-inbox", WorkspaceSlug: workspace.Slug, Name: "Inbox", Color: "slate", Order: 0},
		{ID: "group-doing", WorkspaceSlug: workspace.Slug, Name: "Doing", Color: "rose", Order: 1},
		{ID: "group-review", WorkspaceSlug: workspace.Slug, Name: "Review", Color: "amber", Order: 2},
		{ID: "group-done", WorkspaceSlug: workspace.Slug, Name: "Done", Color: "emerald", Order: 3},
	}

	labels := []Label{
		{ID: "label-launch", WorkspaceSlug: workspace.Slug, Name: "Launch", Color: "rose"},
		{ID: "label-ui", WorkspaceSlug: workspace.Slug, Name: "UI", Color: "amber"},
		{ID: "label-deep", WorkspaceSlug: workspace.Slug, Name: "Deep Work", Color: "emerald"},
		{ID: "label-admin", WorkspaceSlug: workspace.Slug, Name: "Admin", Color: "blue"},
	}

	taskDue := now.Add(10 * time.Hour)
	taskStart := time.Date(now.Year(), now.Month(), now.Day(), 9, 30, 0, 0, time.UTC)
	taskEnd := taskStart.Add(90 * time.Minute)
	linkedTaskID := uuid.NewString()
	completed := now.Add(-2 * time.Hour)
	assigneeID := "member-1"
	members := []Member{
		{ID: "member-1", WorkspaceSlug: workspace.Slug, Name: "Taylor", Email: "taylor@productier.app", Role: "owner", Status: "active"},
		{ID: "member-2", WorkspaceSlug: workspace.Slug, Name: "Alex", Email: "alex@productier.app", Role: "member", Status: "active"},
	}
	invites := []Invite{
		{
			ID:            "invite-1",
			WorkspaceSlug: workspace.Slug,
			Email:         "jamie@productier.app",
			Role:          "member",
			Token:         "invite-jamie",
			CreatedAt:     now.Add(-12 * time.Hour),
			Status:        "pending",
		},
	}
	activities := []ActivityEntry{
		{
			ID:            "activity-1",
			WorkspaceSlug: workspace.Slug,
			Title:         "Board updated",
			Detail:        "Task moved into Review.",
			CreatedAt:     now.Add(-4 * time.Hour),
		},
		{
			ID:            "activity-2",
			WorkspaceSlug: workspace.Slug,
			Title:         "Invite created",
			Detail:        "Jamie was invited to Personal HQ.",
			CreatedAt:     now.Add(-10 * time.Hour),
		},
	}

	return &State{
		Mode:        mode,
		Workspaces:  []Workspace{workspace},
		Members:     members,
		Invites:     invites,
		Activities:  activities,
		BoardGroups: boardGroups,
		Labels:      labels,
		Tasks: []Task{
			{
				ID:             linkedTaskID,
				WorkspaceSlug:  workspace.Slug,
				BoardGroupID:   "group-doing",
				Title:          "Finalize Productier foundation",
				Description:    "Shape the workspace shell, polish the board, and keep the Koffan calmness without copying the layout.",
				Status:         "in_progress",
				Color:          "rose",
				DueAt:          &taskDue,
				ScheduledStart: &taskStart,
				ScheduledEnd:   &taskEnd,
				AssigneeID:     &assigneeID,
				LabelIDs:       []string{"label-launch", "label-ui"},
				Attachments:    []Attachment{},
				Comments: []TaskComment{
					{
						ID:        uuid.NewString(),
						TaskID:    linkedTaskID,
						Author:    "Alex",
						Content:   "Keep the calendar dense, but not cramped.",
						CreatedAt: now.Add(-5 * time.Hour),
					},
				},
				CreatedAt: now.Add(-24 * time.Hour),
				UpdatedAt: now.Add(-2 * time.Hour),
			},
		},
		Events: []CalendarEvent{
			{
				ID:            uuid.NewString(),
				WorkspaceSlug: workspace.Slug,
				Title:         "Design review",
				Description:   "Refine the calendar density and modal patterns.",
				StartsAt:      time.Date(now.Year(), now.Month(), now.Day(), 13, 0, 0, 0, time.UTC),
				EndsAt:        time.Date(now.Year(), now.Month(), now.Day(), 14, 0, 0, 0, time.UTC),
				Color:         "amber",
				LinkedTaskID:  &linkedTaskID,
				Attachments:   []Attachment{},
			},
		},
		Notes: []Note{
			{
				ID:            uuid.NewString(),
				WorkspaceSlug: workspace.Slug,
				Title:         "Calm UI direction",
				Content:       "Use soft stone surfaces, controlled color, and denser layouts than Koffan. Keep interactions obvious and low-friction.",
				UpdatedAt:     now.Add(-90 * time.Minute),
			},
		},
		FocusSessions: []FocusSession{
			{
				ID:                 uuid.NewString(),
				WorkspaceSlug:      workspace.Slug,
				TaskID:             &linkedTaskID,
				Mode:               "focus",
				StartedAt:          now.Add(-55 * time.Minute),
				CompletedAt:        &completed,
				PausedTotalSeconds: 0,
				DurationSeconds:    1500,
			},
		},
		Mailboxes:     []Mailbox{},
		MailboxAuth:   map[string]MailboxConnection{},
		MailMessages:  []MailMessage{},
		OutgoingMails: []OutgoingMail{},
	}
}

func (s *State) ListWorkspaces() []Workspace {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return append([]Workspace(nil), s.Workspaces...)
}

func (s *State) ListMembers(workspaceSlug string) []Member {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return filterByWorkspace(s.Members, workspaceSlug)
}

func (s *State) GetMemberByID(memberID string) (Member, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	for _, member := range s.Members {
		if member.ID == memberID {
			return member, nil
		}
	}
	return Member{}, errors.New("member not found")
}

func (s *State) UpdateMember(memberID string, input UpdateMemberInput) (Member, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	for index, member := range s.Members {
		if member.ID != memberID {
			continue
		}

		nextRole := member.Role
		if input.Role != nil {
			if !isValidWorkspaceRole(*input.Role) {
				return Member{}, errors.New("invalid member role")
			}
			nextRole = *input.Role
		}

		nextStatus := member.Status
		if input.Status != nil {
			if !isValidMemberStatus(*input.Status) {
				return Member{}, errors.New("invalid member status")
			}
			nextStatus = *input.Status
		}

		if member.Role == "owner" && member.Status == "active" && (nextRole != "owner" || nextStatus != "active") {
			activeOwnerCount := 0
			for _, candidate := range s.Members {
				if candidate.WorkspaceSlug == member.WorkspaceSlug && candidate.Role == "owner" && candidate.Status == "active" {
					activeOwnerCount++
				}
			}
			if activeOwnerCount <= 1 {
				return Member{}, errors.New("workspace must have at least one active owner")
			}
		}

		member.Role = nextRole
		member.Status = nextStatus
		s.Members[index] = member
		s.appendActivityLocked(member.WorkspaceSlug, "Member updated", fmt.Sprintf("%s membership settings changed.", member.Email))
		return member, nil
	}
	return Member{}, errors.New("member not found")
}

func (s *State) ListInvites(workspaceSlug string) []Invite {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return filterByWorkspace(s.Invites, workspaceSlug)
}

func (s *State) GetInviteByID(inviteID string) (Invite, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	for _, invite := range s.Invites {
		if invite.ID == inviteID {
			return invite, nil
		}
	}
	return Invite{}, errors.New("invite not found")
}

func (s *State) GetInviteByToken(token string) (Invite, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	for _, invite := range s.Invites {
		if invite.Token == token {
			return invite, nil
		}
	}

	return Invite{}, errors.New("invite not found")
}

func (s *State) CreateInvite(input CreateInviteInput) Invite {
	s.mu.Lock()
	defer s.mu.Unlock()

	invite := Invite{
		ID:            uuid.NewString(),
		WorkspaceSlug: input.WorkspaceSlug,
		Email:         input.Email,
		Role:          defaultString(input.Role, "member"),
		Token:         "join-" + uuid.NewString(),
		CreatedAt:     time.Now().UTC(),
		Status:        "pending",
	}
	s.Invites = append([]Invite{invite}, s.Invites...)
	s.appendActivityLocked(input.WorkspaceSlug, "Invite created", fmt.Sprintf("Shared workspace access with %s.", input.Email))
	return invite
}

func (s *State) RevokeInvite(inviteID string) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	for index, invite := range s.Invites {
		if invite.ID != inviteID {
			continue
		}
		if invite.Status != "pending" {
			return errors.New("only pending invites can be revoked")
		}
		s.Invites = append(s.Invites[:index], s.Invites[index+1:]...)
		s.appendActivityLocked(invite.WorkspaceSlug, "Invite revoked", fmt.Sprintf("Invite for %s was revoked.", invite.Email))
		return nil
	}

	return errors.New("invite not found")
}

func (s *State) AcceptInvite(token string, input AcceptInviteInput) (Invite, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	for index, invite := range s.Invites {
		if invite.Token != token {
			continue
		}
		invite.Status = "accepted"
		s.Invites[index] = invite

		memberExists := false
		for _, member := range s.Members {
			if member.WorkspaceSlug == invite.WorkspaceSlug && member.Email == input.Email {
				memberExists = true
				break
			}
		}
		if !memberExists {
			s.Members = append([]Member{{
				ID:            uuid.NewString(),
				WorkspaceSlug: invite.WorkspaceSlug,
				Name:          input.Name,
				Email:         input.Email,
				Role:          invite.Role,
				Status:        "active",
			}}, s.Members...)
		}

		s.appendActivityLocked(invite.WorkspaceSlug, "Invite accepted", fmt.Sprintf("%s joined the workspace.", input.Email))
		return invite, nil
	}

	return Invite{}, errors.New("invite not found")
}

func (s *State) ListActivities(workspaceSlug string) []ActivityEntry {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return filterByWorkspace(s.Activities, workspaceSlug)
}

func (s *State) ListBoardGroups(workspaceSlug string) []BoardGroup {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return filterByWorkspace(s.BoardGroups, workspaceSlug)
}

func (s *State) GetBoardGroupByID(groupID string) (BoardGroup, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	for _, group := range s.BoardGroups {
		if group.ID == groupID {
			return group, nil
		}
	}

	return BoardGroup{}, errors.New("board group not found")
}

func (s *State) CreateBoardGroup(input CreateBoardGroupInput) BoardGroup {
	s.mu.Lock()
	defer s.mu.Unlock()

	order := 0
	for _, group := range s.BoardGroups {
		if group.WorkspaceSlug == input.WorkspaceSlug {
			order++
		}
	}

	group := BoardGroup{
		ID:            uuid.NewString(),
		WorkspaceSlug: input.WorkspaceSlug,
		Name:          input.Name,
		Color:         defaultString(input.Color, "slate"),
		Order:         order,
	}
	s.BoardGroups = append(s.BoardGroups, group)
	s.appendActivityLocked(input.WorkspaceSlug, "Board group added", fmt.Sprintf("%s is ready for new tasks.", input.Name))
	return group
}

func (s *State) rebalanceBoardGroupOrderLocked(workspaceSlug string) {
	groups := make([]BoardGroup, 0)
	for _, group := range s.BoardGroups {
		if group.WorkspaceSlug == workspaceSlug {
			groups = append(groups, group)
		}
	}

	sort.SliceStable(groups, func(i, j int) bool {
		if groups[i].Order == groups[j].Order {
			return groups[i].Name < groups[j].Name
		}
		return groups[i].Order < groups[j].Order
	})

	nextByID := make(map[string]BoardGroup, len(groups))
	for index, group := range groups {
		group.Order = index
		nextByID[group.ID] = group
	}

	for index, group := range s.BoardGroups {
		updated, ok := nextByID[group.ID]
		if !ok {
			continue
		}
		s.BoardGroups[index] = updated
	}
}

func (s *State) UpdateBoardGroup(groupID string, input UpdateBoardGroupInput) (BoardGroup, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	for index, group := range s.BoardGroups {
		if group.ID != groupID {
			continue
		}

		if input.Name != nil {
			group.Name = *input.Name
		}
		if input.Color != nil {
			group.Color = *input.Color
		}
		if input.Order != nil {
			nextOrder := *input.Order
			if nextOrder < 0 {
				nextOrder = 0
			}
			group.Order = nextOrder
		}

		s.BoardGroups[index] = group
		if input.Order != nil {
			s.rebalanceBoardGroupOrderLocked(group.WorkspaceSlug)
			for _, current := range s.BoardGroups {
				if current.ID == groupID {
					group = current
					break
				}
			}
		}

		s.appendActivityLocked(group.WorkspaceSlug, "Board group updated", fmt.Sprintf("%s settings changed.", group.Name))
		return group, nil
	}

	return BoardGroup{}, errors.New("board group not found")
}

func (s *State) ListLabels(workspaceSlug string) []Label {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return filterByWorkspace(s.Labels, workspaceSlug)
}

func (s *State) CreateLabel(input CreateLabelInput) Label {
	s.mu.Lock()
	defer s.mu.Unlock()

	label := Label{
		ID:            uuid.NewString(),
		WorkspaceSlug: input.WorkspaceSlug,
		Name:          input.Name,
		Color:         input.Color,
	}
	s.Labels = append(s.Labels, label)
	s.appendActivityLocked(input.WorkspaceSlug, "Label added", fmt.Sprintf("%s is now available across the workspace.", input.Name))
	return label
}

func (s *State) ListTasks(workspaceSlug string) []Task {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return filterByWorkspace(s.Tasks, workspaceSlug)
}

func (s *State) GetTaskByID(taskID string) (Task, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	for _, task := range s.Tasks {
		if task.ID == taskID {
			return task, nil
		}
	}

	return Task{}, errors.New("task not found")
}

func (s *State) CreateTask(input CreateTaskInput) Task {
	s.mu.Lock()
	defer s.mu.Unlock()

	now := time.Now().UTC()
	task := Task{
		ID:            uuid.NewString(),
		WorkspaceSlug: input.WorkspaceSlug,
		BoardGroupID:  input.BoardGroupID,
		Title:         input.Title,
		Description:   input.Description,
		Status:        "todo",
		Color:         defaultString(input.Color, "slate"),
		DueAt:         input.DueAt,
		LabelIDs:      []string{},
		Attachments:   []Attachment{},
		Comments:      []TaskComment{},
		CreatedAt:     now,
		UpdatedAt:     now,
	}
	s.Tasks = append(s.Tasks, task)
	s.appendActivityLocked(input.WorkspaceSlug, "Task created", input.Title)
	return task
}

func (s *State) UpdateTask(taskID string, input UpdateTaskInput) (Task, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	for index, task := range s.Tasks {
		if task.ID != taskID {
			continue
		}

		if input.Title != nil {
			task.Title = *input.Title
		}
		if input.Description != nil {
			task.Description = *input.Description
		}
		if input.Status != nil {
			task.Status = *input.Status
		}
		if input.BoardGroupID != nil {
			task.BoardGroupID = *input.BoardGroupID
		}
		if input.Color != nil {
			task.Color = *input.Color
		}
		if input.DueAt != nil {
			task.DueAt = input.DueAt
		}
		if input.ScheduledStart != nil {
			task.ScheduledStart = input.ScheduledStart
		}
		if input.ScheduledEnd != nil {
			task.ScheduledEnd = input.ScheduledEnd
		}
		if input.AssigneeID != nil {
			task.AssigneeID = input.AssigneeID
		}
		if input.LabelIDs != nil {
			task.LabelIDs = input.LabelIDs
		}
		if input.Attachments != nil {
			task.Attachments = input.Attachments
		}
		if input.Comments != nil {
			task.Comments = input.Comments
		}
		task.UpdatedAt = time.Now().UTC()
		s.Tasks[index] = task
		s.appendActivityLocked(task.WorkspaceSlug, "Task updated", task.Title)
		return task, nil
	}

	return Task{}, errors.New("task not found")
}

func (s *State) ListEvents(workspaceSlug string) []CalendarEvent {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return filterByWorkspace(s.Events, workspaceSlug)
}

func (s *State) GetEventByID(eventID string) (CalendarEvent, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	for _, event := range s.Events {
		if event.ID == eventID {
			return event, nil
		}
	}

	return CalendarEvent{}, errors.New("event not found")
}

func (s *State) CreateEvent(input CreateEventInput) CalendarEvent {
	s.mu.Lock()
	defer s.mu.Unlock()

	startsAt, _ := time.Parse(time.RFC3339, input.StartsAt)
	endsAt, _ := time.Parse(time.RFC3339, input.EndsAt)

	event := CalendarEvent{
		ID:            uuid.NewString(),
		WorkspaceSlug: input.WorkspaceSlug,
		Title:         input.Title,
		Description:   input.Description,
		StartsAt:      startsAt,
		EndsAt:        endsAt,
		Color:         defaultString(input.Color, "blue"),
		LinkedTaskID:  input.LinkedTaskID,
		Attachments:   input.Attachments,
	}

	s.Events = append(s.Events, event)
	s.appendActivityLocked(input.WorkspaceSlug, "Event created", input.Title)
	return event
}

func (s *State) UpdateEvent(eventID string, input UpdateEventInput) (CalendarEvent, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	for index, event := range s.Events {
		if event.ID != eventID {
			continue
		}
		if input.Title != nil {
			event.Title = *input.Title
		}
		if input.Description != nil {
			event.Description = *input.Description
		}
		if input.StartsAt != nil {
			event.StartsAt = *input.StartsAt
		}
		if input.EndsAt != nil {
			event.EndsAt = *input.EndsAt
		}
		if input.LinkedTaskID != nil {
			event.LinkedTaskID = input.LinkedTaskID
		}
		if input.Color != nil {
			event.Color = *input.Color
		}
		if input.Attachments != nil {
			event.Attachments = input.Attachments
		}
		s.Events[index] = event
		s.appendActivityLocked(event.WorkspaceSlug, "Event updated", event.Title)
		return event, nil
	}

	return CalendarEvent{}, errors.New("event not found")
}

func (s *State) ListNotes(workspaceSlug string) []Note {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return filterByWorkspace(s.Notes, workspaceSlug)
}

func (s *State) GetNoteByID(noteID string) (Note, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	for _, note := range s.Notes {
		if note.ID == noteID {
			return note, nil
		}
	}

	return Note{}, errors.New("note not found")
}

func (s *State) CreateNote(input CreateNoteInput) Note {
	s.mu.Lock()
	defer s.mu.Unlock()

	note := Note{
		ID:            uuid.NewString(),
		WorkspaceSlug: input.WorkspaceSlug,
		Title:         input.Title,
		Content:       input.Content,
		UpdatedAt:     time.Now().UTC(),
	}
	s.Notes = append(s.Notes, note)
	s.appendActivityLocked(input.WorkspaceSlug, "Note created", input.Title)
	return note
}

func (s *State) UpdateNote(noteID string, input UpdateNoteInput) (Note, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	for index, note := range s.Notes {
		if note.ID != noteID {
			continue
		}
		if input.Title != nil {
			note.Title = *input.Title
		}
		if input.Content != nil {
			note.Content = *input.Content
		}
		note.UpdatedAt = time.Now().UTC()
		s.Notes[index] = note
		s.appendActivityLocked(note.WorkspaceSlug, "Note updated", note.Title)
		return note, nil
	}

	return Note{}, errors.New("note not found")
}

func (s *State) ListFocusSessions(workspaceSlug string) []FocusSession {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return filterByWorkspace(s.FocusSessions, workspaceSlug)
}

func (s *State) GetFocusSessionByID(sessionID string) (FocusSession, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	for _, session := range s.FocusSessions {
		if session.ID == sessionID {
			return session, nil
		}
	}

	return FocusSession{}, errors.New("focus session not found")
}

func (s *State) CreateFocusSession(input CreateFocusSessionInput) FocusSession {
	s.mu.Lock()
	defer s.mu.Unlock()

	session := FocusSession{
		ID:                 uuid.NewString(),
		WorkspaceSlug:      input.WorkspaceSlug,
		TaskID:             input.TaskID,
		Mode:               input.Mode,
		StartedAt:          time.Now().UTC(),
		PausedTotalSeconds: 0,
		DurationSeconds:    input.DurationSeconds,
	}
	s.FocusSessions = append(s.FocusSessions, session)
	s.appendActivityLocked(input.WorkspaceSlug, "Focus session started", input.Mode)
	return session
}

func (s *State) UpdateFocusSession(sessionID string, input UpdateFocusSessionInput) (FocusSession, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	for index, session := range s.FocusSessions {
		if session.ID != sessionID {
			continue
		}
		if input.CompletedAt != nil {
			session.CompletedAt = input.CompletedAt
		}
		if input.PausedAt != nil {
			session.PausedAt = input.PausedAt
		}
		if input.PausedTotalSeconds != nil {
			session.PausedTotalSeconds = *input.PausedTotalSeconds
		}
		s.FocusSessions[index] = session
		s.appendActivityLocked(session.WorkspaceSlug, "Focus session updated", session.Mode)
		return session, nil
	}

	return FocusSession{}, errors.New("focus session not found")
}

func filterByWorkspace[T interface{ GetWorkspaceSlug() string }](items []T, workspaceSlug string) []T {
	if workspaceSlug == "" {
		return append([]T(nil), items...)
	}

	filtered := make([]T, 0, len(items))
	for _, item := range items {
		if item.GetWorkspaceSlug() == workspaceSlug {
			filtered = append(filtered, item)
		}
	}
	return filtered
}

func defaultString(value string, fallback string) string {
	if value == "" {
		return fallback
	}
	return value
}

func isValidWorkspaceRole(role string) bool {
	switch role {
	case "owner", "admin", "member":
		return true
	default:
		return false
	}
}

func isValidMemberStatus(status string) bool {
	switch status {
	case "active", "removed":
		return true
	default:
		return false
	}
}

func (s *State) appendActivityLocked(workspaceSlug string, title string, detail string) {
	entry := ActivityEntry{
		ID:            uuid.NewString(),
		WorkspaceSlug: workspaceSlug,
		Title:         title,
		Detail:        detail,
		CreatedAt:     time.Now().UTC(),
	}
	s.Activities = append([]ActivityEntry{entry}, s.Activities...)
	if len(s.Activities) > 40 {
		s.Activities = s.Activities[:40]
	}
}

func (item Member) GetWorkspaceSlug() string        { return item.WorkspaceSlug }
func (item Invite) GetWorkspaceSlug() string        { return item.WorkspaceSlug }
func (item ActivityEntry) GetWorkspaceSlug() string { return item.WorkspaceSlug }
func (item BoardGroup) GetWorkspaceSlug() string    { return item.WorkspaceSlug }
func (item Label) GetWorkspaceSlug() string         { return item.WorkspaceSlug }
func (item Task) GetWorkspaceSlug() string          { return item.WorkspaceSlug }
func (item CalendarEvent) GetWorkspaceSlug() string { return item.WorkspaceSlug }
func (item Note) GetWorkspaceSlug() string          { return item.WorkspaceSlug }
func (item FocusSession) GetWorkspaceSlug() string  { return item.WorkspaceSlug }
