import SwiftUI
import SwiftData
import UIKit

// MARK: - SessionDetailView
/// The session detail & playback screen.
/// Shows session info, multi-track timeline, playback controls,
/// and a chronological feed of all events.
/// Redesigned to use a minimalist wireframe business style.
struct SessionDetailView: View {
    @Environment(\.modelContext) private var modelContext
    @Environment(\.dismiss) private var dismiss
    @ObservedObject private var bleManager: BLEManager = .shared
    @ObservedObject private var authManager: AuthManager = .shared
    @ObservedObject private var syncManager: SyncManager = .shared
    @ObservedObject private var networkMonitor: NetworkStatusMonitor = .shared

    let session: CelestiaSession

    @State private var playbackVM = PlaybackViewModel()
    @State private var navigateToRecording = false
    @State private var detector = RecordingEnvironmentDetector()
    @State private var showPrepAlert = false
    @State private var activeWarningMsg = ""
    @State private var showRecordingSourcePicker = false
    @State private var selectedRecordingSource: RecordingSourceChoice = .iPhone
    @State private var recordName = ""
    @State private var showRecordNameEditor = false
    @State private var showSessionInfo = false
    @State private var showShareUnavailableAlert = false
    @State private var audioShareItem: AudioShareItem?
    @State private var preparedShareDirectoryURL: URL?
    @State private var pendingTimelineTimeMs: Double = 0
    @State private var noteText = ""
    @State private var noteLocation: TimelineLocation?
    @State private var showNoteEditor = false
    @State private var showPhotoEditor = false
    @State private var selectedTimelineEvent: CelestiaTimelineEvent?
    @State private var selectedPhotoPoint: PhotoPointSelection?
    @State private var timelineEditError: String?
    @State private var showSyncAuthPrompt = false
    @State private var showAuthModal = false
    @State private var showLargeSyncConfirmation = false
    @State private var showSyncInfo = false
    @State private var hasApprovedLargeTransfer = false
    @State private var showDeleteConfirmation = false
    @State private var deletionError: String?
    @State private var isDeletingSession = false

