import SwiftUI
import UIKit
import WebKit
import PhotosUI

// MARK: - UITextView subclass that intercepts image paste from the context menu

final class ImagePasteTextView: UITextView {
    /// Called when the user selects Paste and the clipboard contains an image.
    var onImagePaste: ((UIImage) -> Void)?

    override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
        // Show the standard Paste item whenever the clipboard has an image OR text.
        if action == #selector(paste(_:)) {
            return UIPasteboard.general.hasImages || UIPasteboard.general.hasStrings
        }
        return super.canPerformAction(action, withSender: sender)
    }

    override func paste(_ sender: Any?) {
        // If there is an image on the clipboard, intercept and forward it.
        if let image = UIPasteboard.general.image {
            onImagePaste?(image)
        } else {
            super.paste(sender)
        }
    }
}

// MARK: - UITextView wrapper that exposes selection-aware formatting

struct MarkdownTextEditor: UIViewRepresentable {
    @Binding var text: String
    /// Called with the UITextView so the parent can apply formatting
    var onTextViewReady: (UITextView) -> Void
    /// Called when the user pastes an image via the context menu
    var onImagePaste: ((UIImage) -> Void)? = nil

    func makeUIView(context: Context) -> ImagePasteTextView {
        let tv = ImagePasteTextView()
        tv.font = UIFont.monospacedSystemFont(ofSize: 16, weight: .regular)
        tv.autocorrectionType = .no
        tv.autocapitalizationType = .none
        tv.delegate = context.coordinator
        tv.backgroundColor = .clear
        tv.isScrollEnabled = true
        tv.alwaysBounceVertical = true
        tv.textContainerInset = UIEdgeInsets(top: 8, left: 8, bottom: 80, right: 8)
        // Set initial text (e.g. when editing an existing page)
        tv.text = text
        tv.onImagePaste = onImagePaste
        onTextViewReady(tv)
        return tv
    }

    func updateUIView(_ tv: ImagePasteTextView, context: Context) {
        // Keep the paste closure up to date (captures may change across renders)
        tv.onImagePaste = onImagePaste
        // Only push changes that originated outside the UITextView (e.g. formatting toolbar).
        // Skip updates triggered by the user typing to avoid cursor position resets.
        guard !context.coordinator.isEditing, tv.text != text else { return }
        let sel = tv.selectedRange
        tv.text = text
        // Clamp selection to new text length
        let len = tv.text.utf16.count
        tv.selectedRange = NSRange(location: min(sel.location, len), length: 0)
    }

    func makeCoordinator() -> Coordinator { Coordinator(self) }

    final class Coordinator: NSObject, UITextViewDelegate {
        var parent: MarkdownTextEditor
        /// True while the user is actively editing, so updateUIView won't fight the keyboard.
        var isEditing = false

        init(_ parent: MarkdownTextEditor) { self.parent = parent }

        func textViewDidBeginEditing(_ textView: UITextView) {
            isEditing = true
        }

        func textViewDidEndEditing(_ textView: UITextView) {
            isEditing = false
        }

        func textViewDidChange(_ textView: UITextView) {
            parent.text = textView.text
        }
    }
}

// MARK: - Page Editor

struct PageEditorView: View {
    @State private var viewModel: PageEditorViewModel
    @Environment(\.dismiss) private var dismiss
    @Environment(\.accentTheme) private var theme
    @State private var showDiscardAlert = false
    @State private var showSavedConfirmation = false
    /// Reference to the underlying UITextView for formatting operations
    @State private var textView: UITextView? = nil
    @State private var imagePickerItem: PhotosPickerItem? = nil
    @State private var showTagEditor = false

    init(mode: PageEditorViewModel.Mode) {
        _viewModel = State(initialValue: PageEditorViewModel(mode: mode))
    }

