import SwiftUI
import QuickLook
import Combine

// MARK: - Preview mode mapping (mirrors Rust MaterialType::preview_mode)

func previewMode(for type: MaterialType) -> PreviewMode {
    switch type {
    case .markdown, .text, .image, .epub: .nativeReader
    case .pdf, .word, .excel:              .platformPreview
    case .powerPoint:                      .externalOpen
    case .unknown:                         .unsupported
    }
}

// MARK: - ViewModel

@MainActor
final class MaterialReaderViewModel: ObservableObject {
    @Published var loadingState: LoadState = .loading
    @Published var blocks: [DocumentBlock] = []
    @Published var textContent: String = ""
    @Published var imageMeta: ImageMeta?
    @Published var stats: TextStats?

    let materialId: String
    let filePath: String
    let materialType: MaterialType
    let mode: PreviewMode

    enum LoadState: Equatable {
        case idle, loading, loaded, error(String)
    }

    init(materialId: String, filePath: String, materialType: MaterialType) {
        self.materialId = materialId
        self.filePath = filePath
        self.materialType = materialType
        self.mode = previewMode(for: materialType)
        print("[READER] init — materialId=\(materialId), type=\(materialType), path=\(filePath)")
    }

    func load() async {
        loadingState = .loading
        print("[READER] load() start — materialType=\(materialType)")

        // Check file before doing anything
        let fileExists = FileManager.default.fileExists(atPath: filePath)
        let fileSize: Int = (try? FileManager.default.attributesOfItem(atPath: filePath)[.size] as? Int) ?? -1
        print("[READER] file exists=\(fileExists), size=\(fileSize)")

        do {
            switch materialType {
            case .markdown:
                let t0 = CFAbsoluteTimeGetCurrent()
                let content = try String(contentsOfFile: filePath, encoding: .utf8)
                let t1 = CFAbsoluteTimeGetCurrent()
                print("[READER] markdown read — contentLength=\(content.count), readMs=\((t1-t0)*1000)")
                print("[READER] calling parseMarkdown...")
                blocks = try parseMarkdown(content: content)
                let t2 = CFAbsoluteTimeGetCurrent()
                print("[READER] parseMarkdown done — blockCount=\(blocks.count), parseMs=\((t2-t1)*1000)")
            case .text:
                let t0 = CFAbsoluteTimeGetCurrent()
                let content = try String(contentsOfFile: filePath, encoding: .utf8)
                let t1 = CFAbsoluteTimeGetCurrent()
                print("[READER] text read — contentLength=\(content.count), readMs=\((t1-t0)*1000)")
                print("[READER] calling parseText...")
                blocks = try parseText(content: content)
                let t2 = CFAbsoluteTimeGetCurrent()
                print("[READER] parseText done — blockCount=\(blocks.count), parseMs=\((t2-t1)*1000)")
                print("[READER] calling readTextStats...")
                stats = try? readTextStats(filePath: filePath)
                print("[READER] readTextStats done — stats=\(String(describing: stats))")
            case .image:
                print("[READER] calling readImageMeta...")
                imageMeta = try readImageMeta(filePath: filePath)
                print("[READER] readImageMeta done — meta=\(String(describing: imageMeta))")
            case .pdf, .word, .excel, .powerPoint, .epub, .unknown:
                print("[READER] unsupported/native type — skipping Rust call")
            }
            loadingState = .loaded
            print("[READER] load() finished successfully")
        } catch let error as DocumentError {
            print("[READER] DocumentError — \(error.localizedDescription), type=\(error)")
            loadingState = .error(error.localizedDescription)
        } catch {
            print("[READER] error — \(error.localizedDescription), type=\(type(of: error))")
            loadingState = .error(error.localizedDescription)
        }
    }
}

// MARK: - Main View

struct MaterialReaderView: View {
    @StateObject private var vm: MaterialReaderViewModel
    @Binding var showQuickLook: Bool
    @Binding var showNoteSheet: Bool
    @State private var scrollProgress: CGFloat = 0
    @State private var hasRestoredPosition = false
    @State private var restoreBlockId: String?

    private let title: String

    // Event collector — records reading events during the session
    private let collector = ReadingEventCollector.shared
    private let positionStore = ReadingPositionStore.shared

