import { elicitOrFallback, questionsToFormSchema } from '../../engine/elicitation/elicit-helper.js';
import { ti } from '../../i18n/index.js';
import { AutopilotSummaryCollector } from '../../engine/autopilot/summary-collector.js';
import { hashProjectPath, knowledgeStore, globalStore, licenseStore, technologySelectionStore, } from '../../storage/index.js';
import { addProject } from '../../storage/global-projects-store.js';
import { getCurrentTier } from '../license-gate.js';
import { checkLimits } from '../../engine/license-validator.js';
import { analyzeProject, checkProjectCompleteness } from '../../engine/analyzer.js';
import { parseProjectQualityRules } from '../../engine/auditor-claude-md.js';
import { detectMissingAreas, buildRequestPrompt } from '../../engine/context-merger.js';
import { detectProjectPrivacyConfig, maybeGenerateEnvExample, buildBeginnerEnvGuide, } from './helpers.js';
import { checkBundledVersionGap } from '../../engine/version-detector/bundled-version-checker.js';
import { checkAndFixBundledVersion } from '../../engine/mcp-config/mcp-config-writer.js';
import { buildInitProjectResult } from './result-builder.js';
import { fetchRecommendedSkills } from '../suggest-tooling/skills-fetcher.js';
import { autoCompleteSpecs } from '../git/auto-complete-ops.js';
import { regeneratePages } from '../../engine/doc-generator/portal/index.js';
import { detectStackPatterns } from './stack-detector.js';
import { buildProjectConfig } from './config-builder.js';
import { runSpecMigrations, listProjectSpecs } from './migration-runner.js';
import { runScaffoldWriter } from './scaffold-writer.js';
import { detectAncestorDataDirs, buildAncestorDataWarning } from './ancestor-data-detector.js';
import { readAutoInstallFlag, orchestrateSkillInstalls, runHealthCheckWithBaseline, runConventionScanSafe, fireTelemetry, } from './lifecycle-helpers.js';
import { injectProactiveRules } from '../../engine/claude-md-injector/index.js';
import { discoverSkillsForInit } from '../../engine/skill-bootstrap/registry-fetcher.js';
import { join } from 'node:path';
import { mkdir, stat } from 'node:fs/promises';
import { generateAgentTeamsRulesIfMissing, generateWorkflowRulesIfMissing, } from './rules-generator.js';
import { installUniversalRules } from '../../engine/universal-rules/installer.js';
import { detectHost } from '../../engine/host-detection/detect-host.js';
import { configurePlanuHooks } from './hooks-configurator.js';
import { configureClaudeKeybindings } from '../../engine/claude-setup/keybindings-configurator.js';
import { autoInstallRecommendedSkills } from './skill-auto-installer.js';
import { withAudit } from '../../engine/autopilot/audit-logger.js';
import { trackCost } from '../../engine/cost-tracking/operation-tracker.js';
import { applyStackBasedGroupActivations } from './tool-group-activator.js';
import { checkGate } from '../../engine/clarification-gate/gate.js';
import { upsertToken, hashQuestions } from '../../engine/clarification-gate/token-store.js';
import { reconcileInteractiveQuestionHooks } from '../reconcile-interactive-question-hooks.js';
import { resolveNewProjectOnboarding } from '../../engine/onboarding/new-project-resolver.js';
import { interactiveResult } from '../response-helpers.js';
/** Frontend framework groups — mutually exclusive. Two or more detected → multi-stack conflict. */
const FRONTEND_FRAMEWORK_GROUPS = [
    ['react', 'next', 'next.js', 'nextjs', 'remix', 'preact'],
    ['vue', 'nuxt', 'nuxt.js', 'nuxtjs'],
    ['angular', 'angularjs'],
    ['svelte', 'sveltekit'],
    ['solid', 'solidstart'],
    ['qwik'],
    ['ember'],
];
/**
 * SPEC-619 AC5: Detect when multiple conflicting frontend frameworks are present in the stack.
 * Returns the detected framework groups, or null when there is no conflict.
 */
