/* CasualOS is a set of web-based tools designed to facilitate the creation of real-time, multi-user, context-aware interactive experiences.
 *
 * Copyright (c) 2019-2025 Casual Simulation, Inc.
 *
 * 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 <https://www.gnu.org/licenses/>.
 */
import prompts from 'prompts';
/**
 * A symbol that represents an undefined value.
 * This is used to indicate that a value is not set or not applicable.
 */
const UNDEFINED_SYMBOL = Symbol('undefined');
function resolveValue(value) {
    if (value === UNDEFINED_SYMBOL) {
        return undefined;
    }
    return value;
}
export function onState(state) {
    if (state.aborted) {
        process.nextTick(() => {
            process.exit(1);
        });
    }
}
export async function askForInputs(inputs, name, repl = null) {
    if (inputs) {
        if (inputs.type === 'object') {
            return await askForObjectInputs(inputs, name, repl);
        }
        else if (inputs.type === 'string') {
            const response = await prompts({
                type: 'text',
                name: 'value',
                message: `Enter a value for ${name}.`,
                initial: inputs.defaultValue ?? undefined,
                onState,
            });
            return evalResult(response.value || (inputs.nullable ? null : undefined), repl, String);
        }
        else if (inputs.type === 'number') {
            const response = await prompts({
                type: repl ? 'text' : 'number',
                name: 'value',
                message: `Enter a number for ${name}.`,
                initial: inputs.defaultValue ?? undefined,
                onState,
            });
            return typeof response.value === 'number'
                ? response.value
                : typeof response.value === 'string'
                    ? await evalResult(response.value || (inputs.nullable ? null : undefined), repl, Number)
                    : undefined;
        }
        else if (inputs.type === 'boolean') {
            const response = await prompts({
                type: repl ? 'text' : 'toggle',
                name: 'value',
                message: `Enable ${name}?`,
                initial: inputs.defaultValue ?? undefined,
                active: 'yes',
                inactive: 'no',
                onState,
            });
            return typeof response.value === 'boolean'
                ? response.value
                : typeof response.value === 'string'
                    ? await evalResult(response.value || (inputs.nullable ? null : undefined), repl, Boolean)
                    : inputs.nullable
                        ? null
                        : undefined;
        }
        else if (inputs.type === 'date') {
            const response = await prompts({
                type: repl ? 'text' : 'date',
                name: 'value',
                message: `Enter a date for ${name}.`,
                initial: inputs.defaultValue ?? undefined,
                onState,
            });
            return response.value instanceof Date
                ? response.value
                : typeof response.value === 'string'
                    ? await evalResult(response.value || (inputs.nullable ? null : undefined), repl, Date)
                    : inputs.nullable
                        ? null
                        : undefined;
        }
        else if (inputs.type === 'literal') {
            return inputs.value;
        }
        else if (inputs.type === 'null') {
            return null;
        }
        else if (inputs.type === 'array') {
            return await askForArrayInputs(inputs, name, repl);
        }
        else if (inputs.type === 'enum') {
            return await askForEnumInputs(inputs, name, repl);
        }
        else if (inputs.type === 'union') {
            return await askForUnionInputs(inputs, name, repl);
        }
        else if (inputs.type === 'record') {
            return await askForRecordInputs(inputs, name, repl);
        }
        else if (inputs.type === 'tuple') {
            return await askForTupleInputs(inputs, name, repl);
        }
        else if (inputs.type === 'any') {
            return await askForAnyInputs(name, repl);
        }
    }
    return undefined;
}
async function evalResult(result, repl, type) {
    if (typeof result === 'string' && repl && result.startsWith('.')) {
        const script = result.slice(1);
        if (script.startsWith('.')) {
            // Scripts can be escaped with a double dot.
            return script;
        }
        const promise = new Promise((resolve, reject) => {
            repl.eval(result.slice(1), repl.context, 'repl', (err, value) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve(value);
            });
        });
        const value = await promise;
        return value;
    }
    if (type && result !== undefined && result !== null && result !== '') {
        if (type === Boolean && typeof result === 'string') {
            const resultLowercase = result.toLowerCase();
            return ['true', 'yes', 'on', 'y'].includes(resultLowercase);
        }
        return type(result);
    }
    return result;
}
async function askForObjectInputs(inputs, name, repl) {
    const allowed = await askForOptionalInputs(inputs, name, repl);
    if (!allowed) {
        return inputs.defaultValue;
    }
    const result = {};
    let hasExistingKey = false;
    for (let key in inputs.schema) {
        const prop = inputs.schema[key];
        const value = await askForInputs(prop, `${name}.${key}`, repl);
        result[key] = value;
        hasExistingKey = true;
    }
    if (inputs.catchall) {
        let addMore = true;
        if (hasExistingKey) {
            addMore = (await prompts({
                type: 'confirm',
                name: 'continue',
                message: `Do you want to add more properties to ${name}?`,
                onState,
            })).continue;
        }
        if (addMore) {
            const catchall = await askForRecordInputs({
                type: 'record',
                valueSchema: inputs.catchall,
            }, name, repl);
            for (let key in catchall) {
                result[key] = catchall[key];
            }
        }
    }
    return result;
}
async function askForArrayInputs(inputs, name, repl) {
    const allowed = await askForOptionalInputs(inputs, name, repl);
    if (!allowed) {
        return inputs.defaultValue;
    }
    const result = [];
    let length = 0;
    if (typeof inputs.exactLength === 'number') {
        length = inputs.exactLength;
    }
    else {
        const response = await prompts({
            type: 'number',
            name: 'length',
            message: `Enter the length of the array for ${name}.`,
            min: inputs.minLength ?? undefined,
            max: inputs.maxLength ?? undefined,
            onState,
        });
        length = response.length;
    }
    for (let i = 0; i < length; i++) {
        const value = await askForInputs(inputs.schema, `${name}[${i}]`, repl);
        result.push(value);
    }
    return result;
}
function getOptionalChoices(inputs, choices) {
    if (inputs.nullable && !choices.some((c) => c.value === null)) {
        choices = [
            {
                title: '(null)',
                description: 'A null value.',
                value: null,
            },
            ...choices,
        ];
    }
    if (inputs.optional && !choices.some((c) => c.value === undefined)) {
        choices = [
            {
                title: '(undefined)',
                description: 'A undefined value.',
                value: UNDEFINED_SYMBOL,
            },
            ...choices,
        ];
    }
    return choices;
}
async function askForEnumInputs(inputs, name, repl) {
    let choices = getOptionalChoices(inputs, inputs.values.map((value) => ({
        title: value,
        value: value,
    })));
    const response = await prompts({
        type: 'select',
        name: 'choice',
        message: `Select a value for ${name}.`,
        choices: choices,
        onState,
    });
    return resolveValue(response.choice);
}
async function askForUnionInputs(inputs, name, repl) {
    if ('discriminator' in inputs) {
        return await askForDiscriminatedUnionInputs(inputs, name, repl);
    }
    else {
        const kind = await prompts({
            type: 'select',
            name: 'kind',
            message: `Select a kind for ${name}.`,
            choices: getOptionalChoices(inputs, inputs.options.map((option) => {
                if (option.type === 'literal') {
                    return {
                        title: `(${option.value})`,
                        description: option.description,
                        value: option,
                    };
                }
                else if (option.type === 'null') {
                    return {
                        title: `(null)`,
                        description: option.description,
                        value: {
                            type: 'null',
                        },
                    };
                }
                return {
                    title: option.type,
                    description: option.description,
                    value: option,
                };
            })),
            onState,
        });
        const option = resolveValue(kind.kind);
        if (!option) {
            return option;
        }
        return await askForInputs(option, name, repl);
    }
}
async function askForRecordInputs(inputs, name, repl) {
    const allowed = await askForOptionalInputs(inputs, name, repl);
    if (!allowed) {
        return inputs.defaultValue;
    }
    const result = {};
    while (true) {
        let key = await prompts({
            type: 'text',
            name: 'key',
            message: `Enter a property name for ${name}.`,
        });
        if (!key.key) {
            break;
        }
        const prop = inputs.valueSchema;
        const value = await askForInputs(prop, `${name}.${key.key}`, repl);
        result[key.key] = value;
    }
    return result;
}
async function askForTupleInputs(inputs, name, repl) {
    const allowed = await askForOptionalInputs(inputs, name, repl);
    if (!allowed) {
        return inputs.defaultValue;
    }
    const result = [];
    for (let i = 0; i < inputs.items.length; i++) {
        const prop = inputs.items[i];
        const value = await askForInputs(prop, `${name}[${i}]`, repl);
        result.push(value);
    }
    return result;
}
async function askForDiscriminatedUnionInputs(inputs, name, repl) {
    let choices = getOptionalChoices(inputs, inputs.options.map((option) => {
        const prop = option.schema[inputs.discriminator];
        if (prop.type === 'enum') {
            return {
                title: prop.values.join(', '),
                description: option.description,
                value: option,
            };
        }
        else if (prop.type !== 'literal') {
            return {
                title: option.type,
                description: option.description,
                value: option,
            };
        }
        return {
            title: prop.value,
            description: option.description,
            value: option,
        };
    }));
    const kind = await prompts({
        type: 'select',
        name: 'kind',
        message: `Select a ${inputs.discriminator} for ${name}.`,
        choices: choices,
        onState,
    });
    const option = resolveValue(kind.kind);
    if (option === null || option === undefined) {
        return option;
    }
    return await askForInputs(option, name, repl);
}
async function askForAnyInputs(name, repl) {
    const kind = await prompts({
        type: 'select',
        name: 'kind',
        message: `Select a kind for ${name}.`,
        choices: [
            {
                title: 'json',
                description: 'A JSON value.',
                value: 'json',
            },
            {
                title: 'string',
                description: 'A string value.',
                value: 'string',
            },
            {
                title: 'number',
                description: 'A number value.',
                value: 'number',
            },
            {
                title: 'boolean',
                description: 'A boolean value.',
                value: 'boolean',
            },
            {
                title: '(null)',
                description: 'A null value.',
                value: 'null',
            },
        ],
        onState,
    });
    if (kind.kind === 'null') {
        return null;
    }
    else if (kind.kind === 'json') {
        while (true) {
            try {
                const response = await prompts({
                    type: 'text',
                    name: 'value',
                    message: `Enter a JSON value for ${name}.`,
                    onState,
                });
                if (response.value === '' || response.value === undefined) {
                    return undefined;
                }
                const script = response.value;
                if (typeof script === 'string' && script.startsWith('.')) {
                    return await evalResult(script, repl, null);
                }
                return JSON.parse(response.value);
            }
            catch (err) {
                console.log('Invalid JSON value.', err);
            }
        }
    }
    return await askForInputs({
        type: kind.kind,
        nullable: true,
        optional: true,
    }, name, repl);
}
async function askForOptionalInputs(inputs, name, repl) {
    if (inputs.optional || inputs.nullable) {
        const response = await prompts({
            type: 'confirm',
            name: 'continue',
            message: `Do you want to enter a value for ${name}?`,
            onState,
        });
        if (!response.continue) {
            if (inputs.nullable) {
                return null;
            }
            else {
                return undefined;
            }
        }
    }
    return true;
}
//# sourceMappingURL=schema.js.map