/*
	GoToSocial
	Copyright (C) GoToSocial Authors admin@gotosocial.org
	SPDX-License-Identifier: AGPL-3.0-or-later

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU Affero General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Affero General Public License for more details.

	You should have received a copy of the GNU Affero General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

import {
	ParseConfig as CSVParseConfig,
	parse as csvParse
} from "papaparse";
import { nanoid } from "nanoid";

import { isValidDomainPermission, hasBetterScope } from "../../../util/domain-permission";
import { gtsApi } from "../../gts-api";

import {
	validateDomainPerms,
	type DomainPerm,
} from "../../../types/domain-permission";

/**
 * Parse the given string of domain permissions and return it as an array.
 * Accepts input as a JSON array string, a CSV, or newline-separated domain names.
 * Will throw an error if input is invalid.
 * @param list 
 * @returns
 * @throws
 */
function parseDomainList(list: string): DomainPerm[] {	
	if (list.startsWith("[")) {
		// Assume JSON array.
		const data = JSON.parse(list);
		
		const validateRes = validateDomainPerms(data);
		if (!validateRes.success) {
			throw `parsed JSON was not array of DomainPermission: ${JSON.stringify(validateRes.errors)}`;
		}

		return data;
	} else if (list.startsWith("#domain") || list.startsWith("domain,severity")) {
		// Assume Mastodon-style CSV.
		const csvParseCfg: CSVParseConfig = {
			// Key by header.
			header: true,
			// Remove leading '#' from headers if present.
			transformHeader: (header) => header.startsWith("#") ? header.slice(1) : header,
			// Massage weird boolean values.
			transform: (value, _field) => {				
				if (value == "False" || value == "True") {
					return value.toLowerCase();
				} else {
					return value;
				}
			},
			skipEmptyLines: true,
			// Only dynamic type boolean values,
			// leave the rest as strings.
			dynamicTyping: {
				"domain": false,
				"severity": false,
				"reject_media": true,
				"reject_reports": true,
				"public_comment": false,
				"obfuscate": true,
			}
		};
		
		const { data, errors } = csvParse(list, csvParseCfg);
		if (errors.length > 0) {
			let error = "";
			errors.forEach((err) => {
				error += `${err.message} (line ${err.row})`;
			});
			throw error;
		} 

		const validateRes = validateDomainPerms(data);
		if (!validateRes.success) {
			throw `parsed CSV was not array of DomainPermission: ${JSON.stringify(validateRes.errors)}`;
		}

		return data;
	} else {
		// Fallback: assume newline-separated
		// list of simple domain strings.
		const data: DomainPerm[] = [];
		list.split("\n").forEach((line) => {
			let domain = line.trim();
			let valid = true;

			if (domain.startsWith("http")) {
				try {
					domain = new URL(domain).hostname;
				} catch (e) {
					valid = false;
				}
			}

			if (domain.length > 0) {
				data.push({ domain, valid });
			}
		});

		return data;
	}
}

function deduplicateDomainList(list: DomainPerm[]): DomainPerm[] {
	let domains = new Set();
	return list.filter((entry) => {
		if (domains.has(entry.domain)) {
			return false;
		} else {
			domains.add(entry.domain);
			return true;
		}
	});
}

function validateDomainList(list: DomainPerm[]) {
	list.forEach((entry) => {		
		if (entry.domain.startsWith("*.")) {
			// A domain permission always includes
			// all subdomains, wildcard is meaningless here
			entry.domain = entry.domain.slice(2);
		}

		entry.valid = (entry.valid !== false) && isValidDomainPermission(entry.domain);
		if (entry.valid) {
			entry.suggest = hasBetterScope(entry.domain);
		}
		entry.checked = entry.valid;
	});

	return list;
}

const extended = gtsApi.injectEndpoints({
	endpoints: (build) => ({
		processDomainPermissions: build.mutation<DomainPerm[], any>({
			async queryFn(formData, _api, _extraOpts, _fetchWithBQ) {
				if (formData.domains == undefined || formData.domains.length == 0) {
					throw "No domains entered";
				}
				
				// Parse + tidy up the form data.
				const permissions = parseDomainList(formData.domains);
				const deduped = deduplicateDomainList(permissions);
				const validated = validateDomainList(deduped);
				
				validated.forEach((entry) => {
					// Set unique key that stays stable
					// even if domain gets modified by user.
					entry.key = nanoid();
				});
				
				return { data: validated };
			}
		})
	})
});

/**
 * useProcessDomainPermissionsMutation uses the RTK Query API without actually
 * hitting the GtS API, it's purely an internal function for our own convenience.
 * 
 * It returns the validated and deduplicated domain permission list.
 */
const useProcessDomainPermissionsMutation = extended.useProcessDomainPermissionsMutation;

export { useProcessDomainPermissionsMutation };
