//
//  ModelInfoView.swift
//  Confab
//
//  Rich model information modal
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen
//
// This file is part of Confab.
//
// Confab is licensed under the PolyForm Noncommercial License 1.0.0.
// You may use, study, modify, and share it for any noncommercial
// purpose. Commercial use — including selling Confab or any part of
// it, standalone or bundled into another product or service —
// requires a separate commercial license from the copyright holder.
//
// See the LICENSE file or
// <https://polyformproject.org/licenses/noncommercial/1.0.0> for
// the full license text. For commercial licensing, contact Rune
// Olsen via <https://confab.no>.


import SwiftUI

struct ModelInfoView: View {
    let model: ModelInfo

    @Environment(\.dismiss) var dismiss
    @Bindable private var settings = SettingsService.shared
    @State private var showDescriptionCopied = false

    var body: some View {
        VStack(spacing: 0) {
            // Header
            HStack {
                Text("Model Info")
                    .font(.system(size: 18, weight: .bold))
                Spacer()
                let isFav = settings.favoriteModelIds.contains(model.id)
                Button(action: { settings.toggleFavoriteModel(model.id) }) {
                    Image(systemName: isFav ? "star.fill" : "star")
                        .font(.system(size: 18))
                        .foregroundColor(isFav ? .yellow : .secondary)
                }
                .buttonStyle(.plain)
                .help(isFav ? "Remove from favorites" : "Add to favorites")
                .padding(.trailing, 8)
                Button { dismiss() } label: {
                    Image(systemName: "xmark.circle.fill")
                        .font(.title2)
                        .foregroundStyle(.secondary)
                }
                .buttonStyle(.plain)
                .keyboardShortcut(.escape, modifiers: [])
            }
            .padding(.horizontal, 24)
            .padding(.top, 20)
            .padding(.bottom, 12)

            Divider()

            ScrollView {
                VStack(alignment: .leading, spacing: 20) {
                    // Overview
                    sectionHeader("Overview")
                    infoRow("Name", model.name)
                    infoRow("ID", model.id)
                    if let provider = model.topProvider {
                        infoRow("Provider", provider)
                    }
                    if let releaseDate = model.releaseDate {
                        infoRow("Released", releaseDate.formatted(date: .abbreviated, time: .omitted))
                    }
                    if let desc = model.description {
                        // Always shown in full, no truncate/expand toggle — Text with a lineLimit
                        // nested inside this view's ScrollView doesn't reliably compute wrapping/
                        // truncation (a well-documented SwiftUI/AppKit quirk: without a fixedSize
                        // hint it hard-clips mid-word with no ellipsis; with one, sibling views in
                        // the same VStack — like the former "More…" button — can silently fail to
                        // lay out). The modal itself already scrolls, so a long description just
                        // means more scrolling, which sidesteps the whole bug class.
                        VStack(alignment: .leading, spacing: 6) {
                            HStack(spacing: 6) {
                                Text("Description")
                                    .font(.subheadline.weight(.medium))
                                    .foregroundColor(.secondary)
                                Spacer()
                                // A Copy button instead of .textSelection(.enabled): clicking into
                                // a multi-line selectable Text hands it real AppKit first-responder
                                // status, and Escape then gets consumed by that text view's own
                                // cancelOperation: handling before it ever reaches this modal's
                                // onExitCommand — the beep-instead-of-dismiss bug Rune reported.
                                // infoRow's single-line values keep .textSelection(.enabled); only
                                // this multi-line block reproduced the bug.
                                Button(action: copyDescription) {
                                    HStack(spacing: 3) {
                                        Image(systemName: showDescriptionCopied ? "checkmark" : "doc.on.doc")
                                            .font(.system(size: 11))
                                        if showDescriptionCopied {
                                            Text("Copied!")
                                                .font(.system(size: 11))
                                        }
                                    }
                                    .foregroundColor(showDescriptionCopied ? .green : .secondary)
                                }
                                .buttonStyle(.plain)
                            }
                            Text(desc)
                                .font(.body)
                                .foregroundColor(.primary)
                        }
                        .padding(.leading, 4)
                    }

                    Divider()

                    // Pricing
                    sectionHeader("Pricing")
                    infoRow("Input", model.promptPriceDisplay + " / 1M tokens")
                    infoRow("Output", model.completionPriceDisplay + " / 1M tokens")

                    if model.pricing.prompt > 0 {
                        VStack(alignment: .leading, spacing: 6) {
                            Text("Cost Examples")
                                .font(.caption)
                                .foregroundColor(.secondary)
                            HStack(spacing: 16) {
                                costExample(label: "1K tokens", inputTokens: 1_000)
                                costExample(label: "10K tokens", inputTokens: 10_000)
                                costExample(label: "100K tokens", inputTokens: 100_000)
                            }
                        }
                        .padding(.leading, 4)
                    }

                    Divider()

                    // Context Window
                    sectionHeader("Context Window")
                    infoRow("Max Tokens", model.contextLength.formatted())

                    if model.contextLength > 0 {
                        VStack(alignment: .leading, spacing: 4) {
                            let maxContext = 2_000_000.0
                            let fraction = min(Double(model.contextLength) / maxContext, 1.0)
                            ZStack(alignment: .leading) {
                                RoundedRectangle(cornerRadius: 4)
                                    .fill(Color.gray.opacity(0.2))
                                    .frame(height: 16)
                                GeometryReader { geo in
                                    RoundedRectangle(cornerRadius: 4)
                                        .fill(Color.blue)
                                        .frame(width: geo.size.width * fraction, height: 16)
                                }
                                .frame(height: 16)
                            }
                            Text(model.contextLengthDisplay + " tokens")
                                .font(.caption2)
                                .foregroundColor(.secondary)
                        }
                        .padding(.leading, 4)
                    }

                    Divider()

                    // Capabilities
                    sectionHeader("Capabilities")
                    HStack(spacing: 12) {
                        capabilityBadge(icon: "eye.fill", label: "Vision", active: model.capabilities.vision)
                        capabilityBadge(icon: "wrench.fill", label: "Tools", active: model.capabilities.tools)
                        capabilityBadge(icon: "globe", label: "Online", active: model.capabilities.online)
                        capabilityBadge(icon: "photo.fill", label: "Image Gen", active: model.capabilities.imageGeneration)
                        capabilityBadge(icon: "brain", label: "Thinking", active: model.capabilities.thinking)
                    }

                    // Categories (if any)
                    if !model.categories.isEmpty {
                        Divider()
                        sectionHeader("Categories")
                        FlowLayout(spacing: 8) {
                            ForEach(model.categories, id: \.rawValue) { cat in
                                HStack(spacing: 5) {
                                    Image(systemName: cat.systemImage)
                                        .font(.caption2)
                                    Text(LocalizedStringKey(cat.rawValue))
                                        .font(.caption)
                                }
                                .padding(.horizontal, 10)
                                .padding(.vertical, 6)
                                .background(cat.color.opacity(0.12))
                                .foregroundColor(cat.color)
                                .cornerRadius(6)
                                .overlay(
                                    RoundedRectangle(cornerRadius: 6)
                                        .strokeBorder(cat.color.opacity(0.35), lineWidth: 1)
                                )
                            }
                        }
                        .padding(.leading, 4)
                    }

                    // Architecture (if available)
                    if let arch = model.architecture {
                        Divider()
                        sectionHeader("Architecture")
                        if let modality = arch.modality {
                            infoRow("Modality", modality)
                        }
                        if let tokenizer = arch.tokenizer {
                            infoRow("Tokenizer", tokenizer)
                        }
                        if let instructType = arch.instructType {
                            infoRow("Instruct Type", instructType)
                        }
                    }
                }
                .padding(.horizontal, 24)
                .padding(.vertical, 16)
            }

            Divider()

            // Bottom bar
            HStack {
                Spacer()
                Button("Done") { dismiss() }
                    .keyboardShortcut(.return, modifiers: [])
                    .buttonStyle(.borderedProminent)
                    .controlSize(.regular)
                Spacer()
            }
            .padding(.horizontal, 24)
            .padding(.vertical, 12)
        }
        .frame(minWidth: 550, idealWidth: 650, minHeight: 550, idealHeight: 750)
        // Nearly every value in this modal has .textSelection(.enabled) (infoRow's value text,
        // the description). Once one of those has text-selection focus, Escape can get
        // intercepted by AppKit's text-selection machinery instead of reaching the close
        // button's .keyboardShortcut(.escape) — no action is bound there, so it just beeps
        // instead of dismissing. onExitCommand is macOS's dedicated hook for the "Escape/Cancel"
        // user command and fires regardless of which child currently holds focus, so it's a more
        // reliable place to handle this than a single button's keyboardShortcut alone.
        .onExitCommand { dismiss() }
    }

