#if os(macOS)
import AppKit
import SwiftUI

extension ChatSessionSidebarModel.Node {
    fileprivate var outlineChildren: [Self]? {
        self.children.isEmpty ? nil : self.children
    }
}

@MainActor
struct ChatSessionSidebar: View {
    @Bindable var viewModel: OpenClawChatViewModel
    @Binding var query: String
    @State private var sessionPendingDeletion: OpenClawChatSessionEntry?
    @State private var sessionPendingRename: OpenClawChatSessionEntry?
    @State private var renameText = ""
    @State private var groups: [OpenClawChatSessionGroup] = []
    @State private var groupRefreshNonce = 0
    @State private var groupLoadFailed = false
    @State private var inspectedSession: OpenClawChatSessionEntry?
    @State private var isPresentingNewSessionOptions = false
    @AppStorage("openclaw.chat.collapsedSessionGroups") private var collapsedSessionGroups = ""

    var body: some View {
        let sections = ChatSessionSidebarModel.sections(
            sessions: self.viewModel.sessions,
            currentSessionKey: self.viewModel.sessionKey,
            mainSessionKey: self.viewModel.resolvedMainSessionKey,
            activeAgentID: self.viewModel.activeAgentId,
            groups: self.groups,
            query: self.query)
        List(selection: self.selectionBinding) {
            ForEach(sections) { section in
                if section.id.hasPrefix("group:"), let title = section.title {
                    Section {
                        if !self.isGroupCollapsed(title) || !self.query
                            .trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
                        {
                            self.rows(section.nodes)
                        }
                    } header: {
                        Button {
                            self.toggleGroupCollapsed(title)
                        } label: {
                            HStack {
                                Image(systemName: self.isGroupCollapsed(title) ? "chevron.right" : "chevron.down")
                                Text(verbatim: title)
                                    .font(OpenClawChatTypography.caption)
                            }
                        }
                        .buttonStyle(.plain)
                    }
                } else if let title = section.title {
                    Section {
                        self.rows(section.nodes)
                    } header: {
                        Text(LocalizedStringKey(title))
                            .font(OpenClawChatTypography.caption)
                    }
                } else {
                    self.rows(section.nodes)
                }
            }
        }
        .listStyle(.sidebar)
        .searchable(
            text: self.$query,
            placement: .sidebar,
            prompt: String(localized: "Search threads"))
        .overlay {
            if sections.isEmpty {
                ContentUnavailableView(
                    self.query.isEmpty
                        ? String(localized: "No Threads")
                        : String(localized: "No Results"),
                    systemImage: "bubble.left.and.bubble.right")
            }
        }
        .safeAreaInset(edge: .bottom, spacing: 0) { self.connectionFooter }
        .toolbar {
            ToolbarItem {
                HStack(spacing: 2) {
                    Button {
                        Task { await self.viewModel.startNewSession() }
                    } label: {
                        chatWindowActionLabel("New Thread", systemImage: "square.and.pencil")
                    }
                    .help(String(localized: "New thread"))
                    Button {
                        self.isPresentingNewSessionOptions = true
                    } label: {
                        Image(systemName: "chevron.down")
                    }
                    .help(String(localized: "New thread options"))
                    .popover(isPresented: self.$isPresentingNewSessionOptions) {
                        ChatNewSessionOptionsPopover(viewModel: self.viewModel) {
                            self.isPresentingNewSessionOptions = false
                        }
                    }
                }
            }
        }
        .task(id: self.groupRefreshID) {
            self.viewModel.refreshSessions(limit: 200)
            do {
                let groups = try await self.viewModel.fetchSessionGroups()
                self.groups = groups
                self.groupLoadFailed = false
            } catch {
                self.groupLoadFailed = true
            }
        }
        .onChange(of: self.viewModel.healthOK) { previous, current in
            if !previous, current {
                self.viewModel.refreshSessions(limit: 200)
            }
        }
        .sheet(item: self.$inspectedSession) { session in
            ChatSessionInspectorSheet(viewModel: self.viewModel, session: session)
        }
        .alert(
            String(localized: "Rename Thread"),
            isPresented: self.isPresentingRenameAlert)
        {
            TextField(String(localized: "Thread name"), text: self.$renameText)
            Button(String(localized: "Rename")) {
                if let session = self.sessionPendingRename {
                    self.viewModel.renameSession(key: session.key, label: self.renameText)
                }
                self.sessionPendingRename = nil
            }
            Button(String(localized: "Cancel"), role: .cancel) {
                self.sessionPendingRename = nil
            }
        }
        .confirmationDialog(self.deleteDialogTitle, isPresented: self.isPresentingDeleteDialog) {
                Button(String(localized: "Delete Thread"), role: .destructive) {
                    if let session = self.sessionPendingDeletion {
                        self.viewModel.deleteSession(session.key)
                    }
                    self.sessionPendingDeletion = nil
                }
            } message: {
                Text(String(localized: "The thread and its transcript are removed from the gateway."))
                    .font(OpenClawChatTypography.body(size: 13, weight: .regular, relativeTo: .body))
            }
    }

