import SwiftUI
import AppKit

/// Puts a string on the general pasteboard. The single place clipboard writes go
/// so the clear-then-set dance isn't copy-pasted across call sites.
enum Clipboard {
    static func copy(_ string: String) {
        NSPasteboard.general.clearContents()
        NSPasteboard.general.setString(string, forType: .string)
    }
}

/// A compact clipboard button that copies `text` and briefly flips to a checkmark to
/// confirm. Reused on assistant responses and on fenced code blocks — both surface it
/// on hover (see `CopyAffordance`).
struct CopyButton: View {
    let text: String
    var help: String = "Copy"
    /// Point size of the glyph; the tap target scales around it.
    var size: CGFloat = 11

    @State private var copied = false
    @State private var resetTask: Task<Void, Never>?

    var body: some View {
        Button(action: perform) {
            Image(systemName: copied ? "checkmark" : "doc.on.doc")
                .font(.system(size: size, weight: .medium))
                .foregroundStyle(copied ? Color.green : Color.secondary)
                .frame(width: size + 11, height: size + 11)
                .background(.ultraThinMaterial, in: .rect(cornerRadius: 6))
                .overlay(RoundedRectangle(cornerRadius: 6).strokeBorder(.quaternary, lineWidth: 1))
                .contentShape(.rect)
        }
        .buttonStyle(.plain)
        .help(copied ? "Copied" : help)
        .onDisappear { resetTask?.cancel() }
    }

    private func perform() {
        Clipboard.copy(text)
        withAnimation(.easeOut(duration: 0.12)) { copied = true }
        // A single in-flight reset: rapid re-taps keep the checkmark up rather than
        // stacking timers that fight each other.
        resetTask?.cancel()
        resetTask = Task {
            try? await Task.sleep(for: .seconds(1.4))
            guard !Task.isCancelled else { return }
            withAnimation(.easeIn(duration: 0.2)) { copied = false }
        }
    }
}

/// Overlays a `CopyButton` in the top-trailing corner that fades in on hover. A thin
/// material sits behind the glyph so it stays legible over whatever it floats above.
struct CopyAffordance: ViewModifier {
    let text: String
    var help: String
    @State private var hovering = false

    func body(content: Content) -> some View {
        content
            .overlay(alignment: .topTrailing) {
                CopyButton(text: text, help: help)
                    .padding(6)
                    .opacity(hovering ? 1 : 0)
                    .animation(.easeInOut(duration: 0.15), value: hovering)
                    // Don't let a fully-faded button eat clicks meant for the text
                    // (e.g. selecting prose in the corner).
                    .allowsHitTesting(hovering)
            }
            .onHover { hovering = $0 }
    }
}

extension View {
    /// Reveal a copy-to-clipboard button in the corner on hover. `text` is what lands
    /// on the pasteboard.
    func copyable(_ text: String, help: String = "Copy") -> some View {
        modifier(CopyAffordance(text: text, help: help))
    }
}
