// SPDX-FileCopyrightText: 2026 John Burwell and contributors
// SPDX-License-Identifier: AGPL-3.0-or-later

import AppKit
import SwiftUI
import UniformTypeIdentifiers

struct ContentView: View {
    @StateObject private var library: SourceLibrary
    @State private var selectedItemID: MinecraftContentItem.ID?
    @State private var selectedSidebarSelection: SidebarSelection?
    @State private var columnVisibility: NavigationSplitViewVisibility = .all
    @State private var searchText = ""
    @State private var isDropTargeted = false
    @State private var isPerformingItemAction = false
    @State private var isShowingDeviceSourceSheet = false
    @State private var sortMode: ItemSortMode = .name
    @State private var directoryPreviewContents: [DirectoryEntry] = []
    @State private var showsProjectionLoadingState = false
    @State private var itemListProjection = ItemCollectionProjection.placeholder(
        for: ItemCollectionProjectionRequest(
            selection: nil,
            searchText: "",
            sortMode: .name,
            source: nil
        )
    )

    private let connectedDeviceAccess: AppleMobileDeviceSourceAccess
    private let deviceSourceFactory: ConnectedDeviceSourceFactory
    private let itemActionService: ContentItemActionService
    private let directoryPreviewLimit = 12
    private let projectionLoadingDelay: Duration = .milliseconds(150)

    init(
        dependencies: ContentViewDependencies = ContentViewDependencies.makeDefault(),
        initialSidebarSelection: SidebarSelection? = nil,
        initialItemID: MinecraftContentItem.ID? = nil
    ) {
        self.connectedDeviceAccess = dependencies.connectedDeviceAccess
        self.deviceSourceFactory = dependencies.deviceSourceFactory
        self.itemActionService = dependencies.itemActionService
        _library = StateObject(
            wrappedValue: dependencies.library
        )
        _selectedSidebarSelection = State(initialValue: initialSidebarSelection)
        _selectedItemID = State(initialValue: initialItemID)
    }

