import PhotosUI
import SwiftUI
import UIKit
import UniformTypeIdentifiers

// 液态玻璃原生壳：底部标签栏 + 导航栈（iOS 26 SDK 自动玻璃化）。离屏无头引擎 WebView 挂在
// 背景里保持存活、零尺寸不可见。内容卡片属内容层不加玻璃；悬浮主操作用玻璃强调按钮（功能
// 层）。见 ios/PLAN.md §8.1。用户可见文案全走 i18n（t()，源＝web catalog）。设备 / 传输
// 列表由引擎经桥推来的真实 presence / 传输态驱动（EngineController，arch A）。
struct RootView: View
{
    @Environment(EngineController.self) private var engine
    // auth 经环境注入（AppRoot 已 .environment(auth)），用于把会话源在 EngineWebView.makeUIView
    // 里同步接给引擎——早于 boot 注入，消除冷启重开的「missing injected session」竞态（#11）。
    @Environment(AuthManager.self) private var auth
    // 初始标签可经 CDROP_TAB 环境变量指定（本机截图测试用：直接进设置看引擎状态）。
    @State private var selection = ProcessInfo.processInfo.environment["CDROP_TAB"] ?? "transfer"

    var body: some View
    {
        TabView(selection: $selection)
        {
            // 顺序按领域与频次分组（#1）：文件域（传输 → 收到的文件）相邻，其次消息，最后管理区
            // （设备 → 设置）。传输 / 消息带未读红点（#5），对应页激活即清零。
            Tab(t("ios.tab.transfer"), systemImage: "arrow.up.arrow.down", value: "transfer")
            {
                NavigationStack
                {
                    TransferListView()
                }
            }
            .badge(engine.unreadTransfers)
            Tab(t("ios.tab.files"), systemImage: "folder", value: "files")
            {
                NavigationStack
                {
                    ReceivedFilesView()
                }
            }
            Tab(t("ios.tab.messages"), systemImage: "message", value: "messages")
            {
                NavigationStack
                {
                    MessagesView()
                }
            }
            .badge(engine.unreadMessages)
            Tab(t("ios.tab.devices"), systemImage: "laptopcomputer.and.iphone", value: "devices")
            {
                NavigationStack
                {
                    DeviceListView()
                }
            }
            Tab(t("ios.tab.settings"), systemImage: "gearshape", value: "settings")
            {
                NavigationStack
                {
                    SettingsView()
                }
            }
        }
        .tabBarMinimizeBehavior(.onScrollDown)
        .tint(.cdropAccent)
        // 瞬时通知横幅覆盖在内容顶部（#2）。
        .overlay(alignment: .top)
        {
            NoticeOverlay()
        }
        // 标签切换时同步当前页给引擎，驱动未读清零（#5）。
        .onChange(of: selection) { engine.setActiveTab(selection) }
        .background
        {
            EngineWebView(controller: engine, auth: auth)
                .frame(width: 0, height: 0)
                .opacity(0)
                .allowsHitTesting(false)
        }
        // Share Extension 交接：cdrop://share 唤起后导入收件箱待发文件，弹出选设备发送。
        .onAppear { engine.loadPendingShares() }
        .confirmationDialog(
            t("ios.share.title", [ "n": String(engine.pendingShareFiles.count) ]),
            isPresented: Binding(
                get: { !engine.pendingShareFiles.isEmpty },
                set: { if !$0 { engine.dismissShares() } }
            ),
            titleVisibility: .visible
        )
        {
            ForEach(engine.sendableDevices())
            { dev in
                Button(dev.name) { engine.sendShares(to: dev.name) }
            }
            Button(t("common.cancel"), role: .cancel) { engine.dismissShares() }
        }
    }
}

struct TransferListView: View
{
    @Environment(EngineController.self) private var engine
    @State private var showImporter = false
    @State private var showPhotoPicker = false
    @State private var photoItem: PhotosPickerItem?
    @State private var pickedURL: URL?
    @State private var showDevicePicker = false
    @State private var showNoDevices = false
    @State private var showClearConfirm = false
    // 点按传输卡片进详情：不在 List 内用 NavigationLink（会出现 chevron 改变卡片观感），改经
    // navigationDestination(item:) 程序化推栈（按 sessionId，String 即 Hashable 标识），卡片保持原样。
    @State private var detailSession: String?

