//
//  SidebarView.swift
//  oAI
//
//  Collapsible sidebar: new chat, conversation list, status pills
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen
//
// This file is part of oAI.
//
// oAI 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 oAI 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://oai.pm>.


import SwiftUI
#if os(macOS)
import AppKit
#endif

struct SidebarView: View {
    @Environment(ChatViewModel.self) private var chatViewModel
    @State private var conversations: [Conversation] = []
    @State private var folders: [Folder] = []
    @State private var searchText = ""
    @State private var collapsedFolders: Set<UUID> = []

    private var filteredConversations: [Conversation] {
        guard !searchText.isEmpty else { return conversations }
        return conversations.filter { $0.name.lowercased().contains(searchText.lowercased()) }
    }

    private var conversationsByFolder: [UUID?: [Conversation]] {
        Dictionary(grouping: filteredConversations, by: { $0.folderId })
    }

    var body: some View {
        VStack(spacing: 0) {
            // New Chat / New Folder buttons
            HStack(spacing: 4) {
                Button(action: { chatViewModel.newConversation() }) {
                    HStack(spacing: 8) {
                        Image(systemName: "square.and.pencil")
                            .font(.system(size: 14))
                        Text("New Chat")
                            .font(.system(size: 14, weight: .medium))
                    }
                    .foregroundColor(.oaiPrimary)
                    .contentShape(Rectangle())
                }
                .buttonStyle(.plain)

                Spacer()

                Button(action: { createFolderPrompt() }) {
                    Image(systemName: "folder.badge.plus")
                        .font(.system(size: 14))
                        .foregroundColor(.oaiPrimary)
                        .contentShape(Rectangle())
                }
                .buttonStyle(.plain)
                .help("New Folder")
            }
            .padding(.horizontal, 12)
            .padding(.vertical, 10)

            // Search field
            HStack(spacing: 6) {
                Image(systemName: "magnifyingglass")
                    .font(.system(size: 12))
                    .foregroundStyle(.secondary)
                TextField("Search conversations…", text: $searchText)
                    .textFieldStyle(.plain)
                    .font(.system(size: 13))
                if !searchText.isEmpty {
                    Button {
                        searchText = ""
                    } label: {
                        Image(systemName: "xmark.circle.fill")
                            .foregroundStyle(.tertiary)
                    }
                    .buttonStyle(.plain)
                }
                Divider().frame(height: 12)
                Button {
                    chatViewModel.showConversations = true
                } label: {
                    Image(systemName: "slider.horizontal.3")
                        .font(.system(size: 11))
                        .foregroundStyle(.secondary)
                }
                .buttonStyle(.plain)
                .help("Advanced search — semantic search, bulk delete, export")
            }
            .padding(7)
            .background(.quaternary.opacity(0.5), in: RoundedRectangle(cornerRadius: 6))
            .padding(.horizontal, 8)
            .padding(.bottom, 6)

            Divider()

            // Conversation list
            if filteredConversations.isEmpty {
                Spacer()
                VStack(spacing: 8) {
                    Image(systemName: searchText.isEmpty ? "tray" : "magnifyingglass")
                        .font(.title2)
                        .foregroundStyle(.tertiary)
                    Text(searchText.isEmpty ? "No Saved Conversations" : "No Matches")
                        .font(.callout)
                        .foregroundStyle(.secondary)
                }
                Spacer()
            } else if folders.isEmpty {
                List {
                    ForEach(filteredConversations) { conversation in
                        conversationRow(conversation)
                    }
                }
                .listStyle(.sidebar)
            } else {
                List {
                    ForEach(folders) { folder in
                        let folderConversations = conversationsByFolder[folder.id] ?? []
                        if !folderConversations.isEmpty || searchText.isEmpty {
                            Section {
                                if !collapsedFolders.contains(folder.id) {
                                    ForEach(folderConversations) { conversation in
                                        conversationRow(conversation)
                                    }
                                }
                            } header: {
                                folderHeader(folder)
                            }
                        }
                    }

                    let unfiled = conversationsByFolder[nil] ?? []
                    if !unfiled.isEmpty {
                        Section {
                            ForEach(unfiled) { conversation in
                                conversationRow(conversation)
                            }
                        } header: {
                            Text("Unfiled")
                                .dropDestination(for: String.self) { items, _ in
                                    _ = handleDrop(items, toFolder: nil)
                                }
                        }
                    }
                }
                .listStyle(.sidebar)
            }

        }
        .onAppear {
            loadData()
            collapsedFolders = SettingsService.shared.collapsedFolderIds
        }
        .onChange(of: chatViewModel.currentConversationName) { loadData() }
        .onChange(of: chatViewModel.messages.count) { loadData() }
        .onChange(of: chatViewModel.showConversations) { _, isShowing in
            // Folders/conversations created, renamed, or deleted in the advanced
            // conversation list modal live in its own @State — refresh ours once it closes.
            if !isShowing { loadData() }
        }
    }