    init(materialId: String, filePath: String, materialType: MaterialType, title: String = "", showQuickLook: Binding<Bool> = .constant(false), showNoteSheet: Binding<Bool> = .constant(false)) {
        self.title = title
        self._showQuickLook = showQuickLook
        self._showNoteSheet = showNoteSheet
        _vm = StateObject(wrappedValue: MaterialReaderViewModel(
            materialId: materialId, filePath: filePath, materialType: materialType))
    }

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

            switch vm.loadingState {
            case .idle, .loading:
                VStack(spacing: 12) {
                    ProgressView().tint(Color.zxPrimary)
                    Text("加载资料…").font(.system(size: 14)).foregroundColor(Color.zxF04)
                }
            case .error(let msg):
                VStack(spacing: 16) {
                    Image(systemName: "exclamationmark.triangle").font(.system(size: 40)).foregroundColor(Color.zxCoral)
                    Text(msg).font(.system(size: 14)).foregroundColor(Color.zxF04)
                    Button("重试") { Task { await vm.load() } }
                        .font(.system(size: 14, weight: .medium)).foregroundColor(Color.zxPrimary)
                        .padding(.horizontal, 24).padding(.vertical, 10)
                        .background(Color.zxPrimarySoft).clipShape(RoundedRectangle(cornerRadius: 10))
                }
            case .loaded:
                switch vm.mode {
                case .nativeReader:
                    nativeReaderBody
                case .platformPreview:
                    platformPreviewBody
                case .externalOpen:
                    externalOpenBody
                case .unsupported:
                    unsupportedBody
                }
            }
        }
        .navigationTitle(title.isEmpty ? vm.materialType.displayName : title)
        .navigationBarTitleDisplayMode(.inline)
        .toolbar(.hidden, for: .tabBar)
        .toolbarBackground(.hidden, for: .navigationBar)
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                NavigationLink(value: Route.aiChat(context: ChatEntryContext(
                    scopeType: .material,
                    scopeId: vm.materialId,
                    scopeName: title,
                    parentKnowledgeBaseId: nil,
                    createdFrom: "material_reader"
                ))) {
                    Image(systemName: "bubble.left.and.bubble.right")
                        .font(.system(size: 16))
                        .foregroundColor(Color.zxF05)
                }
            }
        }
        .task { await vm.load() }
        .sheet(isPresented: $showQuickLook) {
            QuickLookPreview(url: URL(fileURLWithPath: vm.filePath))
        }
        .sheet(isPresented: $showNoteSheet) {
            QuickNoteSheet(
                materialId: vm.materialId,
                materialName: vm.materialType.displayName,
                anchor: buildAnchor()
            )
        }
        .onAppear {
            // FIXME: collector calls Rust FFI with struct-passing (broken on ARM64 iOS)
            // collector.open(materialId: vm.materialId)
            hasRestoredPosition = false
        }
        .onDisappear {
            // FIXME: collector calls Rust FFI with struct-passing (broken on ARM64 iOS)
            // if let lastPos = collector.lastPosition {
            //     positionStore.save(materialId: vm.materialId, position: lastPos)
            // }
            // _ = collector.close(materialId: vm.materialId)
        }
        .onChange(of: vm.loadingState) { _, newState in
            if newState == .loaded, !hasRestoredPosition {
                restorePosition()
                hasRestoredPosition = true
            }
        }
    }

    // MARK: - Native reader (Markdown, TXT, Image)

    @ViewBuilder
    var nativeReaderBody: some View {
        if vm.materialType == .image, let meta = vm.imageMeta {
            imageBody(meta: meta)
        } else if !vm.blocks.isEmpty {
            markdownBody
        } else if !vm.textContent.isEmpty {
            textBody
        }
    }

    // MARK: Markdown block list

    var markdownBody: some View {
        ScrollViewReader { scrollProxy in
            ScrollView {
                VStack(alignment: .leading, spacing: 16) {
                    if let s = vm.stats {
                        HStack(spacing: 16) {
                            Label("\(s.lineCount) 行", systemImage: "text.alignleft")
                            Label("\(s.wordCount) 词", systemImage: "character")
                        }
                        .font(.system(size: 12)).foregroundColor(Color.zxF03)
                        .padding(.bottom, 4)
                    }
                    ForEach(Array(vm.blocks.enumerated()), id: \.offset) { _, block in
                        DocumentBlockView(block: block)
                            .id(blockId(from: block))
                    }
                }
                .padding(.horizontal, 20).padding(.top, 8).padding(.bottom, 100)
                .background(GeometryReader { geo in
                    Color.clear.preference(
                        key: ScrollOffsetKey.self,
                        value: geo.frame(in: .named("scroll")).minY
                    )
                })
            }
            .coordinateSpace(name: "scroll")
            .onPreferenceChange(ScrollOffsetKey.self) { offset in
                scrollProgress = min(1, max(0, -offset / max(contentHeightEstimate, 1)))
                reportScrollPosition()
            }
            .onChange(of: restoreBlockId) { _, target in
                if let id = target {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                        withAnimation { scrollProxy.scrollTo(id, anchor: .top) }
                    }
                    restoreBlockId = nil
                }
            }
            .scrollIndicators(.hidden)
        }
    }

    /// Estimate total block area height for scroll progress calculation
    private var contentHeightEstimate: CGFloat {
        let count = max(vm.blocks.count, 1)
        return CGFloat(count) * 80
    }

    /// Build a NoteAnchor from the current scroll position (for quick note).
    private func buildAnchor() -> NoteAnchor? {
        guard let pos = collector.lastPosition else { return nil }
        return createNoteAnchor(materialId: vm.materialId, position: pos)
    }

    /// Restore saved reading position on re-entry.
    private func restorePosition() {
        guard let saved = positionStore.load(materialId: vm.materialId) else { return }
        switch saved {
        case .markdown(let blockId, _):
            restoreBlockId = blockId
        case .text(let lineNumber, _):
            let idx = Int(lineNumber) - 1
            if idx >= 0, idx < vm.blocks.count {
                restoreBlockId = blockId(from: vm.blocks[idx])
            }
        case .pdf, .image, .epub, .unknown:
            break // PDF/image restoration needs platform-specific handling
        }
    }

    /// Report current scroll position to the event collector.
    private func reportScrollPosition() {
        guard !vm.blocks.isEmpty else { return }

        let idx = max(0, min(vm.blocks.count - 1,
                             Int(scrollProgress * CGFloat(vm.blocks.count))))
        let block = vm.blocks[idx]
        let bId = blockId(from: block)
        let sp = Float(scrollProgress)

        let pos: ReadingPosition
        switch vm.materialType {
        case .markdown:
            pos = .markdown(blockId: bId, scrollProgress: sp)
        case .text:
            pos = .text(lineNumber: UInt32(idx + 1), scrollProgress: sp)
        default:
            pos = .markdown(blockId: bId, scrollProgress: sp)
        }
        collector.updatePosition(materialId: vm.materialId, position: pos)
    }

    private func blockId(from block: DocumentBlock) -> String {
        switch block {
        case .heading(let id, _, _):       return id
        case .paragraph(let id, _):        return id
        case .list(let id, _, _):          return id
        case .codeBlock(let id, _, _):     return id
        case .quote(let id, _):            return id
        case .table(let id, _, _):         return id
        case .imageBlock(let id, _, _):    return id
        case .horizontalRule(let id):      return id
        }
    }

    // MARK: Plain text fallback

    var textBody: some View {
        ScrollView {
            Text(vm.textContent)
                .font(.system(size: 15)).foregroundColor(Color.zxF0).lineSpacing(6)
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding(.horizontal, 20).padding(.top, 8).padding(.bottom, 100)
        }
        .scrollIndicators(.hidden)
    }

    // MARK: Image

    func imageBody(meta: ImageMeta) -> some View {
        VStack {
            if let uiImage = UIImage(contentsOfFile: vm.filePath) {
                Image(uiImage: uiImage).resizable().scaledToFit()
                    .frame(maxWidth: .infinity, maxHeight: .infinity).padding(20)
            }
            Text("\(meta.width)×\(meta.height) · \(meta.format.uppercased()) · \(formatFileSize(meta.fileSize))")
                .font(.system(size: 12)).foregroundColor(Color.zxF03)
        }
    }

    // MARK: - Platform preview (PDF, Word, Excel)

    var platformPreviewBody: some View {
        VStack(spacing: 24) {
            VStack(spacing: 12) {
                Image(systemName: "doc.text").font(.system(size: 48)).foregroundColor(Color.zxF03)
                Text(vm.materialType.displayName).font(.system(size: 16, weight: .semibold)).foregroundColor(Color.zxF0)
                Text("使用系统预览打开此文件").font(.system(size: 14)).foregroundColor(Color.zxF04)
            }
            Button { showQuickLook = true } label: {
                HStack(spacing: 8) { Image(systemName: "eye"); Text("打开预览") }
                    .font(.system(size: 14, weight: .bold)).foregroundColor(.white)
                    .frame(maxWidth: .infinity).frame(height: 48)
                    .background(ZXGradient.brand).clipShape(RoundedRectangle(cornerRadius: ZXRadius.lg))
            }.padding(.horizontal, 40)
        }
    }

    // MARK: - External open (PPT)

    var externalOpenBody: some View {
        VStack(spacing: 24) {
            VStack(spacing: 12) {
                Image(systemName: "arrow.up.forward.app").font(.system(size: 48)).foregroundColor(Color.zxF03)
                Text("PowerPoint 演示文稿").font(.system(size: 16, weight: .semibold)).foregroundColor(Color.zxF0)
                Text("此格式需要外部应用打开").font(.system(size: 14)).foregroundColor(Color.zxF04)
            }
            if let url = URL(string: "shareddocuments://" + vm.filePath) {
                ShareLink(item: url) {
                    HStack(spacing: 8) { Image(systemName: "square.and.arrow.up"); Text("用其他应用打开") }
                        .font(.system(size: 14, weight: .bold)).foregroundColor(Color.zxPrimary)
                        .frame(maxWidth: .infinity).frame(height: 48)
                        .background(Color.zxPrimarySoft).clipShape(RoundedRectangle(cornerRadius: ZXRadius.lg))
                }
            }
        }
    }

    // MARK: - Unsupported

    var unsupportedBody: some View {
        VStack(spacing: 16) {
            Image(systemName: "questionmark.folder").font(.system(size: 48)).foregroundColor(Color.zxF03)
            Text("暂不支持此格式").font(.system(size: 16, weight: .semibold)).foregroundColor(Color.zxF0)
            Text(vm.filePath).font(.system(size: 12)).foregroundColor(Color.zxF05)
        }
    }
}