    var body: some View {
        editorContent
            .navigationTitle(navigationTitle)
            .navigationBarTitleDisplayMode(.inline)
            .toolbar { editorToolbar }
            .alert(L("editor.close.unsaved.title"), isPresented: $showDiscardAlert) {
                Button(L("editor.close.unsaved.confirm"), role: .destructive) { dismiss() }
                Button(L("editor.discard.keepediting"), role: .cancel) {}
            } message: {
                Text(L("editor.discard.message"))
            }
            .onChange(of: imagePickerItem) { _, newItem in
                guard let newItem else { return }
                Task {
                    guard let data = try? await newItem.loadTransferable(type: Data.self) else { return }
                    let mimeType: String
                    let filename: String
                    if let uti = newItem.supportedContentTypes.first {
                        if uti.conforms(to: .png) {
                            mimeType = "image/png"; filename = "image.png"
                        } else if uti.conforms(to: .webP) {
                            mimeType = "image/webp"; filename = "image.webp"
                        } else {
                            mimeType = "image/jpeg"; filename = "image.jpg"
                        }
                    } else {
                        mimeType = "image/jpeg"; filename = "image.jpg"
                    }
                    await viewModel.uploadImage(data: data, filename: filename, mimeType: mimeType)
                    imagePickerItem = nil
                }
            }
            .onChange(of: viewModel.pendingImageMarkdown) { _, markdown in
                guard let markdown else { return }
                viewModel.pendingImageMarkdown = nil
                guard let tv = textView else {
                    viewModel.markdownContent += "\n\(markdown)"
                    return
                }
                let range = tv.selectedRange
                let insertion = "\n\(markdown)\n"
                replace(in: tv, range: range, with: insertion, cursorOffset: insertion.count)
            }
            .task { await viewModel.loadAvailableTags() }
            .sheet(isPresented: $showTagEditor) {
                TagEditorSheet(viewModel: viewModel)
            }
    }

    // MARK: - Editor content (extracted for type-checker)

    @ViewBuilder
    private var editorContent: some View {
        VStack(spacing: 0) {
            // Title field
            VStack(spacing: 0) {
                TextField(L("editor.title.placeholder"), text: $viewModel.title)
                    .font(.system(size: 22, weight: .bold))
                    .padding(.horizontal, 16)
                    .padding(.top, 14)
                    .padding(.bottom, 10)
                Rectangle().fill(Color(.separator)).frame(height: 0.5)
            }

            // Tag strip
            TagStripView(
                tags: viewModel.tags,
                onRemove: { viewModel.removeTag($0) },
                onAdd: { showTagEditor = true }
            )
            Rectangle().fill(Color(.separator)).frame(height: 0.5)

            // Content area — fills all remaining vertical space
            Group {
                if viewModel.activeTab == .write {
                    writeArea
                } else {
                    MarkdownPreviewView(markdown: viewModel.markdownContent)
                }
            }
            .frame(maxHeight: .infinity)

            // Save error
            if let error = viewModel.saveError {
                ErrorBanner(error: error) { Task { await viewModel.save() } }
                    .padding()
            }
        }
        .frame(maxHeight: .infinity)
    }

    @ViewBuilder
    private var writeArea: some View {
        VStack(spacing: 0) {
            MarkdownTextEditor(text: $viewModel.markdownContent,
                               onTextViewReady: { tv in textView = tv },
                               onImagePaste: { image in
                Task {
                    let data = image.jpegData(compressionQuality: 0.85) ?? Data()
                    guard !data.isEmpty else { return }
                    await viewModel.uploadImage(data: data, filename: "clipboard.jpg", mimeType: "image/jpeg")
                }
            })
            .frame(maxHeight: .infinity)

            if case .uploading = viewModel.imageUploadState {
                HStack(spacing: 8) {
                    ProgressView().controlSize(.small)
                    Text(L("editor.image.uploading")).font(.footnote).foregroundStyle(.secondary)
                }
                .frame(maxWidth: .infinity)
                .padding(.vertical, 6)
                .background(Color(.secondarySystemBackground))
            }

            if case .failed(let msg) = viewModel.imageUploadState {
                HStack(spacing: 8) {
                    Image(systemName: "exclamationmark.circle.fill").foregroundStyle(.red)
                    Text(msg).font(.footnote).foregroundStyle(.red)
                    Spacer()
                    Button { viewModel.imageUploadState = .idle } label: {
                        Image(systemName: "xmark").font(.footnote)
                    }
                }
                .padding(.horizontal, 12)
                .padding(.vertical, 6)
                .background(Color(.secondarySystemBackground))
            }

            Divider()
            FormattingToolbar(
                imagePickerItem: $imagePickerItem,
                isUploadingImage: {
                    if case .uploading = viewModel.imageUploadState { return true }
                    return false
                }(),
                clipboardHasImage: UIPasteboard.general.hasImages,
                onPasteImage: { Task { await pasteImageFromClipboard() } }
            ) { action in applyFormat(action) }
        }
    }