    var body: some View {
        ZStack {
            Color.spaceBlack.ignoresSafeArea()

            ScrollView {
                VStack(spacing: 20) {
                    detailHeader
                        .padding(.horizontal, 16)
                        .padding(.top, 12)

                    // Section 1: Multi-Track Timeline
                    timelineSection
                        .padding(.horizontal, 16)

                    // Section 2: Playback Controls
                    playbackControls
                        .padding(.horizontal, 16)

                    // Section 3: Chrono Feed
                    if !chronoFeedItems.isEmpty {
                        chronoFeedSection
                            .padding(.horizontal, 16)
                            .padding(.bottom, 32)
                    }
                }
            }
        }
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItemGroup(placement: .topBarTrailing) {
                Button {
                    showSessionInfo = true
                } label: {
                    Image(systemName: "info.circle")
                        .font(.system(size: 16, weight: .regular))
                        .foregroundStyle(Color.primary)
                }
                .accessibilityLabel("记录信息")
                .popover(isPresented: $showSessionInfo, arrowEdge: .top) {
                    sessionInfoCard
                        .padding(16)
                        .frame(idealWidth: 360)
                        .presentationCompactAdaptation(.popover)
                }

                Button {
                    prepareAudioForSharing()
                } label: {
                    Image(systemName: "square.and.arrow.up")
                        .font(.system(size: 16, weight: .regular))
                        .foregroundStyle(Color.primary)
                }
                .accessibilityLabel("分享录音")
            }
        }
        .sheet(item: $audioShareItem, onDismiss: cleanupSharedAudioCopy) { item in
            AudioShareSheet(fileURL: item.fileURL)
        }
        .alert("无法分享录音", isPresented: $showShareUnavailableAlert) {
            Button("知道了", role: .cancel) {}
        } message: {
            Text("当前记录没有可用的本地录音文件，请先完成录制或同步录音。")
        }
        .alert("确认删除？", isPresented: $showDeleteConfirmation) {
            Button("取消", role: .cancel) {}
            Button(session.cloudSessionId == nil ? "删除" : "同时删除", role: .destructive) {
                Task {
                    await deleteSession()
                }
            }
            .disabled(isDeletingSession)
        } message: {
            if session.cloudSessionId != nil {
                Text("删除「\(session.title)」后，本地录音、图片以及已经同步到云端的记录都会一并删除，且无法恢复。")
            } else {
                Text("删除「\(session.title)」后，相关录音和图片也会被删除，且无法恢复。")
            }
        }
        .alert("删除失败", isPresented: Binding(
            get: { deletionError != nil },
            set: { if !$0 { deletionError = nil } }
        )) {
            Button("好", role: .cancel) {}
        } message: {
            Text(deletionError ?? "请稍后重试。")
        }
        .alert("无法完成编辑", isPresented: Binding(
            get: { timelineEditError != nil },
            set: { if !$0 { timelineEditError = nil } }
        )) {
            Button("知道了", role: .cancel) {}
        } message: {
            Text(timelineEditError ?? "请稍后重试。")
        }
        .alert("录音环境提醒", isPresented: $showPrepAlert) {
            Button("继续录制", role: .none) {
                chooseSourceAndContinue()
            }
            Button("取消", role: .cancel) {}
        } message: {
            Text(activeWarningMsg + "\n\n另外请手动确认：\n1. 手机侧边静音开关已拨至红色（静音状态）\n2. 已关闭可能在此期间响起的系统闹钟")
        }
        .fullScreenCover(isPresented: $navigateToRecording) {
            ActiveRecordingView(
                session: session,
                initialDuration: Double(session.durationMs) / 1000.0,
                recordingSource: selectedRecordingSource
            ) {
                playbackVM.totalDurationMs = Double(session.durationMs)
                playbackVM.preparePlayer(path: session.localAudioPath, durationMs: Double(session.durationMs))
                playbackVM.analyzeAudio(audioURL: AudioPathHelper.resolveURL(for: session.localAudioPath))
                scheduleAutoSync()
            }
        }
        .sheet(isPresented: $showRecordingSourcePicker) {
            RecordingSourcePickerView(devices: connectedSparkDevices) { source in
                selectedRecordingSource = source
                showRecordingSourcePicker = false
                navigateToRecording = true
            }
        }
        .sheet(isPresented: $showRecordNameEditor) {
            RecordNameEditorSheet(recordName: $recordName) {
                saveRecordName()
            } onCancel: {
                recordName = session.title
                showRecordNameEditor = false
            }
        }
        .sheet(isPresented: $showNoteEditor) {
            timelineNoteEditor
        }
        .sheet(isPresented: $showPhotoEditor) {
            PhotoRecordEditorSheet(
                timeLabel: formattedTimelineTime(pendingTimelineTimeMs)
            ) { drafts, location in
                addPhotos(
                    drafts,
                    location: location,
                    at: pendingTimelineTimeMs
                )
            }
        }
        .sheet(item: $selectedTimelineEvent) { event in
            TimelineEventDetailSheet(
                event: event,
                onSaveNote: { updatedText, updatedLocation in
                    updateTimelineNote(
                        event,
                        text: updatedText,
                        location: updatedLocation
                    )
                },
                onDelete: {
                    deleteTimelineEvent(event)
                }
            )
        }
        .sheet(item: $selectedPhotoPoint) { selection in
            PhotoPointDetailSheet(
                events: photoEvents(at: selection.relativeTimeMs),
                onSaveEdits: updatePhotoEdits,
                onDelete: deleteTimelineEvent
            )
        }
        .sheet(isPresented: $showSyncAuthPrompt) {
            SyncAuthPromptModal {
                showSyncAuthPrompt = false
                showAuthModal = true
            }
        }
        .sheet(isPresented: $showAuthModal) {
            AuthModalView()
        }
        .alert("同步较大文件", isPresented: $showLargeSyncConfirmation) {
            Button("开始同步") {
                hasApprovedLargeTransfer = true
                performSessionSync()
            }
            Button("取消", role: .cancel) {}
        } message: {
            Text(largeTransferMessage)
        }
        .onAppear {
            playbackVM.totalDurationMs = Double(session.durationMs)
            playbackVM.preparePlayer(path: session.localAudioPath, durationMs: Double(session.durationMs))
            playbackVM.analyzeAudio(audioURL: AudioPathHelper.resolveURL(for: session.localAudioPath))
            if session.needsCloudSync {
                scheduleAutoSync()
            }
        }
        .onChange(of: session.syncStateRaw) { _, newValue in
            guard let state = SessionSyncState(rawValue: newValue),
                  state == .pending || state == .localOnly,
                  session.needsCloudSync else { return }
            scheduleAutoSync()
        }
    }

    private var detailHeader: some View {
        HStack(spacing: 12) {
            Button {
                recordName = session.title
                showRecordNameEditor = true
            } label: {
                Text(session.title)
                    .font(.system(size: 22, weight: .semibold))
                    .lineLimit(1)
                    .minimumScaleFactor(0.75)
                    .foregroundStyle(Color.primary)
                    .contentShape(Rectangle())
            }
            .buttonStyle(.plain)
            .accessibilityLabel("编辑记录名称")
            .layoutPriority(1)

            Spacer(minLength: 0)

            compactSyncButton
                .fixedSize()
        }
        .frame(maxWidth: .infinity, alignment: .leading)
    }

    private func prepareContinuation() {
        playbackVM.pause()
        detector.checkEnvironment()
        if let firstWarning = detector.activeWarnings.first {
            activeWarningMsg = firstWarning
            showPrepAlert = true
        } else {
            chooseSourceAndContinue()
        }
    }

    private func chooseSourceAndContinue() {
        if connectedSparkDevices.isEmpty {
            selectedRecordingSource = .iPhone
            navigateToRecording = true
        } else {
            showRecordingSourcePicker = true
        }
    }

    private func saveRecordName() {
        let trimmedName = recordName.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmedName.isEmpty else { return }

        recordName = trimmedName
        if session.title != trimmedName {
            session.title = trimmedName
            session.markContentModified()
            try? modelContext.save()
            scheduleAutoSync()
        }
        showRecordNameEditor = false
    }

    private func prepareAudioForSharing() {
        guard let sourceURL = AudioPathHelper.resolveURL(for: session.localAudioPath) else {
            showShareUnavailableAlert = true
            return
        }

        let shareDirectory = FileManager.default.temporaryDirectory
            .appendingPathComponent("CelestiaTraceShare", isDirectory: true)
            .appendingPathComponent(UUID().uuidString, isDirectory: true)
        let fileExtension = sourceURL.pathExtension
        let fileName = sanitizedShareFileName(from: session.title)
        let destinationURL = shareDirectory
            .appendingPathComponent(fileName)
            .appendingPathExtension(fileExtension)

        do {
            try FileManager.default.createDirectory(
                at: shareDirectory,
                withIntermediateDirectories: true
            )
            try FileManager.default.copyItem(at: sourceURL, to: destinationURL)
            preparedShareDirectoryURL = shareDirectory
            audioShareItem = AudioShareItem(fileURL: destinationURL)
        } catch {
            audioShareItem = nil
            preparedShareDirectoryURL = nil
            showShareUnavailableAlert = true
        }
    }

    private func sanitizedShareFileName(from title: String) -> String {
        let invalidCharacters = CharacterSet(charactersIn: "/:\\?%*|\"<>")
            .union(.controlCharacters)
            .union(.newlines)
        let components = title.components(separatedBy: invalidCharacters)
        let sanitized = components
            .joined(separator: "-")
            .trimmingCharacters(in: CharacterSet.whitespacesAndNewlines.union(
                CharacterSet(charactersIn: ".")
            ))
        return sanitized.isEmpty ? "现场记录" : sanitized
    }

    private func cleanupSharedAudioCopy() {
        guard let preparedShareDirectoryURL else { return }
        try? FileManager.default.removeItem(at: preparedShareDirectoryURL)
        self.preparedShareDirectoryURL = nil
    }

    private var connectedSparkDevices: [BoundDevice] {
        guard let userID = authManager.currentUser?.id else { return [] }
        return bleManager.connectedDevices(forUserId: userID)
            .sorted { $0.boundAt < $1.boundAt }
    }

    // MARK: - Session Info Card

    private var sessionInfoCard: some View {
        VStack(spacing: 14) {
            // Date range
            HStack {
                VStack(alignment: .leading, spacing: 4) {
                    Label {
                        Text("开始")
                            .font(.system(size: 10, weight: .medium))
                            .foregroundStyle(Color.secondary)
                    } icon: {
                        Image(systemName: "play.circle")
                            .font(.system(size: 11))
                            .foregroundStyle(Color.secondary)
                    }

                    VStack(alignment: .leading, spacing: 1) {
                        Text(Self.chineseDateFormatter.string(from: session.startTime))
                        Text(Self.chineseTimeFormatter.string(from: session.startTime))
                    }
                    .font(.system(size: 13, weight: .medium))
                    .foregroundStyle(Color.primary)
                }

                Spacer()

                VStack(alignment: .trailing, spacing: 4) {
                    Label {
                        Text("结束")
                            .font(.system(size: 10, weight: .medium))
                            .foregroundStyle(Color.secondary)
                    } icon: {
                        Image(systemName: "stop.circle")
                            .font(.system(size: 11))
                            .foregroundStyle(Color.secondary)
                    }

                    if let endTime = session.endTime {
                        VStack(alignment: .trailing, spacing: 1) {
                            Text(Self.chineseDateFormatter.string(from: endTime))
                            Text(Self.chineseTimeFormatter.string(from: endTime))
                        }
                        .font(.system(size: 13, weight: .medium))
                        .foregroundStyle(Color.primary)
                    } else {
                        Text("进行中")
                            .font(.system(size: 13, weight: .medium))
                            .foregroundStyle(Color.primary)
                    }
                }
            }

            Divider()
                .background(Color.lineBorder)

            // Stats row
            HStack(spacing: 0) {
                infoStat(
                    icon: "timer",
                    label: "总时长",
                    value: session.durationFormatted
                )

                infoStat(
                    icon: "camera",
                    label: "图片",
                    value: "\(session.photoCount)"
                )

                infoStat(
                    icon: "doc.text",
                    label: "笔记",
                    value: "\(session.noteCount)"
                )
            }

            Divider()
                .background(Color.lineBorder)

            HStack(spacing: 8) {
                Label {
                    Text("版本号")
                        .font(.system(size: 11, weight: .medium))
                } icon: {
                    Image(systemName: "number")
                        .font(.system(size: 11, weight: .medium))
                }
                .foregroundStyle(Color.secondary)

                Spacer()

                Text("v\(session.localRevision)")
                    .font(.system(size: 12, weight: .semibold, design: .monospaced))
                    .foregroundStyle(Color.primary)
            }

            Divider()
                .background(Color.lineBorder)

            Button {
                showSessionInfo = false
                DispatchQueue.main.async {
                    prepareContinuation()
                }
            } label: {
                HStack(spacing: 8) {
                    Image(systemName: "record.circle")
                        .font(.system(size: 14, weight: .medium))

                    Text("续录")
                        .font(.system(size: 13, weight: .semibold))
                }
                .foregroundStyle(Color.primary)
                .frame(maxWidth: .infinity)
                .padding(.vertical, 10)
                .background(Color.cardBackground.opacity(0.2))
                .businessBorder(cornerRadius: 8)
            }
            .buttonStyle(.plain)
            .accessibilityLabel("继续现场记录")

            Button(role: .destructive) {
                showSessionInfo = false
                DispatchQueue.main.async {
                    showDeleteConfirmation = true
                }
            } label: {
                HStack(spacing: 8) {
                    Image(systemName: "trash")
                        .font(.system(size: 14, weight: .medium))

                    Text("删除")
                        .font(.system(size: 13, weight: .semibold))
                }
                .foregroundStyle(Color.red)
                .frame(maxWidth: .infinity)
                .padding(.vertical, 10)
                .background(Color.red.opacity(0.06))
                .overlay {
                    RoundedRectangle(cornerRadius: 8)
                        .stroke(Color.red.opacity(0.45), lineWidth: 1)
                }
            }
            .buttonStyle(.plain)
            .disabled(isDeletingSession)
            .accessibilityLabel("删除现场记录")
        }
        .padding(14)
        .background(Color.cardBackground.opacity(0.15))
        .businessBorder(cornerRadius: 10)
    }

    @MainActor
    private func deleteSession() async {
        guard !isDeletingSession else { return }
        isDeletingSession = true
        defer { isDeletingSession = false }

        let storedPaths =
            [session.localAudioPath].compactMap { $0 } +
            session.audioChunks.map(\.localFilePath) +
            session.events.compactMap(\.localFilePath)
        let fileURLs = Set(storedPaths.compactMap { AudioPathHelper.resolveURL(for: $0) })

        if let cloudSessionID = session.cloudSessionId {
            do {
                try await RemoteNetworkService().deleteSession(sessionID: cloudSessionID)
            } catch APIError.server(let code, _) where code == 404 {
                // The remote record is already gone, so local cleanup can continue.
            } catch {
                deletionError = "云端记录删除失败，本地记录未删除：\(error.localizedDescription)"
                return
            }
        }

        playbackVM.pause()
        modelContext.delete(session)

        do {
            try modelContext.save()
            for fileURL in fileURLs {
                try? FileManager.default.removeItem(at: fileURL)
            }
            dismiss()
        } catch {
            modelContext.rollback()
            deletionError = error.localizedDescription
        }
    }

    private static let chineseDateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "zh_CN")
        formatter.calendar = Calendar(identifier: .gregorian)
        formatter.dateFormat = "yyyy年M月d日"
        return formatter
    }()

    private static let chineseTimeFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "zh_CN")
        formatter.calendar = Calendar(identifier: .gregorian)
        formatter.dateFormat = "HH:mm:ss"
        return formatter
    }()

    private var compactSyncButton: some View {
        VStack(spacing: 3) {
            Button {
                if isThisSessionSyncing {
                    syncManager.pauseSync(sessionID: session.id)
                } else if isDisplayedSynced {
                    showSyncInfo = true
                } else {
                    requestSessionSync(isAutomatic: false)
                }
            } label: {
                Group {
                    if isDisplayedSyncing {
                        ZStack {
                            Circle()
                                .stroke(Color.secondary.opacity(0.22), lineWidth: 2)

                            Circle()
                                .trim(from: 0, to: max(sessionSyncProgress, 0.04))
                                .stroke(
                                    Color.lessCosmosGold,
                                    style: StrokeStyle(lineWidth: 2, lineCap: .round)
                                )
                                .rotationEffect(.degrees(-90))

                            TimelineView(.animation(minimumInterval: 1.0 / 30.0)) { context in
                                Image(systemName: "arrow.triangle.2.circlepath")
                                    .font(.system(size: 9, weight: .semibold))
                                    .foregroundStyle(Color.lessCosmosGold)
                                    .rotationEffect(syncRotation(at: context.date))
                            }
                        }
                    } else if isDisplayedSynced {
                        Image(systemName: "checkmark.icloud.fill")
                            .symbolRenderingMode(.hierarchical)
                            .foregroundStyle(Color.lessCosmosGold)
                    } else {
                        Image(systemName: "icloud.and.arrow.up")
                            .symbolRenderingMode(.hierarchical)
                            .foregroundStyle(Color.secondary)
                    }
                }
                .font(.system(size: 17, weight: .medium))
                .frame(width: 32, height: 32)
                .contentShape(Rectangle())
            }
            .buttonStyle(.plain)
            .disabled(syncManager.isSyncing && !isThisSessionSyncing)
            .accessibilityLabel(isThisSessionSyncing ? "暂停同步" : "同步记录")
            .accessibilityValue(
                isDisplayedSyncing
                    ? "\(syncStatusText)，\(Int((sessionSyncProgress * 100).rounded()))%"
                    : syncStatusText
            )
            .popover(isPresented: $showSyncInfo, arrowEdge: .top) {
                syncInfoCard
                    .padding(16)
                    .frame(idealWidth: 320)
                    .presentationCompactAdaptation(.popover)
            }

            Text(syncStatusText)
                .font(.system(size: 9, weight: .medium))
                .foregroundStyle(syncStatusColor)
        }
    }

    private var syncInfoCard: some View {
        VStack(alignment: .leading, spacing: 12) {
            HStack(spacing: 8) {
                Image(systemName: "checkmark.icloud.fill")
                    .foregroundStyle(Color.lessCosmosGold)

                Text("同步信息")
                    .font(.system(size: 15, weight: .semibold))
                    .foregroundStyle(Color.primary)
            }

            Divider()
                .background(Color.lineBorder)

            syncInfoRow(
                label: "同步时间",
                value: session.lastSyncedAt.map {
                    Self.syncDateTimeFormatter.string(from: $0)
                } ?? "现场记录未保存"
            )

            syncInfoRow(
                label: "云端版本",
                value: "v\(max(session.serverRevision, 1))",
                monospaced: true
            )

            if let cloudSessionID = session.cloudSessionId {
                syncInfoRow(
                    label: "云端记录",
                    value: cloudSessionID,
                    monospaced: true
                )
            }
        }
        .padding(14)
        .background(Color.cardBackground.opacity(0.15))
        .businessBorder(cornerRadius: 10)
    }

    private func syncInfoRow(
        label: String,
        value: String,
        monospaced: Bool = false
    ) -> some View {
        HStack(alignment: .firstTextBaseline, spacing: 14) {
            Text(label)
                .font(.system(size: 11, weight: .medium))
                .foregroundStyle(Color.secondary)

            Spacer(minLength: 12)

            Text(value)
                .font(.system(size: 12, weight: .medium, design: monospaced ? .monospaced : .default))
                .foregroundStyle(Color.primary)
                .multilineTextAlignment(.trailing)
                .lineLimit(2)
                .textSelection(.enabled)
        }
    }

    private var isThisSessionSyncing: Bool {
        syncManager.isSyncing && syncManager.activeSessionID == session.id
    }

    private var isDisplayedSyncing: Bool {
        isThisSessionSyncing
    }

    private var isDisplayedSynced: Bool {
        !isDisplayedSyncing && session.hasConfirmedCloudSync
    }

    private var sessionSyncProgress: Double {
        if isThisSessionSyncing {
            return min(max(syncManager.syncProgress, 0), 1)
        }
        return session.hasConfirmedCloudSync ? 1 : 0
    }

    private var syncStatusText: String {
        if isDisplayedSyncing { return "正在同步" }
        return isDisplayedSynced ? "已同步" : "未同步"
    }

    private var syncStatusColor: Color {
        isDisplayedSynced || isDisplayedSyncing ? Color.lessCosmosGold : Color.secondary
    }

    private func syncRotation(at date: Date) -> Angle {
        let cycle = date.timeIntervalSinceReferenceDate
            .truncatingRemainder(dividingBy: 1.2)
        return .degrees(cycle / 1.2 * 360)
    }

    private static let syncDateTimeFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "zh_CN")
        formatter.calendar = Calendar(identifier: .gregorian)
        formatter.dateFormat = "yyyy年M月d日 HH:mm:ss"
        return formatter
    }()

    private var largeTransferMessage: String {
        let bytes = syncManager.estimatedUploadBytes(for: session)
        let size = ByteCountFormatter.string(fromByteCount: bytes, countStyle: .file)
        return "预计上传约 \(size)，将占用约 \(size) \(networkMonitor.connectionName)流量。是否继续？"
    }

    private func requestSessionSync(isAutomatic: Bool) {
        guard !syncManager.isSyncing else { return }
        guard networkMonitor.isConnected else {
            if !isAutomatic {
                timelineEditError = "当前没有可用网络，请连接网络后重试。"
            }
            return
        }
        guard authManager.currentUser?.id != nil else {
            if !isAutomatic {
                showSyncAuthPrompt = true
            }
            return
        }

        let largeTransferThreshold: Int64 = 70 * 1_024 * 1_024
        if syncManager.estimatedUploadBytes(for: session) >= largeTransferThreshold,
           !hasApprovedLargeTransfer {
            showLargeSyncConfirmation = true
            return
        }
        performSessionSync()
    }

    private func performSessionSync() {
        guard let userID = authManager.currentUser?.id else { return }
        Task {
            _ = await syncManager.sync(
                sessions: [session],
                modelContext: modelContext,
                userID: userID
            )
        }
    }

    private func scheduleAutoSync() {
        syncManager.scheduleAutomaticSync(
            for: session,
            modelContext: modelContext
        )
    }

    private func infoStat(icon: String, label: String, value: String) -> some View {
        VStack(spacing: 4) {
            Image(systemName: icon)
                .font(.system(size: 14, weight: .light))
                .foregroundStyle(Color.secondary)

            Text(value)
                .font(.system(size: 13, weight: .medium, design: .monospaced))
                .foregroundStyle(Color.primary)

            Text(label)
                .font(.system(size: 9, weight: .regular))
                .foregroundStyle(Color.secondary.opacity(0.8))
        }
        .frame(maxWidth: .infinity)
    }

    // MARK: - Timeline Section

    private var timelineSection: some View {
        VStack(alignment: .leading, spacing: 8) {
            MultiTrackTimeline(
                events: session.events,
                currentTimeMs: Binding(
                    get: { playbackVM.currentPlaybackTimeMs },
                    set: { playbackVM.seekTo(timeMs: $0) }
                ),
                totalDurationMs: playbackVM.totalDurationMs,
                waveformSamples: playbackVM.waveformSamples,
                silentRanges: playbackVM.silentRanges,
                onEmptyTrackTap: handleEmptyTrackTap,
                onEventTap: { event in
                    playbackVM.seekTo(timeMs: Double(event.relativeTimeMs))
                    openTimelineEvent(event)
                    HapticManager.trigger(.tapFeedback)
                }
            )
            .frame(height: 220)
        }
    }

    // MARK: - Playback Controls

    private var playbackControls: some View {
        VStack(spacing: 12) {
            // Time display
            HStack {
                Text(playbackVM.currentTimeFormatted)
                    .font(.system(size: 12, weight: .regular, design: .monospaced))
                    .foregroundStyle(Color.primary)

                Spacer()

                Text(totalTimeFormatted)
                    .font(.system(size: 12, weight: .regular, design: .monospaced))
                    .foregroundStyle(Color.secondary)
            }

            // Seek slider
            Slider(
                value: Binding(
                    get: { playbackVM.progress },
                    set: { newValue in
                        playbackVM.seekTo(timeMs: newValue * playbackVM.totalDurationMs)
                    }
                ),
                in: 0...1
            )
            .tint(Color.primary)

            // Play/Pause button
            HStack(spacing: 36) {
                // Rewind 10s
                Button {
                    let target = max(0, playbackVM.currentPlaybackTimeMs - 10_000)
                    playbackVM.seekTo(timeMs: target)
                } label: {
                    Image(systemName: "gobackward.10")
                        .font(.system(size: 18, weight: .light))
                        .foregroundStyle(Color.secondary)
                }

                // Play/Pause (High-contrast minimalist button)
                Button {
                    playbackVM.togglePlayback()
                    HapticManager.trigger(.tapFeedback)
                } label: {
                    ZStack {
                        Circle()
                            .fill(Color.primary)
                            .frame(width: 48, height: 48)

                        Image(systemName: playbackVM.isPlaying ? "pause.fill" : "play.fill")
                            .font(.system(size: 16, weight: .bold))
                            .foregroundStyle(Color.spaceBlack)
                    }
                }

                // Forward 10s
                Button {
                    let target = min(playbackVM.totalDurationMs, playbackVM.currentPlaybackTimeMs + 10_000)
                    playbackVM.seekTo(timeMs: target)
                } label: {
                    Image(systemName: "goforward.10")
                        .font(.system(size: 18, weight: .light))
                        .foregroundStyle(Color.secondary)
                }
            }
            .padding(.top, 4)
            
            Divider()
                .background(Color.lineBorder)
                .padding(.vertical, 4)
            
            HStack {
                Label {
                    Text("跳过静音")
                        .font(.system(size: 12, weight: .medium))
                        .foregroundStyle(Color.primary)
                } icon: {
                    Image(systemName: playbackVM.isSilenceSkipEnabled ? "waveform.badge.minus" : "waveform")
                        .font(.system(size: 13))
                        .foregroundStyle(playbackVM.isSilenceSkipEnabled ? Color.primary : Color.secondary)
                }
                
                Spacer()
                
                if playbackVM.isAnalyzingSilence {
                    ProgressView()
                        .scaleEffect(0.7)
                        .frame(width: 16, height: 16)
                } else if !playbackVM.silentRanges.isEmpty {
                    Text("检测到 \(playbackVM.silentRanges.count) 处静音")
                        .font(.system(size: 11))
                        .foregroundStyle(Color.secondary)
                }
                
                Toggle("", isOn: Binding(
                    get: { playbackVM.isSilenceSkipEnabled },
                    set: { playbackVM.isSilenceSkipEnabled = $0 }
                ))
                .toggleStyle(SwitchToggleStyle(tint: Color.primary))
                .labelsHidden()
                .scaleEffect(0.8)
            }
        }
        .padding(14)
        .background(Color.cardBackground.opacity(0.15))
        .businessBorder(cornerRadius: 10)
    }

    // MARK: - Chrono Feed

    private var chronoFeedSection: some View {
        VStack(alignment: .leading, spacing: 10) {
            sectionHeader(icon: "list.dash", title: "事件")

            let sorted = chronoFeedItems

            if sorted.isEmpty {
                HStack {
                    Spacer()
                    VStack(spacing: 8) {
                        Image(systemName: "tray")
                            .font(.system(size: 24, weight: .light))
                            .foregroundStyle(Color.secondary.opacity(0.3))
                        Text("暂无会话事件")
                            .font(.system(size: 12))
                            .foregroundStyle(Color.secondary.opacity(0.5))
                    }
                    .padding(.vertical, 24)
                    Spacer()
                }
            } else {
                LazyVStack(spacing: 8) {
                    ForEach(sorted) { item in
                        ChronoFeedRow(
                            event: item.event,
                            photoEvents: item.photoEvents
                        ) {
                            playbackVM.seekTo(timeMs: item.event.relativeTimeMs)
                            openTimelineEvent(item.event)
                            HapticManager.trigger(.tapFeedback)
                        }
                    }
                }
            }
        }
    }

    // MARK: - Helpers

    private func sectionHeader(icon: String, title: String) -> some View {
        HStack(spacing: 6) {
            Image(systemName: icon)
                .font(.system(size: 12, weight: .light))
                .foregroundStyle(Color.secondary)
            Text(title)
                .font(.system(size: 13, weight: .semibold))
                .foregroundStyle(Color.primary.opacity(0.8))
        }
    }

    private var totalTimeFormatted: String {
        let totalSeconds = Int(playbackVM.totalDurationMs / 1000)
        let hours = totalSeconds / 3600
        let minutes = (totalSeconds % 3600) / 60
        let seconds = totalSeconds % 60
        if hours > 0 {
            return String(format: "%d:%02d:%02d", hours, minutes, seconds)
        }
        return String(format: "%02d:%02d", minutes, seconds)
    }

    // MARK: - Timeline Editing

    private var timelineNoteEditor: some View {
        NavigationStack {
            ZStack {
                Color.spaceBlack.ignoresSafeArea()

                VStack(alignment: .leading, spacing: 16) {
                    Label(
                        "添加到 \(formattedTimelineTime(pendingTimelineTimeMs))",
                        systemImage: "clock"
                    )
                    .font(.system(size: 12, weight: .medium))
                    .foregroundStyle(Color.secondary)

                    ZStack(alignment: .topLeading) {
                        if noteText.isEmpty {
                            Text("这一刻的想法")
                                .font(.system(size: 15))
                                .foregroundStyle(Color.secondary.opacity(0.6))
                                .padding(.horizontal, 18)
                                .padding(.vertical, 16)
                                .allowsHitTesting(false)
                        }

                        TextEditor(text: $noteText)
                            .font(.system(size: 15))
                            .foregroundStyle(Color.primary)
                            .scrollContentBackground(.hidden)
                            .padding(10)
                            .frame(minHeight: 160)
                            .background(Color.cardBackground.opacity(0.5))
                            .businessBorder(cornerRadius: 8)
                    }

                    TimelineLocationButton(location: $noteLocation)

                    Spacer()
                }
                .padding(20)
            }
            .navigationTitle("添加笔记")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("取消") {
                        noteText = ""
                        noteLocation = nil
                        showNoteEditor = false
                    }
                }

                ToolbarItem(placement: .confirmationAction) {
                    Button("保存") {
                        addNote(at: pendingTimelineTimeMs)
                    }
                    .disabled(noteText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
                }
            }
        }
        .presentationDetents([.medium])
        .presentationDragIndicator(.visible)
        .presentationBackground(Color.spaceBlack)
    }

    private func handleEmptyTrackTap(
        _ track: MultiTrackTimeline.EditableTrack,
        timeMs: Double
    ) {
        pendingTimelineTimeMs = min(max(timeMs, 0), playbackVM.totalDurationMs)
        playbackVM.seekTo(timeMs: pendingTimelineTimeMs)
        HapticManager.trigger(.tapFeedback)

        switch track {
        case .photo:
            showPhotoEditor = true
        case .note:
            noteText = ""
            noteLocation = nil
            showNoteEditor = true
        }
    }

    private func addNote(at timeMs: Double) {
        let trimmedText = noteText.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmedText.isEmpty else { return }

        let event = CelestiaTimelineEvent(
            relativeTimeMs: Int64(timeMs.rounded()),
            eventType: "NOTE"
        )
        event.textContent = trimmedText
        event.location = noteLocation
        session.events.append(event)

        do {
            try saveTimelineChanges()
            noteText = ""
            noteLocation = nil
            showNoteEditor = false
        } catch {
            modelContext.rollback()
            timelineEditError = "笔记保存失败：\(error.localizedDescription)"
        }
    }

    private func addPhotos(
        _ drafts: [PhotoRecordDraft],
        location: TimelineLocation?,
        at timeMs: Double
    ) -> Bool {
        guard !drafts.isEmpty,
              let documentsURL = FileManager.default.urls(
                for: .documentDirectory,
                in: .userDomainMask
              ).first else {
            timelineEditError = "无法访问本地照片目录。"
            return false
        }

        var storedPhotos: [
            (
                filename: String,
                fileURL: URL,
                note: String
            )
        ] = []

        do {
            for draft in drafts {
                guard let data = draft.image.jpegData(compressionQuality: 0.85) else {
                    throw CocoaError(.fileWriteUnknown)
                }

                let filename = "photo_\(UUID().uuidString).jpg"
                let fileURL = documentsURL.appendingPathComponent(filename)
                try data.write(to: fileURL, options: .atomic)
                storedPhotos.append(
                    (filename, fileURL, draft.note)
                )
            }

            let relativeTimeMs = Int64(timeMs.rounded())
            for storedPhoto in storedPhotos {
                let event = CelestiaTimelineEvent(
                    relativeTimeMs: relativeTimeMs,
                    eventType: "PHOTO"
                )
                event.localFilePath = storedPhoto.filename
                let trimmedNote = storedPhoto.note.trimmingCharacters(in: .whitespacesAndNewlines)
                event.textContent = trimmedNote.isEmpty ? nil : trimmedNote
                event.location = location
                session.events.append(event)
            }

            do {
                try saveTimelineChanges()
                return true
            } catch {
                modelContext.rollback()
                for storedPhoto in storedPhotos {
                    try? FileManager.default.removeItem(at: storedPhoto.fileURL)
                }
                throw error
            }
        } catch {
            for storedPhoto in storedPhotos {
                try? FileManager.default.removeItem(at: storedPhoto.fileURL)
            }
            timelineEditError = "照片保存失败：\(error.localizedDescription)"
            return false
        }
    }

    private func updatePhotoEdits(
        _ notes: [UUID: String],
        location: TimelineLocation?
    ) -> Bool {
        let photoEventsByID = Dictionary(
            uniqueKeysWithValues: session.events
                .filter { $0.eventType == "PHOTO" }
                .map { ($0.id, $0) }
        )
        let previousValues = notes.reduce(
            into: [UUID: TimelineEventEditDraft]()
        ) { result, item in
            guard let event = photoEventsByID[item.key] else { return }
            result[item.key] = TimelineEventEditDraft(
                text: event.textContent ?? "",
                location: event.location
            )
            let trimmedNote = item.value
                .trimmingCharacters(in: .whitespacesAndNewlines)
            event.textContent = trimmedNote.isEmpty ? nil : trimmedNote
            event.location = location
        }

        do {
            try saveTimelineChanges()
            return true
        } catch {
            for (eventID, previousValue) in previousValues {
                photoEventsByID[eventID]?.textContent = previousValue.text.isEmpty
                    ? nil
                    : previousValue.text
                photoEventsByID[eventID]?.location = previousValue.location
            }
            modelContext.rollback()
            timelineEditError = "照片信息保存失败：\(error.localizedDescription)"
            return false
        }
    }

    private func updateTimelineNote(
        _ event: CelestiaTimelineEvent,
        text: String,
        location: TimelineLocation?
    ) -> Bool {
        let trimmedText = text.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmedText.isEmpty else { return false }

        let previousText = event.textContent
        let previousLocation = event.location
        event.textContent = trimmedText
        event.location = location

        do {
            try saveTimelineChanges()
            return true
        } catch {
            event.textContent = previousText
            event.location = previousLocation
            modelContext.rollback()
            timelineEditError = "笔记保存失败：\(error.localizedDescription)"
            return false
        }
    }

    private func deleteTimelineEvent(_ event: CelestiaTimelineEvent) {
        let photoURL = event.eventType == "PHOTO"
            ? AudioPathHelper.resolveURL(for: event.localFilePath)
            : nil

        modelContext.delete(event)

        do {
            try saveTimelineChanges()
            if let photoURL {
                try? FileManager.default.removeItem(at: photoURL)
            }
            selectedTimelineEvent = nil
            selectedPhotoPoint = nil
        } catch {
            modelContext.rollback()
            timelineEditError = "删除失败：\(error.localizedDescription)"
        }
    }

    private func saveTimelineChanges() throws {
        session.markContentModified()
        try modelContext.save()
        scheduleAutoSync()
    }

    private func formattedTimelineTime(_ timeMs: Double) -> String {
        let totalSeconds = max(0, Int(timeMs / 1_000))
        let hours = totalSeconds / 3_600
        let minutes = (totalSeconds % 3_600) / 60
        let seconds = totalSeconds % 60

        if hours > 0 {
            return String(format: "%d:%02d:%02d", hours, minutes, seconds)
        }
        return String(format: "%02d:%02d", minutes, seconds)
    }

    private func openTimelineEvent(_ event: CelestiaTimelineEvent) {
        if event.eventType == "PHOTO" {
            selectedPhotoPoint = PhotoPointSelection(
                eventID: event.id,
                relativeTimeMs: event.relativeTimeMs
            )
        } else if event.eventType == "NOTE" || event.eventType == "MARKER" {
            selectedTimelineEvent = event
        }
    }

    private func photoEvents(at relativeTimeMs: Int64) -> [CelestiaTimelineEvent] {
        session.events
            .filter {
                $0.eventType == "PHOTO"
                    && $0.relativeTimeMs == relativeTimeMs
            }
            .sorted {
                if $0.createdAt == $1.createdAt {
                    return $0.id.uuidString < $1.id.uuidString
                }
                return $0.createdAt < $1.createdAt
            }
    }

    private var chronoFeedItems: [ChronoFeedItem] {
        let sortedEvents = playbackVM.sortedEvents(from: session)
        var includedPhotoTimes = Set<Int64>()

        return sortedEvents.compactMap { event in
            guard event.eventType == "PHOTO" else {
                return ChronoFeedItem(event: event, photoEvents: [])
            }

            guard includedPhotoTimes.insert(event.relativeTimeMs).inserted else {
                return nil
            }

            return ChronoFeedItem(
                event: event,
                photoEvents: photoEvents(at: event.relativeTimeMs)
            )
        }
    }
}

