package sqlite

// export.go — v0.34.0 CS0
//
// ExportStore: batch-read queries for data export and GDPR operations.
// SQLite dialect — uses ? placeholders, st() for time scanning, ScanArray for JSON arrays.

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"time"

	"chat-switchboard/models"
)

// ExportStore implements store.ExportStore for SQLite.
type ExportStore struct{}

// NewExportStore creates a SQLite ExportStore.
func NewExportStore() *ExportStore { return &ExportStore{} }

// ── helpers ──────────────────────────────────

// sqlitePlaceholders builds "?,?,...,?" and a matching []any slice for IN queries.
func sqlitePlaceholders(ids []string) (string, []any) {
	ph := make([]string, len(ids))
	args := make([]any, len(ids))
	for i, id := range ids {
		ph[i] = "?"
		args[i] = id
	}
	return strings.Join(ph, ","), args
}

// ── User-scoped export ───────────────────────

func (s *ExportStore) UserChannels(ctx context.Context, userID string) ([]models.Channel, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, user_id, title, description, type, model, system_prompt,
		       provider_config_id, is_archived, is_pinned, folder_id,
		       team_id, project_id, workspace_id, settings, allow_anonymous,
		       created_at, updated_at
		FROM channels WHERE user_id = ?
		ORDER BY created_at ASC`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: user channels: %w", err)
	}
	defer rows.Close()
	var out []models.Channel
	for rows.Next() {
		var ch models.Channel
		if err := rows.Scan(&ch.ID, &ch.UserID, &ch.Title, &ch.Description,
			&ch.Type, &ch.Model, &ch.SystemPrompt, &ch.ProviderConfigID,
			&ch.IsArchived, &ch.IsPinned, &ch.FolderID, &ch.TeamID,
			&ch.ProjectID, &ch.WorkspaceID, &ch.Settings, &ch.AllowAnonymous,
			st(&ch.CreatedAt), st(&ch.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan channel: %w", err)
		}
		out = append(out, ch)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserMessages(ctx context.Context, channelIDs []string) ([]models.Message, error) {
	if len(channelIDs) == 0 {
		return nil, nil
	}
	ph, args := sqlitePlaceholders(channelIDs)
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT id, channel_id, role, content, model, tokens_used,
		       tool_calls, metadata, parent_id, sibling_index,
		       participant_type, participant_id, provider_config_id,
		       created_at, updated_at
		FROM messages
		WHERE channel_id IN (%s) AND deleted_at IS NULL
		ORDER BY created_at ASC`, ph), args...)
	if err != nil {
		return nil, fmt.Errorf("export: user messages: %w", err)
	}
	defer rows.Close()
	var out []models.Message
	for rows.Next() {
		var m models.Message
		if err := rows.Scan(&m.ID, &m.ChannelID, &m.Role, &m.Content,
			&m.Model, &m.TokensUsed, &m.ToolCalls, &m.Metadata,
			&m.ParentID, &m.SiblingIndex, &m.ParticipantType,
			&m.ParticipantID, &m.ProviderConfigID,
			st(&m.CreatedAt), st(&m.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan message: %w", err)
		}
		out = append(out, m)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserChannelParticipants(ctx context.Context, channelIDs []string) ([]models.ChannelParticipant, error) {
	if len(channelIDs) == 0 {
		return nil, nil
	}
	ph, args := sqlitePlaceholders(channelIDs)
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT id, channel_id, participant_type, participant_id, role,
		       display_name, avatar_url, joined_at, last_read_at
		FROM channel_participants WHERE channel_id IN (%s)`, ph), args...)
	if err != nil {
		return nil, fmt.Errorf("export: channel participants: %w", err)
	}
	defer rows.Close()
	var out []models.ChannelParticipant
	for rows.Next() {
		var cp models.ChannelParticipant
		if err := rows.Scan(&cp.ID, &cp.ChannelID, &cp.ParticipantType,
			&cp.ParticipantID, &cp.Role, &cp.DisplayName, &cp.AvatarURL,
			st(&cp.JoinedAt), st(&cp.LastReadAt)); err != nil {
			return nil, fmt.Errorf("export: scan participant: %w", err)
		}
		out = append(out, cp)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserChannelModels(ctx context.Context, channelIDs []string) ([]models.ChannelModel, error) {
	if len(channelIDs) == 0 {
		return nil, nil
	}
	ph, args := sqlitePlaceholders(channelIDs)
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT id, channel_id, model_id, provider_config_id, persona_id,
		       handle, display_name, system_prompt, is_default
		FROM channel_models WHERE channel_id IN (%s)`, ph), args...)
	if err != nil {
		return nil, fmt.Errorf("export: channel models: %w", err)
	}
	defer rows.Close()
	var out []models.ChannelModel
	for rows.Next() {
		var cm models.ChannelModel
		if err := rows.Scan(&cm.ID, &cm.ChannelID, &cm.ModelID,
			&cm.ProviderConfigID, &cm.PersonaID, &cm.Handle,
			&cm.DisplayName, &cm.SystemPrompt, &cm.IsDefault); err != nil {
			return nil, fmt.Errorf("export: scan channel model: %w", err)
		}
		out = append(out, cm)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserChannelCursors(ctx context.Context, userID string, channelIDs []string) ([]models.ChannelCursor, error) {
	if len(channelIDs) == 0 {
		return nil, nil
	}
	ph, args := sqlitePlaceholders(channelIDs)
	args = append(args, userID)
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT id, channel_id, user_id, active_leaf_id, updated_at
		FROM channel_cursors
		WHERE channel_id IN (%s) AND user_id = ?`, ph), args...)
	if err != nil {
		return nil, fmt.Errorf("export: channel cursors: %w", err)
	}
	defer rows.Close()
	var out []models.ChannelCursor
	for rows.Next() {
		var cc models.ChannelCursor
		if err := rows.Scan(&cc.ID, &cc.ChannelID, &cc.UserID,
			&cc.ActiveLeafID, st(&cc.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan cursor: %w", err)
		}
		out = append(out, cc)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserNotes(ctx context.Context, userID string) ([]models.Note, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, user_id, title, content, folder_path, tags,
		       metadata, source_channel_id, source_message_id, team_id,
		       created_at, updated_at
		FROM notes WHERE user_id = ?
		ORDER BY created_at ASC`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: user notes: %w", err)
	}
	defer rows.Close()
	var out []models.Note
	for rows.Next() {
		var n models.Note
		var tagsJSON, metadataJSON string
		var sourceChannelID, sourceMessageID, teamID sql.NullString
		if err := rows.Scan(&n.ID, &n.UserID, &n.Title, &n.Content,
			&n.FolderPath, &tagsJSON, &metadataJSON,
			&sourceChannelID, &sourceMessageID, &teamID,
			st(&n.CreatedAt), st(&n.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan note: %w", err)
		}
		n.Tags = ScanArray(tagsJSON)
		n.SourceChannelID = NullableStringPtr(sourceChannelID)
		n.SourceMessageID = NullableStringPtr(sourceMessageID)
		n.TeamID = NullableStringPtr(teamID)
		out = append(out, n)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserNoteLinks(ctx context.Context, noteIDs []string) ([]models.ExportNoteLink, error) {
	if len(noteIDs) == 0 {
		return nil, nil
	}
	ph, args := sqlitePlaceholders(noteIDs)
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT source_note_id, target_note_id, target_title,
		       COALESCE(display_text, ''), is_transclusion
		FROM note_links WHERE source_note_id IN (%s)`, ph), args...)
	if err != nil {
		return nil, fmt.Errorf("export: note links: %w", err)
	}
	defer rows.Close()
	var out []models.ExportNoteLink
	for rows.Next() {
		var nl models.ExportNoteLink
		if err := rows.Scan(&nl.SourceNoteID, &nl.TargetNoteID,
			&nl.TargetTitle, &nl.DisplayText, &nl.IsTransclusion); err != nil {
			return nil, fmt.Errorf("export: scan note link: %w", err)
		}
		out = append(out, nl)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserMemories(ctx context.Context, userID string) ([]models.Memory, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, scope, owner_id, user_id, key, value,
		       source_channel_id, confidence, status, created_at, updated_at
		FROM memories
		WHERE owner_id = ? AND scope = 'user' AND status = 'active'
		ORDER BY created_at ASC`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: user memories: %w", err)
	}
	defer rows.Close()
	var out []models.Memory
	for rows.Next() {
		var m models.Memory
		if err := rows.Scan(&m.ID, &m.Scope, &m.OwnerID, &m.UserID,
			&m.Key, &m.Value, &m.SourceChannelID, &m.Confidence,
			&m.Status, st(&m.CreatedAt), st(&m.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan memory: %w", err)
		}
		out = append(out, m)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserProjects(ctx context.Context, userID string) ([]models.Project, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, name, description, color, icon, scope, owner_id,
		       team_id, is_archived, workspace_id, settings, created_at, updated_at
		FROM projects WHERE owner_id = ? AND scope = 'personal'
		ORDER BY created_at ASC`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: user projects: %w", err)
	}
	defer rows.Close()
	var out []models.Project
	for rows.Next() {
		var p models.Project
		if err := rows.Scan(&p.ID, &p.Name, &p.Description, &p.Color,
			&p.Icon, &p.Scope, &p.OwnerID, &p.TeamID, &p.IsArchived,
			&p.WorkspaceID, &p.Settings, st(&p.CreatedAt), st(&p.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan project: %w", err)
		}
		out = append(out, p)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserProjectChannels(ctx context.Context, projectIDs []string) ([]models.ProjectChannel, error) {
	if len(projectIDs) == 0 {
		return nil, nil
	}
	ph, args := sqlitePlaceholders(projectIDs)
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT project_id, channel_id, position, folder, added_at
		FROM project_channels WHERE project_id IN (%s)`, ph), args...)
	if err != nil {
		return nil, fmt.Errorf("export: project channels: %w", err)
	}
	defer rows.Close()
	var out []models.ProjectChannel
	for rows.Next() {
		var pc models.ProjectChannel
		if err := rows.Scan(&pc.ProjectID, &pc.ChannelID, &pc.Position,
			&pc.Folder, &pc.AddedAt); err != nil {
			return nil, fmt.Errorf("export: scan project channel: %w", err)
		}
		out = append(out, pc)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserProjectKBs(ctx context.Context, projectIDs []string) ([]models.ProjectKB, error) {
	if len(projectIDs) == 0 {
		return nil, nil
	}
	ph, args := sqlitePlaceholders(projectIDs)
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT project_id, kb_id, auto_search, added_at
		FROM project_knowledge_bases WHERE project_id IN (%s)`, ph), args...)
	if err != nil {
		return nil, fmt.Errorf("export: project KBs: %w", err)
	}
	defer rows.Close()
	var out []models.ProjectKB
	for rows.Next() {
		var pk models.ProjectKB
		if err := rows.Scan(&pk.ProjectID, &pk.KBID, &pk.AutoSearch, &pk.AddedAt); err != nil {
			return nil, fmt.Errorf("export: scan project KB: %w", err)
		}
		out = append(out, pk)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserProjectNotes(ctx context.Context, projectIDs []string) ([]models.ProjectNote, error) {
	if len(projectIDs) == 0 {
		return nil, nil
	}
	ph, args := sqlitePlaceholders(projectIDs)
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT project_id, note_id, added_at
		FROM project_notes WHERE project_id IN (%s)`, ph), args...)
	if err != nil {
		return nil, fmt.Errorf("export: project notes: %w", err)
	}
	defer rows.Close()
	var out []models.ProjectNote
	for rows.Next() {
		var pn models.ProjectNote
		if err := rows.Scan(&pn.ProjectID, &pn.NoteID, &pn.AddedAt); err != nil {
			return nil, fmt.Errorf("export: scan project note: %w", err)
		}
		out = append(out, pn)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserWorkspaces(ctx context.Context, userID string) ([]models.Workspace, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, owner_type, owner_id, name, root_path, max_bytes, status,
		       indexing_enabled, git_remote_url, git_branch, git_credential_id,
		       git_last_sync, created_at, updated_at
		FROM workspaces WHERE owner_type = 'user' AND owner_id = ?
		ORDER BY created_at ASC`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: user workspaces: %w", err)
	}
	defer rows.Close()
	var out []models.Workspace
	for rows.Next() {
		var w models.Workspace
		if err := rows.Scan(&w.ID, &w.OwnerType, &w.OwnerID, &w.Name,
			&w.RootPath, &w.MaxBytes, &w.Status, &w.IndexingEnabled,
			&w.GitRemoteURL, &w.GitBranch, &w.GitCredentialID,
			stN(&w.GitLastSync), st(&w.CreatedAt), st(&w.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan workspace: %w", err)
		}
		out = append(out, w)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserWorkspaceFiles(ctx context.Context, workspaceIDs []string) ([]models.WorkspaceFile, error) {
	if len(workspaceIDs) == 0 {
		return nil, nil
	}
	ph, args := sqlitePlaceholders(workspaceIDs)
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT id, workspace_id, path, is_directory, content_type,
		       size_bytes, sha256, index_status, chunk_count,
		       created_at, updated_at
		FROM workspace_files WHERE workspace_id IN (%s)
		ORDER BY path ASC`, ph), args...)
	if err != nil {
		return nil, fmt.Errorf("export: workspace files: %w", err)
	}
	defer rows.Close()
	var out []models.WorkspaceFile
	for rows.Next() {
		var wf models.WorkspaceFile
		if err := rows.Scan(&wf.ID, &wf.WorkspaceID, &wf.Path,
			&wf.IsDirectory, &wf.ContentType, &wf.SizeBytes,
			&wf.SHA256, &wf.IndexStatus, &wf.ChunkCount,
			st(&wf.CreatedAt), st(&wf.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan workspace file: %w", err)
		}
		out = append(out, wf)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserFiles(ctx context.Context, userID string) ([]models.File, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, channel_id, message_id, user_id, project_id,
		       origin, filename, content_type, size_bytes, storage_key,
		       display_hint, extracted_text, metadata, created_at, updated_at
		FROM files WHERE user_id = ?
		ORDER BY created_at ASC`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: user files: %w", err)
	}
	defer rows.Close()
	var out []models.File
	for rows.Next() {
		var f models.File
		if err := rows.Scan(&f.ID, &f.ChannelID, &f.MessageID, &f.UserID,
			&f.ProjectID, &f.Origin, &f.Filename, &f.ContentType,
			&f.SizeBytes, &f.StorageKey, &f.DisplayHint,
			&f.ExtractedText, &f.Metadata, st(&f.CreatedAt), st(&f.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan file: %w", err)
		}
		out = append(out, f)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserFolders(ctx context.Context, userID string) ([]models.Folder, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, user_id, name, parent_id, sort_order, created_at, updated_at
		FROM folders WHERE user_id = ?
		ORDER BY sort_order ASC`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: user folders: %w", err)
	}
	defer rows.Close()
	var out []models.Folder
	for rows.Next() {
		var f models.Folder
		if err := rows.Scan(&f.ID, &f.UserID, &f.Name, &f.ParentID,
			&f.SortOrder, st(&f.CreatedAt), st(&f.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan folder: %w", err)
		}
		out = append(out, f)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserModelSettings(ctx context.Context, userID string) ([]models.UserModelSetting, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, user_id, model_id, provider_config_id, hidden,
		       preferred_temperature, preferred_max_tokens, sort_order,
		       created_at, updated_at
		FROM user_model_settings WHERE user_id = ?`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: user model settings: %w", err)
	}
	defer rows.Close()
	var out []models.UserModelSetting
	for rows.Next() {
		var ums models.UserModelSetting
		if err := rows.Scan(&ums.ID, &ums.UserID, &ums.ModelID, &ums.ProviderConfigID,
			&ums.Hidden, &ums.PreferredTemperature, &ums.PreferredMaxTokens,
			&ums.SortOrder, st(&ums.CreatedAt), st(&ums.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan user model setting: %w", err)
		}
		out = append(out, ums)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserNotifPrefs(ctx context.Context, userID string) ([]models.NotificationPreference, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, user_id, type, in_app, email
		FROM notification_preferences WHERE user_id = ?`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: notification prefs: %w", err)
	}
	defer rows.Close()
	var out []models.NotificationPreference
	for rows.Next() {
		var np models.NotificationPreference
		if err := rows.Scan(&np.ID, &np.UserID, &np.Type, &np.InApp, &np.Email); err != nil {
			return nil, fmt.Errorf("export: scan notif pref: %w", err)
		}
		out = append(out, np)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserUsageEntries(ctx context.Context, userID string) ([]models.UsageEntry, error) {
	cutoff := time.Now().AddDate(0, 0, -90).Format(timeFmt)
	rows, err := DB.QueryContext(ctx, `
		SELECT id, channel_id, user_id, provider_config_id, provider_scope,
		       model_id, role, prompt_tokens, completion_tokens,
		       cache_creation_tokens, cache_read_tokens,
		       cost_input, cost_output, routing_decision, created_at
		FROM usage_log
		WHERE user_id = ? AND created_at >= ?
		ORDER BY created_at ASC`, userID, cutoff)
	if err != nil {
		return nil, fmt.Errorf("export: usage entries: %w", err)
	}
	defer rows.Close()
	var out []models.UsageEntry
	for rows.Next() {
		var ue models.UsageEntry
		if err := rows.Scan(&ue.ID, &ue.ChannelID, &ue.UserID,
			&ue.ProviderConfigID, &ue.ProviderScope, &ue.ModelID,
			&ue.Role, &ue.PromptTokens, &ue.CompletionTokens,
			&ue.CacheCreationTokens, &ue.CacheReadTokens,
			&ue.CostInput, &ue.CostOutput, &ue.RoutingDecision,
			st(&ue.CreatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan usage entry: %w", err)
		}
		out = append(out, ue)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserPersonaGroups(ctx context.Context, userID string) ([]models.PersonaGroup, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, name, description, owner_id, scope, team_id,
		       created_at, updated_at
		FROM persona_groups WHERE owner_id = ?
		ORDER BY created_at ASC`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: persona groups: %w", err)
	}
	defer rows.Close()
	var out []models.PersonaGroup
	for rows.Next() {
		var pg models.PersonaGroup
		if err := rows.Scan(&pg.ID, &pg.Name, &pg.Description, &pg.OwnerID,
			&pg.Scope, &pg.TeamID, st(&pg.CreatedAt), st(&pg.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan persona group: %w", err)
		}
		out = append(out, pg)
	}
	return out, rows.Err()
}

func (s *ExportStore) UserPersonaGroupMembers(ctx context.Context, groupIDs []string) ([]models.PersonaGroupMember, error) {
	if len(groupIDs) == 0 {
		return nil, nil
	}
	ph, args := sqlitePlaceholders(groupIDs)
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT id, group_id, persona_id, is_leader, sort_order
		FROM persona_group_members WHERE group_id IN (%s)`, ph), args...)
	if err != nil {
		return nil, fmt.Errorf("export: persona group members: %w", err)
	}
	defer rows.Close()
	var out []models.PersonaGroupMember
	for rows.Next() {
		var pgm models.PersonaGroupMember
		if err := rows.Scan(&pgm.ID, &pgm.GroupID, &pgm.PersonaID,
			&pgm.IsLeader, &pgm.SortOrder); err != nil {
			return nil, fmt.Errorf("export: scan persona group member: %w", err)
		}
		out = append(out, pgm)
	}
	return out, rows.Err()
}

// ── Team-scoped export ───────────────────────

func (s *ExportStore) TeamChannels(ctx context.Context, teamID string) ([]models.Channel, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, user_id, title, description, type, model, system_prompt,
		       provider_config_id, is_archived, is_pinned, folder_id,
		       team_id, project_id, workspace_id, settings, allow_anonymous,
		       created_at, updated_at
		FROM channels WHERE team_id = ?
		ORDER BY created_at ASC`, teamID)
	if err != nil {
		return nil, fmt.Errorf("export: team channels: %w", err)
	}
	defer rows.Close()
	var out []models.Channel
	for rows.Next() {
		var ch models.Channel
		if err := rows.Scan(&ch.ID, &ch.UserID, &ch.Title, &ch.Description,
			&ch.Type, &ch.Model, &ch.SystemPrompt, &ch.ProviderConfigID,
			&ch.IsArchived, &ch.IsPinned, &ch.FolderID, &ch.TeamID,
			&ch.ProjectID, &ch.WorkspaceID, &ch.Settings, &ch.AllowAnonymous,
			st(&ch.CreatedAt), st(&ch.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan team channel: %w", err)
		}
		out = append(out, ch)
	}
	return out, rows.Err()
}

func (s *ExportStore) TeamMembers(ctx context.Context, teamID string) ([]models.TeamMember, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.joined_at,
		       u.email, u.display_name, u.username, u.role
		FROM team_members tm
		JOIN users u ON u.id = tm.user_id
		WHERE tm.team_id = ?`, teamID)
	if err != nil {
		return nil, fmt.Errorf("export: team members: %w", err)
	}
	defer rows.Close()
	var out []models.TeamMember
	for rows.Next() {
		var tm models.TeamMember
		if err := rows.Scan(&tm.ID, &tm.TeamID, &tm.UserID, &tm.Role,
			&tm.JoinedAt, &tm.Email, &tm.DisplayName, &tm.Username,
			&tm.UserRole); err != nil {
			return nil, fmt.Errorf("export: scan team member: %w", err)
		}
		out = append(out, tm)
	}
	return out, rows.Err()
}

func (s *ExportStore) TeamPersonas(ctx context.Context, teamID string) ([]models.Persona, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, name, handle, description, icon, avatar,
		       base_model_id, provider_config_id,
		       system_prompt, temperature, max_tokens, thinking_budget, top_p,
		       scope, owner_id, created_by, is_active, is_shared,
		       memory_enabled, memory_extraction_prompt,
		       created_at, updated_at
		FROM personas WHERE scope = 'team' AND owner_id = ?
		ORDER BY created_at ASC`, teamID)
	if err != nil {
		return nil, fmt.Errorf("export: team personas: %w", err)
	}
	defer rows.Close()
	var out []models.Persona
	for rows.Next() {
		var p models.Persona
		if err := rows.Scan(&p.ID, &p.Name, &p.Handle, &p.Description,
			&p.Icon, &p.Avatar, &p.BaseModelID, &p.ProviderConfigID,
			&p.SystemPrompt, &p.Temperature, &p.MaxTokens,
			&p.ThinkingBudget, &p.TopP, &p.Scope, &p.OwnerID,
			&p.CreatedBy, &p.IsActive, &p.IsShared,
			&p.MemoryEnabled, &p.MemoryExtractionPrompt,
			st(&p.CreatedAt), st(&p.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan persona: %w", err)
		}
		out = append(out, p)
	}
	return out, rows.Err()
}

func (s *ExportStore) TeamPersonaKBs(ctx context.Context, personaIDs []string) ([]models.PersonaKB, error) {
	if len(personaIDs) == 0 {
		return nil, nil
	}
	ph, args := sqlitePlaceholders(personaIDs)
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT persona_id, kb_id, auto_search, added_at
		FROM persona_knowledge_bases WHERE persona_id IN (%s)`, ph), args...)
	if err != nil {
		return nil, fmt.Errorf("export: persona KBs: %w", err)
	}
	defer rows.Close()
	var out []models.PersonaKB
	for rows.Next() {
		var pk models.PersonaKB
		if err := rows.Scan(&pk.PersonaID, &pk.KBID, &pk.AutoSearch, st(&pk.AddedAt)); err != nil {
			return nil, fmt.Errorf("export: scan persona KB: %w", err)
		}
		out = append(out, pk)
	}
	return out, rows.Err()
}

func (s *ExportStore) TeamKnowledgeBases(ctx context.Context, teamID string) ([]models.KnowledgeBase, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, name, description, scope, owner_id, team_id,
		       embedding_config, document_count, chunk_count, total_bytes,
		       discoverable, status, created_at, updated_at
		FROM knowledge_bases WHERE team_id = ?
		ORDER BY created_at ASC`, teamID)
	if err != nil {
		return nil, fmt.Errorf("export: team KBs: %w", err)
	}
	defer rows.Close()
	var out []models.KnowledgeBase
	for rows.Next() {
		var kb models.KnowledgeBase
		if err := rows.Scan(&kb.ID, &kb.Name, &kb.Description, &kb.Scope,
			&kb.OwnerID, &kb.TeamID, &kb.EmbeddingConfig, &kb.DocumentCount,
			&kb.ChunkCount, &kb.TotalBytes, &kb.Discoverable, &kb.Status,
			st(&kb.CreatedAt), st(&kb.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan KB: %w", err)
		}
		out = append(out, kb)
	}
	return out, rows.Err()
}

func (s *ExportStore) TeamKBDocuments(ctx context.Context, kbIDs []string) ([]models.KBDocument, error) {
	if len(kbIDs) == 0 {
		return nil, nil
	}
	ph, args := sqlitePlaceholders(kbIDs)
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT id, kb_id, filename, content_type, size_bytes, storage_key,
		       chunk_count, status, error, uploaded_by, created_at, updated_at
		FROM kb_documents WHERE kb_id IN (%s)
		ORDER BY created_at ASC`, ph), args...)
	if err != nil {
		return nil, fmt.Errorf("export: KB documents: %w", err)
	}
	defer rows.Close()
	var out []models.KBDocument
	for rows.Next() {
		var d models.KBDocument
		if err := rows.Scan(&d.ID, &d.KBID, &d.Filename, &d.ContentType,
			&d.SizeBytes, &d.StorageKey, &d.ChunkCount, &d.Status,
			&d.Error, &d.UploadedBy, st(&d.CreatedAt), st(&d.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan KB document: %w", err)
		}
		out = append(out, d)
	}
	return out, rows.Err()
}

func (s *ExportStore) TeamWorkflows(ctx context.Context, teamID string) ([]models.Workflow, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, team_id, name, slug, description, branding,
		       entry_mode, is_active, version, on_complete, retention,
		       webhook_url, webhook_secret, created_by, created_at, updated_at
		FROM workflows WHERE team_id = ?
		ORDER BY created_at ASC`, teamID)
	if err != nil {
		return nil, fmt.Errorf("export: team workflows: %w", err)
	}
	defer rows.Close()
	var out []models.Workflow
	for rows.Next() {
		var w models.Workflow
		if err := rows.Scan(&w.ID, &w.TeamID, &w.Name, &w.Slug,
			&w.Description, &w.Branding, &w.EntryMode, &w.IsActive,
			&w.Version, &w.OnComplete, &w.Retention, &w.WebhookURL,
			&w.WebhookSecret, &w.CreatedBy, st(&w.CreatedAt), st(&w.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan workflow: %w", err)
		}
		out = append(out, w)
	}
	return out, rows.Err()
}

func (s *ExportStore) TeamWorkflowVersions(ctx context.Context, workflowIDs []string) ([]models.WorkflowVersion, error) {
	if len(workflowIDs) == 0 {
		return nil, nil
	}
	ph, args := sqlitePlaceholders(workflowIDs)
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT id, workflow_id, version_number, snapshot, created_at
		FROM workflow_versions WHERE workflow_id IN (%s)
		ORDER BY version_number ASC`, ph), args...)
	if err != nil {
		return nil, fmt.Errorf("export: workflow versions: %w", err)
	}
	defer rows.Close()
	var out []models.WorkflowVersion
	for rows.Next() {
		var wv models.WorkflowVersion
		if err := rows.Scan(&wv.ID, &wv.WorkflowID, &wv.VersionNumber,
			&wv.Snapshot, st(&wv.CreatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan workflow version: %w", err)
		}
		out = append(out, wv)
	}
	return out, rows.Err()
}

func (s *ExportStore) TeamWorkflowStages(ctx context.Context, workflowIDs []string) ([]models.WorkflowStage, error) {
	if len(workflowIDs) == 0 {
		return nil, nil
	}
	ph, args := sqlitePlaceholders(workflowIDs)
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT id, workflow_id, ordinal, name, persona_id,
		       assignment_team_id, form_template, stage_mode,
		       history_mode, auto_transition, transition_rules,
		       surface_pkg_id, created_at
		FROM workflow_stages WHERE workflow_id IN (%s)
		ORDER BY ordinal ASC`, ph), args...)
	if err != nil {
		return nil, fmt.Errorf("export: workflow stages: %w", err)
	}
	defer rows.Close()
	var out []models.WorkflowStage
	for rows.Next() {
		var ws models.WorkflowStage
		if err := rows.Scan(&ws.ID, &ws.WorkflowID, &ws.Ordinal, &ws.Name,
			&ws.PersonaID, &ws.AssignmentTeamID, &ws.FormTemplate,
			&ws.StageMode, &ws.HistoryMode, &ws.AutoTransition,
			&ws.TransitionRules, &ws.SurfacePkgID, st(&ws.CreatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan workflow stage: %w", err)
		}
		out = append(out, ws)
	}
	return out, rows.Err()
}

func (s *ExportStore) TeamGroups(ctx context.Context, teamID string) ([]models.Group, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, name, description, scope, team_id, created_by, source,
		       permissions, token_budget_daily, token_budget_monthly,
		       allowed_models, created_at, updated_at
		FROM groups WHERE team_id = ?
		ORDER BY created_at ASC`, teamID)
	if err != nil {
		return nil, fmt.Errorf("export: team groups: %w", err)
	}
	defer rows.Close()
	var out []models.Group
	for rows.Next() {
		var g models.Group
		var permJSON, modelsJSON string
		if err := rows.Scan(&g.ID, &g.Name, &g.Description, &g.Scope,
			&g.TeamID, &g.CreatedBy, &g.Source,
			&permJSON, &g.TokenBudgetDaily,
			&g.TokenBudgetMonthly, &modelsJSON,
			st(&g.CreatedAt), st(&g.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan group: %w", err)
		}
		g.Permissions = ScanArray(permJSON)
		g.AllowedModels = ScanArray(modelsJSON)
		out = append(out, g)
	}
	return out, rows.Err()
}

func (s *ExportStore) TeamGroupMembers(ctx context.Context, groupIDs []string) ([]models.GroupMember, error) {
	if len(groupIDs) == 0 {
		return nil, nil
	}
	ph, args := sqlitePlaceholders(groupIDs)
	rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
		SELECT id, group_id, user_id, added_by, added_at
		FROM group_members WHERE group_id IN (%s)`, ph), args...)
	if err != nil {
		return nil, fmt.Errorf("export: group members: %w", err)
	}
	defer rows.Close()
	var out []models.GroupMember
	for rows.Next() {
		var gm models.GroupMember
		if err := rows.Scan(&gm.ID, &gm.GroupID, &gm.UserID, &gm.AddedBy,
			st(&gm.AddedAt)); err != nil {
			return nil, fmt.Errorf("export: scan group member: %w", err)
		}
		out = append(out, gm)
	}
	return out, rows.Err()
}

func (s *ExportStore) TeamResourceGrants(ctx context.Context, teamID string) ([]models.ResourceGrant, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT rg.id, rg.resource_type, rg.resource_id, rg.grant_scope,
		       rg.granted_groups, rg.created_by, rg.created_at, rg.updated_at
		FROM resource_grants rg
		WHERE rg.resource_id IN (
			SELECT id FROM personas WHERE scope = 'team' AND owner_id = ?
			UNION ALL
			SELECT id FROM knowledge_bases WHERE team_id = ?
		)`, teamID, teamID)
	if err != nil {
		return nil, fmt.Errorf("export: team resource grants: %w", err)
	}
	defer rows.Close()
	var out []models.ResourceGrant
	for rows.Next() {
		var rg models.ResourceGrant
		var groupsJSON string
		if err := rows.Scan(&rg.ID, &rg.ResourceType, &rg.ResourceID,
			&rg.GrantScope, &groupsJSON,
			&rg.CreatedBy, st(&rg.CreatedAt), st(&rg.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan resource grant: %w", err)
		}
		rg.GrantedGroups = ScanArray(groupsJSON)
		out = append(out, rg)
	}
	return out, rows.Err()
}

func (s *ExportStore) TeamProjects(ctx context.Context, teamID string) ([]models.Project, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, name, description, color, icon, scope, owner_id,
		       team_id, is_archived, workspace_id, settings, created_at, updated_at
		FROM projects WHERE team_id = ?
		ORDER BY created_at ASC`, teamID)
	if err != nil {
		return nil, fmt.Errorf("export: team projects: %w", err)
	}
	defer rows.Close()
	var out []models.Project
	for rows.Next() {
		var p models.Project
		if err := rows.Scan(&p.ID, &p.Name, &p.Description, &p.Color,
			&p.Icon, &p.Scope, &p.OwnerID, &p.TeamID, &p.IsArchived,
			&p.WorkspaceID, &p.Settings, st(&p.CreatedAt), st(&p.UpdatedAt)); err != nil {
			return nil, fmt.Errorf("export: scan project: %w", err)
		}
		out = append(out, p)
	}
	return out, rows.Err()
}

// ── GDPR ─────────────────────────────────────

func (s *ExportStore) CountActiveAdmins(ctx context.Context) (int, error) {
	var count int
	err := DB.QueryRowContext(ctx, `SELECT COUNT(*) FROM users WHERE role = 'admin' AND is_active = 1`).Scan(&count)
	return count, err
}

func (s *ExportStore) AnonymizeUser(ctx context.Context, userID string, anonHash string) error {
	_, err := DB.ExecContext(ctx, `
		UPDATE users SET
			username = ?,
			email = ?,
			display_name = 'Deleted User',
			avatar_url = '',
			handle = ?,
			settings = NULL,
			external_id = NULL,
			is_active = 0,
			password_hash = '',
			updated_at = datetime('now')
		WHERE id = ?`,
		"deleted-user-"+anonHash,
		"deleted-"+anonHash+"@deleted.local",
		"deleted-"+anonHash,
		userID,
	)
	return err
}

func (s *ExportStore) SoftDeleteUserData(ctx context.Context, userID string) (map[string]int64, error) {
	counts := make(map[string]int64)
	tx, err := DB.BeginTx(ctx, nil)
	if err != nil {
		return nil, fmt.Errorf("export: begin tx: %w", err)
	}
	defer tx.Rollback()

	now := time.Now().UTC().Format(timeFmt)

	// Messages in user-owned channels
	r, err := tx.ExecContext(ctx, `
		UPDATE messages SET deleted_at = ?
		WHERE channel_id IN (SELECT id FROM channels WHERE user_id = ?)
		  AND deleted_at IS NULL`, now, userID)
	if err != nil {
		return nil, fmt.Errorf("export: soft-delete owned messages: %w", err)
	}
	n, _ := r.RowsAffected()
	counts["messages_owned"] = n

	// Messages sent by user in other channels
	r, err = tx.ExecContext(ctx, `
		UPDATE messages SET deleted_at = ?
		WHERE participant_type = 'user' AND participant_id = ?
		  AND deleted_at IS NULL`, now, userID)
	if err != nil {
		return nil, fmt.Errorf("export: soft-delete sent messages: %w", err)
	}
	n, _ = r.RowsAffected()
	counts["messages_sent"] = n

	// Archive channels
	r, err = tx.ExecContext(ctx, `UPDATE channels SET is_archived = 1 WHERE user_id = ?`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: archive channels: %w", err)
	}
	n, _ = r.RowsAffected()
	counts["channels"] = n

	// Notes + links
	tx.ExecContext(ctx, `DELETE FROM note_links WHERE source_note_id IN (SELECT id FROM notes WHERE user_id = ?)`, userID)
	r, err = tx.ExecContext(ctx, `DELETE FROM notes WHERE user_id = ?`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: delete notes: %w", err)
	}
	n, _ = r.RowsAffected()
	counts["notes"] = n

	// Memories
	r, err = tx.ExecContext(ctx, `DELETE FROM memories WHERE owner_id = ? AND scope = 'user'`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: delete memories: %w", err)
	}
	n, _ = r.RowsAffected()
	counts["memories"] = n

	// Workspace cascade
	tx.ExecContext(ctx, `DELETE FROM workspace_chunks WHERE workspace_id IN (SELECT id FROM workspaces WHERE owner_type = 'user' AND owner_id = ?)`, userID)
	tx.ExecContext(ctx, `DELETE FROM workspace_files WHERE workspace_id IN (SELECT id FROM workspaces WHERE owner_type = 'user' AND owner_id = ?)`, userID)
	r, err = tx.ExecContext(ctx, `DELETE FROM workspaces WHERE owner_type = 'user' AND owner_id = ?`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: delete workspaces: %w", err)
	}
	n, _ = r.RowsAffected()
	counts["workspaces"] = n

	// Files
	r, err = tx.ExecContext(ctx, `DELETE FROM files WHERE user_id = ?`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: delete files: %w", err)
	}
	n, _ = r.RowsAffected()
	counts["files"] = n

	// Projects cascade
	tx.ExecContext(ctx, `DELETE FROM project_notes WHERE project_id IN (SELECT id FROM projects WHERE scope = 'personal' AND owner_id = ?)`, userID)
	tx.ExecContext(ctx, `DELETE FROM project_knowledge_bases WHERE project_id IN (SELECT id FROM projects WHERE scope = 'personal' AND owner_id = ?)`, userID)
	tx.ExecContext(ctx, `DELETE FROM project_channels WHERE project_id IN (SELECT id FROM projects WHERE scope = 'personal' AND owner_id = ?)`, userID)
	r, err = tx.ExecContext(ctx, `DELETE FROM projects WHERE scope = 'personal' AND owner_id = ?`, userID)
	if err != nil {
		return nil, fmt.Errorf("export: delete projects: %w", err)
	}
	n, _ = r.RowsAffected()
	counts["projects"] = n

	// Folders, settings, prefs, notifications
	tx.ExecContext(ctx, `DELETE FROM folders WHERE user_id = ?`, userID)
	tx.ExecContext(ctx, `DELETE FROM user_model_settings WHERE user_id = ?`, userID)
	tx.ExecContext(ctx, `DELETE FROM notification_preferences WHERE user_id = ?`, userID)
	tx.ExecContext(ctx, `DELETE FROM notifications WHERE user_id = ?`, userID)

	// Persona groups
	tx.ExecContext(ctx, `DELETE FROM persona_group_members WHERE group_id IN (SELECT id FROM persona_groups WHERE owner_id = ?)`, userID)
	tx.ExecContext(ctx, `DELETE FROM persona_groups WHERE owner_id = ?`, userID)

	// Channel participation
	tx.ExecContext(ctx, `DELETE FROM channel_participants WHERE participant_type = 'user' AND participant_id = ?`, userID)
	tx.ExecContext(ctx, `DELETE FROM channel_cursors WHERE user_id = ?`, userID)

	// Memory extraction log, presence
	tx.ExecContext(ctx, `DELETE FROM memory_extraction_log WHERE user_id = ?`, userID)
	tx.ExecContext(ctx, `DELETE FROM user_presence WHERE user_id = ?`, userID)

	if err := tx.Commit(); err != nil {
		return nil, fmt.Errorf("export: commit: %w", err)
	}
	return counts, nil
}

func (s *ExportStore) DeleteUserTokens(ctx context.Context, userID string) error {
	_, err := DB.ExecContext(ctx, `DELETE FROM refresh_tokens WHERE user_id = ?`, userID)
	if err != nil {
		return fmt.Errorf("export: delete refresh tokens: %w", err)
	}
	_, err = DB.ExecContext(ctx, `DELETE FROM ws_tickets WHERE user_id = ?`, userID)
	if err != nil {
		return fmt.Errorf("export: delete ws tickets: %w", err)
	}
	return nil
}

// ── Import (CS1) ─────────────────────────────

func (s *ExportStore) ImportFolders(ctx context.Context, folders []models.Folder) (int, int, error) {
	imported, skipped := 0, 0
	for _, f := range folders {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO folders (id, user_id, name, parent_id, sort_order, created_at, updated_at)
			VALUES (?,?,?,?,?,?,?)`,
			f.ID, f.UserID, f.Name, f.ParentID, f.SortOrder,
			f.CreatedAt.Format(timeFmt), f.UpdatedAt.Format(timeFmt))
		if err != nil {
			return imported, skipped, fmt.Errorf("import folder %s: %w", f.ID, err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportProjects(ctx context.Context, projects []models.Project) (int, int, error) {
	imported, skipped := 0, 0
	for _, p := range projects {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO projects (id, name, description, color, icon, scope, owner_id,
			       team_id, is_archived, workspace_id, settings, created_at, updated_at)
			VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)`,
			p.ID, p.Name, p.Description, p.Color, p.Icon, p.Scope, p.OwnerID,
			p.TeamID, p.IsArchived, p.WorkspaceID, p.Settings,
			p.CreatedAt.Format(timeFmt), p.UpdatedAt.Format(timeFmt))
		if err != nil {
			return imported, skipped, fmt.Errorf("import project %s: %w", p.ID, err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportWorkspaces(ctx context.Context, workspaces []models.Workspace) (int, int, error) {
	imported, skipped := 0, 0
	for _, w := range workspaces {
		var gitLastSync interface{}
		if w.GitLastSync != nil {
			t := w.GitLastSync.Format(timeFmt)
			gitLastSync = t
		}
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO workspaces (id, owner_type, owner_id, name, root_path, max_bytes, status,
			       indexing_enabled, git_remote_url, git_branch, git_credential_id,
			       git_last_sync, created_at, updated_at)
			VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
			w.ID, w.OwnerType, w.OwnerID, w.Name, w.RootPath, w.MaxBytes, w.Status,
			w.IndexingEnabled, w.GitRemoteURL, w.GitBranch, w.GitCredentialID,
			gitLastSync, w.CreatedAt.Format(timeFmt), w.UpdatedAt.Format(timeFmt))
		if err != nil {
			return imported, skipped, fmt.Errorf("import workspace %s: %w", w.ID, err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportChannels(ctx context.Context, channels []models.Channel) (int, int, error) {
	imported, skipped := 0, 0
	for _, ch := range channels {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO channels (id, user_id, title, description, type, model, system_prompt,
			       provider_config_id, is_archived, is_pinned, folder_id,
			       team_id, project_id, workspace_id, settings, allow_anonymous,
			       created_at, updated_at)
			VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
			ch.ID, ch.UserID, ch.Title, ch.Description, ch.Type, ch.Model, ch.SystemPrompt,
			ch.ProviderConfigID, ch.IsArchived, ch.IsPinned, ch.FolderID, ch.TeamID,
			ch.ProjectID, ch.WorkspaceID, ch.Settings, ch.AllowAnonymous,
			ch.CreatedAt.Format(timeFmt), ch.UpdatedAt.Format(timeFmt))
		if err != nil {
			return imported, skipped, fmt.Errorf("import channel %s: %w", ch.ID, err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportChannelParticipants(ctx context.Context, cps []models.ChannelParticipant) (int, int, error) {
	imported, skipped := 0, 0
	for _, cp := range cps {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO channel_participants (id, channel_id, participant_type, participant_id, role,
			       display_name, avatar_url, joined_at, last_read_at)
			VALUES (?,?,?,?,?,?,?,?,?)`,
			cp.ID, cp.ChannelID, cp.ParticipantType, cp.ParticipantID, cp.Role,
			cp.DisplayName, cp.AvatarURL,
			cp.JoinedAt.Format(timeFmt), cp.LastReadAt.Format(timeFmt))
		if err != nil {
			return imported, skipped, fmt.Errorf("import participant %s: %w", cp.ID, err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportChannelModels(ctx context.Context, cms []models.ChannelModel) (int, int, error) {
	imported, skipped := 0, 0
	for _, cm := range cms {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO channel_models (id, channel_id, model_id, provider_config_id, persona_id,
			       handle, display_name, system_prompt, is_default)
			VALUES (?,?,?,?,?,?,?,?,?)`,
			cm.ID, cm.ChannelID, cm.ModelID, cm.ProviderConfigID, cm.PersonaID,
			cm.Handle, cm.DisplayName, cm.SystemPrompt, cm.IsDefault)
		if err != nil {
			return imported, skipped, fmt.Errorf("import channel model %s: %w", cm.ID, err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportChannelCursors(ctx context.Context, cursors []models.ChannelCursor) (int, int, error) {
	imported, skipped := 0, 0
	for _, cc := range cursors {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO channel_cursors (id, channel_id, user_id, active_leaf_id, updated_at)
			VALUES (?,?,?,?,?)`,
			cc.ID, cc.ChannelID, cc.UserID, cc.ActiveLeafID,
			cc.UpdatedAt.Format(timeFmt))
		if err != nil {
			return imported, skipped, fmt.Errorf("import cursor %s: %w", cc.ID, err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportMessages(ctx context.Context, msgs []models.Message) (int, int, error) {
	imported, skipped := 0, 0
	for _, m := range msgs {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO messages (id, channel_id, role, content, model, tokens_used,
			       tool_calls, metadata, parent_id, sibling_index,
			       participant_type, participant_id, provider_config_id,
			       created_at, updated_at)
			VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
			m.ID, m.ChannelID, m.Role, m.Content, m.Model, m.TokensUsed,
			ToJSON(m.ToolCalls), ToJSON(m.Metadata), m.ParentID, m.SiblingIndex,
			m.ParticipantType, m.ParticipantID, m.ProviderConfigID,
			m.CreatedAt.Format(timeFmt), m.UpdatedAt.Format(timeFmt))
		if err != nil {
			return imported, skipped, fmt.Errorf("import message %s: %w", m.ID, err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportNotes(ctx context.Context, notes []models.Note) (int, int, error) {
	imported, skipped := 0, 0
	for _, n := range notes {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO notes (id, user_id, title, content, folder_path, tags,
			       metadata, source_channel_id, source_message_id, team_id,
			       created_at, updated_at)
			VALUES (?,?,?,?,?,?,?,?,?,?,?,?)`,
			n.ID, n.UserID, n.Title, n.Content, n.FolderPath, ArrayToJSON(n.Tags),
			ToJSON(n.Metadata), models.NullString(n.SourceChannelID),
			models.NullString(n.SourceMessageID), models.NullString(n.TeamID),
			n.CreatedAt.Format(timeFmt), n.UpdatedAt.Format(timeFmt))
		if err != nil {
			return imported, skipped, fmt.Errorf("import note %s: %w", n.ID, err)
		}
		nr, _ := r.RowsAffected()
		if nr > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportNoteLinks(ctx context.Context, links []models.ExportNoteLink) (int, int, error) {
	imported, skipped := 0, 0
	for _, nl := range links {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO note_links (source_note_id, target_note_id, target_title, display_text, is_transclusion)
			VALUES (?,?,?,?,?)`,
			nl.SourceNoteID, nl.TargetNoteID, nl.TargetTitle, nl.DisplayText, nl.IsTransclusion)
		if err != nil {
			return imported, skipped, fmt.Errorf("import note link: %w", err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportMemories(ctx context.Context, mems []models.Memory) (int, int, error) {
	imported, skipped := 0, 0
	for _, m := range mems {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO memories (id, scope, owner_id, user_id, key, value,
			       source_channel_id, confidence, status, created_at, updated_at)
			VALUES (?,?,?,?,?,?,?,?,?,?,?)`,
			m.ID, m.Scope, m.OwnerID, m.UserID, m.Key, m.Value,
			m.SourceChannelID, m.Confidence, m.Status,
			m.CreatedAt.Format(timeFmt), m.UpdatedAt.Format(timeFmt))
		if err != nil {
			return imported, skipped, fmt.Errorf("import memory %s: %w", m.ID, err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportProjectChannels(ctx context.Context, pcs []models.ProjectChannel) (int, int, error) {
	imported, skipped := 0, 0
	for _, pc := range pcs {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO project_channels (project_id, channel_id, position, folder, added_at)
			VALUES (?,?,?,?,?)`,
			pc.ProjectID, pc.ChannelID, pc.Position, pc.Folder, pc.AddedAt)
		if err != nil {
			return imported, skipped, fmt.Errorf("import project channel: %w", err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportProjectKBs(ctx context.Context, pks []models.ProjectKB) (int, int, error) {
	imported, skipped := 0, 0
	for _, pk := range pks {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO project_knowledge_bases (project_id, kb_id, auto_search, added_at)
			VALUES (?,?,?,?)`,
			pk.ProjectID, pk.KBID, pk.AutoSearch, pk.AddedAt)
		if err != nil {
			return imported, skipped, fmt.Errorf("import project KB: %w", err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportProjectNotes(ctx context.Context, pns []models.ProjectNote) (int, int, error) {
	imported, skipped := 0, 0
	for _, pn := range pns {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO project_notes (project_id, note_id, added_at)
			VALUES (?,?,?)`,
			pn.ProjectID, pn.NoteID, pn.AddedAt)
		if err != nil {
			return imported, skipped, fmt.Errorf("import project note: %w", err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportWorkspaceFiles(ctx context.Context, wfs []models.WorkspaceFile) (int, int, error) {
	imported, skipped := 0, 0
	for _, wf := range wfs {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO workspace_files (id, workspace_id, path, is_directory, content_type,
			       size_bytes, sha256, index_status, chunk_count, created_at, updated_at)
			VALUES (?,?,?,?,?,?,?,?,?,?,?)`,
			wf.ID, wf.WorkspaceID, wf.Path, wf.IsDirectory, wf.ContentType,
			wf.SizeBytes, wf.SHA256, wf.IndexStatus, wf.ChunkCount,
			wf.CreatedAt.Format(timeFmt), wf.UpdatedAt.Format(timeFmt))
		if err != nil {
			return imported, skipped, fmt.Errorf("import workspace file %s: %w", wf.ID, err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportFiles(ctx context.Context, files []models.File) (int, int, error) {
	imported, skipped := 0, 0
	for _, f := range files {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO files (id, channel_id, message_id, user_id, project_id,
			       origin, filename, content_type, size_bytes, storage_key,
			       display_hint, extracted_text, metadata, created_at, updated_at)
			VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
			f.ID, f.ChannelID, f.MessageID, f.UserID, f.ProjectID,
			f.Origin, f.Filename, f.ContentType, f.SizeBytes, f.StorageKey,
			f.DisplayHint, f.ExtractedText, f.Metadata,
			f.CreatedAt.Format(timeFmt), f.UpdatedAt.Format(timeFmt))
		if err != nil {
			return imported, skipped, fmt.Errorf("import file %s: %w", f.ID, err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportUserModelSettings(ctx context.Context, ums []models.UserModelSetting) (int, int, error) {
	imported, skipped := 0, 0
	for _, um := range ums {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO user_model_settings (id, user_id, model_id, provider_config_id, hidden,
			       preferred_temperature, preferred_max_tokens, sort_order, created_at, updated_at)
			VALUES (?,?,?,?,?,?,?,?,?,?)`,
			um.ID, um.UserID, um.ModelID, um.ProviderConfigID, um.Hidden,
			um.PreferredTemperature, um.PreferredMaxTokens, um.SortOrder,
			um.CreatedAt.Format(timeFmt), um.UpdatedAt.Format(timeFmt))
		if err != nil {
			return imported, skipped, fmt.Errorf("import user model setting %s: %w", um.ID, err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportNotifPrefs(ctx context.Context, nps []models.NotificationPreference) (int, int, error) {
	imported, skipped := 0, 0
	for _, np := range nps {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO notification_preferences (id, user_id, type, in_app, email)
			VALUES (?,?,?,?,?)`,
			np.ID, np.UserID, np.Type, np.InApp, np.Email)
		if err != nil {
			return imported, skipped, fmt.Errorf("import notif pref %s: %w", np.ID, err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportPersonaGroups(ctx context.Context, pgs []models.PersonaGroup) (int, int, error) {
	imported, skipped := 0, 0
	for _, pg := range pgs {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO persona_groups (id, name, description, owner_id, scope, team_id,
			       created_at, updated_at)
			VALUES (?,?,?,?,?,?,?,?)`,
			pg.ID, pg.Name, pg.Description, pg.OwnerID, pg.Scope, pg.TeamID,
			pg.CreatedAt.Format(timeFmt), pg.UpdatedAt.Format(timeFmt))
		if err != nil {
			return imported, skipped, fmt.Errorf("import persona group %s: %w", pg.ID, err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}

func (s *ExportStore) ImportPersonaGroupMembers(ctx context.Context, pgms []models.PersonaGroupMember) (int, int, error) {
	imported, skipped := 0, 0
	for _, pgm := range pgms {
		r, err := DB.ExecContext(ctx, `
			INSERT OR IGNORE INTO persona_group_members (id, group_id, persona_id, is_leader, sort_order)
			VALUES (?,?,?,?,?)`,
			pgm.ID, pgm.GroupID, pgm.PersonaID, pgm.IsLeader, pgm.SortOrder)
		if err != nil {
			return imported, skipped, fmt.Errorf("import persona group member %s: %w", pgm.ID, err)
		}
		n, _ := r.RowsAffected()
		if n > 0 {
			imported++
		} else {
			skipped++
		}
	}
	return imported, skipped, nil
}
