import SwiftUI
import Combine
import PhotosUI
import UniformTypeIdentifiers

struct CreateLibraryPage: View {
    @Environment(\.dismiss) private var dismiss
    @State private var name = ""; @State private var desc = ""
    @State private var isCreating = false; @State private var isUploadingCover = false
    @State private var coverKey: String?; @State private var coverImage: UIImage?
    @State private var showCoverPicker = false; @State private var coverPhotoItem: PhotosPickerItem?
    @State private var showCoverSheet = false
    var body: some View {
        ZStack { Color.zxBg0.ignoresSafeArea(); VStack(spacing: 0) {
            ScrollView { VStack(spacing: 20) {
                // 封面图
                VStack(alignment: .leading, spacing: 8) {
                    Text("封面图").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035)
                    Button { showCoverSheet = true } label: {
                        ZStack {
                            RoundedRectangle(cornerRadius: 14).fill(Color.zxFill004)
                                .overlay(RoundedRectangle(cornerRadius: 14).stroke(style: StrokeStyle(lineWidth: 1.5, dash: [6, 4])).foregroundColor(Color.zxBorder01))
                                .frame(width: 120, height: 120)
                            if let img = coverImage {
                                Image(uiImage: img).resizable().scaledToFill().frame(width: 120, height: 120).clipShape(RoundedRectangle(cornerRadius: 14))
                            } else {
                                VStack(spacing: 6) {
                                    Image(systemName: "camera").font(.system(size: 22)).foregroundColor(Color.zxF04)
                                    Text("上传").font(.system(size: 12)).foregroundColor(Color.zxF04)
                                }
                            }
                            if isUploadingCover { RoundedRectangle(cornerRadius: 14).fill(Color.black.opacity(0.4)).frame(width: 120, height: 120); ProgressView().tint(.white) }
                        }
                    }
                    .disabled(isUploadingCover)
                }
                .frame(maxWidth: .infinity, alignment: .leading)

                VStack(alignment: .leading, spacing: 8) {
                    HStack(spacing: 2) { Text("知识库名称").font(.system(size: 12, weight: .semibold)); Text("*").foregroundColor(.red) }.foregroundColor(Color.zxF035)
                    TextField("例如：机器学习", text: $name).font(.system(size: 16)).tint(Color.zxPurple).padding(.horizontal, 16).frame(height: 52).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1))
                }
                VStack(alignment: .leading, spacing: 8) {
                    HStack(spacing: 2) { Text("描述").font(.system(size: 12, weight: .semibold)); Text("*").foregroundColor(.red) }.foregroundColor(Color.zxF035)
                    TextEditor(text: $desc).frame(minHeight: 80).scrollContentBackground(.hidden).padding(8).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1))
                }
                Button {
                    guard !name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
                          !desc.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
                    isCreating = true
                    Task {
                        let kb = try? await KnowledgeBaseService.shared.create(
                            title: name, description: desc.isEmpty ? nil : desc, coverKey: coverKey
                        )
                        await MainActor.run {
                            isCreating = false
                            if kb != nil {
                                ZXToastManager.shared.success("知识库已创建")
                                dismiss()
                            } else {
                                ZXToastManager.shared.error("创建失败，请重试")
                            }
                        }
                    }
                } label: {
                    HStack(spacing: 8) {
                        if isCreating { ProgressView().tint(.white) }
                        Text("创建").font(.system(size: 14, weight: .bold))
                    }
                    .foregroundColor(.white).frame(maxWidth: .infinity).frame(height: 52)
                    .background(name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? AnyView(Color.zxHairlineStrong) : AnyView(ZXGradient.ctaPurple))
                    .clipShape(RoundedRectangle(cornerRadius: 16))
                }
                .disabled(isCreating || name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || desc.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
            }.padding(.horizontal, 20).padding(.top, 20) }.scrollIndicators(.hidden) }
        }.navigationBarTitleDisplayMode(.inline).toolbarBackground(.hidden, for: .navigationBar).toolbar(.hidden, for: .tabBar)
        .photosPicker(isPresented: $showCoverPicker, selection: $coverPhotoItem, matching: .images)
        .onChange(of: coverPhotoItem) { _, item in
            guard let item else { return }
            Task { await uploadCover(item) }
        }
        .sheet(isPresented: $showCoverSheet) {
            VStack(spacing: 0) {
                RoundedRectangle(cornerRadius: 3).fill(Color.zxF03).frame(width: 36, height: 5).padding(.top, 12).padding(.bottom, 16)
                Text("封面图").font(.system(size: 16, weight: .semibold)).foregroundColor(Color.zxF0).padding(.bottom, 20)
                Button {
                    showCoverSheet = false
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { showCoverPicker = true }
                } label: {
                    HStack(spacing: 12) {
                        Image("icon-camera").font(.system(size: 20)).foregroundColor(Color.zxPrimary).frame(width: 40, height: 40).background(Color.zxPrimarySoft).clipShape(RoundedRectangle(cornerRadius: 10))
                        VStack(alignment: .leading, spacing: 2) {
                            Text("从相册选择").font(.system(size: 16, weight: .medium)).foregroundColor(Color.zxF0)
                            Text("选择一张图片作为封面").font(.system(size: 12)).foregroundColor(Color.zxF04)
                        }
                        Spacer()
                        Image("icon-chevron-right").resizable().scaledToFit().frame(width: 16, height: 16).foregroundColor(Color.zxF03)
                    }
                    .padding(.horizontal, 16).padding(.vertical, 14)
                    .background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14))
                }
                .foregroundColor(.primary)
                .padding(.horizontal, 20)
                Spacer()
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.zxBg0)
            .presentationDetents([.height(220)])
            .presentationDragIndicator(.hidden)
        }}

    private func uploadCover(_ item: PhotosPickerItem) async {
        isUploadingCover = true
        defer { isUploadingCover = false; coverPhotoItem = nil }
        guard let data = try? await item.loadTransferable(type: Data.self),
              let image = UIImage(data: data) else { return }
        let resized = resizeCoverImage(image, tw: 512, th: 512)
        do {
            let r = try await FileUploadService.shared.uploadImageWithKey(resized, filename: "cover_\(Int(Date().timeIntervalSince1970)).jpg")
            coverKey = r.objectKey; coverImage = resized
        } catch { ZXToastManager.shared.error("封面上传失败") }
    }

    private func resizeCoverImage(_ image: UIImage, tw: CGFloat, th: CGFloat) -> UIImage {
        let tr = tw / th; let ir = image.size.width / image.size.height
        let rect: CGRect
        if ir > tr { let nw = image.size.height * tr; rect = CGRect(x: (image.size.width - nw)/2, y: 0, width: nw, height: image.size.height) }
        else { let nh = image.size.width / tr; rect = CGRect(x: 0, y: (image.size.height - nh)/2, width: image.size.width, height: nh) }
        guard let cg = image.cgImage?.cropping(to: rect) else { return image }
        let r = UIGraphicsImageRenderer(size: CGSize(width: tw, height: th))
        return r.image { _ in UIImage(cgImage: cg).draw(in: CGRect(origin: .zero, size: CGSize(width: tw, height: th))) }
    }
}

