"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.selectWithArrows = selectWithArrows;
exports.promptMaskedInput = promptMaskedInput;
exports.runProviderSetupWizard = runProviderSetupWizard;
exports.confirmPrompt = confirmPrompt;
exports.promptInput = promptInput;
exports.confirmByTypingPrompt = confirmByTypingPrompt;
exports.selectServerTypePrompt = selectServerTypePrompt;
exports.displayDeprecationWarning = displayDeprecationWarning;
exports.displayServerTypeNotFoundError = displayServerTypeNotFoundError;
exports.confirmAlternativeServerType = confirmAlternativeServerType;
const chalk_1 = __importDefault(require("chalk"));
const readline = __importStar(require("node:readline"));
const server_type_validator_service_1 = require("../services/server-type-validator.service");
const config_storage_1 = require("./config-storage");
const provider_credential_schemas_1 = require("./provider-credential-schemas");
const scaleway_validator_1 = require("./scaleway-validator");
const validator = new server_type_validator_service_1.ServerTypeValidatorService();
function makeStdinCleanup(onData) {
    return () => {
        if (process.stdin.isTTY) {
            process.stdin.setRawMode(false);
        }
        process.stdin.removeListener('data', onData);
    };
}
const SUPPORTED_PROVIDERS = [
    { id: 'hetzner', label: 'Hetzner Cloud', available: true },
    { id: 'scaleway', label: 'Scaleway', available: true },
];
/**
 * Generic arrow-key interactive selector.
 * Returns the selected index, or -1 if cancelled (Ctrl+C / ESC).
 */
async function selectWithArrows(title, items) {
    return new Promise((resolve) => {
        let cursor = items.findIndex((i) => !i.disabled);
        if (cursor === -1)
            cursor = 0;
        const ARROW_UP = '\u001B\u005B\u0041';
        const ARROW_DOWN = '\u001B\u005B\u0042';
        const ENTER = '\r';
        const CTRL_C = '\u0003';
        const ESC = '\u001B';
        const renderLine = (item, selected) => {
            const pointer = selected ? chalk_1.default.cyan('❯') : ' ';
            if (item.disabled) {
                const reason = item.disabledReason
                    ? chalk_1.default.dim(` (${item.disabledReason})`)
                    : '';
                return `  ${pointer} ${chalk_1.default.dim(item.label)}${reason}`;
            }
            return selected
                ? `  ${pointer} ${chalk_1.default.cyan(item.label)}`
                : `  ${pointer} ${chalk_1.default.white(item.label)}`;
        };
        const render = (firstRender = false) => {
            if (!firstRender) {
                // Move cursor up to overwrite previous render
                process.stdout.write(`\u001B[${items.length}A`);
            }
            items.forEach((item, i) => {
                process.stdout.write(`\u001B[2K${renderLine(item, i === cursor)}\n`);
            });
        };
        const titleLine = chalk_1.default.bold(`   ${title}`);
        const hintLine = chalk_1.default.dim('Use ↑ ↓ arrows, Enter to confirm, ESC to cancel');
        process.stdout.write(`\n${titleLine}\n   ${hintLine}\n\n`);
        render(true);
        if (process.stdin.isTTY) {
            process.stdin.setRawMode(true);
        }
        process.stdin.resume();
        const onData = (data) => {
            const key = data.toString();
            if (key === CTRL_C || key === ESC) {
                cleanup();
                resolve(-1);
                return;
            }
            if (key === ENTER) {
                if (!items[cursor]?.disabled) {
                    cleanup();
                    resolve(cursor);
                }
                return;
            }
            if (key === ARROW_UP || key === ARROW_DOWN) {
                const direction = key === ARROW_UP ? -1 : 1;
                let next = cursor;
                let attempts = items.length;
                do {
                    next = (next + direction + items.length) % items.length;
                    attempts--;
                } while (items[next]?.disabled && attempts > 0);
                if (!items[next]?.disabled) {
                    cursor = next;
                    render();
                }
            }
        };
        const cleanup = makeStdinCleanup(onData);
        process.stdin.on('data', onData);
    });
}
/**
 * Prompt for masked/hidden input (for API tokens).
 * Shows a blinking cursor while empty, asterisks once input arrives.
 * Supports both typing and paste (Cmd+V), including bracketed paste mode.
 */
