import SwiftUI // MARK: - Pickers struct CommentTypePicker: View { @Binding var selectedType: CommentType var body: some View { HStack(spacing: 8) { ForEach(CommentType.allCases) { type in Button { selectedType = type } label: { HStack(spacing: 8) { Image(systemName: type.systemImage) .font(.system(size: 12, weight: .semibold)) .foregroundStyle( selectedType == type ? AppTheme.accentGreen : AppTheme.textSecondary ) .frame(width: 16) VStack(alignment: .leading, spacing: 1) { Text(type.title) .font(.system(size: 11, weight: .semibold)) .foregroundStyle(AppTheme.textPrimary) Text(type.subtitle) .font(.system(size: 9)) .foregroundStyle(AppTheme.textTertiary) .lineLimit(1) } Spacer(minLength: 0) } .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 10) .padding(.vertical, 8) .background( RoundedRectangle(cornerRadius: 8) .fill( selectedType == type ? AppTheme.accentGreen.opacity(0.15) : AppTheme.panelBackground ) ) .overlay( RoundedRectangle(cornerRadius: 8) .stroke( selectedType == type ? AppTheme.accentGreen.opacity(0.5) : AppTheme.cardBorder, lineWidth: 1 ) ) } .buttonStyle(AppPlainButtonStyle()) .hoverOverlay(cornerRadius: 8) } } } } struct CommentIntentPicker: View { @Binding var selectedIntent: CommentIntent var body: some View { LazyVGrid( columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 8 ) { ForEach(CommentIntent.allCases) { intent in Button { selectedIntent = intent } label: { HStack(spacing: 8) { Image(systemName: intent.systemImage) .font(.system(size: 11, weight: .semibold)) .foregroundStyle( selectedIntent == intent ? AppTheme.accentGreen : AppTheme.textSecondary ) .frame(width: 14) VStack(alignment: .leading, spacing: 1) { Text(intent.title) .font(.system(size: 11, weight: .semibold)) .foregroundStyle(AppTheme.textPrimary) Text(intent.subtitle) .font(.system(size: 9)) .foregroundStyle(AppTheme.textTertiary) .lineLimit(1) } Spacer(minLength: 0) } .padding(.horizontal, 10) .padding(.vertical, 8) .background( RoundedRectangle(cornerRadius: 8) .fill( selectedIntent == intent ? AppTheme.accentGreen.opacity(0.15) : AppTheme.panelBackground ) ) .overlay( RoundedRectangle(cornerRadius: 8) .stroke( selectedIntent == intent ? AppTheme.accentGreen.opacity(0.5) : AppTheme.cardBorder, lineWidth: 1 ) ) } .buttonStyle(AppPlainButtonStyle()) .hoverOverlay(cornerRadius: 8) } } } } struct CommentLengthPicker: View { @Binding var selectedLength: CommentLength var body: some View { HStack(spacing: 8) { ForEach(CommentLength.allCases) { length in Button { selectedLength = length } label: { VStack(spacing: 2) { Text(length.title) .font(.system(size: 10, weight: .semibold)) Text(length.wordRange) .font(.system(size: 8)) .foregroundStyle( selectedLength == length ? .white.opacity(0.8) : AppTheme.textTertiary ) } .foregroundStyle(selectedLength == length ? .white : AppTheme.textSecondary) .padding(.horizontal, 12) .padding(.vertical, 8) .frame(maxWidth: .infinity) .background( RoundedRectangle(cornerRadius: 8) .fill( selectedLength == length ? AppTheme.accentGreen : AppTheme.panelBackground ) ) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(AppTheme.cardBorder, lineWidth: 1) ) } .buttonStyle(AppPlainButtonStyle()) .hoverOverlay(cornerRadius: 8) } } } } struct CommentTonePicker: View { @Binding var selectedTone: PostTone var body: some View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 6) { ForEach(PostTone.allCases) { tone in Button { selectedTone = tone } label: { Text(tone.title) .font(.system(size: 10, weight: .semibold)) .foregroundStyle(selectedTone == tone ? .white : AppTheme.textSecondary) .padding(.horizontal, 10) .padding(.vertical, 6) .background( Capsule() .fill( selectedTone == tone ? AppTheme.accentGreen : AppTheme.panelBackground ) ) .overlay( Capsule() .stroke( selectedTone == tone ? AppTheme.accentGreen.opacity(0.5) : AppTheme.cardBorder, lineWidth: 1 ) ) } .buttonStyle(AppPlainButtonStyle()) .hoverOverlay(cornerRadius: 50) } } } } } struct CommentVariantCountPicker: View { @Binding var selectedCount: CommentVariantCount var body: some View { HStack(spacing: 8) { ForEach(CommentVariantCount.allCases) { count in Button { selectedCount = count } label: { Text(count.label) .font(.system(size: 10, weight: .semibold)) .foregroundStyle(selectedCount == count ? .white : AppTheme.textSecondary) .frame(maxWidth: .infinity) .padding(.vertical, 7) .background( RoundedRectangle(cornerRadius: 7) .fill( selectedCount == count ? AppTheme.accentGreen : AppTheme.panelBackground ) ) .overlay( RoundedRectangle(cornerRadius: 7) .stroke(AppTheme.cardBorder, lineWidth: 1) ) } .buttonStyle(AppPlainButtonStyle()) .hoverOverlay(cornerRadius: 8) } } } } struct CommentToggleRow: View { let title: String let subtitle: String @Binding var isOn: Bool var body: some View { Toggle(isOn: $isOn) { VStack(alignment: .leading, spacing: 2) { Text(title) .font(.system(size: 11, weight: .medium)) .foregroundStyle(AppTheme.textPrimary) Text(subtitle) .font(.system(size: 9)) .foregroundStyle(AppTheme.textTertiary) } } .toggleStyle(.switch) .tint(AppTheme.accentGreen) .padding(.horizontal, 10) .padding(.vertical, 8) .hoverOverlay(cornerRadius: 8) .hoverCursor() } } // MARK: - Reddit Etiquette Card struct RedditCommentEtiquetteCard: View { var body: some View { VStack(alignment: .leading, spacing: 8) { HStack(spacing: 6) { Image(systemName: "info.circle.fill") .font(.system(size: 11)) .foregroundStyle(AppTheme.accentGreen) Text("Reddit Comment Tips") .font(.system(size: 11, weight: .semibold)) .foregroundStyle(AppTheme.textPrimary) } VStack(alignment: .leading, spacing: 4) { etiquetteRow("10,000 character limit per comment") etiquetteRow("Markdown: **bold**, *italic*, > quotes, `code`") etiquetteRow("Stay on-topic for the subreddit") etiquetteRow("Be civil — disagree with ideas, not people") etiquetteRow("Edit with \"Edit:\" to clarify, don't bait-and-switch") } } .padding(12) .frame(maxWidth: .infinity, alignment: .leading) .background( RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall) .fill(AppTheme.accentGreen.opacity(0.06)) .overlay( RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall) .stroke(AppTheme.accentGreen.opacity(0.2), lineWidth: 1) ) ) } private func etiquetteRow(_ text: String) -> some View { HStack(alignment: .top, spacing: 6) { Text("•") .font(.system(size: 10)) .foregroundStyle(AppTheme.accentGreen) Text(text) .font(.system(size: 10)) .foregroundStyle(AppTheme.textSecondary) .fixedSize(horizontal: false, vertical: true) } } } // MARK: - Thread Context Card struct RedditThreadContextCard: View { let postTitle: String let postBody: String let parentComment: String let commentType: CommentType let formattedSubreddit: String var body: some View { VStack(alignment: .leading, spacing: 10) { HStack(spacing: 6) { Image(systemName: "doc.text") .font(.system(size: 11)) .foregroundStyle(AppTheme.accentOrange) Text("Thread Context") .font(.system(size: 11, weight: .semibold)) .foregroundStyle(AppTheme.textPrimary) } VStack(alignment: .leading, spacing: 8) { contextBlock( label: formattedSubreddit, content: postTitle.isEmpty ? "Post title" : postTitle, isTitle: true ) if !postBody.isEmpty { contextBlock(label: "Post body", content: postBody, isTitle: false) } if commentType == .reply, !parentComment.isEmpty { contextBlock(label: "Replying to", content: parentComment, isTitle: false, isQuoted: true) } } } .padding(12) .frame(maxWidth: .infinity, alignment: .leading) .background( RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall) .fill(AppTheme.cardBackground) .overlay( RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall) .stroke(AppTheme.cardBorder, lineWidth: 1) ) ) } private func contextBlock( label: String, content: String, isTitle: Bool, isQuoted: Bool = false ) -> some View { VStack(alignment: .leading, spacing: 4) { Text(label) .font(.system(size: 9, weight: .semibold)) .foregroundStyle(AppTheme.textTertiary) if isQuoted { HStack(spacing: 0) { Rectangle() .fill(AppTheme.accentGreen.opacity(0.5)) .frame(width: 3) Text(content) .font(.system(size: 10)) .foregroundStyle(AppTheme.textSecondary) .lineLimit(3) .padding(.leading, 8) } .padding(.vertical, 4) } else { Text(content) .font(.system(size: isTitle ? 11 : 10, weight: isTitle ? .semibold : .regular)) .foregroundStyle(isTitle ? AppTheme.textPrimary : AppTheme.textSecondary) .lineLimit(isTitle ? 2 : 3) } } } } // MARK: - Comment Preview struct RedditCommentPreviewCard: View { let commentBody: String let formattedSubreddit: String let commentType: CommentType let parentComment: String var isReply: Bool = false var body: some View { VStack(alignment: .leading, spacing: 0) { if commentType == .reply, !parentComment.isEmpty { parentCommentRow .padding(.leading, 14) } commentRow .padding(.leading, commentType == .reply ? 28 : 0) } .background(AppTheme.panelBackground) .clipShape(RoundedRectangle(cornerRadius: AppTheme.cornerRadius)) .overlay( RoundedRectangle(cornerRadius: AppTheme.cornerRadius) .stroke(AppTheme.cardBorder, lineWidth: 1) ) } private var parentCommentRow: some View { HStack(alignment: .top, spacing: 8) { voteColumn VStack(alignment: .leading, spacing: 6) { commentHeader(username: "u/OriginalPoster") Text(parentComment) .font(.system(size: 11)) .foregroundStyle(AppTheme.textSecondary) .lineLimit(3) .fixedSize(horizontal: false, vertical: true) commentActions } } .padding(.horizontal, 14) .padding(.vertical, 12) .opacity(0.7) } private var commentRow: some View { HStack(alignment: .top, spacing: 8) { voteColumn VStack(alignment: .leading, spacing: 6) { commentHeader(username: "u/ReddoraUser") if commentBody.isEmpty { Text("Your comment will appear here…") .font(.system(size: 12)) .foregroundStyle(AppTheme.textTertiary) .italic() } else { PostMarkdownText(text: commentBody, fontSize: 12, color: AppTheme.textPrimary) } commentActions } } .padding(.horizontal, 14) .padding(.vertical, 12) .background( commentType == .reply ? AppTheme.accentGreen.opacity(0.04) : Color.clear ) } private var voteColumn: some View { VStack(spacing: 2) { Image(systemName: "arrow.up") .font(.system(size: 12, weight: .semibold)) .foregroundStyle(AppTheme.textTertiary) Text("1") .font(.system(size: 10, weight: .semibold)) .foregroundStyle(AppTheme.textSecondary) Image(systemName: "arrow.down") .font(.system(size: 12, weight: .semibold)) .foregroundStyle(AppTheme.textTertiary) } .frame(width: 24) } private func commentHeader(username: String) -> some View { HStack(spacing: 4) { Text(username) .font(.system(size: 10, weight: .semibold)) .foregroundStyle(AppTheme.textPrimary) Text("·") .foregroundStyle(AppTheme.textTertiary) Text("just now") .font(.system(size: 9)) .foregroundStyle(AppTheme.textTertiary) if commentType == .reply, username == "u/ReddoraUser" { Text("·") .foregroundStyle(AppTheme.textTertiary) Text(formattedSubreddit) .font(.system(size: 9, weight: .medium)) .foregroundStyle(AppTheme.accentGreen) } } } private var commentActions: some View { HStack(spacing: 12) { actionButton("Reply", icon: "arrowshape.turn.up.left") actionButton("Share", icon: "square.and.arrow.up") actionButton("Award", icon: "seal") actionButton("⋯", icon: nil) } .padding(.top, 2) } private func actionButton(_ title: String, icon: String?) -> some View { HStack(spacing: 4) { if let icon { Image(systemName: icon) .font(.system(size: 9, weight: .semibold)) } Text(title) .font(.system(size: 9, weight: .semibold)) } .foregroundStyle(AppTheme.textTertiary) } } // MARK: - Variant Row struct CommentVariantRow: View { let variant: CommentVariant let isSelected: Bool let onSelect: () -> Void let onApply: () -> Void var body: some View { VStack(alignment: .leading, spacing: 8) { HStack { CommentScoreBadge(score: variant.score) Text(variant.reasoning) .font(.system(size: 9)) .foregroundStyle(AppTheme.textTertiary) .lineLimit(1) Spacer() HStack(spacing: 6) { Button("Select", action: onSelect) .font(.system(size: 9, weight: .semibold)) .foregroundStyle(isSelected ? AppTheme.accentGreen : AppTheme.textSecondary) .buttonStyle(AppPlainButtonStyle()) .hoverOverlay(cornerRadius: 8) Button("Apply", action: onApply) .font(.system(size: 9, weight: .semibold)) .foregroundStyle(.white) .padding(.horizontal, 8) .padding(.vertical, 4) .background(AppTheme.accentGreen.opacity(0.8)) .clipShape(RoundedRectangle(cornerRadius: 5)) .buttonStyle(AppPlainButtonStyle()) .hoverOverlay(cornerRadius: 8) } } Text(variant.body) .font(.system(size: 11)) .foregroundStyle(AppTheme.textSecondary) .lineLimit(4) .fixedSize(horizontal: false, vertical: true) } .padding(12) .frame(maxWidth: .infinity, alignment: .leading) .background( RoundedRectangle(cornerRadius: 8) .fill(isSelected ? AppTheme.accentGreen.opacity(0.1) : AppTheme.panelBackground) ) .overlay( RoundedRectangle(cornerRadius: 8) .stroke( isSelected ? AppTheme.accentGreen.opacity(0.4) : AppTheme.cardBorder, lineWidth: 1 ) ) .hoverCard(cornerRadius: 8, isSelected: isSelected) .hoverCursor() .onTapGesture(perform: onSelect) } } struct CommentScoreBadge: View { let score: Int var body: some View { Text("\(score)") .font(.system(size: 10, weight: .bold)) .foregroundStyle(scoreColor) .padding(.horizontal, 6) .padding(.vertical, 3) .background(scoreColor.opacity(0.15)) .clipShape(RoundedRectangle(cornerRadius: 4)) } private var scoreColor: Color { switch score { case 80...: AppTheme.accentGreen case 60..<80: AppTheme.accentYellow default: AppTheme.textTertiary } } } struct CommentWriterEmptyState: View { let icon: String let title: String let subtitle: String var body: some View { VStack(spacing: 10) { Image(systemName: icon) .font(.system(size: 28)) .foregroundStyle(AppTheme.accentGreen.opacity(0.6)) Text(title) .font(.system(size: 12, weight: .semibold)) .foregroundStyle(AppTheme.textPrimary) Text(subtitle) .font(.system(size: 10)) .foregroundStyle(AppTheme.textSecondary) .multilineTextAlignment(.center) } .frame(maxWidth: .infinity) .padding(.vertical, 32) } } struct CommentSecondaryButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { AppSecondaryButtonStyle().makeBody(configuration: configuration) } }