    // 可发送目标：在线、且非本机（共用 EngineController.sendableDevices）。
    private var sendableDevices: [DeviceItem]
    {
        engine.sendableDevices()
    }

    var body: some View
    {
        Group
        {
            if engine.transfers.isEmpty && engine.history.isEmpty
            {
                ContentUnavailableView(t("ios.transfer.empty"), systemImage: "tray")
            }
            else
            {
                transferList
            }
        }
        .navigationTitle(t("ios.tab.transfer"))
        .navigationDestination(item: $detailSession) { sid in TransferDetailView(sessionId: sid) }
        .toolbar
        {
            if !engine.history.isEmpty
            {
                ToolbarItem(placement: .topBarTrailing)
                {
                    Button(role: .destructive) { showClearConfirm = true }
                    label: { Label(t("ios.records.clear"), systemImage: "trash") }
                }
            }
        }
        .confirmationDialog(t("ios.records.clearConfirm"), isPresented: $showClearConfirm, titleVisibility: .visible)
        {
            Button(t("ios.records.clear"), role: .destructive) { engine.clearHistory() }
            Button(t("common.cancel"), role: .cancel) { }
        }
        .overlay(alignment: .bottomTrailing)
        {
            // 发送入口：文件（Files / 文档）或图库（Photos）。图库走 PhotosPicker，选中后取字节落
            // 临时文件再复用既有发送链路（#14）。
            Menu
            {
                Button { showImporter = true }
                label: { Label(t("ios.send.fromFiles"), systemImage: "folder") }
                Button { showPhotoPicker = true }
                label: { Label(t("ios.send.fromPhotos"), systemImage: "photo.on.rectangle") }
            }
            label:
            {
                Image(systemName: "paperplane.fill")
                    .font(.title2)
                    .padding(18)
            }
            .buttonStyle(.glassProminent)
            .padding(24)
        }
        .fileImporter(isPresented: $showImporter, allowedContentTypes: [ .item ])
        { result in
            if case .success(let url) = result
            {
                pickedURL = url
                if sendableDevices.isEmpty { showNoDevices = true }
                else { showDevicePicker = true }
            }
        }
        .photosPicker(isPresented: $showPhotoPicker, selection: $photoItem,
                      matching: .any(of: [ .images, .videos ]))
        .onChange(of: photoItem) { loadPhoto() }
        .confirmationDialog(t("ios.send.pickDevice"), isPresented: $showDevicePicker, titleVisibility: .visible)
        {
            ForEach(sendableDevices)
            { dev in
                Button(dev.name) { startSend(to: dev.name) }
            }
            Button(t("common.cancel"), role: .cancel) { }
        }
        .alert(t("ios.send.noDevices"), isPresented: $showNoDevices) { }
    }

    // List（而非 ScrollView）：让传输记录与设备 / 文件等长条行操作逻辑一致——支持滑动删除 + 长按菜单。
    // 卡片观感经 .plain 列表 + 透明行背景 + 隐藏分隔线保留。抽成独立属性，避免 body 表达式过大致类型推断超时。
    private var transferList: some View
    {
        List
        {
            if !engine.transfers.isEmpty
            {
                Section
                {
                    ForEach(engine.transfers) { transferRow($0, history: false) }
                }
                header: { sectionHeader(t("home.transfer.active")) }
            }
            if !engine.history.isEmpty
            {
                Section
                {
                    ForEach(engine.history) { transferRow($0, history: true) }
                }
                header: { sectionHeader(t("home.transfer.history")) }
            }
        }
        .listStyle(.plain)
    }

    private func sectionHeader(_ title: String) -> some View
    {
        HStack
        {
            Text(title)
                .font(.subheadline)
                .foregroundStyle(.secondary)
            Spacer()
        }
    }

    // 传输行：卡片观感不变；点按进详情；历史记录支持滑动删除 + 长按删除（与设备 / 文件长条一致）。
    // 进行中的传输不挂删除手势（取消 / 切中继在详情页）。删除是真删除（无需二次确认），故析构滑动按钮
    // 的删除动画与实际一致、不会抖动。
    @ViewBuilder
    private func transferRow(_ item: TransferItem, history: Bool) -> some View
    {
        let card = TransferCardView(item: item)
            .contentShape(Rectangle())
            .onTapGesture { detailSession = item.sessionId }
            .listRowInsets(EdgeInsets(top: 6, leading: 16, bottom: 6, trailing: 16))
            .listRowSeparator(.hidden)
            .listRowBackground(Color.clear)
        if history
        {
            card
                .swipeActions(edge: .trailing)
                {
                    Button(role: .destructive) { engine.deleteTransferRecord(item.sessionId) }
                    label: { Label(t("transfer.action.delete"), systemImage: "trash") }
                }
                .contextMenu
                {
                    Button(role: .destructive) { engine.deleteTransferRecord(item.sessionId) }
                    label: { Label(t("transfer.action.delete"), systemImage: "trash") }
                }
        }
        else
        {
            card
        }
    }