private struct PhotoPointSelection: Identifiable {
    let eventID: UUID
    let relativeTimeMs: Int64

    var id: UUID { eventID }
}

private struct ChronoFeedItem: Identifiable {
    let event: CelestiaTimelineEvent
    let photoEvents: [CelestiaTimelineEvent]

    var id: UUID { event.id }
}

private struct TimelineImagePresentation: Identifiable {
    let id = UUID()
    let image: UIImage
    let capturedAt: Date
    let note: String
}

private struct TimelineEventEditDraft: Equatable {
    var text: String
    var location: TimelineLocation?
}

private struct PhotoPointDetailSheet: View {
    @Environment(\.dismiss) private var dismiss

    let events: [CelestiaTimelineEvent]
    let onSaveEdits: ([UUID: String], TimelineLocation?) -> Bool
    let onDelete: (CelestiaTimelineEvent) -> Void

    @State private var noteDrafts: [UUID: String]
    @State private var locationDraft: TimelineLocation?
    @State private var pendingDeletionEvent: CelestiaTimelineEvent?
    @State private var fullScreenImage: TimelineImagePresentation?

    init(
        events: [CelestiaTimelineEvent],
        onSaveEdits: @escaping ([UUID: String], TimelineLocation?) -> Bool,
        onDelete: @escaping (CelestiaTimelineEvent) -> Void
    ) {
        self.events = events
        self.onSaveEdits = onSaveEdits
        self.onDelete = onDelete
        _noteDrafts = State(
            initialValue: Dictionary(
                uniqueKeysWithValues: events.map {
                    ($0.id, $0.textContent ?? "")
                }
            )
        )
        _locationDraft = State(
            initialValue: events.compactMap(\.location).first
        )
    }