    var body: some View {
        let isEmptyLibrary = library.visibleSources.isEmpty && library.sidebarConnectedDevices.isEmpty && library.sourceCandidates.isEmpty
        let resolvedCurrentSource = currentSource
        let resolvedCurrentSourceCandidate = currentSourceCandidate
        let resolvedCurrentConnectedDevice = currentConnectedDevice
        let currentProjectionRequest = ItemCollectionProjectionRequest(
            selection: selectedSidebarSelection,
            searchText: searchText,
            sortMode: sortMode,
            source: resolvedCurrentSource
        )
        let fastPathProjection = fastProjection(for: currentProjectionRequest, previousRequest: itemListProjection.request)
        let reusesCurrentProjectedItems =
            itemListProjection.request.selection == currentProjectionRequest.selection &&
            itemListProjection.request.source?.id == currentProjectionRequest.source?.id
        let resolvedItemListProjection = if let fastPathProjection {
            fastPathProjection
        } else if itemListProjection.request == currentProjectionRequest {
            itemListProjection
        } else {
            ItemCollectionProjection.placeholder(for: currentProjectionRequest)
        }
        let resolvedCurrentSelectedItem = currentSelectedItem(in: resolvedCurrentSource)
        let resolvedDisplayedItems: [MinecraftContentItem] = if let fastPathProjection {
            fastPathProjection.items
        } else if reusesCurrentProjectedItems {
            itemListProjection.items
        } else {
            []
        }

        NavigationSplitView(columnVisibility: $columnVisibility) {
            SourcesSidebarView(
                sources: library.sidebarSources,
                connectedDevices: library.sidebarConnectedDevices,
                sourceCandidates: library.sourceCandidates,
                isDiscoveringSourceCandidates: library.isDiscoveringSourceCandidates,
                selection: sidebarSelectionBinding,
                addSourceAction: pickFolder,
                discoverSourcesAction: {
                    library.perform(.discoverSourceCandidates)
                },
                addCandidateSourceAction: addCandidateSource(_:),
                addDeviceSourceAction: { isShowingDeviceSourceSheet = true },
                addConnectedDeviceAction: addConnectedDeviceSource(from:),
                rescanSourceAction: { source in
                    selectedSidebarSelection = .source(sourceID: source.id)
                    selectedItemID = nil
                    library.rescanSource(withID: source.id)
                },
                removeSourceAction: { source in
                    removeSource(source.id)
                },
                filters: sidebarFilters(for:)
            )
            .navigationSplitViewColumnWidth(min: 280, ideal: 320, max: 380)
        } content: {
            ItemListColumnView(
                isEmpty: isEmptyLibrary,
                isDropTargeted: $isDropTargeted,
                selectedItemID: $selectedItemID,
                searchText: $searchText,
                sortMode: $sortMode,
                showsHeader: shouldShowItemListHeader,
                sourceName: resolvedItemListProjection.sourceName,
                showsSourceName: !isSidebarVisible,
                title: resolvedItemListProjection.title,
                subtitle: resolvedItemListProjection.subtitle,
                showsSubtitle: isSearching || showsProjectionLoadingState,
                isRefreshing: resolvedCurrentSource?.isScanning == true,
                showsProjectionLoadingState: showsProjectionLoadingState,
                items: resolvedDisplayedItems,
                searchPrompt: resolvedItemListProjection.searchPrompt,
                chooseFolderAction: pickFolder,
                dropAction: handleDroppedProviders(_:),
                itemContextMenu: itemContextMenu(for:)
            )
            .navigationSplitViewColumnWidth(min: 340, ideal: 400, max: 460)
        } detail: {
            ItemDetailColumnView(
                item: resolvedCurrentSelectedItem,
                source: resolvedCurrentSource,
                sourceCandidate: resolvedCurrentSourceCandidate,
                connectedDevice: resolvedCurrentConnectedDevice,
                showsSourceDetails: resolvedCurrentSelectedItem == nil && isSourceOverviewSelection,
                behaviorPacks: resolvedCurrentSelectedItem.map { logicalPackReferences(for: $0, type: .behaviorPack) } ?? [],
                resourcePacks: resolvedCurrentSelectedItem.map { logicalPackReferences(for: $0, type: .resourcePack) } ?? [],
                worldsUsingPack: resolvedCurrentSelectedItem.map(worldsUsingPack(for:)) ?? [],
                backingPackInstances: resolvedCurrentSelectedItem.map(backingPackInstances(for:)) ?? [],
                isSuspiciousPack: resolvedCurrentSelectedItem.map(isSuspiciousPack(_:)) ?? false,
                contents: directoryPreviewContents,
                directoryPreviewLimit: directoryPreviewLimit,
                isEmpty: isEmptyLibrary,
                isPerformingItemAction: isPerformingItemAction,
                areFileActionsEnabled: areCurrentItemFileActionsEnabled,
                exportTitle: resolvedCurrentSelectedItem.map(primaryActionTitle(for:)),
                exportAction: {
                    guard let item = resolvedCurrentSelectedItem else {
                        return
                    }

                    saveItem(item)
                },
                revealAction: {
                    guard let item = resolvedCurrentSelectedItem else {
                        return
                    }

                    revealInFinder(item)
                },
                shareAction: { anchorView in
                    guard let item = resolvedCurrentSelectedItem else {
                        return
                    }

                    shareItem(item, from: anchorView)
                },
                addCandidateSourceAction: addCandidateSource(_:),
                revealCandidateAction: revealCandidateInFinder(_:),
                addConnectedDeviceAction: addConnectedDeviceSource(from:)
            )
            .frame(minWidth: 450)
        }
        .overlay {
            if library.isRestoringPersistedSources && isEmptyLibrary {
                LaunchRestoreOverlayView()
            }
        }
        .sheet(isPresented: $isShowingDeviceSourceSheet) {
            ConnectedDeviceSourcePickerView(
                deviceDiscoveryService: connectedDeviceAccess,
                sourceFactory: deviceSourceFactory,
                onAddSource: { source in
                    let sourceID = library.addSource(source, shouldPersist: true, shouldScan: true)
                    selectedSidebarSelection = .source(sourceID: sourceID)
                    selectedItemID = nil
                    isShowingDeviceSourceSheet = false
                }
            )
        }
        .task {
            AppTerminationCoordinator.shared.register(library: library)
        }
        .disabled(library.isRestoringPersistedSources && isEmptyLibrary)
        .onChange(of: resolvedDisplayedItems.map(\.id)) { _, filteredIDs in
            guard let selectedItemID, !filteredIDs.contains(selectedItemID) else {
                return
            }

            self.selectedItemID = nil
        }
        .onChange(of: library.sources.map(\.id)) { _, _ in
            syncSelection(with: library.visibleSources.map(\.id))
        }
        .onChange(of: library.sidebarConnectedDevices.map { "\($0.id)::\($0.matchedSourceID?.absoluteString ?? "nil")" }) { _, _ in
            syncSelection(with: library.visibleSources.map(\.id))
        }
        .task(id: currentProjectionRequest) {
            let request = currentProjectionRequest
            if let fastPathProjection = fastProjection(for: request, previousRequest: itemListProjection.request) {
                showsProjectionLoadingState = false
                itemListProjection = fastPathProjection
                return
            }

            let projection = await Task.detached(priority: .userInitiated) {
                ItemCollectionProjector.makeProjection(for: request)
            }.value
            guard !Task.isCancelled else {
                return
            }
            showsProjectionLoadingState = false
            itemListProjection = projection
        }
        .task(id: currentProjectionRequest) {
            showsProjectionLoadingState = false

            guard itemListProjection.request != currentProjectionRequest else {
                return
            }

            guard fastProjection(for: currentProjectionRequest, previousRequest: itemListProjection.request) == nil else {
                return
            }

            try? await Task.sleep(for: projectionLoadingDelay)
            guard !Task.isCancelled else {
                return
            }

            if itemListProjection.request != currentProjectionRequest {
                showsProjectionLoadingState = true
            }
        }
        .task(id: resolvedCurrentSelectedItem?.id) {
            await refreshDirectoryPreviewContents()
        }
    }