// MARK: - DocumentBlock renderer (renders Rust-generated blocks via SwiftUI)

struct DocumentBlockView: View {
    let block: DocumentBlock

    var body: some View {
        switch block {
        case .heading(let id, let level, let text):
            headingView(level: Int(level), text: text)
        case .paragraph(let id, let text):
            Text(text).font(.system(size: 15)).foregroundColor(Color.zxF0).lineSpacing(6)
        case .list(let id, let ordered, let items):
            listView(ordered: ordered, items: items)
        case .codeBlock(let id, let language, let code):
            codeView(language: language, code: code)
        case .quote(let id, let text):
            quoteView(text: text)
        case .table(let id, let headers, let rows):
            tableView(headers: headers, rows: rows)
        case .imageBlock(let id, let src, let alt):
            imageBlockView(src: src, alt: alt)
        case .horizontalRule(let id):
            Rectangle().fill(Color.zxBorder006).frame(height: 1)
        }
    }

    func headingView(level: Int, text: String) -> some View {
        let sizes: [CGFloat] = [0, 24, 20, 17, 15, 14, 13]
        let size = level < sizes.count ? sizes[level] : 13
        return Text(text)
            .font(.system(size: size, weight: .bold)).foregroundColor(Color.zxF0)
            .padding(.top, level <= 1 ? 8 : 4)
    }