    @ToolbarContentBuilder
    private var editorToolbar: some ToolbarContent {
        ToolbarItem(placement: .cancellationAction) {
            Button(L("editor.close")) {
                if viewModel.hasUnsavedChanges { showDiscardAlert = true } else { dismiss() }
            }
        }
        ToolbarItem(placement: .principal) {
            EditorTabToggle(activeTab: $viewModel.activeTab)
        }
        ToolbarItem(placement: .confirmationAction) {
            ZStack {
                if showSavedConfirmation {
                    Image(systemName: "checkmark.circle.fill")
                        .foregroundStyle(.green)
                        .transition(.scale.combined(with: .opacity))
                } else {
                    Button(L("editor.save")) {
                        Task {
                            await viewModel.save()
                            if viewModel.saveError == nil {
                                withAnimation(.spring(duration: 0.3)) { showSavedConfirmation = true }
                                try? await Task.sleep(for: .seconds(1.5))
                                withAnimation(.easeOut(duration: 0.3)) { showSavedConfirmation = false }
                            }
                        }
                    }
                    .disabled(viewModel.title.isEmpty || viewModel.markdownContent.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || viewModel.isSaving)
                    .overlay { if viewModel.isSaving { ProgressView().scaleEffect(0.7) } }
                    .transition(.opacity)
                }
            }
            .animation(.easeInOut(duration: 0.2), value: showSavedConfirmation)
        }
    }

    private var navigationTitle: String {
        switch viewModel.mode {
        case .create: return L("editor.new.title")
        case .edit:   return L("editor.edit.title")
        }
    }

    // MARK: - Paste image from clipboard

    private func pasteImageFromClipboard() async {
        guard let uiImage = UIPasteboard.general.image else { return }
        let data = uiImage.jpegData(compressionQuality: 0.85) ?? Data()
        guard !data.isEmpty else { return }
        await viewModel.uploadImage(data: data, filename: "clipboard.jpg", mimeType: "image/jpeg")
    }

    // MARK: - Apply formatting to selected text (or insert at cursor)

    private func applyFormat(_ action: FormatAction) {
        guard let tv = textView else { return }
        let text = tv.text ?? ""
        let nsText = text as NSString
        let range = tv.selectedRange

        switch action {

        // Inline wrap: surround selection or insert markers
        case .bold, .italic, .strikethrough, .inlineCode:
            let marker = action.inlineMarker
            if range.length > 0 {
                let selected = nsText.substring(with: range)
                let replacement = "\(marker)\(selected)\(marker)"
                replace(in: tv, range: range, with: replacement, cursorOffset: replacement.count)
            } else {
                let placeholder = action.placeholder
                let insertion = "\(marker)\(placeholder)\(marker)"
                replace(in: tv, range: range, with: insertion,
                        selectRange: NSRange(location: range.location + marker.count,
                                            length: placeholder.count))
            }

        // Line prefix: prepend to each selected line
        case .h1, .h2, .h3, .bulletList, .numberedList, .blockquote:
            let prefix = action.linePrefix
            applyLinePrefix(tv: tv, text: text, nsText: nsText, range: range, prefix: prefix)

        // Block insert at cursor
        case .codeBlock:
            let block = "```\n\(range.length > 0 ? nsText.substring(with: range) : "code")\n```"
            replace(in: tv, range: range, with: block, cursorOffset: block.count)

        case .link:
            if range.length > 0 {
                let selected = nsText.substring(with: range)
                let insertion = "[\(selected)](url)"
                replace(in: tv, range: range, with: insertion, cursorOffset: insertion.count)
            } else {
                let insertion = "[text](url)"
                replace(in: tv, range: range, with: insertion,
                        selectRange: NSRange(location: range.location + 1, length: 4))
            }

        case .horizontalRule:
            // Insert on its own line
            let before = range.location > 0 ? "\n" : ""
            let insertion = "\(before)---\n"
            replace(in: tv, range: range, with: insertion, cursorOffset: insertion.count)
        }
    }

