import type { WizardPrompter } from './prompter/interface.js';
import type { ConfigService } from './config/config-service.js';
import type { MenuSection, WizardState } from './types.js';
import { welcomeStage } from './stages/welcome.js';
import { detectInstallStage } from './stages/detect-install.js';
import { soulSetupStage } from './stages/soul-setup.js';
import { userSetupStage } from './stages/user-setup.js';
import { toolsSetupStage } from './stages/tools-setup.js';
import { runtimeSetupStage } from './stages/runtime-setup.js';
import { hooksPreviewStage } from './stages/hooks-preview.js';
import { skillsSelectStage } from './stages/skills-select.js';
import { finalizeStage } from './stages/finalize.js';
import { gatewayConfigStage } from './stages/gateway-config.js';
import { gatewayBootstrapStage } from './stages/gateway-bootstrap.js';
import { providerSetupStage } from './stages/provider-setup.js';
import { agentIntentStage } from './stages/agent-intent.js';
import { quickStartPath } from './stages/quick-start.js';
import { DEFAULTS } from './constants.js';

export interface WizardOptions {
  mosaicHome: string;
  sourceDir: string;
  prompter: WizardPrompter;
  configService: ConfigService;
  cliOverrides?: Partial<WizardState>;
  /**
   * Skip the terminal gateway stages. Used by callers that only want to
   * configure the framework (SOUL.md/USER.md/skills/hooks) without touching
   * the gateway daemon. Defaults to `false` — the unified first-run flow
   * runs everything end-to-end.
   */
  skipGateway?: boolean;
  /** Host passed through to the gateway config stage. Defaults to localhost. */
  gatewayHost?: string;
  /** Default gateway port (14242) — overridable by CLI flag. */
  gatewayPort?: number;
  /**
   * Explicit port override from the caller. Honored even when resuming
   * from an existing `.env` (useful when the saved port conflicts with
   * another service).
   */
  gatewayPortOverride?: number;
  /** Skip `npm install -g @mosaicstack/gateway` during the config stage. */
  skipGatewayNpmInstall?: boolean;
}

export async function runWizard(options: WizardOptions): Promise<void> {
  const { prompter, configService, mosaicHome, sourceDir } = options;

  const state: WizardState = {
    mosaicHome,
    sourceDir,
    mode: 'quick',
    installAction: 'fresh',
    soul: {},
    user: {},
    tools: {},
    runtimes: { detected: [], mcpConfigured: false },
    selectedSkills: [],
    completedSections: new Set<MenuSection>(),
  };

  // Apply CLI overrides (strip undefined values)
  if (options.cliOverrides) {
    if (options.cliOverrides.soul) {
      for (const [k, v] of Object.entries(options.cliOverrides.soul)) {
        if (v !== undefined) {
          (state.soul as Record<string, unknown>)[k] = v;
        }
      }
    }
    if (options.cliOverrides.user) {
      for (const [k, v] of Object.entries(options.cliOverrides.user)) {
        if (v !== undefined) {
          (state.user as Record<string, unknown>)[k] = v;
        }
      }
    }
    if (options.cliOverrides.tools) {
      for (const [k, v] of Object.entries(options.cliOverrides.tools)) {
        if (v !== undefined) {
          (state.tools as Record<string, unknown>)[k] = v;
        }
      }
    }
    if (options.cliOverrides.mode) {
      state.mode = options.cliOverrides.mode;
    }
  }

  // Stage 1: Welcome
  await welcomeStage(prompter, state);

  // Stage 2: Existing Install Detection
  await detectInstallStage(prompter, state, configService);

  // ── Headless bypass ────────────────────────────────────────────────────────
  // When MOSAIC_ASSUME_YES=1 or no TTY, run the linear headless path.
  // This preserves full backward compatibility with tools/install.sh --yes.
  const headlessRun = process.env['MOSAIC_ASSUME_YES'] === '1' || !process.stdin.isTTY;
  if (headlessRun) {
    await runHeadlessPath(prompter, state, configService, options);
    return;
  }

  // ── Interactive: Main Menu ─────────────────────────────────────────────────
  if (state.installAction === 'fresh' || state.installAction === 'reset') {
    await runMenuLoop(prompter, state, configService, options);
  } else if (state.installAction === 'reconfigure') {
    state.mode = 'advanced';
    await runMenuLoop(prompter, state, configService, options);
  } else {
    // 'keep' — skip identity setup, go straight to finalize + gateway
    await runKeepPath(prompter, state, configService, options);
  }
}

// ── Menu-driven interactive flow ────────────────────────────────────────────

type MenuChoice =
  | 'quick-start'
  | 'providers'
  | 'identity'
  | 'skills'
  | 'gateway-config'
  | 'advanced'
  | 'finish';