    @ViewBuilder
    private func folderHeader(_ folder: Folder) -> some View {
        HStack(spacing: 4) {
            Image(systemName: "chevron.right")
                .font(.system(size: 9, weight: .bold))
                .rotationEffect(.degrees(collapsedFolders.contains(folder.id) ? 0 : 90))
            Text(folder.name)
                .font(.system(size: 12, weight: .bold))
        }
        .contentShape(Rectangle())
        .onTapGesture {
            withAnimation(.easeInOut(duration: 0.15)) {
                toggleCollapsed(folder.id)
            }
        }
        .contextMenu {
            Button {
                renameFolderPrompt(folder)
            } label: {
                Label("Rename Folder", systemImage: "pencil")
            }
            Button(role: .destructive) {
                deleteFolder(folder)
            } label: {
                Label("Delete Folder", systemImage: "trash")
            }
        }
        .dropDestination(for: String.self) { items, _ in
            _ = handleDrop(items, toFolder: folder.id)
        }
    }

    private func toggleCollapsed(_ folderId: UUID) {
        if collapsedFolders.contains(folderId) {
            collapsedFolders.remove(folderId)
        } else {
            collapsedFolders.insert(folderId)
        }
        SettingsService.shared.collapsedFolderIds = collapsedFolders
    }

    private func handleDrop(_ items: [String], toFolder folderId: UUID?) -> Bool {
        var moved = false
        for idString in items {
            guard let id = UUID(uuidString: idString),
                  let conversation = conversations.first(where: { $0.id == id })
            else { continue }
            moveConversation(conversation, toFolder: folderId)
            moved = true
        }
        return moved
    }

    @ViewBuilder
    private func conversationRow(_ conversation: Conversation) -> some View {
        SidebarConversationRow(conversation: conversation)
            .contentShape(Rectangle())
            .onTapGesture {
                chatViewModel.loadConversation(conversation)
            }
            .listRowBackground(
                chatViewModel.currentConversationName == conversation.name
                    ? Color.oaiAccent.opacity(0.15)
                    : Color.clear
            )
            .draggable(conversation.id.uuidString) {
                Text(conversation.name)
                    .font(.system(size: 13, weight: .medium))
                    .foregroundStyle(.white)
                    .padding(.horizontal, 10)
                    .padding(.vertical, 6)
                    .background(Color.oaiAccent, in: RoundedRectangle(cornerRadius: 6))
            }
            .swipeActions(edge: .trailing, allowsFullSwipe: false) {
                Button(role: .destructive) {
                    deleteConversation(conversation)
                } label: {
                    Label("Delete", systemImage: "trash")
                }
                Button {
                    renameConversation(conversation)
                } label: {
                    Label("Rename", systemImage: "pencil")
                }
                .tint(.orange)
            }
            .contextMenu {
                Menu {
                    if conversation.folderId != nil {
                        Button {
                            moveConversation(conversation, toFolder: nil)
                        } label: {
                            Label("Remove from Folder", systemImage: "folder.badge.minus")
                        }
                        Divider()
                    }
                    ForEach(folders) { folder in
                        if folder.id != conversation.folderId {
                            Button {
                                moveConversation(conversation, toFolder: folder.id)
                            } label: {
                                Text(folder.name)
                            }
                        }
                    }
                    Divider()
                    Button {
                        createFolderPrompt(andMove: conversation)
                    } label: {
                        Label("New Folder…", systemImage: "folder.badge.plus")
                    }
                } label: {
                    Label("Move to Folder", systemImage: "folder")
                }
                Button {
                    renameConversation(conversation)
                } label: {
                    Label("Rename", systemImage: "pencil")
                }
                Button(role: .destructive) {
                    deleteConversation(conversation)
                } label: {
                    Label("Delete", systemImage: "trash")
                }
            }
    }

    private func loadData() {
        conversations = (try? DatabaseService.shared.listConversations()) ?? []
        folders = (try? DatabaseService.shared.listFolders()) ?? []
    }

    private func deleteConversation(_ conversation: Conversation) {
        _ = try? DatabaseService.shared.deleteConversation(id: conversation.id)
        withAnimation {
            conversations.removeAll { $0.id == conversation.id }
        }
        GitSyncService.shared.syncAfterDeletion()
    }