async function promptMaskedInput(message) {
    return new Promise((resolve) => {
        let token = '';
        let hasInput = false;
        let cursorOn = true;
        process.stdout.write(`${message}: `);
        process.stdout.write(chalk_1.default.dim('│'));
        process.stdout.write('\u001B[1D');
        const blink = setInterval(() => {
            if (!hasInput) {
                cursorOn = !cursorOn;
                process.stdout.write(cursorOn ? chalk_1.default.dim('│') : ' ');
                process.stdout.write('\u001B[1D');
            }
        }, 530);
        if (process.stdin.isTTY) {
            process.stdin.setRawMode(true);
        }
        process.stdin.resume();
        const onData = (chunk) => {
            // Strip bracketed paste wrappers (macOS terminal paste mode)
            let str = chunk
                .toString()
                .replaceAll('\x1b[200~', '')
                .replaceAll('\x1b[201~', '');
            let i = 0;
            while (i < str.length) {
                const c = str[i];
                // Skip ANSI escape sequences (e.g. arrow keys, function keys)
                if (c === '\x1b') {
                    i++;
                    if (str[i] === '[') {
                        i++;
                        while (i < str.length && !/[A-Za-z~]/.test(str[i]))
                            i++;
                    }
                    i++;
                    continue;
                }
                if (c === '\r' || c === '\n') {
                    clearInterval(blink);
                    cleanup();
                    process.stdout.write('\n');
                    resolve(token);
                    return;
                }
                if (c === '\u0003') {
                    clearInterval(blink);
                    cleanup();
                    process.stdout.write('\n');
                    resolve('');
                    return;
                }
                if (c === '\u007F' || c === '\b') {
                    if (token.length > 0) {
                        token = token.slice(0, -1);
                        process.stdout.write('\b \b');
                    }
                    i++;
                    continue;
                }
                // Printable character
                if (!hasInput) {
                    hasInput = true;
                    clearInterval(blink);
                    process.stdout.write(' \u001B[1D');
                }
                token += c;
                process.stdout.write('*');
                i++;
            }
        };
        const cleanup = makeStdinCleanup(onData);
        process.stdin.on('data', onData);
    });
}
/**
 * Interactive provider setup wizard.
 * Shows arrow-key provider selection, prompts for token, saves to ConfigStorage.
 * Returns true if setup completed successfully, false if cancelled.
 */