function menuLabel(section: MenuChoice, completed: Set<MenuSection>): string {
  const labels: Record<MenuChoice, string> = {
    'quick-start': 'Quick Start',
    providers: 'Providers',
    identity: 'Agent Identity',
    skills: 'Skills',
    'gateway-config': 'Gateway',
    advanced: 'Advanced',
    finish: 'Finish & Apply',
  };
  const base = labels[section];
  const sectionKey: MenuSection =
    section === 'gateway-config' ? 'gateway' : (section as MenuSection);
  if (completed.has(sectionKey)) {
    return `${base} [done]`;
  }
  return base;
}

async function runMenuLoop(
  prompter: WizardPrompter,
  state: WizardState,
  configService: ConfigService,
  options: WizardOptions,
): Promise<void> {
  const completed = state.completedSections!;

  for (;;) {
    const choice = await prompter.select<MenuChoice>({
      message: 'What would you like to configure?',
      options: [
        {
          value: 'quick-start',
          label: menuLabel('quick-start', completed),
          hint: 'Recommended defaults, minimal questions',
        },
        {
          value: 'providers',
          label: menuLabel('providers', completed),
          hint: 'LLM API keys (Anthropic, OpenAI)',
        },
        {
          value: 'identity',
          label: menuLabel('identity', completed),
          hint: 'Agent name, intent, persona',
        },
        {
          value: 'skills',
          label: menuLabel('skills', completed),
          hint: 'Install agent skills',
        },
        {
          value: 'gateway-config',
          label: menuLabel('gateway-config', completed),
          hint: 'Port, storage, database',
        },
        {
          value: 'advanced',
          label: menuLabel('advanced', completed),
          hint: 'SOUL.md, USER.md, TOOLS.md, runtimes, hooks',
        },
        {
          value: 'finish',
          label: menuLabel('finish', completed),
          hint: 'Write configs and start gateway',
        },
      ],
    });

    switch (choice) {
      case 'quick-start':
        await quickStartPath(prompter, state, configService, options);
        return; // Quick start is a complete flow — exit menu

      case 'providers':
        await providerSetupStage(prompter, state);
        completed.add('providers');
        break;

      case 'identity':
        await agentIntentStage(prompter, state);
        completed.add('identity');
        break;

      case 'skills':
        await skillsSelectStage(prompter, state);
        completed.add('skills');
        break;

      case 'gateway-config':
        // Gateway config is handled during Finish — mark as "configured"
        // after user reviews settings.
        await runGatewaySubMenu(prompter, state, options);
        completed.add('gateway');
        break;

      case 'advanced':
        await runAdvancedSubMenu(prompter, state);
        completed.add('advanced');
        break;

      case 'finish':
        await runFinishPath(prompter, state, configService, options);
        return; // Done
    }
  }
}

// ── Gateway sub-menu ─────────────────────────────────────────────────────────

async function runGatewaySubMenu(
  prompter: WizardPrompter,
  state: WizardState,
  _options: WizardOptions,
): Promise<void> {
  prompter.note(
    'Gateway settings will be applied when you select "Finish & Apply".\n' +
      'Configure the settings you want to customize here.',
    'Gateway Configuration',
  );

  // For now, just let them know defaults will be used and they can
  // override during finish. The actual gateway config stage runs
  // during Finish & Apply. This menu item exists so users know
  // the gateway is part of the wizard.
  const port = await prompter.text({
    message: 'Gateway port',
    initialValue: (_options.gatewayPort ?? 14242).toString(),
    defaultValue: (_options.gatewayPort ?? 14242).toString(),
    validate: (v) => {
      const n = parseInt(v, 10);
      if (Number.isNaN(n) || n < 1 || n > 65535) return 'Port must be 1-65535';
      return undefined;
    },
  });

  // Store for later use in the gateway config stage
  _options.gatewayPort = parseInt(port, 10);
  prompter.log(`Gateway port set to ${port}. Will be applied during Finish & Apply.`);
}

// ── Advanced sub-menu ────────────────────────────────────────────────────────

async function runAdvancedSubMenu(prompter: WizardPrompter, state: WizardState): Promise<void> {
  state.mode = 'advanced';

  // Run the detailed setup stages
  await soulSetupStage(prompter, state);
  await userSetupStage(prompter, state);
  await toolsSetupStage(prompter, state);
  await runtimeSetupStage(prompter, state);
  await hooksPreviewStage(prompter, state);
}

// ── Finish & Apply ──────────────────────────────────────────────────────────

