import { Flags } from '@oclif/core';
import inquirer from 'inquirer';
import { getPMOContext } from './pmo-context.js';
import { styles } from '../styles.js';
import { PromptCommand } from '../prompt-command.js';
import { shouldOutputJson, isNonTTY, outputPromptAsJson, outputErrorAsJson, createMetadata, } from '../prompt-json.js';
import { withSignalSafePrompt } from '../signal-handler.js';
/**
 * Base flags for JSON/agent mode support
 * Include these in your command's flags by spreading: ...jsonModeFlags
 * @deprecated Use machineOutputFlags instead
 */
export const jsonModeFlags = {
    json: Flags.boolean({
        description: 'Output as JSON for AI agents/scripts',
        default: false,
    }),
    machine: Flags.boolean({
        char: 'm',
        description: 'Output as JSON for AI agents/scripts',
        default: false,
    }),
};
/**
 * Base flags for machine-readable output mode
 * Include these in your command's flags by spreading: ...machineOutputFlags
 * --json and --machine/-m both trigger JSON output mode
 */
export const machineOutputFlags = {
    json: Flags.boolean({
        description: 'Output as JSON for AI agents/scripts',
        default: false,
    }),
    machine: Flags.boolean({
        char: 'm',
        description: 'Output as JSON for AI agents/scripts',
        default: false,
    }),
};
/**
 * Base flags shared by all PMO commands
 * Include these in your command's flags by spreading: ...pmoBaseFlags
 */
export const pmoBaseFlags = {
    project: Flags.string({
        char: 'P',
        description: 'Project ID (uses first project if only one exists)',
    }),
    ...machineOutputFlags,
};
/**
 * Base command class for PMO commands
 *
 * Provides automatic PMO context initialization and cleanup:
 * - Initializes storage before run() executes
 * - Ensures storage.close() is called even if errors occur
 * - Provides common PMO flags (--project)
 *
 * Storage is project-agnostic - projectId is passed explicitly to operations.
 *
 * Usage:
 * ```typescript
 * import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/base-command.js';
 *
 * export default class MyCommand extends PMOCommand {
 *   static flags = {
 *     ...pmoBaseFlags,
 *     // additional flags...
 *   };
 *
 *   async execute(): Promise<void> {
 *     // For project-agnostic operations (e.g., get ticket by ID):
 *     const ticket = await this.storage.getTicketById('TKT-123');
 *
 *     // For project-scoped operations, get projectId first:
 *     const projectId = await this.requireProject();
 *     const board = await this.storage.getBoard(projectId);
 *
 *     // Or derive from an entity:
 *     const projectId = ticket.projectId;
 *     await this.storage.moveTicket(projectId, ticket.id, 'Done');
 *   }
 * }
 * ```
 */