    /// Replace a range in the UITextView and sync back to the binding.
    private func replace(in tv: UITextView, range: NSRange, with string: String,
                         cursorOffset: Int? = nil, selectRange: NSRange? = nil) {
        guard let swiftRange = Range(range, in: tv.text) else { return }
        var newText = tv.text!
        newText.replaceSubrange(swiftRange, with: string)
        tv.text = newText
        viewModel.markdownContent = newText
        // Position cursor
        if let sel = selectRange {
            tv.selectedRange = sel
        } else if let offset = cursorOffset {
            let newPos = range.location + offset
            tv.selectedRange = NSRange(location: min(newPos, newText.utf16.count), length: 0)
        }
    }

    /// Toggle a line prefix (e.g. `## `) on all lines touched by the selection.
    private func applyLinePrefix(tv: UITextView, text: String, nsText: NSString,
                                  range: NSRange, prefix: String) {
        // Expand range to full lines
        let lineRange = nsText.lineRange(for: range)
        let linesString = nsText.substring(with: lineRange)
        let lines = linesString.components(separatedBy: "\n")

        // Determine if ALL non-empty lines already have this prefix → toggle off
        let nonEmpty = lines.filter { !$0.isEmpty }
        let allPrefixed = !nonEmpty.isEmpty && nonEmpty.allSatisfy { $0.hasPrefix(prefix) }

        let transformed = lines.map { line -> String in
            if line.isEmpty { return line }
            if allPrefixed {
                return String(line.dropFirst(prefix.count))
            } else {
                return line.hasPrefix(prefix) ? line : prefix + line
            }
        }.joined(separator: "\n")

        replace(in: tv, range: lineRange, with: transformed, cursorOffset: transformed.count)
    }
}

// MARK: - Write / Preview Tab Toggle

struct EditorTabToggle: View {
    @Binding var activeTab: PageEditorViewModel.EditorTab

    var body: some View {
        HStack(spacing: 0) {
            tabButton(L("editor.tab.write"), tab: .write)
            tabButton(L("editor.tab.preview"), tab: .preview)
        }
        .background(Color(.secondarySystemBackground), in: Capsule())
        .padding(.vertical, 4)
    }

    @ViewBuilder
    private func tabButton(_ label: String, tab: PageEditorViewModel.EditorTab) -> some View {
        let isSelected = activeTab == tab
        Text(label)
            .font(.system(size: 13, weight: isSelected ? .semibold : .regular))
            .foregroundStyle(isSelected ? .primary : .secondary)
            .padding(.horizontal, 14)
            .padding(.vertical, 6)
            .background(
                isSelected
                    ? Color(.systemBackground)
                    : Color.clear,
                in: Capsule()
            )
            .padding(3)
            .animation(.easeInOut(duration: 0.18), value: activeTab)
            .onTapGesture { activeTab = tab }
    }
}

// MARK: - Format Actions

enum FormatAction {
    case h1, h2, h3
    case bold, italic, strikethrough, inlineCode
    case bulletList, numberedList, blockquote
    case link, codeBlock, horizontalRule