    private var sidebarSelectionBinding: Binding<SidebarSelection?> {
        Binding(
            get: { selectedSidebarSelection },
            set: { newSelection in
                if newSelection != selectedSidebarSelection {
                    selectedItemID = nil
                }

                selectedSidebarSelection = newSelection
            }
        )
    }

    private var currentSource: MinecraftSource? {
        guard let sourceID = selectedSidebarSelection?.sourceID else {
            return library.visibleSources.first
        }

        return library.source(withID: sourceID)
    }

    private var currentSourceCandidate: SourceCandidate? {
        guard case .sourceCandidate(let candidateID) = selectedSidebarSelection else {
            return nil
        }

        return library.sourceCandidates.first { $0.id == candidateID }
    }

    private var currentConnectedDevice: ConnectedDeviceSidebarEntry? {
        guard case .connectedDevice(let deviceID) = selectedSidebarSelection else {
            return nil
        }

        return library.sidebarConnectedDevices.first { $0.id == deviceID }
    }

    private func currentSelectedItem(in source: MinecraftSource?) -> MinecraftContentItem? {
        guard let selectedItemID else {
            return nil
        }

        if let source,
           let item = source.items.first(where: { $0.id == selectedItemID }) {
            return item
        }

        if let sourceID = library.sourceID(forItemID: selectedItemID),
           let source = library.source(withID: sourceID) {
            return source.items.first(where: { $0.id == selectedItemID })
        }

        return nil
    }

    private var sortComparator: (MinecraftContentItem, MinecraftContentItem) -> Bool {
        switch sortMode {
        case .name:
            return { lhs, rhs in
                lhs.displayName.localizedStandardCompare(rhs.displayName) == .orderedAscending
            }
        case .modifiedDate:
            return { lhs, rhs in
                switch (lhs.displayDate, rhs.displayDate) {
                case let (lhsDate?, rhsDate?):
                    if lhsDate != rhsDate {
                        return lhsDate > rhsDate
                    }
                case (.some, nil):
                    return true
                case (nil, .some):
                    return false
                case (nil, nil):
                    break
                }

                return lhs.displayName.localizedStandardCompare(rhs.displayName) == .orderedAscending
            }
        case .size:
            return { lhs, rhs in
                switch (lhs.sizeBytes, rhs.sizeBytes) {
                case let (lhsSize?, rhsSize?):
                    if lhsSize != rhsSize {
                        return lhsSize > rhsSize
                    }
                case (.some, nil):
                    return true
                case (nil, .some):
                    return false
                case (nil, nil):
                    break
                }

                return lhs.displayName.localizedStandardCompare(rhs.displayName) == .orderedAscending
            }
        }
    }