    var body: some View {
        NavigationStack {
            ZStack {
                Color.spaceBlack.ignoresSafeArea()

                if events.isEmpty {
                    ContentUnavailableView(
                        "照片不可用",
                        systemImage: "photo.badge.exclamationmark",
                        description: Text("这个记录点没有可查看的本地照片。")
                    )
                } else {
                    ScrollView {
                        VStack(alignment: .leading, spacing: 16) {
                            HStack {
                                Label(
                                    events[0].relativeTimeFormatted,
                                    systemImage: "clock"
                                )
                                .font(.system(size: 12, weight: .medium, design: .monospaced))
                                .foregroundStyle(Color.secondary)

                                Spacer()

                                Text("\(events.count) 张照片")
                                    .font(.system(size: 12, design: .monospaced))
                                    .foregroundStyle(Color.secondary)
                            }

                            TimelineLocationButton(location: $locationDraft)

                            LazyVStack(spacing: 28) {
                                ForEach(Array(events.enumerated()), id: \.element.id) { index, event in
                                    photoListItem(event, index: index)
                                }
                            }
                        }
                        .padding(20)
                    }
                }
            }
            .navigationTitle(events.count > 1 ? "查看照片（\(events.count) 张）" : "查看照片")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("完成") {
                        dismiss()
                    }
                }

                ToolbarItem(placement: .confirmationAction) {
                    Button("保存") {
                        if onSaveEdits(noteDrafts, locationDraft) {
                            dismiss()
                        }
                    }
                    .disabled(!hasChanges)
                }
            }
            .confirmationDialog(
                "确定删除这张照片吗？",
                isPresented: Binding(
                    get: { pendingDeletionEvent != nil },
                    set: { if !$0 { pendingDeletionEvent = nil } }
                ),
                titleVisibility: .visible
            ) {
                Button("删除", role: .destructive) {
                    guard let event = pendingDeletionEvent else { return }
                    onDelete(event)
                    dismiss()
                }
                Button("取消", role: .cancel) {}
            } message: {
                Text("本地图片文件和这张照片的备注都会被删除，此操作无法撤销。")
            }
        }
        .fullScreenCover(item: $fullScreenImage) { item in
            FullScreenTimelineImageView(
                image: item.image,
                capturedAt: item.capturedAt,
                note: item.note
            )
        }
        .presentationDetents([.large])
        .presentationDragIndicator(.visible)
        .presentationBackground(Color.spaceBlack)
    }

    private func photoListItem(
        _ event: CelestiaTimelineEvent,
        index: Int
    ) -> some View {
        VStack(alignment: .leading, spacing: 10) {
            HStack {
                Text("照片 \(index + 1)")
                    .font(.system(size: 11, weight: .medium))
                    .foregroundStyle(Color.secondary)

                Spacer()

                Button(role: .destructive) {
                    pendingDeletionEvent = event
                } label: {
                    Image(systemName: "trash")
                        .font(.system(size: 11, weight: .medium))
                        .frame(width: 24, height: 24)
                }
                .buttonStyle(.plain)
                .foregroundStyle(Color.red)
                .accessibilityLabel("删除照片 \(index + 1)")
            }

            if let image = resolvedImage(for: event) {
                Button {
                    fullScreenImage = TimelineImagePresentation(
                        image: image,
                        capturedAt: event.createdAt,
                        note: noteDrafts[event.id] ?? ""
                    )
                } label: {
                    Image(uiImage: image)
                        .resizable()
                        .scaledToFit()
                        .frame(maxWidth: .infinity, maxHeight: 420)
                        .contentShape(Rectangle())
                }
                .buttonStyle(.plain)
                .accessibilityLabel("全屏查看照片 \(index + 1)")
            } else {
                ContentUnavailableView(
                    "图片不可用",
                    systemImage: "photo.badge.exclamationmark",
                    description: Text("本地图片文件可能已被移动或删除。")
                )
                .frame(maxWidth: .infinity, minHeight: 180)
            }

            ZStack(alignment: .topLeading) {
                if (noteDrafts[event.id] ?? "").isEmpty {
                    Text("照片备注")
                        .font(.system(size: 14))
                        .foregroundStyle(Color.secondary.opacity(0.55))
                        .padding(.horizontal, 15)
                        .padding(.vertical, 17)
                        .allowsHitTesting(false)
                }

                TextEditor(text: noteBinding(for: event))
                    .font(.system(size: 14))
                    .foregroundStyle(Color.primary)
                    .scrollContentBackground(.hidden)
                    .padding(8)
                    .frame(height: 72)
            }
            .background(Color.cardBackground.opacity(0.45))
            .businessBorder(cornerRadius: 8)

            if index < events.count - 1 {
                Divider()
                    .overlay(Color.lineBorder.opacity(0.55))
                    .padding(.top, 8)
            }
        }
    }

    private func noteBinding(for event: CelestiaTimelineEvent) -> Binding<String> {
        Binding(
            get: { noteDrafts[event.id] ?? "" },
            set: { newValue in
                noteDrafts[event.id] = newValue
            }
        )
    }

    private var hasChanges: Bool {
        events.contains { event in
            let original = (event.textContent ?? "")
                .trimmingCharacters(in: .whitespacesAndNewlines)
            let draft = (noteDrafts[event.id] ?? "")
                .trimmingCharacters(in: .whitespacesAndNewlines)
            return original != draft || event.location != locationDraft
        }
    }

    private func resolvedImage(for event: CelestiaTimelineEvent) -> UIImage? {
        guard let url = AudioPathHelper.resolveURL(for: event.localFilePath) else {
            return nil
        }
        return UIImage(contentsOfFile: url.path)
    }
}