struct LibraryDetailPage: View {
    let knowledgeBaseId: String
    @Environment(\.dismiss) private var dismiss
    @StateObject private var viewModel = LibraryDetailViewModel()
    @State private var showDeleteConfirm = false
    @State private var isDeleting = false
    @State private var isSelectMode = false
    @State private var selectedIds: Set<String> = []
    @State private var showBatchDeleteConfirm = false
    @State private var detailTab = 0
    @State private var sortOption = 0
    @State private var sources: [KnowledgeSource] = []
    @State private var isLoadingSources = false

    private var allSelected: Bool {
        !viewModel.items.isEmpty && selectedIds.count == viewModel.items.count
    }

    var body: some View {
        ZStack(alignment: .top) {
            Color.zxBg0.ignoresSafeArea()

            // Top gradient wash — blue/purple fading to transparent
            LinearGradient(
                colors: [
                    Color.zxPurple.opacity(0.08),
                    Color.zxPurple.opacity(0.04),
                    Color.clear,
                ],
                startPoint: .top,
                endPoint: .bottom
            )
            .frame(height: 220)
            .ignoresSafeArea(edges: .top)

            VStack(spacing: 0) {
                // KB info header — fixed, non-scrolling
                if let kb = viewModel.knowledgeBase {
                    kbInfoHeader(kb)
                        .padding(.horizontal, 20).padding(.top, 8)
                }

                Picker("", selection: $detailTab) {
                    Text("知识点").tag(0)
                    Text("资料来源").tag(1)
                }
                .pickerStyle(.segmented)
                .padding(.horizontal, 20).padding(.top, 12)

                ScrollView {
                    VStack(spacing: 0) {
                        if detailTab == 0 {
                            if viewModel.isLoading && viewModel.items.isEmpty {
                                VStack(spacing: 12) {
                                    ProgressView()
                                    Text("加载中…").font(.system(size: 14)).foregroundColor(Color.zxF04)
                                }
                                .frame(maxWidth: .infinity).padding(.top, 80)
                            }
                            ForEach(viewModel.items) { item in
                                let icon = fileTypeIcon(for: item)
                                let type = fileTypeText(for: item)
                                let date = formatShortDate(item.updatedAt)
                                let progress = progressFor(item)
                                if isSelectMode {
                                    Button {
                                        if selectedIds.contains(item.id) { selectedIds.remove(item.id) }
                                        else { selectedIds.insert(item.id) }
                                    } label: {
                                        HStack(spacing: 10) {
                                            Image(systemName: selectedIds.contains(item.id) ? "checkmark.circle" : "circle")
                                                .font(.system(size: 20))
                                                .foregroundColor(selectedIds.contains(item.id) ? Color.zxPrimary : Color.zxF03)
                                            itemRow(icon: icon, title: item.title, type: type, date: date, progress: progress)
                                        }
                                    }
                                    .foregroundColor(.primary)
                                } else {
                                    NavigationLink(value: Route.knowledgeDetail(item: item)) {
                                        itemRow(icon: icon, title: item.title, type: type, date: date, progress: progress)
                                    }
                                    .contextMenu {
                                        Button {
                                            isSelectMode = true
                                            selectedIds.insert(item.id)
                                        } label: {
                                            Label("多选", systemImage: "checkmark.circle")
                                        }
                                        Button {
                                            // TODO: rename
                                        } label: {
                                            Label("重命名", image: "icon-pencil")
                                        }
                                        Button {
                                            // TODO: move to folder
                                        } label: {
                                            Label("移动到", image: "icon-folder")
                                        }
                                        Divider()
                                        Button(role: .destructive) {
                                            Task {
                                                await viewModel.batchDeleteItems(ids: [item.id])
                                                await viewModel.refresh(knowledgeBaseId: knowledgeBaseId)
                                            }
                                        } label: {
                                            Label("删除", image: "icon-trash")
                                        }
                                    }
                                }
                            }
                            if viewModel.items.isEmpty && !viewModel.isLoading {
                                Text("暂无知识点").font(.system(size: 14)).foregroundColor(Color.zxF03).padding(.top, 40)
                            }
                            if viewModel.hasMore {
                                ZXLoadMoreFooter { await viewModel.loadMore(knowledgeBaseId: knowledgeBaseId) }
                            }
                        } else {
                            if isLoadingSources {
                                VStack(spacing: 12) {
                                    ProgressView().tint(Color.zxPurple)
                                    Text("加载中…").font(.system(size: 14)).foregroundColor(Color.zxF04)
                                }.padding(.top, 80)
                            } else if sources.isEmpty {
                                Text("暂无资料来源").font(.system(size: 14)).foregroundColor(Color.zxF03).padding(.top, 40)
                            } else {
                                ForEach(sources) { src in
                                    HStack(spacing: 12) {
                                        Image(systemName: src.type == "file" ? "doc" : "link")
                                            .font(.system(size: 18)).foregroundColor(Color.zxPurple)
                                            .frame(width: 36, height: 36)
                                        VStack(alignment: .leading, spacing: 2) {
                                            Text(src.title ?? src.originalFilename ?? "未命名")
                                                .font(.system(size: 15, weight: .medium)).foregroundColor(Color.zxF0).lineLimit(1)
                                            if let len = src.textLength, len > 0 {
                                                Text("\(len) 字")
                                                    .font(.system(size: 13)).foregroundColor(Color.zxF04)
                                            }
                                        }
                                        Spacer()
                                        Button {
                                            Task { await deleteSource(src) }
                                        } label: {
                                            Image("icon-trash").resizable().scaledToFit().frame(width: 16, height: 16).foregroundColor(Color.zxF03)
                                        }
                                    }
                                    .padding(.vertical, 14)
                                    .overlay(alignment: .bottom) {
                                        Color.zxHairline.frame(height: 0.5)
                                    }
                                }
                            }
                        }
                    }
                    .padding(.horizontal, 20).padding(.bottom, 80)
                }
                .scrollIndicators(.hidden)
                .refreshable { await viewModel.refresh(knowledgeBaseId: knowledgeBaseId) }
            }
        }
        .navigationBarTitleDisplayMode(.inline).toolbarBackground(.hidden, for: .navigationBar).toolbar(.hidden, for: .tabBar)
        .onChange(of: detailTab) { _, newTab in
            if newTab == 1 && sources.isEmpty { Task { await loadSources() } }
        }
        .toolbar {
            if isSelectMode {
                ToolbarItem(placement: .topBarLeading) {
                    Button("取消") { isSelectMode = false; selectedIds.removeAll() }
                }
                ToolbarItem(placement: .topBarTrailing) {
                    Button { selectedIds = allSelected ? [] : Set(viewModel.items.map(\.id)) } label: {
                        Text(allSelected ? "取消全选" : "全选").font(.system(size: 14))
                    }
                }
                ToolbarItem(placement: .topBarTrailing) {
                    Button {
                        showBatchDeleteConfirm = true
                    } label: {
                        Image("icon-trash").resizable().scaledToFit().frame(width: 18, height: 18).foregroundColor(selectedIds.isEmpty ? Color.zxF03 : Color.zxCoral)
                    }
                    .disabled(selectedIds.isEmpty)
                }
            } else {
                ToolbarItem(placement: .topBarTrailing) {
                    HStack(spacing: 4) {
                        Menu {
                            Button { sortOption = 0 } label: {
                                Label("默认排序", systemImage: sortOption == 0 ? "checkmark" : "")
                            }
                            Button { sortOption = 1 } label: {
                                Label("文件大小", systemImage: sortOption == 1 ? "checkmark" : "")
                            }
                            Button { sortOption = 2 } label: {
                                Label("创建日期", systemImage: sortOption == 2 ? "checkmark" : "")
                            }
                            Button { sortOption = 3 } label: {
                                Label("更新日期", systemImage: sortOption == 3 ? "checkmark" : "")
                            }
                        } label: {
                            Image(systemName: "arrow.up.arrow.down")
                                .font(.system(size: 16))
                                .foregroundColor(Color.zxF05)
                        }

                        Menu {
                        NavigationLink(value: Route.addKnowledge(knowledgeBaseId: knowledgeBaseId)) {
                            Label("添加知识点", image: "icon-plus")
                        }
                        Button {
                            // TODO: create folder
                        } label: {
                            Label("创建文件夹", image: "icon-folder")
                        }
                        NavigationLink(value: Route.quizList(knowledgeBaseId: knowledgeBaseId)) {
                            Label("答题测验", image: "icon-pencil")
                        }
                        Button {
                            isSelectMode = true
                        } label: {
                            Label("批量选择", systemImage: "checkmark.circle")
                        }
                        Divider()
                        Button {
                            // TODO: knowledge base management page
                        } label: {
                            Label("知识库管理", image: "icon-settings")
                        }
                    } label: {
                        Image(systemName: "ellipsis.circle")
                            .font(.system(size: 20))
                            .foregroundColor(Color.zxF05)
                    }
                    } // HStack
                }
            }
        }
        .alert("删除知识库", isPresented: $showDeleteConfirm) {
            Button("取消", role: .cancel) {}
            Button("删除", role: .destructive) {
                isDeleting = true
                Task {
                    await viewModel.deleteKnowledgeBase(id: knowledgeBaseId)
                    await MainActor.run { isDeleting = false; dismiss() }
                }
            }
        } message: {
            Text("删除后将无法恢复，包括其中的所有知识点。确定要删除吗？")
        }
        .alert("批量删除", isPresented: $showBatchDeleteConfirm) {
            Button("取消", role: .cancel) {}
            Button("删除 \(selectedIds.count) 个", role: .destructive) {
                Task {
                    await viewModel.batchDeleteItems(ids: Array(selectedIds))
                    isSelectMode = false
                    selectedIds.removeAll()
                }
            }
        } message: {
            Text("确定要删除选中的 \(selectedIds.count) 个知识点吗？此操作不可恢复。")
        }
        .task { await viewModel.loadItems(knowledgeBaseId: knowledgeBaseId) }
        .safeAreaInset(edge: .bottom) {
            NavigationLink(value: Route.aiChat(context: ChatEntryContext(scopeType: .knowledgeBase, scopeId: knowledgeBaseId, scopeName: "知识库", parentKnowledgeBaseId: knowledgeBaseId, createdFrom: "knowledge_base_detail"))) {
                HStack(spacing: 8) {
                    Image(systemName: "bubble.left.and.bubble.right").font(.system(size: 16))
                    Text("AI 对话").font(.system(size: 15, weight: .medium))
                }
                .foregroundColor(.white)
                .frame(maxWidth: .infinity)
                .padding(.vertical, 14)
                .background(ZXGradient.brandPurple)
                .clipShape(RoundedRectangle(cornerRadius: 14))
                .padding(.horizontal, 20)
                .padding(.bottom, 8)
            }
            .background(Color.zxBg0)
        }
    }