    var inlineMarker: String {
        switch self {
        case .bold:          return "**"
        case .italic:        return "*"
        case .strikethrough: return "~~"
        case .inlineCode:    return "`"
        default:             return ""
        }
    }

    var linePrefix: String {
        switch self {
        case .h1:           return "# "
        case .h2:           return "## "
        case .h3:           return "### "
        case .bulletList:   return "- "
        case .numberedList: return "1. "
        case .blockquote:   return "> "
        default:            return ""
        }
    }

    var placeholder: String {
        switch self {
        case .bold:          return "bold"
        case .italic:        return "italic"
        case .strikethrough: return "text"
        case .inlineCode:    return "code"
        default:             return ""
        }
    }
}

// MARK: - Formatting Toolbar

struct FormattingToolbar: View {
    @Binding var imagePickerItem: PhotosPickerItem?
    let isUploadingImage: Bool
    let clipboardHasImage: Bool
    let onPasteImage: () -> Void
    let onAction: (FormatAction) -> Void

    var body: some View {
        VStack(spacing: 0) {
            Rectangle()
                .fill(Color(.separator))
                .frame(height: 0.5)

            // Row 1: Headings + inline formatting
            HStack(spacing: 0) {
                FormatButton("H1", action: .h1, onAction: onAction)
                FormatButton("H2", action: .h2, onAction: onAction)
                FormatButton("H3", action: .h3, onAction: onAction)
                toolbarDivider
                FormatButton(systemImage: "bold", action: .bold, onAction: onAction)
                FormatButton(systemImage: "italic", action: .italic, onAction: onAction)
                FormatButton(systemImage: "strikethrough", action: .strikethrough, onAction: onAction)
                FormatButton(systemImage: "chevron.left.forwardslash.chevron.right", action: .inlineCode, onAction: onAction)
            }
            .frame(maxWidth: .infinity, minHeight: 28)

            Rectangle()
                .fill(Color(.separator))
                .frame(height: 0.5)

            // Row 2: Lists + block elements + image
            HStack(spacing: 0) {
                FormatButton(systemImage: "list.bullet", action: .bulletList, onAction: onAction)
                FormatButton(systemImage: "list.number", action: .numberedList, onAction: onAction)
                FormatButton(systemImage: "text.quote", action: .blockquote, onAction: onAction)
                toolbarDivider
                FormatButton(systemImage: "link", action: .link, onAction: onAction)
                FormatButton(systemImage: "curlybraces", action: .codeBlock, onAction: onAction)
                FormatButton(systemImage: "minus", action: .horizontalRule, onAction: onAction)
                toolbarDivider
                // Image picker (from photo library)
                PhotosPicker(
                    selection: $imagePickerItem,
                    matching: .images,
                    photoLibrary: .shared()
                ) {
                    Group {
                        if isUploadingImage {
                            ProgressView().controlSize(.small)
                        } else {
                            Image(systemName: "photo")
                                .font(.system(size: 13, weight: .regular))
                        }
                    }
                    .frame(maxWidth: .infinity, minHeight: 28)
                    .foregroundStyle(.secondary)
                }
                .disabled(isUploadingImage)
                // Paste image from clipboard
                Button(action: onPasteImage) {
                    Image(systemName: clipboardHasImage ? "doc.on.clipboard.fill" : "doc.on.clipboard")
                        .font(.system(size: 13, weight: .regular))
                        .frame(maxWidth: .infinity, minHeight: 28)
                        .foregroundStyle(clipboardHasImage ? .primary : .tertiary)
                }
                .disabled(!clipboardHasImage || isUploadingImage)
            }
            .frame(maxWidth: .infinity, minHeight: 28)
        }
        .background(Color(.systemBackground))
    }

    private var toolbarDivider: some View {
        Rectangle()
            .fill(Color(.separator))
            .frame(width: 0.5, height: 16)
            .padding(.horizontal, 2)
    }
}

struct FormatButton: View {
    let label: String?
    let systemImage: String?
    let action: FormatAction
    let onAction: (FormatAction) -> Void