export class PMOCommand extends PromptCommand {
    /**
     * PMO context with storage, pmoPath, etc.
     * Available after init() runs (before execute())
     */
    pmoContext;
    /**
     * Flag to track if context was successfully initialized
     */
    contextInitialized = false;
    /**
     * Cached project ID from -P flag
     */
    projectFlag;
    /**
     * Logger function for PMO context
     * Can be overridden to customize logging behavior
     */
    pmoLogger(msg) {
        this.log(styles.muted(msg));
    }
    /**
     * oclif init hook - runs before the command executes
     * Initializes PMO context with storage access
     */
    async init() {
        await super.init();
        // Parse flags to get project ID if provided
        const { flags } = await this.parse(this.constructor);
        this.projectFlag = flags.project;
        this.pmoContext = await getPMOContext({
            logger: (msg) => this.pmoLogger(msg),
        });
        this.contextInitialized = true;
    }
    /**
     * Require a project to be selected.
     * Returns projectId that should be passed to storage operations.
     *
     * Priority:
     * 1. If -P flag was provided, uses that
     * 2. If only one project exists, uses that
     * 3. If multiple projects exist, prompts user to select one (or outputs JSON if jsonMode)
     *
     * @param options.filterEmptyProjects - Only show projects with tickets
     * @param options.jsonMode - JSON mode configuration for AI agents
     * @returns The selected project ID - pass this to storage operations
     */
    async requireProject(options) {
        // If -P flag was provided, use it
        if (this.projectFlag) {
            return this.projectFlag;
        }
        // Get all projects
        const projects = await this.storage.listProjects();
        if (projects.length === 0) {
            throw new Error('No projects found. Run "prlt pmo init" first.');
        }
        // Filter to projects with tickets if requested
        let filteredProjects = projects;
        if (options?.filterEmptyProjects) {
            const projectsWithTickets = [];
            for (const p of projects) {
                // eslint-disable-next-line no-await-in-loop -- Sequential filtering for project selection
                const tickets = await this.storage.listTickets(p.id);
                if (tickets.length > 0) {
                    projectsWithTickets.push(p);
                }
            }
            filteredProjects = projectsWithTickets;
            if (filteredProjects.length === 0) {
                throw new Error('No projects with tickets found. Create a ticket first.');
            }
        }
        // If only one project, use it
        if (filteredProjects.length === 1) {
            return filteredProjects[0].id;
        }
        // Multiple projects - check for JSON mode
        // Sort projects by leading number in name (e.g., "1. MVP" before "10. Infra")
        const sortedProjects = [...filteredProjects].sort((a, b) => {
            const numA = parseInt(a.name.match(/^(\d+)/)?.[1] || '999', 10);
            const numB = parseInt(b.name.match(/^(\d+)/)?.[1] || '999', 10);
            return numA - numB;
        });
        // Auto-detect non-TTY: switch to JSON mode when no TTY present
        const effectiveJsonMode = options?.jsonMode ?? (isNonTTY()
            ? {
                flags: { json: true },
                commandName: this.id ?? 'unknown',
                baseCommand: `prlt ${(this.id ?? 'unknown').replace(/:/g, ' ')}`,
            }
            : null);
        // If JSON mode is active, output project choices as JSON
        if (effectiveJsonMode && shouldOutputJson(effectiveJsonMode.flags)) {
            const choices = sortedProjects.map(p => ({
                name: `${p.name} (${p.id})`,
                value: p.id,
                command: `${effectiveJsonMode.baseCommand} -P ${p.id} --json`,
            }));
            outputPromptAsJson({
                type: 'list',
                name: 'project',
                message: 'Select project:',
                choices,
            }, createMetadata(effectiveJsonMode.commandName, effectiveJsonMode.flags));
            // outputPromptAsJson calls process.exit, so this is unreachable
            return '';
        }
        // Interactive mode - prompt for selection
        const { selectedProjectId } = await withSignalSafePrompt(() => inquirer.prompt([{
                type: 'list',
                name: 'selectedProjectId',
                message: 'Select project:',
                choices: sortedProjects.map(p => ({
                    name: `${p.name} (${p.id})`,
                    value: p.id,
                })),
            }]));
        return selectedProjectId;
    }
    /**
     * Get project name by ID
     */
    async getProjectName(projectId) {
        const project = await this.storage.getProject(projectId);
        return project?.name || projectId;
    }
    /**
     * Select from a list of items with JSON mode support for AI agents.
     *
     * In JSON mode: outputs choices as JSON with command field and exits
     * In interactive mode: shows prompt and returns selected value
     *
     * @param options Configuration for the selection
     * @returns The selected value (only in interactive mode)
     *
     * @example
     * ```typescript
     * const ticketId = await this.selectFromList({
     *   message: 'Select ticket:',
     *   items: tickets,
     *   getName: (t) => `${t.id}: ${t.title}`,
     *   getValue: (t) => t.id,
     *   getCommand: (t) => `prlt ticket view ${t.id} --json`,
     *   jsonMode: { flags, commandName: 'ticket view' },
     * });
     * ```
     */
    async selectFromList(options) {
        const { message, items, getName, getValue, getCommand, jsonMode, allowCancel = false, cancelValue, } = options;
        // Auto-detect non-TTY: switch to JSON mode when no TTY present
        const effectiveJsonMode = jsonMode ?? (isNonTTY()
            ? { flags: { json: true }, commandName: this.id ?? 'unknown' }
            : null);
        // Build choices with command field
        const choices = items.map(item => ({
            name: getName(item),
            value: getValue(item),
            command: getCommand(item),
        }));
        // Check for JSON mode
        if (effectiveJsonMode && shouldOutputJson(effectiveJsonMode.flags)) {
            outputPromptAsJson({
                type: 'list',
                name: 'selection',
                message,
                choices,
            }, createMetadata(effectiveJsonMode.commandName, effectiveJsonMode.flags));
            // outputPromptAsJson exits, so this is unreachable
            return null;
        }
        // Interactive mode
        const interactiveChoices = choices.map(c => ({
            name: c.name,
            value: c.value,
        }));
        if (allowCancel) {
            interactiveChoices.push({ name: '─'.repeat(20), value: '__separator__' }, { name: 'Cancel', value: cancelValue ?? '__cancel__' });
        }
        const { selection } = await withSignalSafePrompt(() => inquirer.prompt([{
                type: 'list',
                name: 'selection',
                message,
                choices: interactiveChoices,
            }]));
        if (selection === '__cancel__' || selection === '__separator__') {
            return null;
        }
        return selection;
    }
    /**
     * Prompt for input with JSON mode support for AI agents.
     *
     * In JSON mode: outputs field info as JSON and exits
     * In interactive mode: shows prompt and returns input value
     *
     * @param options Configuration for the input
     * @returns The input value (only in interactive mode)
     */
    async promptForInput(options) {
        const { message, fieldName, defaultValue, validate, jsonMode } = options;
        // Auto-detect non-TTY: switch to JSON mode when no TTY present
        const effectiveJsonMode = jsonMode ?? (isNonTTY()
            ? { flags: { json: true }, commandName: this.id ?? 'unknown', commandHint: '', example: undefined }
            : null);
        // Check for JSON mode
        if (effectiveJsonMode && shouldOutputJson(effectiveJsonMode.flags)) {
            outputPromptAsJson({
                type: 'input',
                name: fieldName,
                message,
                default: defaultValue,
                context: {
                    hint: effectiveJsonMode.commandHint,
                    example: effectiveJsonMode.example,
                },
            }, createMetadata(effectiveJsonMode.commandName, effectiveJsonMode.flags));
            // outputPromptAsJson exits, so this is unreachable
            return '';
        }
        // Interactive mode
        const { value } = await withSignalSafePrompt(() => inquirer.prompt([{
                type: 'input',
                name: 'value',
                message,
                default: defaultValue,
                validate,
            }]));
        return value;
    }
    /**
     * Unified error handler for JSON/interactive modes.
     *
     * Consolidates error handling to avoid message drift between JSON and interactive modes.
     * In JSON mode: outputs structured error JSON and exits
     * In interactive mode: calls this.error() with the message
     *
     * @param code - Error code for JSON output (e.g., 'NOT_FOUND', 'DOCKER_NOT_RUNNING')
     * @param message - Human-readable error message (used in both modes)
     * @param options - Configuration for error handling
     *
     * @example
     * ```typescript
     * // Instead of duplicating messages:
     * // if (jsonMode) { outputErrorAsJson('CODE', 'msg', ...); }
     * // this.error('msg');
     *
     * // Use:
     * this.handleError('DOCKER_NOT_RUNNING', 'Docker is not running.', {
     *   jsonMode,
     *   commandName: 'agent auth',
     *   flags,
     * });
     * ```
     */
    handleError(code, message, options) {
        if (options.jsonMode) {
            outputErrorAsJson(code, message, createMetadata(options.commandName, options.flags));
            this.exit(1);
        }
        this.error(message);
    }
    /**
     * Override run() to delegate to execute() and ensure cleanup
     * Subclasses should implement execute() instead of run()
     */
    async run() {
        try {
            await this.execute();
        }
        finally {
            await this.cleanup();
        }
    }
    /**
     * Cleanup handler - ensures storage is closed
     * Called automatically after execute() completes or throws
     */
    async cleanup() {
        if (this.contextInitialized && this.pmoContext?.storage) {
            try {
                await this.pmoContext.storage.close();
            }
            catch {
                // Ignore close errors - storage might already be closed
            }
        }
    }
    /**
     * oclif catch hook - called when an error occurs
     * Ensures cleanup even on errors
     */
    async catch(error) {
        await this.cleanup();
        throw error;
    }
    /**
     * oclif finally hook - called after run() completes
     */
    async finally(_) {
        await this.cleanup();
    }
    // Convenience getters for common context properties
    /** Storage instance */
    get storage() {
        return this.pmoContext.storage;
    }
    /** PMO directory path */
    get pmoPath() {
        return this.pmoContext.pmoPath;
    }
}
//# sourceMappingURL=base-command.js.map