    private func kbInfoHeader(_ kb: KnowledgeBase) -> some View {
        VStack(alignment: .leading, spacing: 10) {
            HStack(alignment: .top, spacing: 16) {
                // Cover image
                ZStack {
                    RoundedRectangle(cornerRadius: 16)
                        .fill(Color.zxPurpleBG(0.12))
                        .frame(width: 80, height: 80)
                    if let coverUrl = kb.coverUrl, let url = resolvedCoverURL(coverUrl) {
                        AsyncImage(url: url) { phase in
                            switch phase {
                            case .success(let img):
                                img.resizable().scaledToFill()
                                    .frame(width: 80, height: 80)
                                    .clipShape(RoundedRectangle(cornerRadius: 16))
                            default:
                                Image("icon-books")
                                    .font(.system(size: 34))
                                    .foregroundColor(Color.zxPurple.opacity(0.4))
                            }
                        }
                    } else {
                        Image("icon-books")
                            .font(.system(size: 34))
                            .foregroundColor(Color.zxPurple.opacity(0.4))
                    }
                }

                VStack(alignment: .leading, spacing: 4) {
                    Text(kb.title)
                        .font(.system(size: 20, weight: .bold))
                        .foregroundColor(Color.zxF0)
                    if let desc = kb.description, !desc.isEmpty {
                        Text(desc)
                            .font(.system(size: 14))
                            .foregroundColor(Color.zxF04)
                            .lineLimit(2)
                    } else {
                        Text("暂无简介")
                            .font(.system(size: 14))
                            .foregroundColor(Color.zxF04)
                    }
                }
            }

            // Info rows
            VStack(alignment: .leading, spacing: 2) {
                HStack {
                    infoLabel("来源", kb.ownerType == "official" ? "官方" : "个人")
                        .frame(maxWidth: .infinity, alignment: .leading)
                    if let created = kb.createdAt {
                        infoLabel("创建", String(created.prefix(10)))
                            .frame(maxWidth: .infinity, alignment: .leading)
                    }
                }
                HStack {
                    if let count = kb.itemCount {
                        infoLabel("文件", "\(count) 个")
                            .frame(maxWidth: .infinity, alignment: .leading)
                    }
                    if let updated = kb.updatedAt {
                        infoLabel("更新", String(updated.prefix(10)))
                            .frame(maxWidth: .infinity, alignment: .leading)
                    }
                }
            }
            .font(.system(size: 13))
        }
    }

    private func infoLabel(_ label: String, _ value: String) -> some View {
        HStack(spacing: 4) {
            Text("\(label)：")
                .foregroundColor(Color.zxF03)
            Text(value)
                .foregroundColor(Color.zxF0)
        }
    }

    private func resolvedCoverURL(_ urlString: String) -> URL? {
        if urlString.hasPrefix("http") {
            return URL(string: urlString)
        }
        return URL(string: "https://longde.cloud\(urlString)")
    }

    private func fileTypeText(for item: KnowledgeItem) -> String {
        if let t = item.sourceType?.trimmingCharacters(in: .whitespaces), !t.isEmpty {
            return t.uppercased()
        }
        let title = item.title.lowercased()
        if title.hasSuffix(".pdf") { return "PDF" }
        if title.hasSuffix(".md") { return "MD" }
        if title.hasSuffix(".html") { return "HTML" }
        if title.hasSuffix(".txt") { return "TXT" }
        if title.hasSuffix(".png") || title.hasSuffix(".jpg") || title.hasSuffix(".jpeg") { return "图片" }
        return "文件"
    }

    private func fileTypeIcon(for item: KnowledgeItem) -> String {
        let t = item.sourceType?.lowercased() ?? ""
        let title = item.title.lowercased()
        if t.contains("pdf") || title.hasSuffix(".pdf") { return "doc.richtext" }
        if t.contains("markdown") || title.hasSuffix(".md") { return "doc.plaintext" }
        if t.contains("html") || t.contains("code") || title.hasSuffix(".html") { return "doc.text" }
        if t.contains("image") || title.hasSuffix(".png") || title.hasSuffix(".jpg") { return "photo" }
        return "doc"
    }