    private var areCurrentItemFileActionsEnabled: Bool {
        guard currentSelectedItem(in: currentSource) != nil else {
            return false
        }

        return currentSource?.availability == .available
    }

    private var isSearching: Bool {
        !searchText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
    }

    private var isSidebarVisible: Bool {
        columnVisibility == .all
    }

    private var shouldShowItemListHeader: Bool {
        isSearching || !isSidebarVisible
    }

    private func sidebarFilters(for source: MinecraftSource) -> [SidebarFilter] {
        let orderedKinds: [MinecraftContentKind] = [
            .world,
            .behaviorPack,
            .resourcePack,
            .dataPack,
            .skinPack,
            .worldTemplate,
            .shaderPack,
            .mod
        ]

        return orderedKinds.compactMap { contentKind in
            guard let count = source.displayItemCountsByKind[contentKind], count > 0 else {
                return nil
            }

            return SidebarFilter(
                title: sidebarTitle(for: contentKind),
                iconName: sidebarIcon(for: contentKind),
                count: count,
                selection: .contentKind(sourceID: source.id, contentKind: contentKind)
            )
        }
    }

    private var isSourceOverviewSelection: Bool {
        guard let selectedSidebarSelection else {
            return false
        }

        if case .source = selectedSidebarSelection {
            return true
        }

        return false
    }

    private func sidebarTitle(for contentType: MinecraftContentType) -> String {
        switch contentType {
        case .world:
            return "Worlds"
        case .behaviorPack:
            return "Behavior Packs"
        case .resourcePack:
            return "Resource Packs"
        case .skinPack:
            return "Skin Packs"
        case .worldTemplate:
            return "World Templates"
        }
    }

    private func sidebarTitle(for contentKind: MinecraftContentKind) -> String {
        switch contentKind {
        case .world:
            return "Worlds"
        case .behaviorPack:
            return "Behavior Packs"
        case .resourcePack:
            return "Resource Packs"
        case .dataPack:
            return "Data Packs"
        case .skinPack:
            return "Skin Packs"
        case .worldTemplate:
            return "World Templates"
        case .shaderPack:
            return "Shader Packs"
        case .mod:
            return "Mods"
        }
    }

    private func sidebarIcon(for contentType: MinecraftContentType) -> String {
        switch contentType {
        case .world:
            return "globe.europe.africa"
        case .behaviorPack:
            return "shippingbox"
        case .resourcePack:
            return "paintpalette"
        case .skinPack:
            return "person.crop.square"
        case .worldTemplate:
            return "doc.on.doc"
        }
    }

    private func sidebarIcon(for contentKind: MinecraftContentKind) -> String {
        switch contentKind {
        case .world:
            return "globe.europe.africa"
        case .behaviorPack:
            return "shippingbox"
        case .resourcePack:
            return "paintpalette"
        case .dataPack:
            return "curlybraces.square"
        case .skinPack:
            return "person.crop.square"
        case .worldTemplate:
            return "map"
        case .shaderPack:
            return "camera.filters"
        case .mod:
            return "hammer"
        }
    }

    @ViewBuilder
    private func itemContextMenu(for item: MinecraftContentItem) -> some View {
        Button("Share...") {
            shareItem(item, from: nil)
        }
        .disabled(!areFileActionsEnabled(for: item))

        Button(exportMenuTitle(for: item)) {
            saveItem(item)
        }
        .disabled(!areFileActionsEnabled(for: item))

        Divider()

        Button("Reveal in Finder") {
            revealInFinder(item)
        }
        .disabled(!areFileActionsEnabled(for: item))
    }

    private func exportMenuTitle(for item: MinecraftContentItem) -> String {
        switch item.contentType {
        case .world:
            return "Create Minecraft World File..."
        case .behaviorPack, .resourcePack, .skinPack:
            return "Create Minecraft Pack File..."
        case .worldTemplate:
            return "Create Minecraft Template File..."
        }
    }

