import SwiftUI
import Inject
import KZFileWatchers

extension Widget {
    static func registerDecoder(in decoders: inout [String: WidgetRegistry.WidgetDecoder]) {
        decoders[Self.kind] = { decoder in
            let data = try decoder.decode(Self.Data.self, forKey: .payload)
            return AnyWidget(Self(data: data))
        }

    }
}

class WidgetRegistry: ObservableObject {
    typealias WidgetDecoder = (KeyedDecodingContainer<WidgetRegistry.WidgetContainer.CodingKeys>) throws -> AnyWidget
    let fileWatcher: FileWatcher.Local

    @Published
    var counter: Int = 0

    @Published
    var widgets: [AnyWidget] = []

    var widgetDecoders: [String: WidgetDecoder] = [:]

    init() {
        registerAllWidgetDecoders(in: &widgetDecoders)
//        guard let url = Bundle.main.url(forResource: "Widgets", withExtension: "json"), let data = try? Data(contentsOf: url) else {
//            return
//        }
//
//        self.widgets = decode(from: data)
        #error("Either uncomment bundle asset above instead of the following, or update the path to your local directory for live reloads")
        fileWatcher = .init(path: "/Users/merowing/Desktop/widgets.json")

        try! fileWatcher.start { [unowned self] update in
            self.counter += 1
            switch update {
            case .noChanges:
                break
            case let .updated(data: newData):
                self.widgets = self.decode(from: newData)
            }
        }
    }

    struct InvalidFormat: Error {}
    struct UnknownWidget: Error {
        var kind: WidgetKind
    }

    struct WidgetContainer: Decodable {
        enum CodingKeys: CodingKey {
            case kind
            case payload
        }

        let widget: AnyWidget
        static let decodersKey = CodingUserInfoKey(rawValue: "decoders")!

        struct DecodersNotFound: Error {}

        init(from decoder: Decoder) throws {
            guard let decoders = decoder.userInfo[Self.decodersKey] as? [String: WidgetDecoder] else {
                throw DecodersNotFound()
            }

            let container = try decoder.container(keyedBy: CodingKeys.self)
            let kind = try container.decode(String.self, forKey: .kind)

            guard let factory = decoders[kind] else {
                throw UnknownWidget(kind: kind)
            }

            self.widget = try factory(container)
        }
    }

    func decode(from data: Data) -> [AnyWidget] {
        do {
            let decoder = JSONDecoder()
            decoder.userInfo[WidgetContainer.decodersKey] = self.widgetDecoders
            let containers = try decoder.decode([WidgetContainer].self, from: data)
            return containers.map { $0.widget }
        } catch {
            print("Failed to decode \(error)")
            return []
        }
    }
}

struct HomeWidgetView: View {
    @ObserveInjection private var iO
    @StateObject var registry: WidgetRegistry = .init()

    var body: some View {
        List {
            Text("Update \(registry.counter)")
            Divider()
            ForEach(registry.widgets, id: \.id) { widget in
                widget.contentView
            }
        }
    }
}