    private var selectionBinding: Binding<String?> {
        Binding(
            get: {
                ChatSessionSidebarModel.selectedSessionKey(
                    sessions: self.viewModel.sessions,
                    currentSessionKey: self.viewModel.sessionKey,
                    mainSessionKey: self.viewModel.resolvedMainSessionKey,
                    activeAgentID: self.viewModel.activeAgentId)
            },
            set: { next in
                guard let next, next != self.viewModel.sessionKey else { return }
                self.viewModel.switchSession(to: next)
            })
    }

    private var groupRefreshID: String {
        let categories = self.viewModel.sessions.compactMap(\.category).sorted().joined(separator: "|")
        let revision = self.viewModel.sessionGroupsRevision
        return "\(self.viewModel.healthOK)|\(categories)|\(revision)|\(self.groupRefreshNonce)"
    }

    private var deleteDialogTitle: String {
        let name = self.sessionPendingDeletion.map(ChatSessionSidebarModel.displayName(for:)) ?? ""
        return String(format: String(localized: "Delete “%@”?"), name)
    }

    private var isPresentingDeleteDialog: Binding<Bool> {
        Binding(
            get: { self.sessionPendingDeletion != nil },
            set: { if !$0 { self.sessionPendingDeletion = nil } })
    }

    private var isPresentingRenameAlert: Binding<Bool> {
        Binding(
            get: { self.sessionPendingRename != nil },
            set: { if !$0 { self.sessionPendingRename = nil } })
    }

    private func rows(_ nodes: [ChatSessionSidebarModel.Node]) -> some View {
        OutlineGroup(nodes, children: \.outlineChildren) { node in
            self.row(for: node)
        }
    }

    private func row(for node: ChatSessionSidebarModel.Node) -> some View {
        let session = node.session
        return HStack(spacing: 6) {
            VStack(alignment: .leading, spacing: 2) {
                Text(ChatSessionSidebarModel.displayName(for: session))
                    .font(OpenClawChatTypography.body(size: 13, weight: .medium, relativeTo: .body))
                    .lineLimit(1)
                if let subtitle = self.rowSubtitle(for: session) {
                    Text(subtitle)
                        .font(OpenClawChatTypography.caption)
                        .foregroundStyle(.secondary)
                        .lineLimit(1)
                }
            }
            Spacer(minLength: 0)
            self.badges(for: node)
        }
        // The tag type must equal the List selection type (String?) exactly.
        .tag(Optional(session.key))
        .contextMenu { self.contextMenu(for: session) }
    }

    @ViewBuilder
    private func badges(for node: ChatSessionSidebarModel.Node) -> some View {
        if node.badges.runningCount > 0 {
            ProgressView()
                .controlSize(.small)
                .accessibilityLabel(String(localized: "Thread running"))
        }
        if node.badges.failedCount > 0 {
            Image(systemName: "exclamationmark.triangle.fill")
                .foregroundStyle(OpenClawChatTheme.warning)
                .accessibilityLabel(String(localized: "Thread failed"))
        }
        let isCurrentSession = self.viewModel.matchesCurrentSessionKey(
            incoming: node.session.key,
            current: self.viewModel.sessionKey)
        if node.children.contains(where: \.badges.hasUnread) ||
            (node.session.unread == true && !isCurrentSession)
        {
            Circle()
                .fill(.tint)
                .frame(width: 7, height: 7)
                .accessibilityLabel(String(localized: "Unread"))
        }
    }

