import SwiftUI
import SFSafeSymbols
import UniformTypeIdentifiers

struct ContentView: View {

    @Environment(\.colorScheme)
    var defaultColorScheme: ColorScheme

    @State
    var content: [CVInfo]

    let style: CVStyle

    init(content: [CVInfo], style: CVStyle) {
        self._content = .init(initialValue: content)
        self.style = style
    }

    @State
    var darkModeEnabled = true

    @State
    var didReadDarkMode = false

    @State
    var selectedLanguageIndex = 0

    @State
    private var showExportFormatPicker = false

    var colorStyle: ColorScheme {
        darkModeEnabled ? .dark : .light
    }

    var info: CVInfo {
        content[selectedLanguageIndex]
    }
    
    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                Picker("", selection: $selectedLanguageIndex) {
                    ForEach(Array(content.enumerated()), id: \.element) { content in
                        Text(content.element.language)
                            .tag(content.offset)
                    }
                }.frame(maxWidth: 100)
                Button(action: {
                    showExportFormatPicker = true
                }) {
                    Label("Export", systemSymbol: .squareAndArrowDown)
                        .padding(3)
                        .frame(maxHeight: 20)
                }.confirmationDialog(
                    "Which format would you like?",
                    isPresented: $showExportFormatPicker
                ) {
                    Button("JSON data", action: exportJsonData)
                    Button("PDF", action: exportPdf)
                    Button("Image", action: exportImage)
                    Button("Cancel", role: .cancel, action: {})
                } message: {
                    Text(" Choose the export format")
                }
                Button(action: importData) {
                    Label("Import", systemSymbol: .docBadgePlus)
                        .padding(8)
                        .frame(maxHeight: 20)
                }
                Spacer()
                Toggle("Dark mode", isOn: $darkModeEnabled)
                    .toggleStyle(SwitchToggleStyle())
            }
            .padding([.top, .trailing])
            .padding(.leading, 6)
            .padding(.bottom, 4)
            ScrollView(.vertical) {
                CV(info: info, style: style)
            }.frame(width: style.pageWidth)
        }
        .preferredColorScheme(colorStyle)
        .onAppear {
            guard !didReadDarkMode else {
                return
            }
            darkModeEnabled = defaultColorScheme == .dark
            didReadDarkMode = true
        }
    }

    private func importData() {
        guard let url = showOpenFilePanel() else {
            print("No url to import")
            return
        }
        let data: Data
        do {
            data = try Data(contentsOf: url)
        } catch {
            print("Failed to open file: \(error)")
            return
        }
        let newData: CVInfo
        do {
            newData = try JSONDecoder().decode(CVInfo.self, from: data)
        } catch {
            print("Failed to decode data: \(error)")
            return
        }
        guard let index = content.firstIndex(where: { $0.language == newData.language }) else {
            content.append(newData)
            selectedLanguageIndex = content.count - 1
            return
        }
        content[index] = newData
        selectedLanguageIndex = index
    }

    private func exportPdf() {
        DispatchQueue.main.async {
            guard let url = showPdfSavePanel() else {
                print("No url to save PDF")
                return
            }
            savePDF(to: url)
        }
    }

    private func exportImage() {
        DispatchQueue.main.async {
            guard let url = showJpgSavePanel() else {
                print("No image url")
                return
            }
            self.saveImage(to: url)
        }
    }

    private func exportJsonData() {
        guard let url = showJsonSavePanel() else {
            print("No url to save data")
            return
        }
        saveJson(to: url)
    }

    private func showOpenFilePanel() -> URL? {
        let panel = NSOpenPanel()
        panel.allowedContentTypes = [.json]
        panel.canCreateDirectories = true
        panel.isExtensionHidden = false
        panel.allowsOtherFileTypes = false
        panel.title =  "Load JSON"
        panel.message = "Choose a JSON file of resume data to import"
        panel.nameFieldLabel = "File name:"
        panel.nameFieldStringValue = "CV.json"
        let response = panel.runModal()
        guard response == .OK else {
            return nil
        }
        return panel.url
    }

    private func showJsonSavePanel() -> URL? {
        showSavePanel(
            type: .json,
            title: "Save JSON",
            fileName: "CV.json",
            message: "Choose a location to save a JSON file of the resume data")
    }

    private func showJpgSavePanel() -> URL? {
        showSavePanel(
            type: .jpeg,
            title: "Save image",
            fileName: "CV.jpg",
            message: "Choose a location to save an image of the resume")
    }

    private func showPdfSavePanel() -> URL? {
        showSavePanel(
            type: .pdf,
            title: "Save PDF",
            fileName: "CV.pdf",
            message: "Choose a location to save a PDF of the resume")
    }

    private func showSavePanel(type: UTType, title: String, fileName: String, message: String) -> URL? {
        let savePanel = NSSavePanel()
        savePanel.allowedContentTypes = [type]
        savePanel.canCreateDirectories = true
        savePanel.isExtensionHidden = false
        savePanel.allowsOtherFileTypes = false
        savePanel.title = title
        savePanel.message = message
        savePanel.nameFieldLabel = "File name:"
        savePanel.nameFieldStringValue = fileName

        let response = savePanel.runModal()
        guard response == .OK else {
            return nil
        }
        return savePanel.url
    }

    private var renderContent: some View {
        CV(info: info, style: style)
            .frame(width: style.pageWidth, height: style.pageHeight)
            .preferredColorScheme(colorStyle)
    }

    @MainActor
    private func saveImage(to url: URL) {
        let renderer = ImageRenderer(content: renderContent)
        renderer.scale = 3
        guard let image = renderer.nsImage else {
            print("No image from renderer")
            return
        }

        let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil)!
        let bitmapRep = NSBitmapImageRep(cgImage: cgImage)
        let data = bitmapRep.representation(using: .jpeg, properties: [:])!

        do {
            try data.write(to: url)
            print("Data saved")
        } catch {
            print("Failed to save image: \(error)")
        }
    }

    @MainActor
    private func savePDF(to url: URL) {
        let renderer = ImageRenderer(content: renderContent)

        var didFinish = false
        renderer.render { size, context in
            var box = CGRect(x: 0, y: 0, width: size.width, height: size.height)

            guard let pdf = CGContext(url as CFURL, mediaBox: &box, nil) else {
                print("Failed to create CGContext")
                return
            }

            let options: [CFString: Any] = [
                kCGPDFContextMediaBox: CGRect(origin: .zero, size: size)
            ]

            pdf.beginPDFPage(options as CFDictionary)
            context(pdf)
            pdf.endPDFPage()
            pdf.closePDF()
            didFinish = true
        }
        guard didFinish else {
            return
        }
        print("PDF created")
    }

    private func saveJson(to url: URL) {
        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted
        let data: Data
        do {
           data = try encoder.encode(info)
        } catch {
            print("Failed to encode data: \(error)")
            return
        }
        do {
            try data.write(to: url)
        } catch {
            print("Failed to write data: \(error)")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(content: [cvInfoEnglish, cvInfoGerman], style: cvStyle)
            .frame(width: 600, height: 600 * sqrt(2))
    }
}