async function runProviderSetupWizard() {
    console.log(chalk_1.default.yellow('\n⚠  No cloud provider API token configured.'));
    const items = SUPPORTED_PROVIDERS.map((p) => ({
        label: p.label,
        disabled: !p.available,
        disabledReason: p.unavailableReason,
    }));
    const index = await selectWithArrows('Select a provider to configure:', items);
    if (index === -1) {
        console.log(chalk_1.default.dim('\n   Cancelled. Run: flui config set hetzner YOUR_TOKEN\n'));
        return false;
    }
    const selectedProvider = SUPPORTED_PROVIDERS[index];
    const schema = (0, provider_credential_schemas_1.getCredentialSchema)(selectedProvider.id);
    if (!schema) {
        console.log(chalk_1.default.red(`\n   Missing credential schema for ${selectedProvider.label}\n`));
        return false;
    }
    console.log(chalk_1.default.dim(`\n   Enter your ${selectedProvider.label} credentials below.\n   They will be stored encrypted on disk.\n`));
    const collected = {};
    for (const field of schema.fields) {
        const label = `   ${field.label}`;
        const value = field.secret
            ? await promptMaskedInput(label)
            : (await promptInput({
                message: field.label + (field.hint ? chalk_1.default.dim(` (${field.hint})`) : ''),
            })).trim();
        if (!value.trim()) {
            console.log(chalk_1.default.red(`\n   ${field.label} is required. Setup cancelled.\n`));
            return false;
        }
        collected[field.key] = value.trim();
    }
    if (selectedProvider.id === 'scaleway') {
        process.stdout.write(chalk_1.default.dim('\n   Validating credentials with Scaleway IAM...'));
        const result = await (0, scaleway_validator_1.validateScalewayCredentials)(collected.accessKey, collected.secretKey);
        process.stdout.write('\r\u001B[2K');
        if (!result.success) {
            console.log(chalk_1.default.red(`   ✖ ${result.message}\n`));
            return false;
        }
        console.log(chalk_1.default.green(`   ✔ ${result.message}`));
    }
    try {
        const storage = new config_storage_1.ConfigStorage();
        if (schema.type === 'access_key_secret') {
            storage.saveCredentials(selectedProvider.id, collected);
        }
        else {
            storage.saveToken(selectedProvider.id, collected[schema.fields[0].key]);
        }
        console.log(chalk_1.default.green(`\n   ✔ ${selectedProvider.label} credentials saved (AES-256-GCM encrypted)\n`));
        return true;
    }
    catch (err) {
        console.log(chalk_1.default.red(`\n   Failed to save credentials: ${err instanceof Error ? err.message : String(err)}\n`));
        return false;
    }
}
/**
 * Prompt user for yes/no confirmation
 */
async function confirmPrompt(message, defaultValue = true) {
    if (!process.stdin.isTTY) {
        throw new Error(`Refusing to prompt for "${message}" in non-TTY mode. Pass --yes to confirm non-interactively.`);
    }
    return new Promise((resolve) => {
        process.stdout.write(`${message} (${defaultValue ? 'Y/n' : 'y/N'}): `);
        let answer = '';
        process.stdin.setRawMode(true);
        process.stdin.resume();
        const onData = (char) => {
            const c = char.toString();
            if (c === '\r' || c === '\n') {
                cleanup();
                process.stdout.write('\n');
                const normalized = answer.trim().toLowerCase();
                resolve(normalized === ''
                    ? defaultValue
                    : normalized === 'y' || normalized === 'yes');
            }
            else if (c === '\u0003') {
                cleanup();
                process.stdout.write('\n');
                resolve(false);
            }
            else if (c === '\u007F' || c === '\b') {
                if (answer.length > 0) {
                    answer = answer.slice(0, -1);
                    process.stdout.write('\b \b');
                }
            }
            else {
                answer += c;
                process.stdout.write(c);
            }
        };
        const cleanup = () => {
            process.stdin.setRawMode(false);
            process.stdin.removeListener('data', onData);
        };
        process.stdin.on('data', onData);
    });
}
/**
 * Free-text prompt with optional default and per-character validation loop.
 *
 * Use this whenever a required value must come from the user — e.g. the
 * preferences resolver falls through here when nothing is set at any layer.
 *
 * Fails loudly in non-TTY contexts (CI, piped stdin) so commands break instead
 * of hanging on input that never arrives. For yes/no use `confirmPrompt`,
 * for destructive type-the-name use `confirmByTypingPrompt`, for masked
 * tokens use `promptMaskedInput`.
 */