    private func primaryActionTitle(for item: MinecraftContentItem) -> String {
        switch item.contentType {
        case .world:
            return "Create Minecraft World File..."
        case .behaviorPack, .resourcePack, .skinPack:
            return "Create Minecraft Pack File..."
        case .worldTemplate:
            return "Create Minecraft Template File..."
        }
    }

    private func primaryActionSubtitle(for item: MinecraftContentItem) -> String {
        switch item.contentType {
        case .world:
            return "Creates a .mcworld file that can be opened on another device to import this world into Minecraft."
        case .behaviorPack, .resourcePack, .skinPack:
            return "Creates a .mcpack file that can be shared or opened on another device."
        case .worldTemplate:
            return "Creates a .mctemplate file that can be opened on another device."
        }
    }

    private func logicalPackReferences(for item: MinecraftContentItem, type: MinecraftContentType) -> [ContentPackReference] {
        guard
            item.contentType == .world,
            let source = currentSource
        else {
            return []
        }

        return source.resolvedPackReferences(for: item.id, type: type)
    }

    private func worldsUsingPack(for item: MinecraftContentItem) -> [MinecraftContentItem] {
        guard
            (item.contentType == .behaviorPack || item.contentType == .resourcePack),
            let source = currentSource,
            let logicalPack = source.logicalPack(forRepresentativeItemID: item.id)
        else {
            return []
        }

        return source.worldsUsingPack(logicalPack.id).sorted(by: sortComparator)
    }

    private func backingPackInstances(for item: MinecraftContentItem) -> [MinecraftContentItem] {
        guard
            (item.contentType == .behaviorPack || item.contentType == .resourcePack),
            let source = currentSource,
            let logicalPack = source.logicalPack(forRepresentativeItemID: item.id)
        else {
            return []
        }

        return source
            .packInstances(for: logicalPack.id)
            .compactMap { source.rawItem(withID: $0.itemID) }
            .sorted(by: WorldScanner.sortItems)
    }

    private func isSuspiciousPack(_ item: MinecraftContentItem) -> Bool {
        guard
            (item.contentType == .behaviorPack || item.contentType == .resourcePack),
            let source = currentSource,
            let logicalPack = source.logicalPack(forRepresentativeItemID: item.id)
        else {
            return false
        }

        return logicalPack.isSuspicious
    }

    private func refreshDirectoryPreviewContents() async {
        guard let source = currentSource,
              let item = currentSelectedItem(in: source) else {
            await MainActor.run {
                directoryPreviewContents = []
            }
            return
        }

        let contents = (try? await library.listContents(for: item, in: source)) ?? []
        guard !Task.isCancelled else {
            return
        }

        await MainActor.run {
            directoryPreviewContents = Array(contents.prefix(directoryPreviewLimit))
        }
    }

    private func pickFolder() {
        let panel = NSOpenPanel()
        panel.allowsMultipleSelection = true
        panel.canChooseDirectories = true
        panel.canChooseFiles = false
        panel.title = "Add Minecraft Source Folders"

        guard panel.runModal() == .OK else {
            return
        }

        for url in panel.urls {
            Task { @MainActor in
                let sourceID = await library.addSource(at: url)
                selectSourceIfNeeded(sourceID)
            }
        }
    }

    private func addCandidateSource(_ candidate: SourceCandidate) {
        Task {
            let sourceID = await library.addSource(candidate: candidate)
            selectedSidebarSelection = .source(sourceID: sourceID)
            selectedItemID = nil
        }
    }

    private func revealCandidateInFinder(_ candidate: SourceCandidate) {
        NSWorkspace.shared.activateFileViewerSelecting([candidate.sourceRootURL])
    }

    private func handleDroppedProviders(_ providers: [NSItemProvider]) -> Bool {
        let fileURLType = UTType.fileURL.identifier
        let supportedProviders = providers.filter { $0.hasItemConformingToTypeIdentifier(fileURLType) }
        guard !supportedProviders.isEmpty else {
            return false
        }

        for provider in supportedProviders {
            provider.loadDataRepresentation(forTypeIdentifier: fileURLType) { data, _ in
                guard
                    let data,
                    let url = NSURL(absoluteURLWithDataRepresentation: data, relativeTo: nil) as URL?
                else {
                    return
                }

                Task { @MainActor in
                    let sourceID = await library.addSource(at: url)
                    selectSourceIfNeeded(sourceID)
                }
            }
        }

        return true
    }

