import { NavigationState, NavigationError } from './navigation_state.mjs'
import { NavigationStateProtected } from './navigation_state_protected.mjs'



export class NavigationStateRegistry {

	#shortener_url
	#url_parser
	#shortened_states

	//TODO: allow pre-population of #shortened_states, to allow initial page open with no extra roundtrip to the server (if html carries the initial key and its state)
	constructor(shortener_url, url_parser) {
		this.#shortener_url = shortener_url
		this.can_shorten = this.#shortener_url != null
		this.#url_parser = url_parser
		this.#shortened_states = []
	}

	async #fetch_with_retries(url, abort_signal, options) {
		while(true) {
			if (abort_signal?.aborted) throw new DOMException("Aborted on request", "AbortError")
			//TODO: what to do on unfixable server issues?
			//TODO: consider passing abort_signal to fetch, at least on retries
			const response = await fetch(url, options)
			if (response.ok) return response.json()
			if (response.status >= 400 && response.status < 500) throw new NavigationError("ShortenerRequestFailed", `Received status ${response.status} from server, with message: ${await response.text()}`)
			console.warn(`URL shortener fetch (${url}, ${options}) failed with response ${response.status}: ${response.text()}`)
			await new Promise(resolve=>setTimeout(resolve, 1000));
		}
	}

	async url_to_navigation_state(url, abort_signal) {
		try {
			const { key, diff, base_diff, version } = this.#url_parser.parse_url(url)
			if (key) {
				if (!this.can_shorten) {
					const navigation_state = new NavigationError('ShortenerURLNotSupplied', `Url has non-empty state key (${JSON.stringify(key)}), but we don't know the shortener's URL. Consider running StateManager builder with .with_url_shortener()`)
					return { navigation_state, url_version: undefined, base_state: navigation_state }
				}
				//TODO: check in this.#shortened_states if we don't already know the key
				const url = this.#shortener_url + (this.#shortener_url.endsWith("/") ? "" : "/") + "states/1.0/" + key
				const body = await this.#fetch_with_retries(url, abort_signal, { headers: { "Content-Type": "application/json" } })
				const key_state = NavigationState[NavigationStateProtected.deobjectify](body["state"], null, [])
				this.#shortened_states.push({ key: key, navigation_state: key_state })
				return { navigation_state: diff.patch(key_state), url_version: version, base_state: base_diff.patch(key_state) }
			} else {
				return { navigation_state: diff.patch(NavigationState.create()), url_version: version, base_state: base_diff.patch(NavigationState.create()) }
			}
		} catch (error) {
			//TODO: should we differentiate between state successfully resolved as NavigationError, and failure to resolve?
			const navigation_state = NavigationError.from(error)
			return { navigation_state, url_version: undefined, base_state: navigation_state }
		}
	}

	navigation_state_to_url(navigation_state) {
		let shortest_url = this.#url_parser.generate_url(null, NavigationState.create(), navigation_state, true)
		for (const shortened_state of this.#shortened_states) {
			const alternative_url = this.#url_parser.generate_url(shortened_state.key, shortened_state.navigation_state, navigation_state, true)
			if ((shortest_url.url == null && alternative_url.url !=null) || (shortest_url.url != null && alternative_url.url != null && alternative_url.url.length <= shortest_url.url.length)) shortest_url = alternative_url
		}
		return shortest_url
	}

	async navigation_state_to_shortened_url(navigation_state, abort_signal) {
		const state_to_save = this.#url_parser.state_to_save(navigation_state)
		const url = this.#shortener_url+(this.#shortener_url.endsWith("/") ? "" : "/")+"states/1.0/"
		const body = await this.#fetch_with_retries(url, abort_signal, { method: "POST", body: JSON.stringify({ state: state_to_save[NavigationStateProtected.objectify]() }), headers: { "Content-Type": "application/json" } })
		this.#shortened_states.push({key: body["key"], navigation_state: state_to_save})
		return this.#url_parser.generate_url(body["key"], state_to_save, navigation_state, true)
	}

}