    func listView(ordered: Bool, items: [String]) -> some View {
        VStack(alignment: .leading, spacing: 6) {
            ForEach(Array(items.enumerated()), id: \.offset) { i, item in
                HStack(alignment: .top, spacing: 8) {
                    Text(ordered ? "\(i + 1)." : "•")
                        .font(.system(size: 14, weight: ordered ? .medium : .regular))
                        .foregroundColor(Color.zxF04).frame(width: 20, alignment: ordered ? .trailing : .center)
                    Text(item).font(.system(size: 14)).foregroundColor(Color.zxF0)
                }
            }
        }
    }

    func codeView(language: String?, code: String) -> some View {
        VStack(alignment: .leading, spacing: 4) {
            if let lang = language {
                Text(lang).font(.system(size: 11, weight: .medium)).foregroundColor(Color.zxF03)
                    .padding(.horizontal, 12).padding(.top, 8)
            }
            Text(code).font(.system(size: 13, design: .monospaced)).foregroundColor(Color.zxF0)
                .padding(12).frame(maxWidth: .infinity, alignment: .leading)
        }
        .background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 10))
        .overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.zxBorder008, lineWidth: 1))
    }

    func quoteView(text: String) -> some View {
        HStack(spacing: 0) {
            Rectangle().fill(Color.zxPrimary.opacity(0.3)).frame(width: 3)
            Text(text).font(.system(size: 14)).foregroundColor(Color.zxF04).italic().padding(.leading, 12)
        }
    }

    func tableView(headers: [String], rows: [[String]]) -> some View {
        VStack(spacing: 0) {
            HStack(spacing: 0) {
                ForEach(Array(headers.enumerated()), id: \.offset) { _, h in
                    Text(h).font(.system(size: 13, weight: .semibold)).foregroundColor(Color.zxF0)
                        .frame(maxWidth: .infinity, alignment: .leading).padding(8)
                }
            }.background(Color.zxFill004)
            ForEach(Array(rows.enumerated()), id: \.offset) { _, row in
                HStack(spacing: 0) {
                    ForEach(Array(row.enumerated()), id: \.offset) { _, cell in
                        Text(cell).font(.system(size: 13)).foregroundColor(Color.zxF0)
                            .frame(maxWidth: .infinity, alignment: .leading).padding(8)
                    }
                }
            }
        }
        .background(Color.zxSurface).clipShape(RoundedRectangle(cornerRadius: 8))
        .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.zxBorder008, lineWidth: 1))
    }

    func imageBlockView(src: String, alt: String?) -> some View {
        VStack(spacing: 6) {
            if let url = URL(string: src), src.hasPrefix("http") {
                AsyncImage(url: url) { phase in
                    switch phase {
                    case .success(let img): img.resizable().scaledToFit().clipShape(RoundedRectangle(cornerRadius: 10))
                    case .failure: Rectangle().fill(Color.zxFill004).frame(height: 160).overlay(Image(systemName: "photo").foregroundColor(Color.zxF03))
                    default: ProgressView().frame(height: 160)
                    }
                }
            } else {
                Rectangle().fill(Color.zxFill004).frame(height: 120)
                    .overlay(VStack(spacing: 4) { Image(systemName: "photo"); Text(src).font(.system(size: 11)) }.foregroundColor(Color.zxF03))
            }
            if let alt = alt { Text(alt).font(.system(size: 12)).foregroundColor(Color.zxF04) }
        }
    }
}

