//! TTY bridge for `aipm` wizard flows (init, migrate, make plugin).
//!
//! This module wires up interactive wizard flows by calling
//! [`libaipm::wizard::execute_prompts()`] for TTY prompt execution.
//! It is excluded from the coverage gate because it requires a real TTY.
//!
//! All logic (prompt definitions, answer resolution, theming) lives in
//! [`super::wizard`] and is fully tested (snapshot + unit tests).

use std::io::Write;
use std::path::Path;

use libaipm::manifest::types::PluginType;

use super::wizard;
use super::wizard::{
    format_wizard_summary, migrate_cleanup_prompt_steps, resolve_defaults,
    resolve_migrate_cleanup_answer, resolve_workspace_answers, styled_render_config,
    workspace_prompt_steps, PromptAnswer, PromptKind, PromptStep, WizardAnswers,
};

/// Resolved make-plugin output: `(name, engine, features)`.
type MakePluginResult = (String, String, Vec<String>);

/// Resolved pack-init output: `(name, plugin_type)`.
type PackInitResult = (Option<String>, Option<PluginType>);

/// Resolve workspace init options, launching the interactive wizard if needed.
///
/// When `interactive` is `true`, sets the global render config, prompts the
/// user for any values not provided via flags, and returns the resolved
/// [`WizardAnswers`]. When `false`, applies today's defaulting logic
/// (marketplace only if no flags; Copilot-only scaffold default per spec
/// §5.2.3).
///
/// `flags` is `(workspace, marketplace, no_starter)` from CLI args.
/// `engine_flag` is the parsed `--engine <list>` from `aipm init`; when
/// non-empty the wizard skips the engine prompts and the caller is
/// responsible for converting the value into the final `engines_scaffold`.
pub fn resolve(
    interactive: bool,
    flags: (bool, bool, bool),
    flag_name: Option<&str>,
    engine_flag: &[String],
) -> Result<WizardAnswers, Box<dyn std::error::Error>> {
    let (workspace, marketplace, no_starter) = flags;
    let flag_engine_provided = !engine_flag.is_empty();
    if interactive {
        inquire::set_global_render_config(styled_render_config());
        let steps = workspace_prompt_steps(
            workspace,
            marketplace,
            no_starter,
            flag_name,
            flag_engine_provided,
        );
        let answers = libaipm::wizard::execute_prompts(&steps)?;
        let resolved = resolve_workspace_answers(
            &answers,
            workspace,
            marketplace,
            no_starter,
            flag_name,
            flag_engine_provided,
        );

        // Spec §5.2.4: print a confirmation block to stderr so the user
        // sees what the wizard decided before scaffolding starts. Write
        // failures are intentionally ignored — the user-facing summary
        // is informational and shouldn't fail the init.
        let stderr = std::io::stderr();
        let mut handle = stderr.lock();
        let _ = handle.write_all(format_wizard_summary(&resolved).as_bytes());
        let _ = handle.flush();

        Ok(resolved)
    } else {
        resolve_defaults(workspace, marketplace, no_starter, flag_name, engine_flag)
            .map_err(|e| -> Box<dyn std::error::Error> { e.into() })
    }
}

/// Prompt the user about removing migrated source files.
///
/// Returns `true` if the user confirmed cleanup, `false` otherwise.
/// When `interactive` is `false`, returns `Ok(false)` immediately.
pub fn resolve_migrate_cleanup(
    interactive: bool,
    outcome: &libaipm::migrate::Outcome,
) -> Result<bool, Box<dyn std::error::Error>> {
    if !interactive {
        return Ok(false);
    }

    let migrated_count = outcome.migrated_sources().len();
    let steps = migrate_cleanup_prompt_steps(migrated_count);

    if steps.is_empty() {
        return Ok(false);
    }

    inquire::set_global_render_config(styled_render_config());
    let answers = libaipm::wizard::execute_prompts(&steps)?;
    Ok(resolve_migrate_cleanup_answer(&answers))
}