private struct TimelineEventDetailSheet: View {
    @Environment(\.dismiss) private var dismiss

    let event: CelestiaTimelineEvent
    let onSaveNote: (String, TimelineLocation?) -> Bool
    let onDelete: () -> Void

    @State private var noteDraft: String
    @State private var locationDraft: TimelineLocation?
    @State private var showDeleteConfirmation = false
    @State private var showFullScreenImage = false

    init(
        event: CelestiaTimelineEvent,
        onSaveNote: @escaping (String, TimelineLocation?) -> Bool,
        onDelete: @escaping () -> Void
    ) {
        self.event = event
        self.onSaveNote = onSaveNote
        self.onDelete = onDelete
        _noteDraft = State(initialValue: event.textContent ?? "")
        _locationDraft = State(initialValue: event.location)
    }

    var body: some View {
        NavigationStack {
            ZStack {
                Color.spaceBlack.ignoresSafeArea()

                VStack(alignment: .leading, spacing: 16) {
                    Label(event.relativeTimeFormatted, systemImage: "clock")
                        .font(.system(size: 12, weight: .medium, design: .monospaced))
                        .foregroundStyle(Color.secondary)

                    eventContent

                    Spacer(minLength: 0)

                    HStack {
                        Spacer()

                        Button(role: .destructive) {
                            showDeleteConfirmation = true
                        } label: {
                            Image(systemName: "trash")
                                .font(.system(size: 13, weight: .medium))
                                .frame(width: 30, height: 30)
                        }
                        .buttonStyle(.bordered)
                        .buttonBorderShape(.circle)
                        .tint(.red)
                        .accessibilityLabel("删除这条记录")
                    }
                }
                .padding(20)
            }
            .navigationTitle(detailTitle)
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("完成") {
                        dismiss()
                    }
                }

                if event.eventType == "NOTE" {
                    ToolbarItem(placement: .confirmationAction) {
                        Button("保存") {
                            if onSaveNote(noteDraft, locationDraft) {
                                dismiss()
                            }
                        }
                        .disabled(
                            trimmedNoteDraft.isEmpty
                                || !hasChanges
                        )
                    }
                }
            }
            .confirmationDialog(
                "确定删除这条记录吗？",
                isPresented: $showDeleteConfirmation,
                titleVisibility: .visible
            ) {
                Button("删除", role: .destructive) {
                    onDelete()
                    dismiss()
                }
                Button("取消", role: .cancel) {}
            } message: {
                Text(event.eventType == "PHOTO" ? "本地图片文件也会被删除，此操作无法撤销。" : "此操作无法撤销。")
            }
        }
        .fullScreenCover(isPresented: $showFullScreenImage) {
            if let image = resolvedImage {
                FullScreenTimelineImageView(
                    image: image,
                    capturedAt: event.createdAt,
                    note: event.textContent ?? ""
                )
            }
        }
        .presentationDetents(event.eventType == "PHOTO" ? [.medium, .large] : [.medium])
        .presentationDragIndicator(.visible)
        .presentationBackground(Color.spaceBlack)
    }

    @ViewBuilder
    private var eventContent: some View {
        if event.eventType == "PHOTO" {
            if let image = resolvedImage {
                Button {
                    showFullScreenImage = true
                } label: {
                    Image(uiImage: image)
                        .resizable()
                        .scaledToFit()
                        .frame(maxWidth: .infinity)
                        .clipShape(RoundedRectangle(cornerRadius: 10))
                        .contentShape(Rectangle())
                }
                .buttonStyle(.plain)
                .accessibilityLabel("全屏查看图片")
            } else {
                ContentUnavailableView(
                    "图片不可用",
                    systemImage: "photo.badge.exclamationmark",
                    description: Text("本地图片文件可能已被移动或删除。")
                )
                .frame(maxWidth: .infinity, minHeight: 180)
            }
        } else if event.eventType == "NOTE" {
            VStack(alignment: .leading, spacing: 14) {
                ZStack(alignment: .topLeading) {
                    if noteDraft.isEmpty {
                        Text("这一刻的想法")
                            .font(.system(size: 15))
                            .foregroundStyle(Color.secondary.opacity(0.55))
                            .padding(.horizontal, 18)
                            .padding(.vertical, 16)
                            .allowsHitTesting(false)
                    }

                    TextEditor(text: $noteDraft)
                        .font(.system(size: 15))
                        .foregroundStyle(Color.primary)
                        .scrollContentBackground(.hidden)
                        .padding(10)
                        .frame(maxWidth: .infinity, minHeight: 150, alignment: .topLeading)
                }
                .background(Color.cardBackground.opacity(0.5))
                .businessBorder(cornerRadius: 8)

                TimelineLocationButton(location: $locationDraft)
            }
        } else {
            Text(event.textContent ?? "暂无记录内容")
                .font(.system(size: 15))
                .foregroundStyle(Color.primary)
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding(16)
                .background(Color.cardBackground.opacity(0.5))
                .businessBorder(cornerRadius: 8)
        }
    }

    private var resolvedImage: UIImage? {
        guard let url = AudioPathHelper.resolveURL(for: event.localFilePath) else {
            return nil
        }
        return UIImage(contentsOfFile: url.path)
    }

    private var trimmedNoteDraft: String {
        noteDraft.trimmingCharacters(in: .whitespacesAndNewlines)
    }

    private var hasChanges: Bool {
        trimmedNoteDraft
            != (event.textContent ?? "")
                .trimmingCharacters(in: .whitespacesAndNewlines)
            || locationDraft != event.location
    }

    private var detailTitle: String {
        if event.isContinuationMarker {
            return "续录详情"
        }
        return event.eventType == "PHOTO" ? "查看图片" : "查看笔记"
    }
}