async function runFinishPath(
  prompter: WizardPrompter,
  state: WizardState,
  configService: ConfigService,
  options: WizardOptions,
): Promise<void> {
  // Apply defaults for anything not explicitly configured
  state.soul.agentName ??= 'Mosaic';
  state.soul.roleDescription ??= DEFAULTS.roleDescription;
  state.soul.communicationStyle ??= 'direct';
  state.user.background ??= DEFAULTS.background;
  state.user.accessibilitySection ??= DEFAULTS.accessibilitySection;
  state.user.personalBoundaries ??= DEFAULTS.personalBoundaries;
  state.tools.gitProviders ??= [];
  state.tools.credentialsLocation ??= DEFAULTS.credentialsLocation;
  state.tools.customToolsSection ??= DEFAULTS.customToolsSection;

  // Runtime detection if not already done
  if (state.runtimes.detected.length === 0 && !state.completedSections?.has('advanced')) {
    await runtimeSetupStage(prompter, state);
    await hooksPreviewStage(prompter, state);
  }

  // Skills defaults if not already configured
  if (!state.completedSections?.has('skills')) {
    await skillsSelectStage(prompter, state);
  }

  // Finalize (writes configs, links runtime assets, syncs skills)
  await finalizeStage(prompter, state, configService);

  // Gateway stages
  if (!options.skipGateway) {
    try {
      const configResult = await gatewayConfigStage(prompter, state, {
        host: options.gatewayHost ?? 'localhost',
        defaultPort: options.gatewayPort ?? 14242,
        portOverride: options.gatewayPortOverride,
        skipInstall: options.skipGatewayNpmInstall,
        providerKey: state.providerKey,
        providerType: state.providerType ?? 'none',
      });

      if (configResult.ready && configResult.host && configResult.port) {
        const bootstrapResult = await gatewayBootstrapStage(prompter, state, {
          host: configResult.host,
          port: configResult.port,
        });
        if (!bootstrapResult.completed) {
          prompter.warn('Admin bootstrap failed — aborting wizard.');
          process.exit(1);
        }
      }
    } catch (err) {
      prompter.warn(`Gateway setup failed: ${err instanceof Error ? err.message : String(err)}`);
      throw err;
    }
  }
}

// ── Headless linear path (backward compat) ──────────────────────────────────

async function runHeadlessPath(
  prompter: WizardPrompter,
  state: WizardState,
  configService: ConfigService,
  options: WizardOptions,
): Promise<void> {
  // Provider setup from env vars
  await providerSetupStage(prompter, state);

  // Agent intent from env vars
  await agentIntentStage(prompter, state);

  // SOUL.md
  await soulSetupStage(prompter, state);

  // USER.md
  await userSetupStage(prompter, state);

  // TOOLS.md
  await toolsSetupStage(prompter, state);

  // Runtime Detection
  await runtimeSetupStage(prompter, state);

  // Hooks
  await hooksPreviewStage(prompter, state);

  // Skills
  await skillsSelectStage(prompter, state);

  // Finalize
  await finalizeStage(prompter, state, configService);

  // Gateway stages
  if (!options.skipGateway) {
    try {
      const configResult = await gatewayConfigStage(prompter, state, {
        host: options.gatewayHost ?? 'localhost',
        defaultPort: options.gatewayPort ?? 14242,
        portOverride: options.gatewayPortOverride,
        skipInstall: options.skipGatewayNpmInstall,
        providerKey: state.providerKey,
        providerType: state.providerType ?? 'none',
      });

      if (!configResult.ready || !configResult.host || !configResult.port) {
        prompter.warn('Gateway configuration failed in headless mode — aborting wizard.');
        process.exit(1);
      } else {
        const bootstrapResult = await gatewayBootstrapStage(prompter, state, {
          host: configResult.host,
          port: configResult.port,
        });
        if (!bootstrapResult.completed) {
          prompter.warn('Admin bootstrap failed — aborting wizard.');
          process.exit(1);
        }
      }
    } catch (err) {
      prompter.warn(`Gateway setup failed: ${err instanceof Error ? err.message : String(err)}`);
      throw err;
    }
  }
}

// ── Keep path (preserve existing identity) ──────────────────────────────────

async function runKeepPath(
  prompter: WizardPrompter,
  state: WizardState,
  configService: ConfigService,
  options: WizardOptions,
): Promise<void> {
  // Runtime detection
  await runtimeSetupStage(prompter, state);

  // Hooks
  await hooksPreviewStage(prompter, state);

  // Skills
  await skillsSelectStage(prompter, state);

  // Finalize
  await finalizeStage(prompter, state, configService);

  // Gateway stages
  if (!options.skipGateway) {
    try {
      const configResult = await gatewayConfigStage(prompter, state, {
        host: options.gatewayHost ?? 'localhost',
        defaultPort: options.gatewayPort ?? 14242,
        portOverride: options.gatewayPortOverride,
        skipInstall: options.skipGatewayNpmInstall,
      });

      if (configResult.ready && configResult.host && configResult.port) {
        const bootstrapResult = await gatewayBootstrapStage(prompter, state, {
          host: configResult.host,
          port: configResult.port,
        });
        if (!bootstrapResult.completed) {
          prompter.warn('Admin bootstrap failed — aborting wizard.');
          process.exit(1);
        }
      }
    } catch (err) {
      prompter.warn(`Gateway setup failed: ${err instanceof Error ? err.message : String(err)}`);
      throw err;
    }
  }
}