    private func copyDescription() {
        guard let desc = model.description else { return }
        let pasteboard = NSPasteboard.general
        pasteboard.clearContents()
        pasteboard.setString(desc, forType: .string)
        withAnimation {
            showDescriptionCopied = true
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
            withAnimation {
                showDescriptionCopied = false
            }
        }
    }

    // MARK: - Layout Helpers

    private func sectionHeader(_ title: LocalizedStringKey) -> some View {
        Text(title)
            .font(.system(size: 13, weight: .semibold))
            .foregroundStyle(.secondary)
            .textCase(.uppercase)
    }

    private func infoRow(_ label: LocalizedStringKey, _ value: String) -> some View {
        HStack {
            Text(label)
                .font(.body)
            Spacer()
            Text(value)
                .font(.body)
                .foregroundColor(.secondary)
                .textSelection(.enabled)
        }
        .padding(.leading, 4)
    }

    @ViewBuilder
    private func costExample(label: LocalizedStringKey, inputTokens: Int) -> some View {
        let cost = (Double(inputTokens) * model.pricing.prompt / 1_000_000) +
                   (Double(inputTokens) * model.pricing.completion / 1_000_000)
        VStack(spacing: 2) {
            Text(label)
                .font(.caption2)
                .foregroundColor(.secondary)
            Text(String(format: "$%.4f", cost))
                .font(.caption.monospacedDigit())
                .foregroundColor(.primary)
        }
        .padding(.horizontal, 8)
        .padding(.vertical, 4)
        .background(Color.gray.opacity(0.1))
        .cornerRadius(4)
    }