    private func selectSourceIfNeeded(_ sourceID: URL) {
        guard selectedSidebarSelection == nil else {
            return
        }

        selectedSidebarSelection = .source(sourceID: sourceID)
    }

    private func removeSource(_ sourceID: URL) {
        let fallbackSourceID = library.visibleSources.first(where: { $0.id != sourceID })?.id
        library.removeSource(withID: sourceID)

        if selectedSidebarSelection?.sourceID == sourceID {
            selectedSidebarSelection = fallbackSourceID.map { .source(sourceID: $0) }
        }

        if let selectedItemID, currentSelectedItem(in: currentSource)?.id != selectedItemID {
            self.selectedItemID = nil
        }
    }

    private func addConnectedDeviceSource(from entry: ConnectedDeviceSidebarEntry) {
        guard let container = entry.minecraftContainer else {
            return
        }

        let source = deviceSourceFactory.makeSource(device: entry.device, container: container)
        let sourceID = library.addSource(source, shouldPersist: true, shouldScan: true)
        selectedSidebarSelection = .source(sourceID: sourceID)
        selectedItemID = nil
    }

    private func syncSelection(with sourceIDs: [URL]) {
        if let selectedSidebarSelection {
            switch selectedSidebarSelection {
            case .sourceCandidate(let candidateID):
                if !library.sourceCandidates.contains(where: { $0.id == candidateID }) {
                    self.selectedSidebarSelection = sourceIDs.first.map { .source(sourceID: $0) }
                }
            case .connectedDevice(let deviceID):
                if !library.sidebarConnectedDevices.contains(where: { $0.id == deviceID }) {
                    self.selectedSidebarSelection = sourceIDs.first.map { .source(sourceID: $0) }
                }
            case .source, .allContent, .contentType, .contentKind:
                if let selectedSourceID = selectedSidebarSelection.sourceID,
                   !sourceIDs.contains(selectedSourceID) {
                    self.selectedSidebarSelection = sourceIDs.first.map { .source(sourceID: $0) }
                }
            }
        } else if self.selectedSidebarSelection == nil, let firstSourceID = sourceIDs.first {
            self.selectedSidebarSelection = .source(sourceID: firstSourceID)
        }

        if let selectedItemID {
            if !library.containsItem(withID: selectedItemID) {
                self.selectedItemID = nil
            }
        }
    }

    private func areFileActionsEnabled(for item: MinecraftContentItem) -> Bool {
        guard
            let sourceID = library.sourceID(forItemID: item.id),
            let source = library.source(withID: sourceID)
        else {
            return false
        }

        return source.availability == .available && source.capabilities.canExportPortablePackages
    }

    private func sourceForItem(_ item: MinecraftContentItem) -> MinecraftSource? {
        guard let sourceID = library.sourceID(forItemID: item.id) else {
            return nil
        }

        return library.source(withID: sourceID)
    }

    private func fastProjection(
        for request: ItemCollectionProjectionRequest,
        previousRequest: ItemCollectionProjectionRequest
    ) -> ItemCollectionProjection? {
        guard
            let sourceID = request.source?.id,
            sourceID == previousRequest.source?.id,
            request.searchText == previousRequest.searchText,
            request.sortMode == previousRequest.sortMode,
            request.selection != previousRequest.selection
        else {
            return nil
        }

        return ItemCollectionProjector.makeProjection(for: request)
    }