    private func itemRow(icon: String, title: String, type: String, date: String, progress: CGFloat) -> some View {
        HStack(spacing: 12) {
            Image(systemName: icon)
                .font(.system(size: 18))
                .foregroundColor(Color.zxPurple)
                .frame(width: 36, height: 36)
            VStack(alignment: .leading, spacing: 4) {
                Text(title)
                    .font(.system(size: 15, weight: .medium))
                    .foregroundColor(Color.zxF0)
                    .lineLimit(1)
                HStack(spacing: 16) {
                    HStack(spacing: 3) {
                        Text("类型：")
                            .foregroundColor(Color.zxF03)
                        Text(type)
                            .foregroundColor(Color.zxF04)
                    }
                    if !date.isEmpty {
                        HStack(spacing: 3) {
                            Text("创建：")
                                .foregroundColor(Color.zxF03)
                            Text(date)
                                .foregroundColor(Color.zxF04)
                        }
                    }
                    HStack(spacing: 3) {
                        Text("学习：")
                            .foregroundColor(Color.zxF03)
                        Text(formatDuration(progress))
                            .foregroundColor(Color.zxF04)
                    }
                }
                .font(.system(size: 12))
            }
            Spacer()
            Image("icon-chevron-right")
                .resizable().scaledToFit().frame(width: 14, height: 14)
                .foregroundColor(Color.zxF03)
        }
        .padding(.vertical, 14)
        .overlay(alignment: .bottom) {
            Color.zxHairline.frame(height: 0.5)
        }
    }

    private func formatShortDate(_ iso: String?) -> String {
        guard let iso else { return "" }
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        let date = formatter.date(from: iso) ?? {
            formatter.formatOptions = [.withInternetDateTime]
            return formatter.date(from: iso)
        }()
        guard let date else { return "" }
        let cal = Calendar.current
        if cal.isDate(date, equalTo: Date(), toGranularity: .year) {
            let df = DateFormatter()
            df.dateFormat = "MM/dd"
            return df.string(from: date)
        }
        let df = DateFormatter()
        df.dateFormat = "yyyy/MM/dd"
        return df.string(from: date)
    }

    private func formatDuration(_ progress: CGFloat) -> String {
        let totalSeconds = Int(progress * 1800) // up to 30 min
        let h = totalSeconds / 3600
        let m = (totalSeconds % 3600) / 60
        let s = totalSeconds % 60
        if h > 0 {
            return String(format: "%d:%02d:%02d", h, m, s)
        }
        return String(format: "%02d:%02d", m, s)
    }

    private func progressFor(_ item: KnowledgeItem) -> CGFloat {
        if item.status == "completed" { return 1.0 }
        if item.status == "active" { return 0.5 }
        return 0
    }

    private func loadSources() async {
        isLoadingSources = true
        do { sources = try await KnowledgeSourceService.shared.list(kbId: knowledgeBaseId) } catch {}
        isLoadingSources = false
    }

    private func deleteSource(_ src: KnowledgeSource) async {
        do {
            let _ = try await KnowledgeSourceService.shared.delete(kbId: knowledgeBaseId, id: src.id)
            sources.removeAll { $0.id == src.id }
            ZXToastManager.shared.success("已删除")
        } catch { ZXToastManager.shared.error("删除失败") }
    }
}
struct ZXCardRow: View { let icon: String; let title: String; let desc: String; let status: String; let c: Color
    var body: some View { HStack(spacing: 12) { Image(systemName: icon).font(.system(size: 18)).foregroundColor(c).frame(width: 40, height: 40).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 10)); VStack(alignment: .leading, spacing: 2) { Text(title).font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0); Text(desc).font(.system(size: 12)).foregroundColor(Color.zxF03) }; Spacer(); Text(status).font(.system(size: 10, weight: .semibold)).foregroundColor(c).padding(.horizontal, 8).padding(.vertical, 2).background(c.opacity(0.12)).clipShape(Capsule()) }
        .padding(14).background(Color.zxFill003).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder006, lineWidth: 1)) }
}

struct AddKnowledgePage: View {
    let knowledgeBaseId: String
    @Environment(\.dismiss) private var dismiss
    @State private var title = ""
    @State private var content = ""
    @State private var sourceType: SourceType = .file
    @State private var selectedFiles: [SelectedFile] = []
    @State private var isUploading = false
    @State private var isSaving = false
    @State private var showFilePicker = false
    @State private var showPhotoPicker = false
    @State private var selectedPhotoItems: [PhotosPickerItem] = []

    struct SelectedFile: Identifiable { let id = UUID(); let name: String; let data: Data; let icon: String; let size: String }

    enum SourceType: String, CaseIterable { case manual = "手写", file = "文件" }

