import { RemoteStateDefiner } from "./remote_state_definer.mjs"

export class RemoteState {
	//TODO: make this a #manager, as RemoteState is a singleton now
	static manager

	#contracts
	#results
	#to_observe
	#hakuban_exchange

	constructor(hakuban_exchange) {
		this.#contracts = {}
		this.#to_observe = null
		this.#results = {}
		this.#hakuban_exchange = hakuban_exchange
	}


	#build_contract(key, proto_descriptor) {
		if (proto_descriptor.tag != null) {
			console.debug(`Ramistat: building tag contract ${JSON.stringify(proto_descriptor)}`)
			this.#contracts[key] = { proto_descriptor, contract: this.#hakuban_exchange.tag_observe_contract(proto_descriptor.tag).build() }
			this.#results[key] = {}
			this.#contracts[key].contract.for_each_concurrent(async (state_stream) => {
				const subkey = proto_descriptor.key(state_stream.descriptor())
				if (subkey == null) throw "Key function returned null."
				await state_stream.for_each((state) => {
					if (this.#contracts[key] != null && this.#contracts[key].proto_descriptor.json == proto_descriptor.json && this.#contracts[key].proto_descriptor.tags == proto_descriptor.tags) {
						//TODO only assert if version is higher than already owned
						this.#results[key][subkey] = state.json_deserialize()
						RemoteState.manager.state_changed()
					}
				})
				if (this.#contracts[key] != null && this.#contracts[key].proto_descriptor.json == proto_descriptor.json && this.#contracts[key].proto_descriptor.tags == proto_descriptor.tags) {
					delete this.#results[key][subkey]
				}
			})
		} else {
			console.debug(`Ramistat: building object contract ${JSON.stringify(proto_descriptor)}`)
			this.#contracts[key] = { proto_descriptor, contract: this.#hakuban_exchange.object_observe_contract(proto_descriptor.tags, proto_descriptor.json).build() }
			this.#contracts[key].contract.for_each(async (state_stream) => {
				await state_stream.for_each((state) => {
					if (this.#contracts[key] != null && this.#contracts[key].proto_descriptor.json == proto_descriptor.json && this.#contracts[key].proto_descriptor.tags == proto_descriptor.tags) {
						//TODO only assert if version is higher than already owned
						this.#results[key] = state.json_deserialize()
						RemoteState.manager.state_changed()
					}
				})
			})
		}
	}

	#drop_contract(key) {
		console.debug(`Ramistat: dropping contract ${JSON.stringify(this.#contracts[key].proto_descriptor)}`)
		this.#contracts[key].contract.drop()
		delete this.#contracts[key]
		delete this.#results[key]
	}

	observe(address, fn) {
		address = [address].flat()
		if (this.#to_observe == null) {
			this.#to_observe = {}
			fn(new RemoteStateDefiner(address, this.#to_observe))
			const address_str = JSON.stringify(address).slice(1, -1)
			for (const key of Object.keys(this.#contracts)) {
				if (key.startsWith(address_str) && (this.#to_observe[key] == null)) this.#drop_contract(key)
			}
			for (const [key, value] of Object.entries(this.#to_observe)) {
				//TODO: make this comparison smarter
				if (JSON.stringify(value) == JSON.stringify(this.#contracts[key]?.proto_descriptor)) {
						//console.debug(`Ramistat: retaining contract ${JSON.stringify(this.#contracts[key].proto_descriptor)}`)
						continue
				}
				if (this.#contracts[key] != null) this.#drop_contract(key)
				this.#build_contract(key, value)
			}
			this.#to_observe = null
		} else {
			fn(new RemoteStateDefiner(address, this.#to_observe))
		}
	}

	results(scope) {
		return new Proxy(this.#results, {
			set(_obj, _prop, _value) { return false },
			get(obj, prop) {
				return obj[JSON.stringify([scope].concat([prop]).flat()).slice(1, -1)]
			}
		})
	}
}
