namespace AmeKoFreeExtensions {
	type ExtraFieldList = Array<[string, string | KnockoutObservable<string> | (KnockoutObservable<any> & (() => string))]>;

	interface SaveFormConfig {
		submitUrl: string;
		action: string;
		actionNonce: string;
		referer?: string;
		settingsGetter: () => object;

		saveButtonText?: string;
		wrapButton?: boolean;

		formClasses?: string[] | KnockoutObservable<string[]>;
		extraFields?: ExtraFieldList;
		selectedActor?: KnockoutObservable<IAmeActor | null>;
	}

	/**
	 * Partial save form configuration that can be received from the server, generated by the PHP
	 * component of this feature (KnockoutSaveForm).
	 */
	export type SaveFormConfigFromServer = Pick<
		SaveFormConfig,
		'submitUrl' | 'action' | 'actionNonce' | 'referer' | 'saveButtonText'
	>;

	export class SaveSettingsForm {
		public readonly submitUrl: string;
		public readonly action: string;
		public readonly actionNonce: string;
		public readonly settingsGetter: () => object;
		public readonly settingsJson: KnockoutObservable<string>;

		public readonly saveButtonText: string;
		public readonly wrapButton: boolean;
		public readonly formClassString: KnockoutObservable<string>;
		public readonly referer: string;

		public readonly includesSelectedActor: boolean;
		public readonly selectedActorId: KnockoutObservable<string>;
		public readonly extraFields: ExtraFieldList;

		public readonly isSaving: KnockoutObservable<boolean> = ko.observable(false);

		constructor(config: SaveFormConfig) {
			this.submitUrl = config.submitUrl;
			this.action = config.action;
			this.actionNonce = config.actionNonce;
			this.referer = config.referer || '';

			this.settingsGetter = config.settingsGetter;
			this.settingsJson = ko.observable('');

			this.saveButtonText = config.saveButtonText || 'Save Changes';
			this.wrapButton = (typeof config.wrapButton !== 'undefined') ? config.wrapButton : true;

			let formClasses: KnockoutObservable<string[]>;
			if (config.formClasses) {
				formClasses = ko.isObservable<string[]>(config.formClasses)
					? config.formClasses
					: ko.observable(config.formClasses || []);
			} else {
				formClasses = ko.observable<string[]>([]);
			}
			this.formClassString = ko.pureComputed(() => formClasses().join(' '));

			this.includesSelectedActor = !!config.selectedActor;
			this.selectedActorId = ko.pureComputed(() => {
				if (config.selectedActor) {
					const actor = config.selectedActor();
					return actor ? actor.getId() : '';
				}
				return '';
			});

			this.extraFields = config.extraFields || [];
		}

		saveChanges() {
			const settings = this.settingsGetter();
			this.settingsJson(JSON.stringify(settings));

			this.isSaving(true);
			return true;
		}
	}

	ko.components.register('ame-save-settings-form', {
		viewModel: {
			createViewModel: function (params: { form: SaveSettingsForm | unknown }) {
				if (params.form instanceof SaveSettingsForm) {
					return params.form;
				}
				throw new Error('Invalid form parameter');
			}
		},
		template: `
			<form method="post" class="ame-ko-save-form" 
				  data-bind="submit: saveChanges, class: formClassString, attr: {action: submitUrl}">
				   
				<input type="hidden" name="settings" data-bind="value: settingsJson">
				<input type="hidden" name="action" data-bind="value: action">
				<input type="hidden" name="_wpnonce" data-bind="value: actionNonce">
				<!-- ko if: referer -->
					<input type="hidden" name="_wp_http_referer" data-bind="value: referer">
				<!-- /ko -->
				
				<!-- ko if: includesSelectedActor -->
					<input type="hidden" name="selectedActor" data-bind="value: selectedActorId">
				<!-- /ko -->
				
				<!-- ko foreach: extraFields -->
					<!-- ko if: $data[0] -->
						<input type="hidden" data-bind="attr: {name: $data[0]}, value: $data[1]">
					<!-- /ko -->
				<!-- /ko -->
				
				<!-- ko if: wrapButton -->
				<p class="submit">
					<input type="submit" class="button button-primary" disabled="disabled" 
						   data-bind="disable: isSaving(), value: saveButtonText" />
				</p>	
				<!-- /ko -->
				<!-- ko ifnot: wrapButton -->
					<input type="submit" class="button button-primary" disabled="disabled" 
						   data-bind="disable: isSaving(), value: saveButtonText" />
				<!-- /ko -->
			</form>
		`
	});
}