    var body: some View {
        ZStack { Color.zxBg0.ignoresSafeArea(); VStack(spacing: 0) {
            ScrollView { VStack(spacing: 16) {
                // 内容来源选择
                VStack(alignment: .leading, spacing: 8) {
                    Text("内容来源").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035)
                    Picker("", selection: $sourceType) {
                        ForEach(SourceType.allCases, id: \.self) { t in Text(t.rawValue).tag(t) }
                    }
                    .pickerStyle(.segmented)
                }

                // 内容区
                switch sourceType {
                case .manual:
                    VStack(alignment: .leading, spacing: 8) {
                        Text("标题").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035)
                        TextField("输入知识点标题", text: $title).font(.system(size: 16)).tint(Color.zxPurple)
                            .padding(.horizontal, 16).frame(height: 52)
                            .background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14))
                            .overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1))
                    }
                    VStack(alignment: .leading, spacing: 8) {
                        Text("内容").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035)
                        TextEditor(text: $content)
                            .frame(minHeight: 200).scrollContentBackground(.hidden).padding(12)
                            .background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14))
                            .overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1))
                    }

                case .file:
                    VStack(spacing: 10) {
                        HStack(spacing: 8) {
                            Button { showFilePicker = true } label: {
                                HStack {
                                    Image("icon-plus")
                                    Text("选择文件")
                                }
                                .font(.system(size: 14, weight: .medium))
                                .foregroundColor(Color.zxPrimary)
                                .frame(maxWidth: .infinity).frame(height: 48)
                                .background(Color.zxPrimarySoft)
                                .clipShape(RoundedRectangle(cornerRadius: 12))
                            }
                            Button { showPhotoPicker = true } label: {
                                HStack {
                                    Image("icon-camera")
                                    Text("图片")
                                }
                                .font(.system(size: 14, weight: .medium))
                                .foregroundColor(Color.zxAccent)
                                .frame(maxWidth: .infinity).frame(height: 48)
                                .background(Color.zxAccent.opacity(0.1))
                                .clipShape(RoundedRectangle(cornerRadius: 12))
                            }
                        }

                        // 已选文件列表
                        if !selectedFiles.isEmpty {
                            VStack(spacing: 6) {
                                ForEach(selectedFiles) { f in
                                    HStack(spacing: 8) {
                                        Image(systemName: f.icon).foregroundColor(Color.zxGreen)
                                        Text(f.name).font(.system(size: 14)).foregroundColor(Color.zxF0).lineLimit(1)
                                        Spacer()
                                        Text(f.size).font(.system(size: 12)).foregroundColor(Color.zxF04)
                                        Button {
                                            selectedFiles.removeAll { $0.id == f.id }
                                        } label: {
                                            Image(systemName: "xmark.circle").font(.system(size: 16)).foregroundColor(Color.zxF04)
                                        }
                                    }
                                    .padding(.horizontal, 12).padding(.vertical, 8)
                                    .background(Color.zxFill004)
                                    .clipShape(RoundedRectangle(cornerRadius: 12))
                                }
                            }
                        }

                        HStack {
                            Image(systemName: "info.circle").font(.system(size: 12))
                            Text("支持多选，每个文件生成一个知识点")
                        }
                        .font(.system(size: 12)).foregroundColor(Color.zxF04)
                    }

                    if isUploading {
                        HStack(spacing: 8) {
                            ProgressView()
                            Text("上传中 \(currentUploadIndex)/\(selectedFiles.count)...").font(.system(size: 14)).foregroundColor(Color.zxF04)
                        }
                    }
                }

                // 保存按钮
                Button {
                    Task { await save() }
                } label: {
                    HStack(spacing: 8) {
                        if isSaving { ProgressView().tint(.white) }
                        Text(sourceType == .file && selectedFiles.count > 1 ? "批量添加 (\(selectedFiles.count) 个)" : "保存").font(.system(size: 14, weight: .bold))
                    }
                    .foregroundColor(.white).frame(maxWidth: .infinity).frame(height: 52)
                    .background(canSave ? AnyView(ZXGradient.ctaPurple) : AnyView(Color.zxHairlineStrong))
                    .clipShape(RoundedRectangle(cornerRadius: 16))
                }
                .disabled(!canSave || isSaving)
            }.padding(.horizontal, 20).padding(.top, 8).padding(.bottom, 80) }.scrollIndicators(.hidden) }
        }
        .navigationBarTitleDisplayMode(.inline).toolbarBackground(.hidden, for: .navigationBar).toolbar(.hidden, for: .tabBar)
        .fileImporter(isPresented: $showFilePicker, allowedContentTypes: [.plainText, .pdf, .image], allowsMultipleSelection: true) { result in
            if case .success(let urls) = result { handleFiles(urls) }
        }
        .photosPicker(isPresented: $showPhotoPicker, selection: $selectedPhotoItems, maxSelectionCount: 10, matching: .images)
        .onChange(of: selectedPhotoItems) { _, items in
            guard !items.isEmpty else { return }
            Task { await handlePhotos(items) }
        }
    }

    // MARK: - File handling

    @State private var currentUploadIndex = 0

    private var canSave: Bool {
        switch sourceType {
        case .manual: return !title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
        case .file: return !selectedFiles.isEmpty && !isUploading
        }
    }

    private func handleFiles(_ urls: [URL]) {
        for url in urls {
            guard url.startAccessingSecurityScopedResource() else { continue }
            defer { url.stopAccessingSecurityScopedResource() }
            guard let data = try? Data(contentsOf: url) else { continue }
            let name = url.lastPathComponent
            selectedFiles.append(SelectedFile(
                name: name,
                data: data,
                icon: fileIcon(name: name),
                size: formatSize(data.count)
            ))
        }
    }

    private func handlePhotos(_ items: [PhotosPickerItem]) async {
        for item in items {
            guard let data = try? await item.loadTransferable(type: Data.self) else { continue }
            let compressed: Data
            if let image = UIImage(data: data), data.count > 2 * 1024 * 1024 {
                compressed = image.jpegData(compressionQuality: 0.6) ?? data
            } else {
                compressed = data
            }
            let name = "image_\(Int(Date().timeIntervalSince1970))_\(selectedFiles.count).jpg"
            selectedFiles.append(SelectedFile(
                name: name,
                data: compressed,
                icon: "photo",
                size: formatSize(compressed.count)
            ))
        }
        selectedPhotoItems = []
    }

    private func save() async {
        isSaving = true
        defer { isSaving = false }

        switch sourceType {
        case .manual:
            do {
                _ = try await KnowledgeItemService.shared.create(
                    knowledgeBaseId: knowledgeBaseId,
                    title: title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "手写笔记" : title,
                    content: content,
                    itemType: "manual"
                )
                ZXToastManager.shared.success("知识点已添加")
                dismiss()
            } catch {
                ZXToastManager.shared.error("添加失败: \(error.localizedDescription)")
            }

        case .file:
            isUploading = true
            defer { isUploading = false }
            var successCount = 0
            let total = selectedFiles.count

            for (i, file) in selectedFiles.enumerated() {
                currentUploadIndex = i + 1
                do {
                    let mime = mimeType(name: file.name)
                    let ext = file.name.lowercased()
                    let itemType = ext.hasSuffix(".md") || ext.hasSuffix(".markdown") ? "markdown"
                        : ext.hasSuffix(".txt") ? "text"
                        : ext.hasSuffix(".pdf") ? "pdf"
                        : ext.hasSuffix(".jpg") || ext.hasSuffix(".jpeg") || ext.hasSuffix(".png") || ext.hasSuffix(".heic") ? "image"
                        : "file"

                    let fileId: String
                    if let image = UIImage(data: file.data) {
                        fileId = try await FileUploadService.shared.uploadImage(image, filename: file.name)
                    } else {
                        fileId = try await FileUploadService.shared.uploadData(file.data, filename: file.name, mimeType: mime)
                    }
                    let downloadUrl = try await FileUploadService.shared.getDownloadUrl(fileId: fileId)

                    // 标题：优先用户填写，否则用文件名
                    let itemTitle: String
                    if !title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
                        itemTitle = total == 1 ? title : "\(title) (\(i+1))"
                    } else {
                        itemTitle = file.name
                    }

                    _ = try await KnowledgeItemService.shared.create(
                        knowledgeBaseId: knowledgeBaseId,
                        title: itemTitle,
                        content: downloadUrl,
                        itemType: itemType
                    )
                    successCount += 1
                } catch {
                    ZXToastManager.shared.error("「\(file.name)」上传失败")
                }
            }

            if successCount == total {
                ZXToastManager.shared.success("\(total) 个知识点已添加")
                dismiss()
            } else if successCount > 0 {
                ZXToastManager.shared.success("\(successCount)/\(total) 个知识点已添加")
                dismiss()
            }
        }
    }

    private func fileIcon(name: String) -> String {
        let ext = name.lowercased()
        if ext.hasSuffix(".md") || ext.hasSuffix(".markdown") { return "doc.richtext" }
        if ext.hasSuffix(".txt") { return "doc.plaintext" }
        if ext.hasSuffix(".pdf") { return "doc" }
        if ext.hasSuffix(".jpg") || ext.hasSuffix(".jpeg") || ext.hasSuffix(".png") || ext.hasSuffix(".heic") { return "photo" }
        return "doc"
    }

    private func formatSize(_ size: Int) -> String {
        if size < 1024 { return "\(size) B" }
        if size < 1024 * 1024 { return "\(size / 1024) KB" }
        return String(format: "%.1f MB", Double(size) / 1024 / 1024)
    }

    private func mimeType(name: String) -> String {
        let ext = name.lowercased()
        if ext.hasSuffix(".md") || ext.hasSuffix(".markdown") { return "text/markdown" }
        if ext.hasSuffix(".txt") { return "text/plain" }
        if ext.hasSuffix(".pdf") { return "application/pdf" }
        if ext.hasSuffix(".jpg") || ext.hasSuffix(".jpeg") { return "image/jpeg" }
        if ext.hasSuffix(".png") { return "image/png" }
        if ext.hasSuffix(".heic") { return "image/heic" }
        return "application/octet-stream"
    }
}