    private func startSend(to device: String)
    {
        guard let url = pickedURL else { return }
        engine.sendFile(to: device, fileURL: url)
        pickedURL = nil
    }

    // 图库选中后：异步取字节 → 落临时文件 → 走与文件相同的「选设备发送」流程（#14）。
    private func loadPhoto()
    {
        guard let item = photoItem else { return }
        Task
        {
            guard let data = try? await item.loadTransferable(type: Data.self),
                  let url = engine.stagePhotoData(data, suggestedName: suggestedPhotoName(item))
            else { return }
            await MainActor.run
            {
                pickedURL = url
                photoItem = nil
                if sendableDevices.isEmpty { showNoDevices = true }
                else { showDevicePicker = true }
            }
        }
    }

    // 据所选项的内容类型推一个带正确扩展名的文件名（图片默认 jpg，视频默认 mov）。
    private func suggestedPhotoName(_ item: PhotosPickerItem) -> String
    {
        let ext = item.supportedContentTypes.first?.preferredFilenameExtension ?? "jpg"
        return "photo-\(UUID().uuidString.prefix(8)).\(ext)"
    }
}

// 传输卡片：内容层，用语义材质背景而非玻璃（玻璃专属功能 / 导航层）。状态 / 阶段 / 速率
// 文案全部复用共享 transfer.* catalog（强约束②）。
struct TransferCardView: View
{
    let item: TransferItem

    var body: some View
    {
        VStack(alignment: .leading, spacing: 8)
        {
            HStack(spacing: 6)
            {
                Image(systemName: item.direction == "incoming"
                    ? "arrow.down.circle" : "arrow.up.circle")
                    .foregroundStyle(.secondary)
                Text(item.fileName)
                    .font(.headline)
                    .lineLimit(1)
                Spacer()
            }
            if isActive(item)
            {
                ProgressView(value: progressOf(item))
            }
            Text(detailLine)
                .font(.caption)
                .foregroundStyle(.secondary)
        }
        .padding()
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16))
    }

    // 卡片副行：活跃时显示阶段，否则显示终态；附文件大小，活跃且有速率时附速度。
    private var detailLine: String
    {
        var parts: [String] = []
        if isActive(item), let phase = item.phase
        {
            parts.append(t("transfer.phase.\(phase)"))
        }
        else
        {
            parts.append(t("transfer.state.\(item.state)"))
        }
        parts.append(formatBytes(item.fileSize))
        if isActive(item), let rate = item.bytesPerSec, rate > 0
        {
            parts.append(t("transfer.bytesRate", [ "rate": formatBytes(Int(rate)) ]))
        }
        return parts.joined(separator: " · ")
    }
}

// 传输详情（TODO #5）：按 sessionId 从引擎实时态查当前记录，故进度 / 速率随传输刷新；
// 传输完成后从 transfers 移入 history，仍按 sessionId 命中。
struct TransferDetailView: View
{
    @Environment(EngineController.self) private var engine
    let sessionId: String
    @State private var previewItem: PreviewItem?

    private var item: TransferItem?
    {
        engine.transfers.first { $0.sessionId == sessionId }
            ?? engine.history.first { $0.sessionId == sessionId }
    }