/// Resolve `aipm make plugin` wizard values.
///
/// In interactive mode, runs a two-phase wizard:
/// 1. Prompt for name and engine (if not set via flags)
/// 2. Resolve engine, compute engine-filtered feature options, prompt for features
///
/// In non-interactive mode, validates that required flags are present
/// (`--name` and `--feature`) and defaults engine to `"claude"`.
pub fn resolve_make_plugin(
    interactive: bool,
    flag_name: Option<&str>,
    flag_engine: Option<&str>,
    flag_features: &[String],
) -> Result<MakePluginResult, Box<dyn std::error::Error>> {
    if !interactive {
        let name = flag_name
            .map(str::to_string)
            .ok_or_else(|| Box::new(libaipm::make::Error::MissingFlag("name".to_string())))?;
        if flag_features.is_empty() {
            return Err(Box::new(libaipm::make::Error::MissingFlag("feature".to_string())));
        }
        let engine = flag_engine.map_or_else(|| "claude".to_string(), str::to_string);
        return Ok((name, engine, flag_features.to_vec()));
    }

    inquire::set_global_render_config(styled_render_config());

    // Phase 1: Name + Engine
    let mut phase1_steps: Vec<PromptStep> = Vec::new();
    if flag_name.is_none() {
        phase1_steps.push(PromptStep {
            label: "Plugin name",
            kind: PromptKind::Text { placeholder: "my-plugin".to_string(), validate: true },
            help: Some("Lowercase, hyphens allowed"),
        });
    }
    if flag_engine.is_none() {
        phase1_steps.push(PromptStep {
            label: "Target engine",
            kind: PromptKind::Select { options: wizard::ENGINE_OPTIONS.to_vec(), default_index: 0 },
            help: Some("Which AI coding tool will this plugin target?"),
        });
    }
    let phase1_answers = libaipm::wizard::execute_prompts(&phase1_steps)?;

    // Resolve name + engine from phase 1 answers
    let mut idx = 0;
    let name = flag_name.map_or_else(
        || {
            let r = match phase1_answers.get(idx) {
                Some(PromptAnswer::Text(t)) => t.clone(),
                _ => String::new(),
            };
            idx += 1;
            r
        },
        str::to_string,
    );

    let engine = flag_engine.map_or_else(
        || match phase1_answers.get(idx) {
            Some(PromptAnswer::Selected(i)) => wizard::engine_from_index(*i).to_string(),
            _ => "claude".to_string(),
        },
        str::to_string,
    );

    // Validate the resolved engine before using it to filter features.
    // Accepts the legacy "claude"/"copilot"/"both" CLI strings as well
    // as canonical kebab-case names (e.g. "copilot").
    let engine_set = libaipm::make::engine_features::parse_engine_arg(&engine)
        .ok_or_else(|| Box::new(libaipm::make::Error::InvalidEngine(engine.clone())))?;

    // Phase 2: Features (filtered by resolved engine)
    if flag_features.is_empty() {
        use libaipm::make::FeatureExt;
        let available_set = libaipm::make::engine_features::features_for_engine_set(engine_set);
        let available: Vec<libaipm::make::Feature> = libaipm::make::Feature::all()
            .iter()
            .copied()
            .filter(|f| {
                available_set.contains(libaipm::make::engine_features::feature_kind_bit(*f))
            })
            .collect();
        let labels: Vec<&'static str> = available.iter().copied().map(FeatureExt::label).collect();
        let defaults: Vec<bool> = labels.iter().map(|_| false).collect();
        let cli_names: Vec<&str> = available.iter().copied().map(FeatureExt::cli_name).collect();

        let feature_step = PromptStep {
            label: "AI features to include",
            kind: PromptKind::MultiSelect { options: labels, defaults, min_selections: 0 },
            help: Some("Select the features for your plugin"),
        };
        let feature_answers = libaipm::wizard::execute_prompts(&[feature_step])?;

        let features: Vec<String> = match feature_answers.first() {
            Some(PromptAnswer::MultiSelected(indices)) => {
                indices.iter().filter_map(|&i| cli_names.get(i).map(|s| (*s).to_string())).collect()
            },
            _ => Vec::new(),
        };
        if features.is_empty() {
            return Err(Box::new(libaipm::make::Error::MissingFlag("feature".to_string())));
        }
        Ok((name, engine, features))
    } else {
        Ok((name, engine, flag_features.to_vec()))
    }
}

/// Resolve `aipm pack init` wizard values.
///
/// In interactive mode, prompts for package name, description, and plugin type
/// (skipping prompts for values already supplied via CLI flags).
/// In non-interactive mode, returns the flag values as-is.
pub fn resolve_pack_init(
    interactive: bool,
    dir: &Path,
    flag_name: Option<String>,
    flag_type: Option<PluginType>,
) -> Result<PackInitResult, Box<dyn std::error::Error>> {
    if !interactive {
        return Ok((flag_name, flag_type));
    }
    inquire::set_global_render_config(styled_render_config());
    let steps = wizard::package_prompt_steps(dir, flag_name.as_deref(), flag_type);
    let answers = libaipm::wizard::execute_prompts(&steps)?;
    Ok(wizard::resolve_package_answers(&answers, flag_name.as_deref(), flag_type))
}