// MARK: - Knowledge Detail ViewModel

@MainActor
final class KnowledgeDetailViewModel: ObservableObject {
    @Published var localPath: String?
    @Published var detectedType: MaterialType?
    @Published var loadState: LoadState = .loading

    enum LoadState { case loading, ready, error(String) }

    let item: KnowledgeItem

    init(item: KnowledgeItem) { self.item = item }

    var isURL: Bool {
        guard let c = item.content else { return false }
        return c.hasPrefix("http://") || c.hasPrefix("https://")
    }

    func load() async {
        print("[KD_DETAIL] load() start — item.id=\(item.id), title=\(item.title)")
        print("[KD_DETAIL] isURL=\(isURL), content prefix=\(item.content?.prefix(200) ?? "nil")")

        guard isURL, let urlStr = item.content, let url = URL(string: urlStr) else {
            // Not a URL — plain text content, ready immediately
            let contentLen = item.content?.count ?? 0
            print("[KD_DETAIL] plain text path — content length=\(contentLen), setting .ready")
            loadState = .ready
            return
        }
        loadState = .loading
        let ext = url.pathExtension.isEmpty ? "bin" : url.pathExtension.lowercased()
        let localURL = FileManager.default.temporaryDirectory
            .appendingPathComponent("kd_\(item.id).\(ext)")
        print("[KD_DETAIL] URL download — ext=\(ext), local=\(localURL.path)")

        // Check cached file — skip if it's a COS error XML
        if FileManager.default.fileExists(atPath: localURL.path) {
            let fileSize = (try? FileManager.default.attributesOfItem(atPath: localURL.path)[.size] as? Int) ?? -1
            // Detect COS error responses (small XML files < 2KB)
            if fileSize > 0 && fileSize < 2048 {
                if let content = try? String(contentsOfFile: localURL.path, encoding: .utf8),
                   content.contains("<Error>"), content.contains("AccessDenied") {
                    print("[KD_DETAIL] cached file is COS error, deleting and re-fetching...")
                    try? FileManager.default.removeItem(at: localURL)
                }
            }
            if FileManager.default.fileExists(atPath: localURL.path) {
                print("[KD_DETAIL] file cached — size=\(fileSize), type=\(typeFromExtension(ext))")
                localPath = localURL.path
                detectedType = typeFromExtension(ext)
                loadState = .ready
                return
            }
        }

        // Re-fetch item from API to get a fresh signed URL
        print("[KD_DETAIL] re-fetching item from API for fresh URL...")
        let freshItem = try? await KnowledgeItemService.shared.detail(id: item.id)
        let downloadURL: URL
        if let fresh = freshItem, let freshStr = fresh.content, let freshURL = URL(string: freshStr) {
            print("[KD_DETAIL] got fresh URL")
            downloadURL = freshURL
        } else {
            print("[KD_DETAIL] re-fetch failed, using original URL")
            downloadURL = url
        }

        do {
            let (data, _) = try await URLSession.shared.data(from: downloadURL)
            print("[KD_DETAIL] download done — size=\(data.count)")
            // If it's a COS error XML, show error
            if data.count < 2048, let text = String(data: data, encoding: .utf8), text.contains("<Error>") {
                print("[KD_DETAIL] downloaded content is an error XML: \(text.prefix(200))")
                loadState = .error("文件访问已过期，请刷新后重试")
                return
            }
            try data.write(to: localURL)
            localPath = localURL.path
            detectedType = typeFromExtension(ext)
            print("[KD_DETAIL] saved — path=\(localURL.path), type=\(String(describing: detectedType))")
            loadState = .ready
        } catch {
            print("[KD_DETAIL] download ERROR — \(error.localizedDescription)")
            loadState = .error(error.localizedDescription)
        }
    }

    private func typeFromExtension(_ ext: String) -> MaterialType {
        switch ext {
        case "md", "markdown": return .markdown
        case "txt", "text":     return .text
        case "pdf":             return .pdf
        case "png", "jpg", "jpeg", "webp", "gif": return .image
        case "doc", "docx":     return .word
        case "xls", "xlsx":     return .excel
        case "ppt", "pptx":     return .powerPoint
        case "epub":            return .epub
        default:                return .unknown
        }
    }
}

// MARK: - Knowledge Detail Page

struct KnowledgeDetailPage: View {
    let item: KnowledgeItem
    @StateObject private var vm: KnowledgeDetailViewModel

    @State private var showQuickLook = false
    @State private var showNoteSheet = false

    init(item: KnowledgeItem) {
        self.item = item
        _vm = StateObject(wrappedValue: KnowledgeDetailViewModel(item: item))
    }

    var body: some View {
        Group {
            switch vm.loadState {
            case .loading:
                VStack(spacing: 12) {
                    ProgressView().tint(Color.zxPrimary)
                    Text("加载中…").font(.system(size: 14)).foregroundColor(Color.zxF04)
                }.frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.zxCanvas)
            case .error(let msg):
                VStack(spacing: 16) {
                    Image(systemName: "exclamationmark.triangle").font(.system(size: 40)).foregroundColor(Color.zxCoral)
                    Text(msg).font(.system(size: 14)).foregroundColor(Color.zxF04)
                    // Fallback: show summary or raw text
                    if let summary = item.summary, !summary.isEmpty {
                        ScrollView { Text(summary).font(.system(size: 14)).foregroundColor(Color.zxF0).lineSpacing(6).padding(20) }
                    }
                    Button("重试") { Task { await vm.load() } }
                        .font(.system(size: 14, weight: .medium)).foregroundColor(Color.zxPrimary)
                        .padding(.horizontal, 24).padding(.vertical, 10)
                        .background(Color.zxPrimarySoft).clipShape(RoundedRectangle(cornerRadius: 10))
                }.frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.zxCanvas)
            case .ready:
                if let path = vm.localPath, let mt = vm.detectedType {
                    MaterialReaderView(materialId: item.id, filePath: path, materialType: mt, title: item.title, showQuickLook: $showQuickLook, showNoteSheet: $showNoteSheet)
                } else if let content = item.content {
                    // Plain text content — render directly
                    ZStack { Color.zxCanvas.ignoresSafeArea()
                        ScrollView {
                            VStack(alignment: .leading, spacing: 12) {
                                if let summary = item.summary, !summary.isEmpty {
                                    Text(summary).font(.system(size: 14)).foregroundColor(Color.zxF0).lineSpacing(6)
                                        .padding(16).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14))
                                }
                                Text(content).font(.system(size: 14)).foregroundColor(Color.zxF0).lineSpacing(6)
                            }.padding(.horizontal, 20).padding(.top, 8).padding(.bottom, 100)
                        }.scrollIndicators(.hidden)
                    }
                }
            }
        }
        .navigationTitle(item.title)
        .navigationBarTitleDisplayMode(.inline)
        .toolbar(.hidden, for: .tabBar)
        .toolbarBackground(.hidden, for: .navigationBar)
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                Menu {
                    NavigationLink(value: Route.aiChat(context: ChatEntryContext(scopeType: .knowledgeItem, scopeId: item.id, scopeName: item.title, parentKnowledgeBaseId: item.knowledgeBaseId, createdFrom: "knowledge_item_detail"))) {
                        Label("AI 对话", systemImage: "bubble.left.and.bubble.right")
                    }
                    NavigationLink(value: Route.studyHome) {
                        Label("学习", systemImage: "arrow.triangle.2.circlepath")
                    }
                    NavigationLink(value: Route.editKnowledge(item: item)) {
                        Label("编辑", systemImage: "pencil")
                    }
                    if let mt = vm.detectedType {
                        let pm = previewMode(for: mt)
                        if pm == .nativeReader {
                            Button { showNoteSheet = true } label: {
                                Label("笔记", systemImage: "square.and.pencil")
                            }
                        }
                        if pm == .platformPreview {
                            Button { showQuickLook = true } label: {
                                Label("预览", systemImage: "eye")
                            }
                        }
                    }
                } label: {
                    Image(systemName: "ellipsis.circle")
                        .font(.system(size: 16))
                        .foregroundColor(Color.zxF05)
                }
            }
        }
        .task { await vm.load() }
    }
}

