import Foundation
import XCTestDynamicOverlay

public struct Stored<Value> {
  public var get: () -> Value
  public var set: (Value) -> Void

  public func callAsFunction() -> Value {
    get()
  }
}

extension Stored {
  public static func inMemory(_ value: Value) -> Stored<Value> {
    let memory = Memory(value)
    return Stored(
      get: { memory.value },
      set: { memory.value = $0 }
    )
  }

  public static func inMemory<V>() -> Stored<Optional<V>> where Value == Optional<V> {
    inMemory(nil)
  }
}

private final class Memory<Value> {
  init(_ value: Value) {
    self.value = value
  }

  var value: Value
}

extension Stored {
  public static func userDefaults<T>(
    key: String,
    userDefaults: UserDefaults = .standard
  ) -> Stored<T?> where T: Codable {
    let encoder = JSONEncoder()
    let decoder = JSONDecoder()
    return Stored<T?>(
      get: {
        guard let data = userDefaults.data(forKey: key) else { return nil }
        return try? decoder.decode(T.self, from: data)
      },
      set: { newValue in
        let data = try? encoder.encode(newValue)
        userDefaults.set(data, forKey: key)
      }
    )
  }
}

extension Stored {
  public struct MissingValueError: Error, Equatable {
    public init(typeDescription: String) {
      self.typeDescription = typeDescription
    }

    public var typeDescription: String
  }

  public func tryGet<T>() throws -> T where Value == Optional<T> {
    guard let value = get() else {
      throw MissingValueError(typeDescription: "\(Self.self)")
    }
    return value
  }
}

extension Stored {
  public static func unimplemented(placeholder: Value) -> Stored<Value> {
    Stored<Value>(
      get: XCTUnimplemented("\(Self.self).get", placeholder: placeholder),
      set: XCTUnimplemented("\(Self.self).set")
    )
  }

  public static func unimplemented<V>() -> Stored<Optional<V>> where Value == Optional<V> {
    Stored<Value>(
      get: XCTUnimplemented("\(Self.self).get"),
      set: XCTUnimplemented("\(Self.self).set")
    )
  }
}