    var body: some View
    {
        Group
        {
            if let item
            {
                List
                {
                    // 进行中的传输：主动取消 / 立即切中继（O）。已是中继则不再显示「立即中继」。
                    if isActive(item)
                    {
                        Section
                        {
                            Button(role: .destructive) { engine.cancelTransfer(item.sessionId) }
                            label: { Label(t("ios.transfer.cancel"), systemImage: "xmark.circle") }
                            if item.mode != "relay"
                            {
                                Button { engine.switchToRelay(item.sessionId) }
                                label: { Label(t("ios.transfer.forceRelay"),
                                               systemImage: "antenna.radiowaves.left.and.right") }
                            }
                        }
                    }
                    Section
                    {
                        LabeledContent(t("ios.detail.direction"),
                            value: item.direction == "incoming"
                                ? t("ios.transfer.incoming") : t("ios.transfer.outgoing"))
                        LabeledContent(t("ios.detail.peer"), value: item.peerName)
                        LabeledContent(t("ios.detail.state"), value: t("transfer.state.\(item.state)"))
                        if let phase = item.phase
                        {
                            LabeledContent(t("ios.detail.phase"), value: t("transfer.phase.\(phase)"))
                        }
                        if let mode = item.mode, !mode.isEmpty
                        {
                            LabeledContent(t("ios.detail.channel"), value: t("transfer.mode.\(mode)"))
                        }
                    }
                    Section
                    {
                        LabeledContent(t("ios.detail.size"), value: formatBytes(item.fileSize))
                        if let sent = item.bytesTransferred
                        {
                            LabeledContent(t("ios.detail.progress"),
                                value: "\(formatBytes(sent)) / \(formatBytes(item.fileSize))")
                        }
                        if let rate = item.bytesPerSec, rate > 0
                        {
                            LabeledContent(t("ios.detail.speed"),
                                value: t("transfer.bytesRate", [ "rate": formatBytes(Int(rate)) ]))
                        }
                        ProgressView(value: progressOf(item))
                    }
                    // 收到的文件「直达」：完成的接收传输可直接打开对应文件。
                    if item.direction == "incoming", item.state == "DONE",
                       let url = engine.receivedFile(matching: item.fileName)
                    {
                        Section
                        {
                            Button { previewItem = PreviewItem(url: url) }
                            label: { Label(t("ios.detail.openFile"), systemImage: "doc") }
                        }
                    }
                    // ICE 连接诊断：选中候选对含 "relay" 即 P2P 实走 TURN 中继（解释慢速）。
                    if item.iceConn != nil || item.iceLocal != nil
                    {
                        Section
                        {
                            if let conn = item.iceConn
                            {
                                LabeledContent(t("transfer.debug.iceConnection"), value: conn)
                            }
                            if let local = item.iceLocal, let remote = item.iceRemote
                            {
                                LabeledContent(t("transfer.debug.selectedPair"), value: "\(local) ⇄ \(remote)")
                            }
                        }
                    }
                }
            }
            else
            {
                ContentUnavailableView(t("ios.transfer.empty"), systemImage: "tray")
            }
        }
        .navigationTitle(item?.fileName ?? t("ios.detail.title"))
        .navigationBarTitleDisplayMode(.inline)
        .sheet(item: $previewItem) { item in FilePreviewSheet(item: item) }
    }
}

struct DeviceListView: View
{
    @Environment(EngineController.self) private var engine
    @State private var revokeTarget: DeviceItem?
    @State private var showRevoke = false
    @State private var showScanner = false

    // 全部设备：含本机（本机标「本机」、不可移除自己——登出在设置页）。本机排最前。
    private var allDevices: [DeviceItem]
    {
        engine.devices.sorted { a, b in isSelf(a) && !isSelf(b) }
    }

    // 判本机按稳定 device_id（与设备名解耦——改名后仍判得准）；selfDeviceID 缺失时回落按名。
    private func isSelf(_ dev: DeviceItem) -> Bool
    {
        let selfID = engine.selfDeviceID
        if !selfID.isEmpty, !dev.deviceID.isEmpty { return dev.deviceID == selfID }
        return dev.name == engine.deviceName
    }