    init(_ label: String, action: FormatAction, onAction: @escaping (FormatAction) -> Void) {
        self.label = label
        self.systemImage = nil
        self.action = action
        self.onAction = onAction
    }

    init(systemImage: String, action: FormatAction, onAction: @escaping (FormatAction) -> Void) {
        self.label = nil
        self.systemImage = systemImage
        self.action = action
        self.onAction = onAction
    }

    var body: some View {
        // Use onTapGesture instead of Button so the toolbar tap doesn't
        // resign the UITextView's first responder (which would clear the selection).
        Group {
            if let label {
                Text(label)
                    .font(.system(size: 11, weight: .medium, design: .rounded))
            } else if let systemImage {
                Image(systemName: systemImage)
                    .font(.system(size: 13, weight: .regular))
            }
        }
        .frame(maxWidth: .infinity, minHeight: 28)
        .foregroundStyle(.secondary)
        .contentShape(Rectangle())
        .onTapGesture {
            onAction(action)
        }
    }
}

// MARK: - Markdown Preview

struct MarkdownPreviewView: View {
    let markdown: String
    @State private var webPage = WebPage()
    @Environment(\.colorScheme) private var colorScheme

    var body: some View {
        WebView(webPage)
            .onAppear { loadPreview() }
            .onChange(of: markdown) { loadPreview() }
            .onChange(of: colorScheme) { loadPreview() }
    }

    private func loadPreview() {
        let html = markdownToHTML(markdown)
        let isDark = colorScheme == .dark
        let bg = isDark ? "#1c1c1e" : "#ffffff"
        let fg = isDark ? "#f2f2f7" : "#000000"
        let codeBg = isDark ? "#2c2c2e" : "#f2f2f7"

        let fullHTML = """
        <!DOCTYPE html><html>
        <head>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <style>
        body { font-family: -apple-system; font-size: 16px; line-height: 1.6; padding: 16px;
               background: \(bg); color: \(fg); }
        pre { background: \(codeBg); padding: 12px; border-radius: 8px; overflow-x: auto; }
        code { font-family: "SF Mono", monospace; font-size: 14px; background: \(codeBg);
               padding: 2px 4px; border-radius: 4px; }
        pre code { background: transparent; padding: 0; }
        blockquote { border-left: 3px solid #0a84ff; padding-left: 12px; color: #8e8e93; margin-left: 0; }
        </style>
        </head>
        <body>\(html)</body></html>
        """
        // Use the real server URL as base so WKWebView permits loading images from it.
        let serverBase = UserDefaults.standard.string(forKey: "serverURL").flatMap { URL(string: $0) }
            ?? URL(string: "about:blank")!
        webPage.load(html: fullHTML, baseURL: serverBase)
    }