    @ViewBuilder
    private func capabilityBadge(icon: String, label: LocalizedStringKey, active: Bool) -> some View {
        VStack(spacing: 4) {
            Image(systemName: icon)
                .font(.title3)
                .foregroundColor(active ? .blue : .gray.opacity(0.4))
            Text(label)
                .font(.caption2)
                .foregroundColor(active ? .primary : .secondary)
        }
        .frame(maxWidth: .infinity)
        .padding(.vertical, 8)
        .background(active ? Color.blue.opacity(0.1) : Color.gray.opacity(0.05))
        .cornerRadius(8)
    }
}

// MARK: - Flow Layout

struct FlowLayout: Layout {
    var spacing: CGFloat = 8

    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) -> CGSize {
        let rows = rows(for: subviews, width: proposal.width ?? .infinity)
        let height = rows.map { row in
            row.map { $0.sizeThatFits(.unspecified).height }.max() ?? 0
        }.reduce(0, +) + CGFloat(max(0, rows.count - 1)) * spacing
        return CGSize(width: proposal.width ?? 0, height: height)
    }

    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) {
        let rows = rows(for: subviews, width: bounds.width)
        var y = bounds.minY
        for row in rows {
            var x = bounds.minX
            let rowH = row.map { $0.sizeThatFits(.unspecified).height }.max() ?? 0
            for sub in row {
                let size = sub.sizeThatFits(.unspecified)
                sub.place(at: CGPoint(x: x, y: y), proposal: ProposedViewSize(size))
                x += size.width + spacing
            }
            y += rowH + spacing
        }
    }

    private func rows(for subviews: Subviews, width: CGFloat) -> [[LayoutSubview]] {
        var rows: [[LayoutSubview]] = [[]]
        var rowWidth: CGFloat = 0
        for sub in subviews {
            let w = sub.sizeThatFits(.unspecified).width
            if rowWidth + w > width, !rows.last!.isEmpty {
                rows.append([])
                rowWidth = 0
            }
            rows[rows.count - 1].append(sub)
            rowWidth += w + spacing
        }
        return rows
    }
}

#Preview {
    ModelInfoView(model: ModelInfo(
        id: "anthropic/claude-sonnet-4",
        name: "Claude Sonnet 4",
        description: "Balanced intelligence and speed. This is a longer description to test how the modal handles multi-line text that wraps across several lines in the description field.",
        contextLength: 200_000,
        pricing: .init(prompt: 3.0, completion: 15.0),
        capabilities: .init(vision: true, tools: true, online: false),
        architecture: .init(tokenizer: "claude", instructType: "claude", modality: "text+image->text"),
        topProvider: "anthropic"
    ))
}