struct ZXChip: View { let text: String; let color: Color
    var body: some View { Text(text).font(.system(size: 10, weight: .semibold)).foregroundColor(color).padding(.horizontal, 8).padding(.vertical, 2).background(color.opacity(0.12)).clipShape(Capsule()) }
}

struct ImportPage: View {
    @Environment(\.dismiss) private var dismiss
    @State private var showFilePicker = false
    @State private var showPhotoPicker = false
    @State private var showLinkSheet = false
    @State private var linkURL = ""
    @State private var isImporting = false
    @State private var kbId: String?
    @State private var statusMessage = ""; @State private var importError: String?
    @State private var selectedPhotoItem: PhotosPickerItem?

    var body: some View {
        ZStack { Color.zxBg0.ignoresSafeArea(); VStack(spacing: 0) {
            ScrollView { VStack(spacing: 12) {
                if let error = importError {
                    HStack(spacing: 8) { Image(systemName: "exclamationmark.triangle").foregroundColor(.red); Text(error).font(.system(size: 14)).foregroundColor(.red) }
                        .padding(12)

                }
                if !statusMessage.isEmpty {
                    HStack(spacing: 8) { ProgressView(); Text(statusMessage).font(.system(size: 14)).foregroundColor(Color.zxF04) }
                        .padding(12)
                }
                Button { showFilePicker = true } label: {
                    ZXImportRow(icon: "doc.text", title: "文件导入", desc: "支持 PDF、Word、Markdown、TXT")
                }
                Button { showPhotoPicker = true } label: {
                    ZXImportRow(icon: "photo.on.rectangle", title: "相册导入", desc: "从相册选择截图或图片，AI 自动识别文字")
                }
                Button { showLinkSheet = true } label: {
                    ZXImportRow(icon: "link", title: "链接导入", desc: "粘贴网页链接，自动提取内容")
                }
            }.padding(.horizontal, 20).padding(.top, 8) }.scrollIndicators(.hidden) }
        }
        .navigationBarTitleDisplayMode(.inline).toolbarBackground(.hidden, for: .navigationBar).toolbar(.hidden, for: .tabBar)
        .disabled(isImporting)
        .task { do { let kbs = try await KnowledgeBaseService.shared.list(page: 1, limit: 1); kbId = kbs.first?.id } catch {} }
        .fileImporter(isPresented: $showFilePicker, allowedContentTypes: [.pdf, .plainText], allowsMultipleSelection: true) { result in
            if case .success(let urls) = result { Task { await handleFiles(urls) } }
        }
        .photosPicker(isPresented: $showPhotoPicker, selection: $selectedPhotoItem, matching: .images)
        .onChange(of: selectedPhotoItem) { _, item in
            guard let item else { return }
            Task { await handlePhoto(item) }
        }
        .alert("链接导入", isPresented: $showLinkSheet) {
            TextField("输入网页 URL", text: $linkURL)
            Button("导入") { Task { await handleLink() } }
            Button("取消", role: .cancel) {}
        } message: { Text("输入要导入的网页链接地址") }
    }

    private func ensureKB() async -> String? {
        if let id = kbId { return id }
        do { let kbs = try await KnowledgeBaseService.shared.list(page: 1, limit: 1); kbId = kbs.first?.id; return kbId } catch { return nil }
    }

    private func handleFiles(_ urls: [URL]) async {
        guard let kb = await ensureKB() else { importError = "请先创建一个知识库"; return }
        isImporting = true; importError = nil
        for url in urls {
            guard url.startAccessingSecurityScopedResource() else { continue }
            defer { url.stopAccessingSecurityScopedResource() }
            guard let data = try? Data(contentsOf: url) else { continue }
            let name = url.lastPathComponent
            statusMessage = "上传 \(name)..."
            do {
                let mime = url.pathExtension.lowercased() == "pdf" ? "application/pdf" : "text/plain"
                let _ = try await FileUploadService.shared.uploadData(data, filename: name, mimeType: mime)
                statusMessage = "开始导入 \(name)..."
                let _ = try await DocumentImportService.shared.create(knowledgeBaseId: kb, fileName: name, sourceType: "file")
                statusMessage = "\(name) 导入任务已创建"
            } catch { importError = "\(name) 导入失败: \(error.localizedDescription)" }
        }
        isImporting = false; statusMessage = ""
        if importError == nil { ZXToastManager.shared.success("导入完成"); dismiss() }
    }

    private func handlePhoto(_ item: PhotosPickerItem) async {
        guard let kb = await ensureKB() else { importError = "请先创建一个知识库"; return }
        isImporting = true; importError = nil; selectedPhotoItem = nil
        guard let data = try? await item.loadTransferable(type: Data.self),
              let image = UIImage(data: data) else { isImporting = false; return }
        let name = "photo_\(Int(Date().timeIntervalSince1970)).jpg"
        statusMessage = "上传图片..."
        do {
            let _ = try await FileUploadService.shared.uploadImage(image, filename: name)
            statusMessage = "开始识别..."
            let _ = try await DocumentImportService.shared.create(knowledgeBaseId: kb, fileName: name, sourceType: "file")
            ZXToastManager.shared.success("图片已提交导入")
            dismiss()
        } catch { importError = "导入失败: \(error.localizedDescription)" }
        isImporting = false; statusMessage = ""
    }

    private func handleLink() async {
        guard !linkURL.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
        guard let kb = await ensureKB() else { importError = "请先创建一个知识库"; return }
        isImporting = true; importError = nil
        do {
            let _ = try await DocumentImportService.shared.create(knowledgeBaseId: kb, sourceType: "link", rawText: linkURL)
            ZXToastManager.shared.success("链接已提交导入")
            dismiss()
        } catch { importError = "导入失败: \(error.localizedDescription)" }
        isImporting = false
    }
}