    /// Minimal Markdown → HTML converter for preview purposes.
    /// For editing purposes the full rendering happens server-side.
    private func markdownToHTML(_ md: String) -> String {
        var html = md
        // Code blocks (must come before inline code)
        let codeBlockPattern = #"```[\w]*\n([\s\S]*?)```"#
        html = html.replacingOccurrences(of: codeBlockPattern, with: "<pre><code>$1</code></pre>",
                                          options: .regularExpression)
        // Inline code
        html = html.replacingOccurrences(of: "`([^`]+)`", with: "<code>$1</code>",
                                          options: .regularExpression)
        // Bold + italic
        html = html.replacingOccurrences(of: "\\*\\*\\*(.+?)\\*\\*\\*", with: "<strong><em>$1</em></strong>",
                                          options: .regularExpression)
        html = html.replacingOccurrences(of: "\\*\\*(.+?)\\*\\*", with: "<strong>$1</strong>",
                                          options: .regularExpression)
        html = html.replacingOccurrences(of: "\\*(.+?)\\*", with: "<em>$1</em>",
                                          options: .regularExpression)
        // Headings (use (?m) inline flag for multiline anchors)
        for h in stride(from: 6, through: 1, by: -1) {
            html = html.replacingOccurrences(of: "(?m)^#{" + "\(h)" + "} (.+)$",
                                              with: "<h\(h)>$1</h\(h)>",
                                              options: .regularExpression)
        }
        // Images (must come before links to avoid partial matches)
        html = html.replacingOccurrences(of: #"!\[([^\]]*)\]\(([^)]+)\)"#,
                                          with: "<img src=\"$2\" alt=\"$1\" style=\"max-width:100%;height:auto;border-radius:6px;\">",
                                          options: .regularExpression)
        // Links
        html = html.replacingOccurrences(of: #"\[([^\]]+)\]\(([^)]+)\)"#,
                                          with: "<a href=\"$2\">$1</a>",
                                          options: .regularExpression)
        // Horizontal rule
        html = html.replacingOccurrences(of: "(?m)^---$", with: "<hr>",
                                          options: .regularExpression)
        // Unordered list items
        html = html.replacingOccurrences(of: "(?m)^[\\-\\*] (.+)$", with: "<li>$1</li>",
                                          options: .regularExpression)
        // Blockquote
        html = html.replacingOccurrences(of: "(?m)^> (.+)$", with: "<blockquote>$1</blockquote>",
                                          options: .regularExpression)
        // Paragraphs: double newlines become <br><br>
        html = html.replacingOccurrences(of: "\n\n", with: "<br><br>")
        return html
    }
}

// MARK: - Tag Strip (inline in editor)

struct TagStripView: View {
    let tags: [TagDTO]
    let onRemove: (TagDTO) -> Void
    let onAdd: () -> Void
    @Environment(\.accentTheme) private var theme

    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 6) {
                ForEach(tags) { tag in
                    HStack(spacing: 4) {
                        Text(tag.value.isEmpty ? tag.name : "\(tag.name): \(tag.value)")
                            .font(.caption.weight(.medium))
                            .foregroundStyle(theme.accentColor)
                        Button {
                            onRemove(tag)
                        } label: {
                            Image(systemName: "xmark")
                                .font(.system(size: 9, weight: .bold))
                                .foregroundStyle(theme.accentColor.opacity(0.7))
                        }
                        .buttonStyle(.plain)
                    }
                    .padding(.horizontal, 8)
                    .padding(.vertical, 4)
                    .background(theme.accentColor.opacity(0.12), in: Capsule())
                }

                // Add tag button
                Button(action: onAdd) {
                    Label(L("editor.tags.add"), systemImage: "tag")
                        .font(.caption.weight(.medium))
                        .foregroundStyle(.secondary)
                }
                .buttonStyle(.plain)
                .padding(.horizontal, 8)
                .padding(.vertical, 4)
                .background(Color(.secondarySystemBackground), in: Capsule())
            }
            .padding(.horizontal, 16)
            .padding(.vertical, 8)
        }
    }
}

// MARK: - Tag Editor Sheet

struct TagEditorSheet: View {
    /// Using the viewModel directly ensures live updates to availableTags and
    /// that mutations to tags are always reflected back in the editor.
    var viewModel: PageEditorViewModel
    @Environment(\.dismiss) private var dismiss
    @Environment(\.accentTheme) private var theme

    @State private var newTagName: String = ""
    @State private var newTagValue: String = ""
    @State private var searchText: String = ""

    // Tags from the server not yet assigned to this page
    private var unassignedTags: [TagDTO] {
        let assignedIds = Set(viewModel.tags.map(\.id))
        let all = viewModel.availableTags.filter { !assignedIds.contains($0.id) }
        // Deduplicate by name+value, then filter by search
        var seen = Set<String>()
        let unique = all.filter { seen.insert($0.id).inserted }
        guard !searchText.isEmpty else { return unique }
        return unique.filter {
            $0.name.localizedCaseInsensitiveContains(searchText) ||
            $0.value.localizedCaseInsensitiveContains(searchText)
        }
    }