async function promptInput(opts) {
    if (!process.stdin.isTTY) {
        throw new Error(`Missing required value for "${opts.message}". Set it via flag, env var, or \`flui config set\` — interactive prompts are disabled in non-TTY mode.`);
    }
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    });
    const suffix = opts.default ? chalk_1.default.dim(` [${opts.default}]`) : '';
    const prefix = chalk_1.default.cyan('?') + ' ';
    const ask = () => new Promise((resolve) => {
        rl.question(`${prefix}${opts.message}${suffix}: `, (answer) => {
            resolve(answer.trim());
        });
    });
    try {
        while (true) {
            const raw = await ask();
            const value = raw === '' && opts.default !== undefined ? opts.default : raw;
            if (value === '') {
                console.log(chalk_1.default.red('  A value is required.'));
                continue;
            }
            if (opts.validate) {
                const err = opts.validate(value);
                if (err) {
                    console.log(chalk_1.default.red(`  ${err}`));
                    continue;
                }
            }
            return value;
        }
    }
    finally {
        rl.close();
    }
}
/**
 * Prompt the user to type an exact string to confirm a destructive action.
 * Returns true only when the input matches `expected` verbatim.
 */
async function confirmByTypingPrompt(message, expected) {
    return new Promise((resolve) => {
        process.stdout.write(`${message}: `);
        let answer = '';
        if (process.stdin.isTTY) {
            process.stdin.setRawMode(true);
        }
        process.stdin.resume();
        const onData = (char) => {
            const c = char.toString();
            if (c === '\r' || c === '\n') {
                cleanup();
                process.stdout.write('\n');
                resolve(answer.trim() === expected);
            }
            else if (c === '\u0003' || c === '\u001B') {
                cleanup();
                process.stdout.write('\n');
                resolve(false);
            }
            else if (c === '\u007F' || c === '\b') {
                if (answer.length > 0) {
                    answer = answer.slice(0, -1);
                    process.stdout.write('\b \b');
                }
            }
            else {
                answer += c;
                process.stdout.write(c);
            }
        };
        const cleanup = makeStdinCleanup(onData);
        process.stdin.on('data', onData);
    });
}
/**
 * Prompt user to select from a list of server types using arrow keys
 */
async function selectServerTypePrompt(serverTypes, message = 'Select a server type:') {
    const items = serverTypes.map((type) => {
        const price = validator.getFormattedPrice(type);
        let priceStr = '';
        if (price.monthly)
            priceStr = `€${price.monthly}/mo`;
        else if (price.hourly)
            priceStr = `€${price.hourly}/hr`;
        return {
            label: `${type.name.padEnd(10)} — ${type.cores} vCPU, ${type.memory}GB RAM, ${type.disk}GB ${type.storageType.padEnd(7)} ${priceStr}`,
        };
    });
    const index = await selectWithArrows(message, items);
    if (index === -1)
        return null;
    return serverTypes[index];
}
/**
 * Display a warning message about deprecated server type
 */
function displayDeprecationWarning(deprecatedType, suggestedType, originalPrice) {
    console.log(`\n⚠️  Server type '${deprecatedType}' is no longer available\n`);
    console.log(`   Recommended alternative: ${suggestedType.name} (${suggestedType.cores} vCPU, ${suggestedType.memory}GB RAM)`);
    const price = validator.getFormattedPrice(suggestedType);
    if (price.monthly) {
        const priceInfo = originalPrice
            ? `€${price.monthly}/mo (was €${originalPrice}/mo)`
            : `€${price.monthly}/mo`;
        console.log(`   • Monthly cost: ${priceInfo}`);
    }
    if (price.hourly) {
        console.log(`   • Hourly cost: €${price.hourly}`);
    }
    const regions = validator.getAvailableRegions(suggestedType);
    if (regions.length > 0) {
        console.log(`   • Available in: ${regions.join(', ')}`);
    }
    console.log('');
}
/**
 * Display server type not found error
 */
function displayServerTypeNotFoundError(requestedType, provider) {
    console.log(`\n❌ Server type '${requestedType}' not found for provider '${provider}'\n`);
    console.log(`   Run: flui server-types list --provider=${provider}\n`);
}
/**
 * Prompt user to confirm alternative server type
 */
async function confirmAlternativeServerType(deprecatedType, suggestedType, originalPrice) {
    displayDeprecationWarning(deprecatedType, suggestedType, originalPrice);
    return confirmPrompt(`Proceed with ${suggestedType.name}?`, true);
}
