"use strict";

const Promise = require("bluebird");
const pathToRegexp = require("path-to-regexp");
const url = require("url");
const xtend = require("xtend");
const defaultValue = require("default-value");

const formDataToObject = require("../formdata-to-object");
const objectToURLSearchParams = require("../object-to-urlsearchparams");
const objectToFormData = require("../object-to-formdata");

module.exports = function() {
	let routes = [];

	function addRoute(method, path, handler) {
		// Mutable arguments? WTF.
		let keys = [];
		let regex = pathToRegexp(path, keys);

		routes.push({ method, path, regex, keys, handler });
	}

	function getRoute(method, path) {
		let matches;
		let matchingRoute = routes.find((route) => route.method === method && (matches = route.regex.exec(path)));

		if (matchingRoute == null) {
			throw new Error("No matching routes found");
		} else {
			let params = {};

			matchingRoute.keys.forEach((key, i) => {
				params[key] = matches[i + 1];
			});

			return {
				handler: matchingRoute.handler,
				params: params
			}
		}
	}

	function handle(method, uri, data, handleOptions = {}) {
		return Promise.try(() => {
			// FIXME: Support relative paths?
			let {pathname, query} = url.parse(uri, true);
			let route = getRoute(method, pathname);
			let tasks = [];

			let body;

			if (data instanceof FormData) {
				body = formDataToObject(data);
			} else if (data instanceof URLSearchParams) {
				body = formDataToObject(data); // FIXME: Does this work?
			} else {
				body = data;
			}

			let req = {
				path: pathname,
				query: query,
				body: body,
				params: route.params,
				pass: function(options = {}) {
					return Promise.try(() => {
						let body;

						if (handleOptions.multipart) {
							body = objectToFormData(this.body)
						} else {
							body = objectToURLSearchParams(this.body);
						}

						return window.fetch(uri, Object.assign({ // FIXME: Override URI but maintain query?
							method: method,
							credentials: true,
							body: body
						}, options));
					}).then((response) => {
						if (!response.ok) {
							// FIXME: Is this what we want?
							throw new Error(`Got a non-200 response: ${response.status}`, {response: response});
						} else {
							return Promise.try(() => {
								return response.json();
							}).then((json) => {
								return {
									status: response.status,
									body: json
								}
							});
						}
					});

					// FIXME: window.fetch passthrough
				},
				// MARKER: passActions?
				passRender: function(viewName, options = {}) {
					return Promise.try(() => {
						return this.pass(options.requestOptions);
					}).then((response) => {
						let locals = defaultValue(options.locals, {});
						let combinedLocals = xtend(locals, response.body);

						res.render(viewName, combinedLocals, options.renderOptions);
					});
				}
			}

			let res = {
				render: function(viewName, locals = {}, options = {}) {
					tasks.push({
						type: "render",
						viewName, locals, options
					});
				},
				open: function(path, options = {}) {
					tasks.push({
						type: "open",
						path, options
					});
				},
				close: function(options = {}) {
					tasks.push({
						type: "close",
						options
					});
				},
				notify: function(message, options = {}) {
					tasks.push({
						type: "notify",
						message, options
					});
				},
				error: function(error, context = {}) {
					tasks.push({
						type: "error",
						error, context
					});
				}
			}

			return Promise.try(() => {
				return route.handler(req, res);
			}).then((result) => {
				return {
					result: result,
					actions: tasks
				}
			});
		});
	}

	let api = {
		get: addRoute.bind(api, "get"),
		post: addRoute.bind(api, "post"),
		put: addRoute.bind(api, "put"),
		delete: addRoute.bind(api, "delete"),
		head: addRoute.bind(api, "head"),
		patch: addRoute.bind(api, "patch"),
		addRoute: addRoute,
		handle: handle
	}

	return api;
}
