/*
 * Copyright (C) 2026 Fluxer Contributors
 *
 * This file is part of Fluxer.
 *
 * Fluxer 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.
 *
 * Fluxer 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 Fluxer. If not, see <https://www.gnu.org/licenses/>.
 */

import {
	ChannelOverwriteTypes,
	ChannelOverwriteTypesDescriptions,
	ChannelTypes,
} from '@fluxer/constants/src/ChannelConstants';
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
import {
	createInt32EnumType,
	createNamedLiteralUnion,
	MAX_STRING_PROCESSING_LENGTH,
	normalizeString,
	normalizeWhitespace,
	stripInvisibles,
	withOpenApiType,
	withStringLengthRangeValidation,
} from '@fluxer/schema/src/primitives/SchemaPrimitives';
import {z} from 'zod';

export const ChannelTypeSchema = withOpenApiType(
	createInt32EnumType(
		[
			[ChannelTypes.GUILD_TEXT, 'GUILD_TEXT', 'A text channel within a guild'],
			[ChannelTypes.DM, 'DM', 'A direct message between users'],
			[ChannelTypes.GUILD_VOICE, 'GUILD_VOICE', 'A voice channel within a guild'],
			[ChannelTypes.GROUP_DM, 'GROUP_DM', 'A group direct message between users'],
			[ChannelTypes.GUILD_CATEGORY, 'GUILD_CATEGORY', 'A category that contains channels'],
			[ChannelTypes.GUILD_LINK, 'GUILD_LINK', 'A link channel for external resources'],
			[ChannelTypes.DM_PERSONAL_NOTES, 'DM_PERSONAL_NOTES', 'Personal notes DM channel'],
		],
		'The type of the channel',
	),
	'ChannelType',
);

export const ChannelOverwriteTypeSchema = withOpenApiType(
	createNamedLiteralUnion(
		[
			[ChannelOverwriteTypes.ROLE, 'ROLE', ChannelOverwriteTypesDescriptions.ROLE],
			[ChannelOverwriteTypes.MEMBER, 'MEMBER', ChannelOverwriteTypesDescriptions.MEMBER],
		] as const,
		'The type of entity the overwrite applies to',
	),
	'ChannelOverwriteType',
);

const WHITESPACE_REGEX = /\s+/g;
const MULTIPLE_HYPHENS_REGEX = /-{2,}/g;
const VANITY_URL_REGEX = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;

const DISALLOWED_CHARS = new Set(' !"#$%&\'()*+,/:;<=>?@[\\]^`{|}~');

function sanitizeChannelName(value: string): string {
	if (value.length > MAX_STRING_PROCESSING_LENGTH) {
		throw new Error(ValidationErrorCodes.STRING_LENGTH_INVALID);
	}
	let s = normalizeString(value);
	s = stripInvisibles(s);
	s = normalizeWhitespace(s);
	return s;
}

export const ChannelNameType = z
	.string()
	.superRefine((value, ctx) => {
		if (value.length > MAX_STRING_PROCESSING_LENGTH) {
			ctx.addIssue({
				code: 'custom',
				message: ValidationErrorCodes.STRING_LENGTH_INVALID,
				params: {min: 1, max: 100},
			});
			return z.NEVER;
		}
		const normalized = normalizeString(value);
		const processed =
			normalized
				.toLowerCase()
				.replace(WHITESPACE_REGEX, '-')
				.split('')
				.filter((char) => !DISALLOWED_CHARS.has(char))
				.join('') || '-';
		if (processed.length < 1) {
			ctx.addIssue({
				code: 'custom',
				message: ValidationErrorCodes.CHANNEL_NAME_EMPTY_AFTER_NORMALIZATION,
			});
			return z.NEVER;
		}
	})
	.transform((value) => {
		if (value.length > MAX_STRING_PROCESSING_LENGTH) {
			throw new Error(ValidationErrorCodes.STRING_LENGTH_INVALID);
		}
		const normalized = normalizeString(value);
		return (
			normalized
				.toLowerCase()
				.replace(WHITESPACE_REGEX, '-')
				.split('')
				.filter((char) => !DISALLOWED_CHARS.has(char))
				.join('') || '-'
		);
	})
	.pipe(withStringLengthRangeValidation(z.string(), 1, 100, ValidationErrorCodes.STRING_LENGTH_INVALID));

export const GeneralChannelNameType = z
	.string()
	.superRefine((value, ctx) => {
		if (value.length > MAX_STRING_PROCESSING_LENGTH) {
			ctx.addIssue({
				code: 'custom',
				message: ValidationErrorCodes.STRING_LENGTH_INVALID,
				params: {min: 1, max: 100},
			});
			return z.NEVER;
		}
	})
	.transform((value) => {
		let sanitized = sanitizeChannelName(value);
		sanitized = sanitized.replace(WHITESPACE_REGEX, ' ');
		return sanitized;
	})
	.refine((v) => v.trim().length > 0, ValidationErrorCodes.NAME_EMPTY_AFTER_NORMALIZATION)
	.pipe(withStringLengthRangeValidation(z.string(), 1, 100, ValidationErrorCodes.STRING_LENGTH_INVALID));

export const VanityURLCodeType = z
	.string()
	.superRefine((value, ctx) => {
		const normalized = normalizeString(value);
		const processed = normalized.toLowerCase().replace(WHITESPACE_REGEX, '-').replace(MULTIPLE_HYPHENS_REGEX, '-');
		if (!VANITY_URL_REGEX.test(processed)) {
			ctx.addIssue({
				code: 'custom',
				message: ValidationErrorCodes.VANITY_URL_INVALID_CHARACTERS,
			});
			return z.NEVER;
		}
	})
	.transform((value) => {
		const normalized = normalizeString(value);
		return normalized.toLowerCase().replace(WHITESPACE_REGEX, '-').replace(MULTIPLE_HYPHENS_REGEX, '-');
	})
	.pipe(withStringLengthRangeValidation(z.string(), 2, 32, ValidationErrorCodes.VANITY_URL_CODE_LENGTH_INVALID));

const AUDIT_LOG_REASON_MAX_LENGTH = 512;

export const AuditLogReasonType = z
	.string()
	.nullable()
	.optional()
	.transform((value) => {
		if (!value || value.trim().length === 0) {
			return null;
		}
		const normalized = normalizeString(value);
		if (normalized.length < 1 || normalized.length > AUDIT_LOG_REASON_MAX_LENGTH) {
			return null;
		}
		return normalized;
	});
