import path from 'path';
import { promises as fs } from 'fs';

import { commandSystem } from './index';
import { clientSideCommands } from '../../../shared/const/clientside-commands';
import { logSystem } from '../log';

type GuardWithPermissions = CommandGuard & {
    __permissions?: string[];
};

function formatValidator(validator: CommandParamValidator): string {
    if (Array.isArray(validator)) {
        return `one-of(${validator.map((value) => String(value)).join('|')})`;
    }

    if (validator instanceof RegExp) {
        return `regex(${validator.toString()})`;
    }

    return String(validator);
}

function buildParamString(params?: CommandParamMetaData<any>): string {
    if (!params || !Object.keys(params).length) {
        return 'none';
    }

    return Object.entries(params)
        .map(([key, meta]) => {
            const optional = meta.default !== undefined;
            const optionalHint = optional ? ` optional default=${String(meta.default)}` : ' required';
            return `${key} <${formatValidator(meta.validator)}> (${meta.hint};${optionalHint})`;
        })
        .join(', ');
}

function extractPermissions(guards?: CommandGuard[]): string[] {
    if (!guards?.length) {
        return [];
    }

    const permissions = guards
        .flatMap((guard) => {
            const typedGuard = guard as GuardWithPermissions;
            return typedGuard.__permissions ?? [];
        })
        .filter((permission, index, all) => permission && all.indexOf(permission) === index);

    return permissions;
}

export async function writeCommandManifestTxt(): Promise<void> {
    const lines: string[] = [];
    lines.push('Southwest Roleplay Command Manifest');
    lines.push(`Generated: ${new Date().toISOString()}`);
    lines.push('');

    const commandEntries = Array.from(commandSystem.commands.entries()).sort(([left], [right]) => left.localeCompare(right));

    lines.push(`Server Commands (${commandEntries.length})`);
    lines.push('----------------------------------------');

    for (const [commandName, command] of commandEntries) {
        const params = buildParamString(command.$options.params);
        const permissions = extractPermissions(command.$options.guards);

        lines.push(`/${commandName}`);
        lines.push(`  params: ${params}`);
        lines.push(`  required_permissions: ${permissions.length ? permissions.join(', ') : 'none'}`);
        lines.push(`  grouped: ${command.$options.grouped ?? 'none'}`);
        lines.push(`  description: ${command.$options.description ?? 'none'}`);
        lines.push('');
    }

    const uniqueClientCommands = [...new Set(clientSideCommands)].sort((left, right) => left.localeCompare(right));
    lines.push(`Client-side Commands (${uniqueClientCommands.length})`);
    lines.push('----------------------------------------');

    for (const commandName of uniqueClientCommands) {
        lines.push(`/${commandName}`);
        lines.push('  params: n/a (handled client-side)');
        lines.push('  required_permissions: n/a (handled client-side)');
        lines.push('');
    }

    const outputPath = path.resolve(process.cwd(), 'command-manifest.txt');
    await fs.writeFile(outputPath, `${lines.join('\n')}\n`, 'utf8');

    logSystem.initLog('Command Manifest', `Wrote ${commandEntries.length} server command entries to ${outputPath}.`);
}