    @ViewBuilder
    private func contextMenu(for session: OpenClawChatSessionEntry) -> some View {
        Button {
            self.inspectedSession = session
        } label: {
            self.actionLabel(String(localized: "Get Info…"), systemImage: "info.circle")
        }
        Divider()
        Button {
            self.renameText = session.label ?? session.displayName ?? ""
            self.sessionPendingRename = session
        } label: {
            self.actionLabel(String(localized: "Rename…"), systemImage: "pencil")
        }
        Button {
            self.viewModel.setSessionPinned(key: session.key, pinned: session.pinned != true)
        } label: {
            self.actionLabel(
                session.pinned == true ? String(localized: "Unpin") : String(localized: "Pin"),
                systemImage: session.pinned == true ? "pin.slash" : "pin")
        }
        Button {
            Task { await self.viewModel.forkSession(key: session.key) }
        } label: {
            self.actionLabel(String(localized: "Fork"), systemImage: "arrow.triangle.branch")
        }
        Button {
            self.viewModel.setSessionUnread(key: session.key, unread: session.unread != true)
        } label: {
            self.actionLabel(
                session.unread == true ? String(localized: "Mark Read") : String(localized: "Mark Unread"),
                systemImage: session.unread == true ? "envelope.open" : "envelope.badge")
        }
        if session.isArchived || ChatSessionSidebarModel.canArchiveSession(
            session,
            mainSessionKey: self.viewModel.resolvedMainSessionKey)
        {
            Button {
                self.viewModel.setSessionArchived(key: session.key, archived: !session.isArchived)
            } label: {
                self.actionLabel(
                    session.isArchived ? String(localized: "Restore") : String(localized: "Archive"),
                    systemImage: session.isArchived ? "tray.and.arrow.up" : "archivebox")
            }
        }
        Divider()
        Button {
            NSPasteboard.general.clearContents()
            NSPasteboard.general.setString(session.key, forType: .string)
        } label: {
            self.actionLabel(String(localized: "Copy Session Key"), systemImage: "doc.on.doc")
        }
        if ChatSessionSidebarModel.canDeleteSession(
            key: session.key,
            mainSessionKey: self.viewModel.resolvedMainSessionKey)
        {
            Button(role: .destructive) {
                self.sessionPendingDeletion = session
            } label: {
                self.actionLabel(String(localized: "Delete Thread…"), systemImage: "trash")
            }
        }
    }

    private func isGroupCollapsed(_ name: String) -> Bool {
        Set(self.collapsedSessionGroups.split(separator: "\u{1F}").map(String.init)).contains(name)
    }

    private func toggleGroupCollapsed(_ name: String) {
        var names = Set(self.collapsedSessionGroups.split(separator: "\u{1F}").map(String.init))
        if !names.insert(name).inserted {
            names.remove(name)
        }
        self.collapsedSessionGroups = names.sorted().joined(separator: "\u{1F}")
    }

    private func actionLabel(_ title: String, systemImage: String) -> some View {
        Label(title, systemImage: systemImage)
            .font(OpenClawChatTypography.body(size: 13, weight: .regular, relativeTo: .body))
    }

    private func rowSubtitle(for session: OpenClawChatSessionEntry) -> String? {
        var parts: [String] = []
        if let branch = session.worktree?.branch?.trimmingCharacters(in: .whitespacesAndNewlines),
           !branch.isEmpty
        {
            parts.append(branch)
        }
        if let updatedAt = session.updatedAt ?? session.lastActivityAt, updatedAt > 0 {
            let date = Date(timeIntervalSince1970: updatedAt / 1000)
            parts.append(date.formatted(.relative(presentation: .named)))
        }
        return ChatSessionSidebarModel.subtitle(
            for: session,
            workSubtitle: parts.isEmpty ? nil : parts.joined(separator: " · "))
    }

    private var connectionFooter: some View {
        HStack(spacing: 6) {
            Circle()
                .fill(self.viewModel.healthOK ? .green : .orange)
                .frame(width: 7, height: 7)
            Text(self.viewModel.healthOK
                ? String(localized: "Gateway connected")
                : String(localized: "Connecting…"))
                .font(OpenClawChatTypography.caption)
                .foregroundStyle(.secondary)
            Spacer(minLength: 0)
            if self.groupLoadFailed {
                Button {
                    self.groupRefreshNonce += 1
                } label: {
                    Image(systemName: "arrow.clockwise")
                }
                .buttonStyle(.borderless)
                .help(String(localized: "Retry thread groups"))
            }
        }
        .padding(.horizontal, 12)
        .padding(.vertical, 8)
        .background(.bar)
    }
}
#endif