private struct FullScreenTimelineImageView: View {
    @Environment(\.dismiss) private var dismiss

    let image: UIImage
    let capturedAt: Date
    let note: String

    var body: some View {
        ZStack {
            Color.black.ignoresSafeArea()

            ZoomableTimelineImage(image: image)
                .ignoresSafeArea()

            VStack(spacing: 0) {
                HStack {
                    Spacer()

                    Button {
                        dismiss()
                    } label: {
                        Image(systemName: "xmark")
                            .font(.system(size: 14, weight: .semibold))
                            .foregroundStyle(.white)
                            .frame(width: 34, height: 34)
                            .background(.black.opacity(0.55), in: Circle())
                    }
                    .accessibilityLabel("关闭全屏图片")
                }
                .padding(.top, 12)
                .padding(.trailing, 16)

                Spacer()

                VStack(alignment: .leading, spacing: 6) {
                    Text(Self.photoDateFormatter.string(from: capturedAt))
                        .font(.system(size: 12, weight: .medium))
                        .foregroundStyle(.white.opacity(0.78))

                    if !trimmedNote.isEmpty {
                        Text(trimmedNote)
                            .font(.system(size: 15))
                            .foregroundStyle(.white)
                            .fixedSize(horizontal: false, vertical: true)
                    }
                }
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding(.horizontal, 20)
                .padding(.top, 54)
                .padding(.bottom, 28)
                .background(
                    LinearGradient(
                        colors: [.clear, .black.opacity(0.82)],
                        startPoint: .top,
                        endPoint: .bottom
                    )
                )
            }
        }
        .statusBarHidden()
    }