function detectMultipleFrameworks(stack) {
    const lower = stack.map((s) => s.toLowerCase());
    const matched = [];
    for (const group of FRONTEND_FRAMEWORK_GROUPS) {
        const hit = group.find((fw) => lower.includes(fw));
        if (hit) {
            // Use the first item in the group as the canonical label (e.g. "react", "vue")
            const canonical = group[0];
            matched.push(canonical);
        }
    }
    if (matched.length >= 2) {
        return { detected: matched };
    }
    return null;
}
// eslint-disable-next-line max-lines-per-function
export async function handleInitProject(params, server) {
    // SPEC-584: Gate check — block if pending clarification token exists and no answers provided
    const gateProjectId = hashProjectPath(params.projectPath);
    const gateResult = await checkGate({
        projectId: gateProjectId,
        toolName: 'init_project',
        clarificationAnswers: params.clarificationAnswers,
    });
    if (!gateResult.allow) {
        return gateResult.error;
    }
    // eslint-disable-next-line max-lines-per-function, complexity
    return trackCost(params.projectPath, 'init_project', async () => {
        const { projectPath, mode, locale, hourlyRate, experienceLevel, userProfile, autoInstallSkills, workMode, } = params;
        try {
            let approvedTechnologyContract = params.technologySelectionContract ?? null;
            if (mode === 'new_project' && approvedTechnologyContract === null) {
                const resolution = await resolveNewProjectOnboarding({
                    description: params.appName ?? params.projectType ?? 'New project',
                    parentWorkspacePath: params.parentWorkspacePath,
                    projectPath,
                    appName: params.appName,
                    appSlug: params.appSlug,
                    projectType: params.projectType,
                    platform: params.platform,
                    language: params.language,
                    framework: params.framework,
                    database: params.database,
                    createDirectory: params.createDirectory,
                    clarificationAnswers: params.clarificationAnswers,
                });
                if (!resolution.ready) {
                    return interactiveResult(resolution.questions, 'init_project new_project mode needs a confirmed child app folder and technology choices before writing project files.', 'universal');
                }
                approvedTechnologyContract = resolution.contract ?? null;
            }
            // Validate projectPath exists and is a directory before any I/O
            try {
                const pathStat = await stat(projectPath);
                if (!pathStat.isDirectory()) {
                    return {
                        content: [
                            {
                                type: 'text',
                                text: `init_project requires a directory path. "${projectPath}" is not a directory.`,
                            },
                        ],
                        isError: true,
                    };
                }
            }
            catch {
                if (mode === 'new_project' && params.createDirectory === true) {
                    await mkdir(projectPath, { recursive: true });
                }
                else {
                    return {
                        content: [
                            {
                                type: 'text',
                                text: `Project directory does not exist: "${projectPath}". Create the directory first and try again.`,
                            },
                        ],
                        isError: true,
                    };
                }
            }
            const projectId = hashProjectPath(projectPath);
            // SPEC-1007: migrate legacy <cwd>/data/projects/{id} into ~/.planu/data/
            // before any storage call so the rest of init_project sees a single
            // canonical location. Best-effort — errors here are surfaced via the
            // ancestor warning below rather than aborting the whole init.
            try {
                const { migrateLegacyDataDir } = await import('../../storage/migrations/cwd-to-absolute.js');
                await migrateLegacyDataDir(projectId, projectPath);
            }
            catch {
                // ignore — fall through to detectAncestorDataDirs which will warn.
            }
            // Detect divergent ancestor data dirs that would otherwise corrupt state
            // silently (e.g. user ran init_project from /repo/ then from /repo/sub/).
            // Read-only — we surface the finding but never touch existing data.
            const ancestorReport = await detectAncestorDataDirs(projectPath, projectId);
            const ancestorWarning = buildAncestorDataWarning(ancestorReport);
            if (ancestorWarning !== null) {
                console.warn(`[Planu] init_project: ${ancestorWarning}`);
            }
            // Check if project is already initialized
            const existing = await knowledgeStore.getKnowledge(projectId);
            const isUpdate = existing !== null;
            // License: check project limit only for new projects
            if (!isUpdate) {
                const tier = await getCurrentTier();
                const projectCount = await licenseStore.countActiveProjects();
                const limitCheck = checkLimits(tier, projectCount, 'maxProjects');
                if (!limitCheck.allowed) {
                    return { content: [{ type: 'text', text: limitCheck.message }], isError: true };
                }
            }
            // Get global config for defaults
            const globalConfig = await globalStore.getGlobalConfig();
            const effectiveLocale = locale ?? existing?.locale ?? globalConfig.defaultLocale;
            const effectiveExperience = experienceLevel ?? existing?.experienceLevel ?? globalConfig.defaultExperienceLevel;
            // Run full project analysis
            const knowledge = await analyzeProject(projectPath, projectId, effectiveLocale, effectiveExperience);
            if (approvedTechnologyContract !== null) {
                knowledge.language = approvedTechnologyContract.language ?? knowledge.language;
                knowledge.framework =
                    approvedTechnologyContract.framework !== undefined
                        ? approvedTechnologyContract.framework
                        : knowledge.framework;
                const stackAdditions = [
                    approvedTechnologyContract.language,
                    approvedTechnologyContract.framework ?? undefined,
                    approvedTechnologyContract.database ?? undefined,
                ].filter((item) => typeof item === 'string' && item.length > 0);
                knowledge.stack = Array.from(new Set([...knowledge.stack, ...stackAdditions]));
            }
            // Apply overrides
            if (userProfile) {
                knowledge.userProfile = userProfile;
            }
            // SPEC-619 AC5: Emit interactiveQuestions when multiple frontend frameworks detected
            const multiStackResult = detectMultipleFrameworks(knowledge.stack);
            if (multiStackResult !== null && !params.clarificationAnswers) {
                const questions = [
                    {
                        question: 'Multiple frontend frameworks were detected. Which is the primary framework?',
                        header: 'Primary Framework',
                        options: multiStackResult.detected.map((fw, idx) => ({
                            label: idx === 0 ? `${fw} (Recommended)` : fw,
                            description: `Use ${fw} as the primary UI framework`,
                        })),
                        multiSelect: false,
                    },
                ];
                const earlyReturn = {
                    content: [
                        {
                            type: 'text',
                            text: '⚡ INTERACTIVE — call AskUserQuestion now with the interactiveQuestions from structuredContent',
                        },
                    ],
                    structuredContent: {
                        needsClarification: true,
                        message: 'Multiple frontend frameworks detected. Please choose the primary one to configure the project correctly.',
                        interactiveQuestions: questions,
                    },
                };
                upsertToken({
                    toolName: 'init_project',
                    projectId,
                    questionsHash: hashQuestions(questions),
                    emittedAt: new Date().toISOString(),
                    questions,
                }).catch(() => {
                    /* best-effort */
                });
                return earlyReturn;
            }
            // SPEC-026a: Compute project completeness score
            const completeness = await checkProjectCompleteness(projectPath);
            // Auto-configure qualityProfile from CLAUDE.md / .cursorrules (SPEC-010)
            const claudeMdRules = await parseProjectQualityRules(projectPath);
            if (claudeMdRules.length > 0) {
                knowledge.qualityProfile.customRules = [
                    ...knowledge.qualityProfile.customRules,
                    ...claudeMdRules,
                ];
            }
            // If hourly rate provided, update global config too
            if (hourlyRate !== undefined) {
                await globalStore.updateGlobalConfig({ hourlyRate });
            }
            // SPEC-033: Detect modern architecture patterns and stack info
            const stackResult = await detectStackPatterns(projectPath);
            const { modernPatterns, dddConfig, agileDetection, orchestratorInfo, composeMultiplatform } = stackResult;
            // SPEC-568: Smart tool group activation based on detected stack.
            // Runs before saveKnowledge so the snapshot can be persisted in one write.
            const groupProfileSnapshot = applyStackBasedGroupActivations(knowledge.stack);
            if (groupProfileSnapshot) {
                knowledge.groupProfile = groupProfileSnapshot;
            }
            // Save initial project knowledge
            await knowledgeStore.saveKnowledge(projectId, knowledge);
            if (approvedTechnologyContract !== null) {
                await technologySelectionStore.writeTechnologySelectionContract(projectPath, {
                    ...approvedTechnologyContract,
                    projectPath,
                });
            }
            // SPEC-789: Register project in global workspace registry so workspace_health
            // and list_registered_projects can discover it without manual registration.
            addProject(projectPath).catch(() => {
                /* best-effort — never block init_project */
            });
            // SPEC-131: Build legal profile and generate planu.json / What's New
            // SPEC-313: Pass workMode to persist in planu.json
            const configResult = await buildProjectConfig(projectPath, projectId, knowledge, workMode);
            const { planuConfigPath, planuConfigGenerated, whatsNew } = configResult;
            // Run spec migrations (v0.30 -> v0.40, legacy paths, prefix normalization)
            const { discoveryResult, migrationResult, folderMigrationResult } = await runSpecMigrations(projectPath, projectId, knowledge);
            // Auto-fetch recommended skills based on detected stack
            const recommendedSkills = await fetchRecommendedSkills(knowledge);
            // Auto-install or queue skills (opt-out via planu.json or input param, SPEC-185)
            const autoInstallFromConfig = autoInstallSkills ?? (await readAutoInstallFlag(planuConfigPath));
            const { skillsAutoInstalled, skillsPendingInstall, skillsSkipped } = await orchestrateSkillInstalls(recommendedSkills, projectPath, autoInstallFromConfig);
            // Write scaffold files: rules, git setup, constitution, CLAUDE.md, lint, architecture rules
            const scaffoldResult = await runScaffoldWriter(projectPath, projectId, knowledge, recommendedSkills, params.permissionsMode, params.pluginsMode);
            const { platform, generatedRules, rulesWritten, additionalFilesWritten, hooksInstalled, gitFlowType, gitignoreUpdated, gitRepoDetected, constitutionInitialized, claudeMdUpdated, eslintCreated, prettierCreated, lintSuggestions, architectureRulesWritten, architectureRulesSkipped, planuWorkflowInjected, planuHooksConfigured, planuRulesWritten, gitAutoStageInjected, } = scaffoldResult;
            // SPEC-444: Inject proactive behavior rules into project CLAUDE.md
            const proactiveRulesInjected = await injectProactiveRules(join(projectPath, 'CLAUDE.md'), '1.22.0')
                .then(() => true)
                .catch(() => false);
            // SPEC-451: Auto-discover skills from registries using the detected stack
            const discoveredRegistrySkills = await discoverSkillsForInit(projectPath, knowledge.stack.length > 0 ? knowledge.stack : undefined).catch(() => []);
            // SPEC-176: Auto-mark specs as done when their branch is merged to develop
            // SPEC-720: autoCompleteSpecs routes through gates — extract completed IDs for display
            const autoCompletedSpecs = await autoCompleteSpecs(projectId, projectPath)
                .then((r) => r.completed)
                .catch(() => []);
            // SPEC-036: Request context when knowledge is incomplete
            const missingAreas = detectMissingAreas(knowledge);
            let contextRequestPrompt = null;
            const needsContext = missingAreas.length > 0 && knowledge.contextRequested !== true;
            if (needsContext) {
                contextRequestPrompt = buildRequestPrompt(missingAreas, knowledge);
                await knowledgeStore.saveKnowledge(projectId, { ...knowledge, contextRequested: true });
            }
            // SPEC-025 tech-debt: Auto-generate .env.example and beginner guide
            const envVars = knowledge.envSetup.variables;
            const envExampleGenerated = await maybeGenerateEnvExample(projectPath, envVars);
            const beginnerEnvGuide = effectiveExperience === 'beginner' && envVars.length > 0
                ? buildBeginnerEnvGuide(envVars)
                : null;
            // SPEC-030: Detect legal framework and third-party data processors
            const privacyConfig = await detectProjectPrivacyConfig(projectPath);
            // SPEC-180: Project health check (best-effort)
            const allSpecs = await listProjectSpecs(projectId);
            const specSummaries = allSpecs.map((s) => ({
                specId: s.id,
                status: s.status,
                gitBranch: s.gitBranch,
                updatedAt: s.updatedAt,
            }));
            const healthReport = await runHealthCheckWithBaseline(projectPath, projectId, knowledge, specSummaries);
            // SPEC-491: Auto-cleanup planu/ — remove legacy HTMLs, migrate to lean spec format (fire-and-forget)
            import('../../engine/spec-migrator/planu-root-cleaner.js')
                .then(({ cleanPlanuRoot }) => cleanPlanuRoot(join(projectPath, 'planu')))
                .catch(() => {
                /* best-effort */
            });
            // SPEC-193: Generate portal pages (fire-and-forget)
            void withAudit(projectPath, 'init_project', 'regeneratePages', () => regeneratePages(projectPath, ['knowledge', 'risks', 'decisions']), (pages) => ({ pages: pages.length })).catch(() => {
                /* best-effort — never fail init */
            });
            // SPEC-228: Deep scan — detect conventions and write planu/conventions.json (best-effort)
            const conventionScanStatus = await runConventionScanSafe(projectPath);
            // SPEC-645: Detect LLM client and cache in conventions.json (fire-and-forget)
            void import('../../engine/client-detection.js')
                .then(({ detectAndCacheClient }) => detectAndCacheClient(projectPath))
                .catch(() => {
                /* best-effort */
            });
            // SPEC-644: Fetch and cache model tier mapping from provider APIs (fire-and-forget)
            void import('../../engine/model-tier-resolver.js')
                .then(({ fetchAndCacheModelMapping }) => fetchAndCacheModelMapping(projectPath))
                .catch(() => {
                /* best-effort */
            });
            // SPEC-668: Refresh stale skills (>30 days) in background (fire-and-forget)
            void import('../../engine/skill-registry/ttl-refresh.js')
                .then(({ triggerSkillTTLRefresh }) => {
                triggerSkillTTLRefresh(projectPath);
            })
                .catch(() => {
                /* best-effort */
            });
            // SPEC-200: Anonymous opt-in telemetry — fire-and-forget, never blocks response
            const neverPrompted = await fireTelemetry(knowledge).catch(() => false);
            // SPEC-457: Auto-bootstrap rules, hooks, skills for detected stack (fire-and-forget)
            void withAudit(projectPath, 'init_project', 'generateAgentTeamsRulesIfMissing', () => generateAgentTeamsRulesIfMissing(projectPath)).catch(() => {
                /* best-effort */
            });
            void withAudit(projectPath, 'init_project', 'generateWorkflowRulesIfMissing', () => generateWorkflowRulesIfMissing(projectPath)).catch(() => {
                /* best-effort */
            });
            void withAudit(projectPath, 'init_project', 'configurePlanuHooks', () => configurePlanuHooks(projectPath)).catch(() => {
                /* best-effort */
            });
            // SPEC-779: Install universal rules catalog before returning so init_project
            // has no delayed project mutations after the tool result is delivered.
            try {
                const hostHint = detectHost();
                const host = hostHint === 'codex' ? 'codex' : hostHint === 'gemini' ? 'gemini' : 'claude-code';
                await installUniversalRules(projectPath, host);
            }
            catch {
                /* best-effort */
            }
            // SPEC-566: Auto-configure Claude Code keybindings (shift+enter → submit) — fire-and-forget
            void configureClaudeKeybindings()
                .then((configured) => configured ? globalStore.updateGlobalConfig({ keybindingsConfigured: true }) : undefined)
                .catch(() => {
                /* best-effort */
            });
            if (autoInstallFromConfig && recommendedSkills.length > 0) {
                void withAudit(projectPath, 'init_project', 'autoInstallRecommendedSkills', () => autoInstallRecommendedSkills(projectPath, recommendedSkills), (installed) => ({ count: Array.isArray(installed) ? installed.length : 0 })).catch(() => {
                    /* best-effort */
                });
            }
            // SPEC-467: Detect branches for LLM branch-awareness
            let branchInfo = null;
            try {
                const { detectBranches } = await import('../../engine/git/branch-detector.js');
                branchInfo = detectBranches(projectPath);
            }
            catch {
                /* best-effort */
            }
            // SPEC-469: Build autopilot summary from setup steps performed
            const collector = new AutopilotSummaryCollector();
            if (knowledge.stack.length > 0) {
                collector.pushOk('stack-detection', `Stack detected: ${knowledge.stack.join(', ')}`);
            }
            if (rulesWritten) {
                collector.pushOk('rules-generated', `Generated ${String(additionalFilesWritten)} rule files for ${platform}`);
            }
            if (hooksInstalled.length > 0) {
                collector.pushOk('hooks-installed', 'Git hooks installed (pre-commit, commit-msg, pre-push)');
            }
            if (gitignoreUpdated) {
                collector.pushOk('gitignore', 'Updated .gitignore with planu/ and project rules');
            }
            if (skillsAutoInstalled.length > 0) {
                collector.pushOk('skills-installed', `Auto-installed skills: ${skillsAutoInstalled.join(', ')}`);
            }
            const toolResult = buildInitProjectResult({
                projectId,
                projectPath,
                isUpdate,
                knowledge,
                claudeMdRules,
                completeness,
                modernPatterns,
                dddConfig,
                agileDetection,
                orchestratorInfo,
                composeMultiplatform,
                privacyConfig,
                envExampleGenerated,
                beginnerEnvGuide,
                contextRequestPrompt,
                missingAreas,
                recommendedSkills,
                platform,
                generatedRules,
                rulesWritten,
                additionalFilesWritten,
                planuConfigGenerated,
                planuConfigPath,
                migrationResult,
                discoveryResult,
                folderMigrationResult,
                hooksInstalled,
                gitFlowType,
                gitignoreUpdated,
                gitRepoDetected,
                constitutionInitialized,
                claudeMdUpdated,
                whatsNew,
                autoCompletedSpecs,
                skillsAutoInstalled,
                skillsPendingInstall,
                skillsSkipped,
                eslintCreated,
                prettierCreated,
                lintSuggestions,
                healthReport,
                architectureRulesWritten,
                architectureRulesSkipped,
                planuWorkflowInjected,
                planuHooksConfigured,
                planuRulesWritten,
                gitAutoStageInjected,
                telemetryOptInPrompt: neverPrompted ? 'show' : null,
                conventionScanStatus,
                proactiveRulesInjected,
                discoveredRegistrySkills,
                branchInfo,
            });
            // Inject autopilotSummary into structuredContent
            if (collector.hasEntries() && toolResult.structuredContent) {
                toolResult.structuredContent.autopilotSummary = collector.getMessages();
            }
            // SPEC-584: Auto-install PostToolUse hook in the client project (fire-and-forget)
            reconcileInteractiveQuestionHooks(projectPath).catch(() => {
                /* best-effort — never block init_project */
            });
            // SPEC-585: Install PreCompaction session-safeguard hook (fire-and-forget)
            import('../reconcile-session-safeguard-hook.js')
                .then(({ installSessionSafeguardHook }) => installSessionSafeguardHook(projectPath))
                .catch(() => {
                /* best-effort — never block init_project */
            });
            // SPEC-595: If interactiveQuestions are present and client supports elicitation, use native UI
            const rawEmitted = toolResult.structuredContent?.interactiveQuestions;
            if (server !== undefined && Array.isArray(rawEmitted) && rawEmitted.length > 0) {
                const emittedQuestions = rawEmitted;
                const formSchema = questionsToFormSchema(emittedQuestions);
                const outcome = await elicitOrFallback(server, 'A few questions to configure your project. Please answer them.', formSchema, emittedQuestions);
                if (outcome.mode === 'native' && outcome.action === 'accept' && outcome.content) {
                    // Embed elicited answers and strip interactiveQuestions from structuredContent
                    const { interactiveQuestions: _removed, ...restStructured } = toolResult.structuredContent ?? {};
                    return {
                        ...toolResult,
                        structuredContent: {
                            ...restStructured,
                            elicitedAnswers: outcome.content,
                        },
                    };
                }
                // fallback or cancel: fall through to SPEC-584 token path below
            }
            // SPEC-584: Persist token when emitting interactiveQuestions so gate blocks retries
            if (Array.isArray(rawEmitted) && rawEmitted.length > 0) {
                const emittedQuestions = rawEmitted;
                upsertToken({
                    toolName: 'init_project',
                    projectId,
                    questionsHash: hashQuestions(emittedQuestions),
                    emittedAt: new Date().toISOString(),
                    questions: emittedQuestions,
                }).catch(() => {
                    /* best-effort — token persistence should not block the response */
                });
            }
            // SPEC-756: lazy version check — fire-and-forget, non-blocking
            checkBundledVersionGap(2000)
                .then((gap) => {
                if (gap.gap) {
                    checkAndFixBundledVersion(projectPath, gap.current, gap.latest).catch(() => {
                        /* best-effort — never block init_project */
                    });
                }
            })
                .catch(() => {
                /* best-effort */
            });
            return toolResult;
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return {
                content: [{ type: 'text', text: ti('errors.internalError', { message }) }],
                isError: true,
            };
        }
    }); // end trackCost
}
//# sourceMappingURL=handler.js.map