    private func saveItem(_ item: MinecraftContentItem) {
        guard !isPerformingItemAction, areFileActionsEnabled(for: item) else {
            return
        }

        let panel = NSSavePanel()
        panel.canCreateDirectories = true
        panel.isExtensionHidden = false
        panel.showsTagField = false
        panel.title = exportMenuTitle(for: item)
        panel.prompt = "Save"
        panel.nameFieldStringValue = itemActionService.suggestedFilename(for: item)
        panel.allowedContentTypes = [archiveType(for: item)]

        guard panel.runModal() == .OK, let destinationURL = panel.url else {
            return
        }

        isPerformingItemAction = true

        Task {
            do {
                guard let source = currentSource else {
                    await MainActor.run {
                        isPerformingItemAction = false
                    }
                    return
                }

                let finalURL = try await Task.detached(priority: .userInitiated) {
                    let representation = try await library.externalRepresentation(
                        for: item,
                        in: source,
                        preferredKind: .portablePackage
                    )
                    return try itemActionService.persistExternalRepresentation(
                        representation,
                        to: destinationURL
                    )
                }.value

                await MainActor.run {
                    isPerformingItemAction = false
                    _ = finalURL
                }
            } catch {
                await MainActor.run {
                    isPerformingItemAction = false
                }
            }
        }
    }

    private func shareItem(_ item: MinecraftContentItem, from anchorView: NSView?) {
        guard !isPerformingItemAction, areFileActionsEnabled(for: item) else {
            return
        }

        isPerformingItemAction = true

        Task {
            do {
                guard let source = currentSource else {
                    await MainActor.run {
                        isPerformingItemAction = false
                    }
                    return
                }

                let shareURL = try await Task.detached(priority: .userInitiated) {
                    let representation = try await library.externalRepresentation(
                        for: item,
                        in: source,
                        preferredKind: .portablePackage
                    )
                    return representation.url
                }.value

                await MainActor.run {
                    isPerformingItemAction = false

                    let presentationView = anchorView ?? NSApp.keyWindow?.contentView
                    guard let presentationView else {
                        return
                    }

                    let picker = NSSharingServicePicker(items: [shareURL])
                    let targetRect = anchorView?.bounds ?? presentationView.bounds.insetBy(
                        dx: presentationView.bounds.width / 2,
                        dy: presentationView.bounds.height / 2
                    )
                    picker.show(relativeTo: targetRect, of: presentationView, preferredEdge: .minY)
                }
            } catch {
                await MainActor.run {
                    isPerformingItemAction = false
                }
            }
        }
    }

    private func revealInFinder(_ item: MinecraftContentItem) {
        guard let source = currentSource, areFileActionsEnabled(for: item) else {
            return
        }

        guard !isPerformingItemAction else {
            return
        }

        isPerformingItemAction = true

        Task {
            do {
                let representation = try await library.externalRepresentation(
                    for: item,
                    in: source,
                    preferredKind: .nativeFolder
                )

                await MainActor.run {
                    isPerformingItemAction = false
                    NSWorkspace.shared.activateFileViewerSelecting([representation.url])
                }
            } catch {
                await MainActor.run {
                    isPerformingItemAction = false
                }
            }
        }
    }

    private func archiveType(for item: MinecraftContentItem) -> UTType {
        itemActionService.archiveContentType(for: item)
    }

    private func dragProvider(for item: MinecraftContentItem) -> NSItemProvider {
        let provider = NSItemProvider()
        let contentType = archiveType(for: item)
        provider.suggestedName = itemActionService.suggestedArchiveFilename(for: item)

        provider.registerFileRepresentation(
            forTypeIdentifier: contentType.identifier,
            fileOptions: [],
            visibility: .all
        ) { completion in
            guard let source = sourceForItem(item), areFileActionsEnabled(for: item) else {
                completion(nil, false, SourceAccessError.accessFailed(reason: "This item is not currently available for export."))
                return nil
            }

            let task = Task {
                do {
                    let representation = try await library.externalRepresentation(
                        for: item,
                        in: source,
                        preferredKind: .portablePackage
                    )
                    completion(representation.url, representation.isTemporary, nil)
                } catch {
                    completion(nil, false, error)
                }
            }

            let progress = Progress(totalUnitCount: 1)
            progress.cancellationHandler = {
                task.cancel()
            }
            return progress
        }

        return provider
    }
}

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(
            dependencies: .makePreview(),
            initialSidebarSelection: .contentKind(
                sourceID: PreviewFixtures.primarySource.id,
                contentKind: .world
            ),
            initialItemID: PreviewFixtures.featuredWorld.id
        )
        .frame(width: 1_440, height: 900)
        .previewDisplayName("Full Window")
    }
}
#endif