    private var trimmedNote: String {
        note.trimmingCharacters(in: .whitespacesAndNewlines)
    }

    private static let photoDateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "zh_CN")
        formatter.dateFormat = "yyyy年M月d日 HH:mm:ss"
        return formatter
    }()
}

private struct ZoomableTimelineImage: View {
    let image: UIImage

    @State private var scale: CGFloat = 1
    @State private var settledScale: CGFloat = 1
    @State private var offset: CGSize = .zero
    @State private var settledOffset: CGSize = .zero

    private let maximumScale: CGFloat = 5

    var body: some View {
        GeometryReader { proxy in
            Image(uiImage: image)
                .resizable()
                .scaledToFit()
                .frame(width: proxy.size.width, height: proxy.size.height)
                .scaleEffect(scale)
                .offset(offset)
                .contentShape(Rectangle())
                .gesture(magnificationGesture(in: proxy.size))
                .simultaneousGesture(dragGesture(in: proxy.size))
        }
        .clipped()
        .accessibilityLabel("全屏图片，可双指缩放并拖动")
    }

    private func magnificationGesture(in containerSize: CGSize) -> some Gesture {
        MagnifyGesture()
            .onChanged { value in
                scale = min(max(settledScale * value.magnification, 1), maximumScale)
                offset = clampedOffset(offset, scale: scale, in: containerSize)
            }
            .onEnded { _ in
                if scale <= 1 {
                    scale = 1
                    offset = .zero
                } else {
                    offset = clampedOffset(offset, scale: scale, in: containerSize)
                }

                settledScale = scale
                settledOffset = offset
            }
    }