    var body: some View
    {
        Group
        {
            if engine.devices.isEmpty
            {
                ContentUnavailableView(t("ios.devices.empty"), systemImage: "laptopcomputer.slash")
            }
            else
            {
                List
                {
                    ForEach(allDevices)
                    { dev in
                        deviceRow(dev)
                            .contentShape(Rectangle())
                            // 长条行统一操作逻辑：滑动 + 长按都能移除（点按不再删除——破坏性操作不应误触）。
                            .swipeActions(edge: .trailing)
                            {
                                if !isSelf(dev)
                                {
                                    // 不用 destructive 角色：「析构滑动按钮」会预演删除动画（行收起、下方上移），
                                    // 但移除需先弹二次确认、此刻并不真删 → 行随即复位造成抖动。改普通按钮 + 红色
                                    // 仅作视觉提示，消除抖动；真正删除发生在确认之后（设备离线即正常移除）。
                                    Button { revokeTarget = dev; showRevoke = true }
                                    label: { Label(t("ios.devices.remove"), systemImage: "trash") }
                                        .tint(.red)
                                }
                            }
                            .contextMenu
                            {
                                if !isSelf(dev)
                                {
                                    Button(role: .destructive) { revokeTarget = dev; showRevoke = true }
                                    label: { Label(t("ios.devices.remove"), systemImage: "trash") }
                                }
                            }
                    }
                }
            }
        }
        .navigationTitle(t("ios.tab.devices"))
        .toolbar
        {
            // 扫码登录他端（#8）：扫另一台设备的登录二维码以批准其登入本账号。
            ToolbarItem(placement: .topBarTrailing)
            {
                Button { showScanner = true }
                label: { Label(t("ios.scan.entry"), systemImage: "qrcode.viewfinder") }
            }
        }
        .sheet(isPresented: $showScanner)
        {
            ScanSheet { requestId, code in engine.fetchLoginRequest(requestId: requestId, code: code) }
        }
        .confirmationDialog(revokeTarget?.name ?? "", isPresented: $showRevoke, titleVisibility: .visible)
        {
            Button(t("ios.devices.remove"), role: .destructive)
            {
                if let target = revokeTarget { engine.revokeDevice(target.deviceID) }
            }
            Button(t("common.cancel"), role: .cancel) { }
        }
        // 扫码后拉到请求信息即弹批准确认（#8）：展示对端设备名 / 类型 / 来源 IP，确认后才批准。
        .confirmationDialog(
            t("ios.scan.approveTitle"),
            isPresented: Binding(
                get: { engine.pendingLoginRequest != nil },
                set: { if !$0 { engine.cancelLoginRequest() } }),
            titleVisibility: .visible)
        {
            Button(t("ios.scan.approve")) { engine.approveLogin() }
            Button(t("ios.scan.deny"), role: .destructive) { engine.denyLogin() }
            Button(t("common.cancel"), role: .cancel) { engine.cancelLoginRequest() }
        }
        message:
        {
            if let req = engine.pendingLoginRequest
            {
                Text(loginRequestInfo(req))
            }
        }
    }

    // 批准确认页展示的对端信息：设备名 · 类型，附来源 IP（次行）。
    private func loginRequestInfo(_ req: LoginRequestItem) -> String
    {
        var head: [String] = []
        if !req.deviceName.isEmpty { head.append(req.deviceName) }
        if !req.deviceType.isEmpty { head.append(req.deviceType) }
        var s = head.joined(separator: " · ")
        if !req.requestIp.isEmpty
        {
            if !s.isEmpty { s += "\n" }
            s += "\(t("ios.scan.requestIp")): \(req.requestIp)"
        }
        return s
    }

    private func deviceRow(_ dev: DeviceItem) -> some View
    {
        Label
        {
            VStack(alignment: .leading, spacing: 2)
            {
                HStack(spacing: 6)
                {
                    Text(dev.name)
                    if isSelf(dev)
                    {
                        Text(t("ios.devices.thisDevice"))
                            .font(.caption2)
                            .foregroundStyle(Color.cdropAccent)
                    }
                }
                Text(dev.online
                    ? t("settings.online")
                    : t("settings.lastSeen", [ "time": relativeTime(dev.lastSeen) ]))
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }
        }
        icon:
        {
            Image(systemName: deviceSymbol(dev.type))
                .foregroundStyle(dev.online ? Color.green : Color.secondary)
        }
    }
}

struct SettingsView: View
{
    @Environment(EngineController.self) private var engine
    @Environment(AuthManager.self) private var auth
    // 设备名编辑态：持久在 UserDefaults，提交时落库。当前会话名烤在令牌里，改名下次登录生效。
    @State private var deviceNameDraft = DeviceNameStore.value