struct ZXImportRow: View { let icon: String; let title: String; let desc: String
    var body: some View { HStack(spacing: 14) { Image(systemName: icon).font(.system(size: 22)).foregroundColor(Color.zxPurple).frame(width: 48, height: 48)
; VStack(alignment: .leading, spacing: 2) { Text(title).font(.system(size: 16, weight: .semibold)).foregroundColor(Color.zxF0); Text(desc).font(.system(size: 12)).foregroundColor(Color.zxF04) }; Spacer(); Image("icon-chevron-right").resizable().scaledToFit().frame(width: 16, height: 16).foregroundColor(Color.zxF03) }.padding(16).background(Color.zxFill003).clipShape(RoundedRectangle(cornerRadius: 16)).overlay(RoundedRectangle(cornerRadius: 16).stroke(Color.zxBorder006, lineWidth: 1)) }
}

// MARK: - Import Review

struct ImportReviewPage: View {
    let sourceId: String
    @Environment(\.dismiss) private var dismiss
    @State private var candidates: [ImportCandidate] = []
    @State private var isLoading = true
    @State private var isProcessing = false
    @State private var errorMessage: String?
    @State private var expandedId: String?

    var body: some View {
        ZStack { Color.zxBg0.ignoresSafeArea(); VStack(spacing: 0) {
            if isLoading {
                VStack(spacing: 12) { ProgressView().tint(Color.zxPurple); Text("加载候选...").font(.system(size: 14)).foregroundColor(Color.zxF04) }
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            } else if candidates.isEmpty {
                VStack(spacing: 12) {
                    Image(systemName: "checkmark.circle").font(.system(size: 36)).foregroundColor(Color.zxGreen)
                    Text("没有待审批的候选").font(.system(size: 14)).foregroundColor(Color.zxF04)
                }.frame(maxWidth: .infinity, maxHeight: .infinity)
            } else {
                ScrollView {
                    VStack(spacing: 12) {
                        HStack {
                            Text("\(candidates.count) 个候选知识点").font(.system(size: 14, weight: .medium)).foregroundColor(Color.zxF04)
                            Spacer()
                            Button {
                                Task { await batchAccept() }
                            } label: {
                                Text("全部接受").font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxPrimary)
                            }
                            .disabled(isProcessing)
                        }
                        ForEach(candidates) { c in
                            VStack(spacing: 0) {
                                Button {
                                    withAnimation { expandedId = expandedId == c.id ? nil : c.id }
                                } label: {
                                    HStack(spacing: 12) {
                                        if let conf = c.confidence {
                                            Text("\(Int(conf * 100))%").font(.system(size: 10, weight: .bold))
                                                .foregroundColor(conf > 0.7 ? Color.zxGreen : Color.zxAmber)
                                                .padding(.horizontal, 6).padding(.vertical, 2)
                                                .background((conf > 0.7 ? Color.zxGreen : Color.zxAmber).opacity(0.12))
                                                .clipShape(Capsule())
                                        }
                                        VStack(alignment: .leading, spacing: 4) {
                                            Text(c.title ?? "无标题").font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0).lineLimit(1)
                                            if let content = c.content {
                                                Text(content).font(.system(size: 12)).foregroundColor(Color.zxF04).lineLimit(2)
                                            }
                                        }
                                        Spacer()
                                        Image(systemName: expandedId == c.id ? "chevron.up" : "chevron.down").font(.system(size: 12)).foregroundColor(Color.zxF03)
                                    }
                                    .padding(12).background(Color.zxFill003).clipShape(RoundedRectangle(cornerRadius: 14))
                                }
                                .foregroundColor(.primary)

                                if expandedId == c.id {
                                    HStack(spacing: 8) {
                                        Button {
                                            Task { await rejectCandidate(c) }
                                        } label: {
                                            Label("拒绝", systemImage: "xmark").font(.system(size: 14, weight: .medium))
                                                .foregroundColor(Color.zxCoral).frame(maxWidth: .infinity).frame(height: 40)
                                                

                                        }
                                        Button {
                                            Task { await acceptCandidate(c) }
                                        } label: {
                                            Label("接受", systemImage: "checkmark").font(.system(size: 14, weight: .medium))
                                                .foregroundColor(Color.zxGreen).frame(maxWidth: .infinity).frame(height: 40)
                                                

                                        }
                                    }
                                    .padding(.horizontal, 12).padding(.top, 8)
                                }
                            }
                        }
                    }
                    .padding(.horizontal, 20).padding(.top, 8).padding(.bottom, 100)
                }
                .scrollIndicators(.hidden)
            }
        }}
        .navigationTitle("候选审批").navigationBarTitleDisplayMode(.inline).toolbarBackground(.hidden, for: .navigationBar).toolbar(.hidden, for: .tabBar)
        .disabled(isProcessing)
        .task { await load() }
    }

    private func load() async {
        isLoading = true; errorMessage = nil
        do { candidates = try await ImportCandidateService.shared.listBySource(sourceId: sourceId) } catch { errorMessage = error.localizedDescription }
        isLoading = false
    }

    private func acceptCandidate(_ c: ImportCandidate) async {
        isProcessing = true
        do {
            let _ = try await ImportCandidateService.shared.accept(id: c.id)
            candidates.removeAll { $0.id == c.id }
            ZXToastManager.shared.success("已接受")
        } catch { ZXToastManager.shared.error("操作失败") }
        isProcessing = false
    }

    private func rejectCandidate(_ c: ImportCandidate) async {
        isProcessing = true
        do {
            let _ = try await ImportCandidateService.shared.reject(id: c.id)
            candidates.removeAll { $0.id == c.id }
            ZXToastManager.shared.success("已拒绝")
        } catch { ZXToastManager.shared.error("操作失败") }
        isProcessing = false
    }

    private func batchAccept() async {
        isProcessing = true
        do {
            let _ = try await ImportCandidateService.shared.batchAccept(sourceId: sourceId)
            candidates.removeAll()
            ZXToastManager.shared.success("全部接受")
        } catch { ZXToastManager.shared.error("操作失败") }
        isProcessing = false
    }
}

struct EditKnowledgePage: View {
    let item: KnowledgeItem
    @State private var title: String; @State private var content: String

    init(item: KnowledgeItem) {
        self.item = item
        _title = State(initialValue: item.title)
        _content = State(initialValue: item.content ?? "")
    }
    var body: some View {
        ZStack { Color.zxBg0.ignoresSafeArea(); VStack(spacing: 0) {
            ScrollView { VStack(spacing: 16) {
                VStack(alignment: .leading, spacing: 8) { Text("标题").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035); TextField("", text: $title).font(.system(size: 16)).tint(Color.zxPurple).padding(.horizontal, 16).frame(height: 52).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)) }
                VStack(alignment: .leading, spacing: 8) { Text("内容").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035); TextEditor(text: $content).frame(minHeight: 200).scrollContentBackground(.hidden).padding(12).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)) }
                Button {
                    Task { _ = try? await KnowledgeItemService.shared.update(id: item.id, title: title, content: content, summary: nil) }
                } label: { Text("保存修改").font(.system(size: 14, weight: .bold)).foregroundColor(.white).frame(maxWidth: .infinity).frame(height: 52).background(ZXGradient.ctaPurple).clipShape(RoundedRectangle(cornerRadius: 16)) }
            }.padding(.horizontal, 20).padding(.top, 8).padding(.bottom, 80) }.scrollIndicators(.hidden) }
        }.navigationBarTitleDisplayMode(.inline).toolbarBackground(.hidden, for: .navigationBar).toolbar(.hidden, for: .tabBar)
    }
}