    var body: some View {
        NavigationStack {
            List {
                // Currently assigned tags
                if !viewModel.tags.isEmpty {
                    Section(L("editor.tags.current")) {
                        ForEach(viewModel.tags) { tag in
                            HStack {
                                Image(systemName: "tag.fill")
                                    .foregroundStyle(theme.accentColor)
                                    .font(.footnote)
                                VStack(alignment: .leading, spacing: 1) {
                                    Text(tag.name)
                                        .font(.body)
                                    if !tag.value.isEmpty {
                                        Text(tag.value)
                                            .font(.caption)
                                            .foregroundStyle(.secondary)
                                    }
                                }
                                Spacer()
                                Button {
                                    viewModel.removeTag(tag)
                                } label: {
                                    Image(systemName: "minus.circle.fill")
                                        .foregroundStyle(.red)
                                        .font(.title3)
                                }
                                .buttonStyle(.plain)
                            }
                        }
                    }
                }

                // Available tags from server — tap to assign
                if !unassignedTags.isEmpty || viewModel.isLoadingTags {
                    Section(L("editor.tags.available")) {
                        if viewModel.isLoadingTags {
                            HStack(spacing: 8) {
                                ProgressView().controlSize(.small)
                                Text(L("editor.tags.loading"))
                                    .foregroundStyle(.secondary)
                                    .font(.subheadline)
                            }
                        } else {
                            ForEach(unassignedTags) { tag in
                                HStack {
                                    Image(systemName: "tag")
                                        .foregroundStyle(theme.accentColor)
                                        .font(.footnote)
                                    VStack(alignment: .leading, spacing: 1) {
                                        Text(tag.name)
                                            .font(.body)
                                            .foregroundStyle(.primary)
                                        if !tag.value.isEmpty {
                                            Text(tag.value)
                                                .font(.caption)
                                                .foregroundStyle(.secondary)
                                        }
                                    }
                                    Spacer()
                                    Image(systemName: "plus.circle")
                                        .foregroundStyle(theme.accentColor)
                                }
                                .contentShape(Rectangle())
                                .onTapGesture {
                                    viewModel.addTag(name: tag.name, value: tag.value)
                                }
                            }
                        }
                    }
                }

                // Create a brand-new tag
                Section(L("editor.tags.new")) {
                    HStack {
                        Image(systemName: "tag")
                            .foregroundStyle(.secondary)
                            .font(.footnote)
                        TextField(L("editor.tags.name"), text: $newTagName)
                            .autocorrectionDisabled()
                    }
                    HStack {
                        Image(systemName: "textformat")
                            .foregroundStyle(.secondary)
                            .font(.footnote)
                        TextField(L("editor.tags.value"), text: $newTagValue)
                            .autocorrectionDisabled()
                    }
                    Button {
                        addTag(name: newTagName, value: newTagValue)
                    } label: {
                        Label(L("editor.tags.create"), systemImage: "plus.circle.fill")
                            .foregroundStyle(newTagName.trimmingCharacters(in: .whitespaces).isEmpty
                                             ? .secondary : theme.accentColor)
                    }
                    .disabled(newTagName.trimmingCharacters(in: .whitespaces).isEmpty)
                }
            }
            .navigationTitle(L("editor.tags.title"))
            .navigationBarTitleDisplayMode(.inline)
            .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: L("editor.tags.search"))
            .toolbar {
                ToolbarItem(placement: .confirmationAction) {
                    Button(L("common.done")) { dismiss() }
                }
            }
        }
    }

    private func addTag(name: String, value: String) {
        let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
        let trimmedValue = value.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmedName.isEmpty else { return }
        viewModel.addTag(name: trimmedName, value: trimmedValue)
        newTagName = ""
        newTagValue = ""
    }
}

#Preview("New Page") {
    NavigationStack { PageEditorView(mode: .create(bookId: 1)) }
}

#Preview("Edit Page") {
    NavigationStack { PageEditorView(mode: .edit(page: .mock)) }
}