// MARK: - MaterialType display name

extension MaterialType {
    var displayName: String {
        switch self {
        case .markdown: "Markdown"
        case .text:     "纯文本"
        case .image:    "图片"
        case .pdf:      "PDF"
        case .word:     "Word 文档"
        case .excel:    "Excel 表格"
        case .powerPoint: "PowerPoint"
        case .epub:     "EPUB"
        case .unknown:  "未知格式"
        }
    }
}

// MARK: - QuickLook wrapper

struct QuickLookPreview: UIViewControllerRepresentable {
    let url: URL

    func makeUIViewController(context: Context) -> QLPreviewController {
        let c = QLPreviewController()
        c.dataSource = context.coordinator
        return c
    }

    func updateUIViewController(_ uiViewController: QLPreviewController, context: Context) {}
    func makeCoordinator() -> Coordinator { Coordinator(url: url) }

    class Coordinator: NSObject, QLPreviewControllerDataSource {
        let url: URL
        init(url: URL) { self.url = url }
        func numberOfPreviewItems(in controller: QLPreviewController) -> Int { 1 }
        func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { url as QLPreviewItem }
    }
}

// MARK: - Scroll tracking

private struct ScrollOffsetKey: PreferenceKey {
    static let defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() }
}

// MARK: - Helpers

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