    var body: some View
    {
        List
        {
            if let user = auth.session?.user
            {
                Section
                {
                    LabeledContent(t("ios.settings.user"), value: user.name)
                    // 即时改名（设备名与令牌解耦）：提交即经引擎 PATCH /api/devices/{device_id} 改名，
                    // 不换 token、不重连、不产生重复项，全端即时看到新名（见 EngineController.renameSelf）。
                    TextField(t("ios.settings.deviceName"), text: $deviceNameDraft)
                        .submitLabel(.done)
                        .onSubmit { commitRename() }
                    Button(role: .destructive) { logout() }
                    label:
                    {
                        Text(t("ios.settings.logout"))
                    }
                }
                header:
                {
                    Text(t("ios.settings.account"))
                }
                footer:
                {
                    Text(t("ios.settings.deviceNameNote"))
                }
            }
            Section(t("ios.settings.engine"))
            {
                LabeledContent(t("ios.settings.status"), value: engine.status)
                LabeledContent(t("ios.settings.device"),
                               value: engine.deviceName.isEmpty ? "—" : engine.deviceName)
                LabeledContent(t("ios.settings.signaling"), value: signalingState)
                LabeledContent(t("ios.settings.presenceEvents"), value: "\(engine.presenceCount)")
                LabeledContent(t("ios.settings.deviceCount"), value: "\(engine.devices.count)")
                // 日志独立界面（#3）：进入查看最近 N 条引擎日志；副标题预览最新一条。
                NavigationLink
                {
                    LogView()
                }
                label:
                {
                    VStack(alignment: .leading, spacing: 2)
                    {
                        Text(t("ios.settings.viewLogs"))
                        if !engine.lastEngineLog.isEmpty
                        {
                            Text(engine.lastEngineLog)
                                .font(.caption2)
                                .foregroundStyle(.secondary)
                                .lineLimit(1)
                        }
                    }
                }
            }
            Section
            {
                Button { engine.uploadClipboard() }
                label: { Label(t("ios.clipboard.upload"), systemImage: "arrow.up.doc.on.clipboard") }
                Button { engine.pullClipboard() }
                label: { Label(t("ios.clipboard.pull"), systemImage: "arrow.down.doc") }
            }
            header:
            {
                Text(t("ios.settings.clipboard"))
            }
            footer:
            {
                Text(t("ios.settings.clipboardNote"))
            }
        }
        .navigationTitle(t("ios.tab.settings"))
    }

    // 信令连接态：已连接 / 重连中 / 未连接，三态便于区分「在努力连但连不上」与「根本没连」。
    private var signalingState: String
    {
        if engine.hubConnected { return t("app.connected") }
        if engine.hubReconnecting { return t("ios.settings.reconnecting") }
        return t("ios.engine.disconnected")
    }

    // 即时改名：提交时把草稿交引擎改名（PATCH /api/devices/{device_id}）。空 / 未变则忽略。
    // 成功后引擎经 renamed 事件落本地名 + Keychain 会话名 + 刷新 engine.deviceName。
    private func commitRename()
    {
        let trimmed = deviceNameDraft.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmed.isEmpty, trimmed != (auth.session?.deviceName ?? "") else { return }
        engine.renameSelf(to: trimmed)
    }

    // 登出：先复位引擎（断 SSE + 丢 WebView），再清会话回登录页（清 Keychain 由 auth 负责）。
    private func logout()
    {
        engine.reset()
        auth.logout()
    }
}

// 收到的文件（TODO #2 数据侧延伸 + 解 send 测试无源文件）：列 Documents 沙盒里已落盘的
// 接收文件，可 QuickLook 预览 / 系统分享 / 转发给在线设备 / 删除。这批文件也经 Info.plist
// 的 UIFileSharingEnabled + LSSupportsOpeningDocumentsInPlace 在 Files app「我的 iPhone」
// 下可见。转发用已有发送链路（engine.sendFile），故收到的文件可直接再发。
struct ReceivedFilesView: View
{
    @Environment(EngineController.self) private var engine
    @State private var files: [URL] = []
    @State private var previewItem: PreviewItem?
    @State private var pickedURL: URL?
    @State private var showDevicePicker = false
    @State private var showNoDevices = false