    private func dragGesture(in containerSize: CGSize) -> some Gesture {
        DragGesture()
            .onChanged { value in
                guard scale > 1 else {
                    offset = .zero
                    return
                }

                let proposedOffset = CGSize(
                    width: settledOffset.width + value.translation.width,
                    height: settledOffset.height + value.translation.height
                )
                offset = clampedOffset(proposedOffset, scale: scale, in: containerSize)
            }
            .onEnded { _ in
                offset = clampedOffset(offset, scale: scale, in: containerSize)
                settledOffset = offset
            }
    }

    private func clampedOffset(
        _ proposedOffset: CGSize,
        scale: CGFloat,
        in containerSize: CGSize
    ) -> CGSize {
        let fittedSize = aspectFitSize(for: image.size, in: containerSize)
        let horizontalLimit = max(0, (fittedSize.width * scale - containerSize.width) / 2)
        let verticalLimit = max(0, (fittedSize.height * scale - containerSize.height) / 2)

        return CGSize(
            width: min(max(proposedOffset.width, -horizontalLimit), horizontalLimit),
            height: min(max(proposedOffset.height, -verticalLimit), verticalLimit)
        )
    }

    private func aspectFitSize(for imageSize: CGSize, in containerSize: CGSize) -> CGSize {
        guard imageSize.width > 0,
              imageSize.height > 0,
              containerSize.width > 0,
              containerSize.height > 0 else {
            return .zero
        }

        let fitScale = min(
            containerSize.width / imageSize.width,
            containerSize.height / imageSize.height
        )
        return CGSize(
            width: imageSize.width * fitScale,
            height: imageSize.height * fitScale
        )
    }
}

private struct AudioShareItem: Identifiable {
    let id = UUID()
    let fileURL: URL
}

private struct AudioShareSheet: UIViewControllerRepresentable {
    let fileURL: URL

    func makeUIViewController(context: Context) -> UIActivityViewController {
        UIActivityViewController(activityItems: [fileURL], applicationActivities: nil)
    }

    func updateUIViewController(
        _ uiViewController: UIActivityViewController,
        context: Context
    ) {}
}

// MARK: - Preview

#Preview {
    NavigationStack {
        SessionDetailView(session: {
            let s = CelestiaSession(title: "Preview 现场记录 · 6.24 14:30")
            s.endTime = Date().addingTimeInterval(3600)
            return s
        }())
    }
    .modelContainer(for: CelestiaSession.self, inMemory: true)
}