    private func renameConversation(_ conversation: Conversation) {
        #if os(macOS)
        let alert = NSAlert()
        alert.messageText = "Rename Conversation"
        alert.addButton(withTitle: "Rename")
        alert.addButton(withTitle: "Cancel")
        let input = NSTextField(frame: NSRect(x: 0, y: 0, width: 260, height: 24))
        input.stringValue = conversation.name
        input.selectText(nil)
        alert.accessoryView = input
        alert.window.initialFirstResponder = input
        guard alert.runModal() == .alertFirstButtonReturn else { return }
        let newName = input.stringValue.trimmingCharacters(in: .whitespaces)
        guard !newName.isEmpty, newName != conversation.name else { return }
        do {
            _ = try DatabaseService.shared.updateConversation(id: conversation.id, name: newName, messages: nil)
            if let i = conversations.firstIndex(where: { $0.id == conversation.id }) {
                conversations[i].name = newName
                conversations[i].updatedAt = Date()
            }
            chatViewModel.didRenameConversation(id: conversation.id, newName: newName)
        } catch {
            Log.db.error("Failed to rename conversation: \(error.localizedDescription)")
        }
        #endif
    }

    private func moveConversation(_ conversation: Conversation, toFolder folderId: UUID?) {
        do {
            try DatabaseService.shared.moveConversation(id: conversation.id, toFolder: folderId)
            if let i = conversations.firstIndex(where: { $0.id == conversation.id }) {
                conversations[i].folderId = folderId
            }
        } catch {
            Log.db.error("Failed to move conversation: \(error.localizedDescription)")
        }
    }

    private func createFolderPrompt(andMove conversation: Conversation? = nil) {
        #if os(macOS)
        let alert = NSAlert()
        alert.messageText = "New Folder"
        alert.addButton(withTitle: "Create")
        alert.addButton(withTitle: "Cancel")
        let input = NSTextField(frame: NSRect(x: 0, y: 0, width: 260, height: 24))
        alert.accessoryView = input
        alert.window.initialFirstResponder = input
        guard alert.runModal() == .alertFirstButtonReturn else { return }
        let name = input.stringValue.trimmingCharacters(in: .whitespaces)
        guard !name.isEmpty else { return }
        do {
            let folder = try DatabaseService.shared.createFolder(name: name)
            folders.append(folder)
            sortFolders()
            if let conversation = conversation {
                moveConversation(conversation, toFolder: folder.id)
            }
        } catch {
            Log.db.error("Failed to create folder: \(error.localizedDescription)")
        }
        #endif
    }

    private func sortFolders() {
        folders.sort { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending }
    }

    private func renameFolderPrompt(_ folder: Folder) {
        #if os(macOS)
        let alert = NSAlert()
        alert.messageText = "Rename Folder"
        alert.addButton(withTitle: "Rename")
        alert.addButton(withTitle: "Cancel")
        let input = NSTextField(frame: NSRect(x: 0, y: 0, width: 260, height: 24))
        input.stringValue = folder.name
        input.selectText(nil)
        alert.accessoryView = input
        alert.window.initialFirstResponder = input
        guard alert.runModal() == .alertFirstButtonReturn else { return }
        let newName = input.stringValue.trimmingCharacters(in: .whitespaces)
        guard !newName.isEmpty, newName != folder.name else { return }
        do {
            try DatabaseService.shared.renameFolder(id: folder.id, name: newName)
            if let i = folders.firstIndex(where: { $0.id == folder.id }) {
                folders[i].name = newName
            }
            sortFolders()
        } catch {
            Log.db.error("Failed to rename folder: \(error.localizedDescription)")
        }
        #endif
    }

    private func deleteFolder(_ folder: Folder) {
        do {
            try DatabaseService.shared.deleteFolder(id: folder.id)
            folders.removeAll { $0.id == folder.id }
            for i in conversations.indices where conversations[i].folderId == folder.id {
                conversations[i].folderId = nil
            }
        } catch {
            Log.db.error("Failed to delete folder: \(error.localizedDescription)")
        }
    }
}

// MARK: - Sidebar conversation row

struct SidebarConversationRow: View {
    let conversation: Conversation

    private var formattedDate: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "dd.MM.yyyy"
        return formatter.string(from: conversation.updatedAt)
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 2) {
            Text(conversation.name)
                .font(.system(size: 13, weight: .medium))
                .foregroundStyle(.primary)
                .lineLimit(1)
            HStack(spacing: 4) {
                Text("^[\(conversation.messageCount) message](inflect: true)")
                    .font(.system(size: 11))
                Text("·")
                    .font(.system(size: 11))
                Text(formattedDate)
                    .font(.system(size: 11))
            }
            .foregroundStyle(.secondary)
        }
        .padding(.vertical, 2)
    }
}

#Preview {
    SidebarView()
        .environment(ChatViewModel())
        .frame(width: 240, height: 600)
}