    var body: some View
    {
        Group
        {
            if files.isEmpty
            {
                ContentUnavailableView(t("ios.files.empty"), systemImage: "tray")
            }
            else
            {
                List
                {
                    ForEach(files, id: \.self)
                    { url in
                        Button { previewItem = PreviewItem(url: url) }
                        label: { fileRow(url) }
                            .buttonStyle(.plain)
                            .swipeActions(edge: .leading)
                            {
                                Button { startSend(url) }
                                label: { Label(t("ios.transfer.outgoing"), systemImage: "paperplane") }
                                    .tint(.cdropAccent)
                            }
                            .contextMenu
                            {
                                ShareLink(item: url)
                                { Label(t("ios.files.share"), systemImage: "square.and.arrow.up") }
                                Button { startSend(url) }
                                label: { Label(t("ios.transfer.outgoing"), systemImage: "paperplane") }
                                Button(role: .destructive) { delete(url) }
                                label: { Label(t("transfer.action.delete"), systemImage: "trash") }
                            }
                    }
                    .onDelete { offsets in deleteAt(offsets) }   // 尾滑删除 + EditButton 批量删
                }
            }
        }
        .navigationTitle(t("ios.files.title"))
        .toolbar
        {
            if !files.isEmpty
            {
                ToolbarItem(placement: .topBarTrailing) { EditButton() }
            }
        }
        .sheet(item: $previewItem) { item in FilePreviewSheet(item: item) }
        .onAppear { reload() }
        .onChange(of: engine.history.count) { reload() }
        .confirmationDialog(t("ios.send.pickDevice"), isPresented: $showDevicePicker, titleVisibility: .visible)
        {
            ForEach(engine.sendableDevices())
            { dev in
                Button(dev.name) { send(to: dev.name) }
            }
            Button(t("common.cancel"), role: .cancel) { }
        }
        .alert(t("ios.send.noDevices"), isPresented: $showNoDevices) { }
    }

    private func fileRow(_ url: URL) -> some View
    {
        HStack(spacing: 10)
        {
            Image(systemName: "doc")
                .foregroundStyle(.secondary)
            VStack(alignment: .leading, spacing: 2)
            {
                Text(url.lastPathComponent)
                    .lineLimit(1)
                Text(fileSizeString(url))
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }
        }
    }

    private func reload() { files = engine.receivedFiles() }

    private func delete(_ url: URL)
    {
        engine.deleteReceivedFile(url)
        reload()
    }

    private func deleteAt(_ offsets: IndexSet)
    {
        for i in offsets { engine.deleteReceivedFile(files[i]) }
        reload()
    }

    private func startSend(_ url: URL)
    {
        pickedURL = url
        if engine.sendableDevices().isEmpty { showNoDevices = true }
        else { showDevicePicker = true }
    }

    private func send(to device: String)
    {
        guard let url = pickedURL else { return }
        engine.sendFile(to: device, fileURL: url)
        pickedURL = nil
    }
}

// MARK: - 展示助手

// 是否仍在进行中（非终态）：决定显示进度条 / 阶段还是终态标签。
private func isActive(_ item: TransferItem) -> Bool
{
    return item.state != "DONE" && item.state != "FAILED" && item.state != "CANCELLED"
}

private func progressOf(_ item: TransferItem) -> Double
{
    guard item.fileSize > 0, let sent = item.bytesTransferred else { return 0 }
    return min(1.0, Double(sent) / Double(item.fileSize))
}

private func formatBytes(_ bytes: Int) -> String
{
    let f = ByteCountFormatter()
    f.countStyle = .file
    return f.string(fromByteCount: Int64(bytes))
}

private func fileSizeString(_ url: URL) -> String
{
    let size = (try? url.resourceValues(forKeys: [ .fileSizeKey ]))?.fileSize ?? 0
    return formatBytes(size)
}

// lastSeen 是 epoch 秒（后端 devices.last_seen＝now.Unix()，web formatRelative 也按秒）；
// 转相对时间填进共享 settings.lastSeen 文案的 {{time}}。
private func relativeTime(_ epochSec: Double) -> String
{
    let date = Date(timeIntervalSince1970: epochSec)
    let f = RelativeDateTimeFormatter()
    f.unitsStyle = .short
    return f.localizedString(for: date, relativeTo: Date())
}

// 设备类型 → SF Symbol（对齐 web mapKind 的语义）。
private func deviceSymbol(_ type: String) -> String
{
    let s = type.lowercased()
    if s.contains("mac") || s == "darwin" { return "laptopcomputer" }
    if s.contains("win") { return "pc" }
    if s.contains("linux") { return "terminal" }
    if s.contains("ipad") { return "ipad" }
    if s.contains("ios") || s.contains("iphone") { return "iphone" }
    if s.contains("android") { return "candybarphone" }
    if s == "shortcut" { return "bolt" }
    if s == "browser" { return "globe" }
    return "desktopcomputer"
}

// 品牌主色 Color.cdropAccent 已移至 Shared/Theme.swift（主 app 与控件 / 小组件扩展共用单一真源）。
