/**
 * Create Feature Use Case
 *
 * Creates a new feature entity with a git worktree for isolated development,
 * initializes the spec directory with YAML templates, then spawns the
 * feature agent to begin autonomous SDLC processing.
 *
 * Business Rules:
 * - Metadata generation delegated to MetadataGenerator service
 * - Slug uniqueness delegated to SlugResolver service
 * - Full user input is preserved as-is in spec.yaml
 * - A git worktree is created at ~/.shep/repos/HASH/wt/SLUG/
 * - Spec YAML files are scaffolded at WORKTREE/specs/NNN-SLUG/
 * - Feature agent is spawned with the spec dir path
 * - specPath is persisted on the Feature record
 * - Initial lifecycle is Requirements
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
import { injectable, inject } from 'tsyringe';
import { randomUUID } from 'node:crypto';
import { SdlcLifecycle, AgentRunStatus, BuildMode, } from '../../../../domain/generated/output.js';
import { createDefaultSettings } from '../../../../domain/factories/settings-defaults.factory.js';
import { POST_IMPLEMENTATION } from '../../../../domain/lifecycle-gates.js';
import { MetadataGenerator } from './metadata-generator.js';
import { SlugResolver } from './slug-resolver.js';
let CreateFeatureUseCase = class CreateFeatureUseCase {
    featureRepo;
    worktreeService;
    agentProcess;
    runRepository;
    specInitializer;
    metadataGenerator;
    slugResolver;
    repositoryRepo;
    gitPrService;
    attachmentStorage;
    agentValidator;
    skillInjector;
    settingsRepository;
    logger;
    constructor(featureRepo, worktreeService, agentProcess, runRepository, specInitializer, metadataGenerator, slugResolver, repositoryRepo, gitPrService, attachmentStorage, agentValidator, skillInjector, settingsRepository, logger) {
        this.featureRepo = featureRepo;
        this.worktreeService = worktreeService;
        this.agentProcess = agentProcess;
        this.runRepository = runRepository;
        this.specInitializer = specInitializer;
        this.metadataGenerator = metadataGenerator;
        this.slugResolver = slugResolver;
        this.repositoryRepo = repositoryRepo;
        this.gitPrService = gitPrService;
        this.attachmentStorage = attachmentStorage;
        this.agentValidator = agentValidator;
        this.skillInjector = skillInjector;
        this.settingsRepository = settingsRepository;
        this.logger = logger;
    }
    /**
     * Load settings from the repository, throwing if unavailable. Replaces the
     * former getSettings() module-level singleton import — application layer
     * must not reach into infrastructure directly.
     */
    async loadSettingsOrThrow() {
        const settings = await this.settingsRepository.load();
        if (settings === null) {
            throw new Error('Settings not initialized. Cannot create feature.');
        }
        return settings;
    }
    /**
     * Full synchronous execution: creates record, initializes worktree/spec, spawns agent.
     * Used by the CLI which shows a spinner and needs everything done before returning.
     */
    async execute(input) {
        const { feature, shouldSpawn } = await this.createRecord(input);
        const { warning, updatedFeature } = await this.initializeAndSpawn(feature, input, shouldSpawn);
        return { feature: updatedFeature, warning };
    }
    /**
     * Phase 1 (fast): Creates the feature + agent run records in DB and returns immediately.
     * The feature has a real UUID and a preliminary slug derived from the provided name.
     * No AI calls, no git operations — just DB writes.
     */
    async createRecord(input) {
        // Resolve the effective build mode. When the caller does not specify one,
        // we derive a sensible default from the legacy `fast` flag so existing
        // callers (web action / CLI) continue to compile and behave identically.
        const effectiveBuildMode = input.buildMode ?? ((input.fast ?? false) ? BuildMode.Fast : BuildMode.Application);
        const isFastMode = effectiveBuildMode === BuildMode.Fast;
        // Soft-warn when a spec-mode feature is created without a parent
        // application. The legacy "FAB → New feature → Spec" path intentionally
        // creates spec features without an application context, so this MUST NOT
        // throw — it is a breadcrumb only.
        if (effectiveBuildMode === BuildMode.Spec && input.applicationId === undefined) {
            this.logger.warn('[CreateFeatureUseCase] spec-mode feature created without an applicationId — proceeding');
        }
        let initialLifecycle = isFastMode
            ? SdlcLifecycle.Implementation
            : SdlcLifecycle.Requirements;
        let shouldSpawn = true;
        let effectiveRepoPath = input.repositoryPath.replace(/\\/g, '/');
        // Pending flag takes precedence — user explicitly defers agent execution.
        // Parent gate validation is deferred to StartFeatureUseCase.
        if (input.pending) {
            initialLifecycle = SdlcLifecycle.Pending;
            shouldSpawn = false;
        }
        if (input.parentId) {
            const parent = await this.featureRepo.findById(input.parentId);
            if (!parent) {
                throw new Error(`Parent feature not found: ${input.parentId}`);
            }
            effectiveRepoPath = parent.repositoryPath;
            // Cycle detection — O(depth) upward walk through ancestor chain
            const visited = new Set([input.parentId]);
            let cursor = parent.parentId;
            while (cursor) {
                if (visited.has(cursor)) {
                    throw new Error(`Cycle detected in feature dependency chain at feature: ${cursor}`);
                }
                visited.add(cursor);
                const ancestor = await this.featureRepo.findById(cursor);
                cursor = ancestor?.parentId ?? undefined;
            }
            // Skip gate logic when pending — parent gate is deferred to start time
            if (!input.pending) {
                if (parent.lifecycle === SdlcLifecycle.Blocked ||
                    !POST_IMPLEMENTATION.has(parent.lifecycle)) {
                    initialLifecycle = SdlcLifecycle.Blocked;
                    shouldSpawn = false;
                }
                else {
                    initialLifecycle = SdlcLifecycle.Started;
                    shouldSpawn = true;
                }
            }
        }
        // Resolve or create repository entity for this path
        const normalizedPath = effectiveRepoPath.replace(/\\/g, '/').replace(/\/+$/, '') || effectiveRepoPath;
        let repository = await this.repositoryRepo.findByPath(normalizedPath);
        const now = new Date();
        if (!repository) {
            const repoName = normalizedPath.split('/').pop() ?? normalizedPath;
            repository = {
                id: randomUUID(),
                name: repoName,
                path: normalizedPath,
                bedrockEnabled: false,
                createdAt: now,
                updatedAt: now,
            };
            repository = await this.repositoryRepo.create(repository);
            if (!repository) {
                throw new Error(`Failed to create or retrieve repository record for path: ${normalizedPath}`);
            }
        }
        const featureName = input.name ?? input.userInput.slice(0, 100);
        const runId = randomUUID();
        const featureId = randomUUID();
        // Use the feature ID as a temporary slug so it never collides with the
        // AI-generated slug in initializeAndSpawn() → resolveUniqueSlug().
        const feature = {
            id: featureId,
            name: featureName,
            slug: featureId,
            description: input.description ?? '',
            userQuery: input.userInput,
            repositoryPath: effectiveRepoPath,
            branch: '',
            lifecycle: initialLifecycle,
            messages: [],
            relatedArtifacts: [],
            buildMode: effectiveBuildMode,
            fast: isFastMode,
            ...(input.applicationId ? { applicationId: input.applicationId } : {}),
            push: input.push ?? false,
            openPr: input.openPr ?? false,
            forkAndPr: input.forkAndPr ?? false,
            commitSpecs: input.commitSpecs ?? true,
            ciWatchEnabled: input.ciWatchEnabled ?? true,
            enableEvidence: input.enableEvidence ?? false,
            injectSkills: input.injectSkills ?? false,
            commitEvidence: input.commitEvidence ?? false,
            approvalGates: input.approvalGates ?? {
                allowPrd: false,
                allowPlan: false,
                allowMerge: false,
            },
            agentRunId: runId,
            specPath: '',
            repositoryId: repository.id,
            ...(input.parentId ? { parentId: input.parentId } : {}),
            bedrockEnabled: false,
            createdAt: now,
            updatedAt: now,
        };
        await this.featureRepo.create(feature);
        // Create agent run record (pending state — agent not spawned yet)
        const settings = await this.loadSettingsOrThrow();
        const agentRun = {
            id: runId,
            agentType: input.agentType ?? settings.agent.type,
            agentName: 'feature-agent',
            status: AgentRunStatus.pending,
            prompt: input.userInput,
            threadId: randomUUID(),
            featureId: feature.id,
            repositoryPath: effectiveRepoPath,
            ...(input.approvalGates ? { approvalGates: input.approvalGates } : {}),
            ...(input.model
                ? { modelId: input.model }
                : settings.models?.default
                    ? { modelId: settings.models.default }
                    : {}),
            createdAt: now.toISOString(),
            updatedAt: now.toISOString(),
        };
        await this.runRepository.create(agentRun);
        return { feature, shouldSpawn };
    }
    /**
     * Phase 2 (slow): AI metadata generation, git worktree, spec init, agent spawn.
     * Updates the feature record with refined metadata, branch, and specPath.
     */
    async initializeAndSpawn(feature, input, shouldSpawn) {
        const effectiveRepoPath = feature.repositoryPath;
        // Ensure the target directory is a git repository (auto-init if needed)
        await this.worktreeService.ensureGitRepository(effectiveRepoPath);
        const metadata = await this.metadataGenerator.generateMetadata(input.userInput, input.agentType, effectiveRepoPath);
        const originalSlug = metadata.slug;
        const { slug, branch, warning } = await this.slugResolver.resolveUniqueSlug(originalSlug, effectiveRepoPath);
        // Determine next feature number for this repo
        const existingFeatures = await this.featureRepo.list({
            repositoryPath: effectiveRepoPath,
        });
        const featureNumber = existingFeatures.length;
        // Create git worktree branching from the repo's default branch
        const defaultBranch = await this.gitPrService.getDefaultBranch(effectiveRepoPath);
        // Sync the default branch from remote before branching so the feature
        // starts from the latest upstream state. Enabled by default; callers
        // can opt out with `rebaseBeforeBranch: false`.
        if (input.rebaseBeforeBranch !== false) {
            try {
                await this.gitPrService.syncMain(effectiveRepoPath, defaultBranch);
            }
            catch {
                // Sync failure is non-fatal — proceed with local state.
                // Common case: no remote configured (local-only repos).
            }
        }
        const worktreePath = this.worktreeService.getWorktreePath(effectiveRepoPath, branch);
        await this.worktreeService.create(effectiveRepoPath, branch, worktreePath, defaultBranch);
        // Initialize spec directory — full user input goes into spec.yaml as-is
        const { specDir } = await this.specInitializer.initialize(worktreePath, slug, featureNumber, input.userInput, input.fast ? 'fast' : undefined);
        // Commit pending attachments if sessionId was provided (web UI flow)
        let committedAttachments = input.attachments;
        if (input.sessionId) {
            try {
                committedAttachments = this.attachmentStorage.commit(input.sessionId, slug);
            }
            catch {
                // Attachment commit failure should not block feature creation
            }
        }
        // Store CLI --attach files directly (CLI flow)
        if (input.attachmentPaths?.length) {
            try {
                const { readFileSync } = await import('fs');
                const { basename } = await import('path');
                const cliSessionId = `cli-${feature.id}`;
                for (const filePath of input.attachmentPaths) {
                    const buffer = readFileSync(filePath);
                    const name = basename(filePath);
                    this.attachmentStorage.store(buffer, name, 'application/octet-stream', cliSessionId);
                }
                committedAttachments = this.attachmentStorage.commit(cliSessionId, slug);
            }
            catch {
                // Attachment storage failure should not block feature creation
            }
        }
        // Inject curated skills into the worktree (opt-in, guarded by settings or CLI flag)
        const settings = await this.loadSettingsOrThrow();
        const shouldInject = input.injectSkills ?? settings.workflow.skillInjection?.enabled ?? false;
        let injectedSkillNames;
        const skillConfig = settings.workflow.skillInjection ?? createDefaultSettings().workflow.skillInjection;
        if (shouldInject && skillConfig.skills?.length) {
            try {
                const result = await this.skillInjector.inject(worktreePath, skillConfig, effectiveRepoPath);
                injectedSkillNames = [...result.injected, ...result.skipped];
            }
            catch {
                // Skill injection failure must not block feature creation (NFR-3)
            }
        }
        // Update feature record with refined metadata, branch, specPath, and attachments
        const updatedFeature = {
            ...feature,
            name: metadata.name,
            slug,
            description: metadata.description,
            branch,
            specPath: specDir,
            ...(committedAttachments?.length ? { attachments: committedAttachments } : {}),
            ...(injectedSkillNames?.length ? { injectedSkills: injectedSkillNames } : {}),
            updatedAt: new Date(),
        };
        await this.featureRepo.update(updatedFeature);
        // Spawn agent if not blocked
        if (shouldSpawn) {
            // Validate that the configured agent is available before spawning
            // a background worker — prevents the feature from getting stuck
            // with a silent failure in the detached worker process.
            // Skip validation when using mock executor (E2E tests, CI without real agents).
            const effectiveAgentType = input.agentType ?? settings.agent.type;
            const isMockExecutor = process.env.SHEP_MOCK_EXECUTOR === '1';
            const validation = isMockExecutor
                ? { available: true }
                : await this.agentValidator.isAvailable(effectiveAgentType);
            if (!validation.available) {
                // Mark the agent run as failed so the UI shows the error
                await this.runRepository.updateStatus(feature.agentRunId, AgentRunStatus.failed, {
                    error: `Agent "${effectiveAgentType}" is not available: ${validation.error}`,
                    completedAt: new Date(),
                    updatedAt: new Date(),
                });
                // Update feature lifecycle to Started so it doesn't appear as actively running
                await this.featureRepo.update({
                    ...updatedFeature,
                    lifecycle: SdlcLifecycle.Started,
                    updatedAt: new Date(),
                });
                throw new Error(`Agent "${effectiveAgentType}" is not available. ${validation.error ?? 'Please install it and try again.'}`);
            }
            const agentRun = await this.runRepository.findById(feature.agentRunId);
            this.agentProcess.spawn(feature.id, feature.agentRunId, effectiveRepoPath, specDir, worktreePath, {
                ...(input.approvalGates ? { approvalGates: input.approvalGates } : {}),
                threadId: agentRun?.threadId ?? randomUUID(),
                push: input.push ?? false,
                openPr: input.openPr ?? false,
                forkAndPr: input.forkAndPr ?? false,
                commitSpecs: input.commitSpecs ?? true,
                ciWatchEnabled: input.ciWatchEnabled ?? true,
                enableEvidence: input.enableEvidence ?? false,
                commitEvidence: input.commitEvidence ?? false,
                ...(input.fast ? { fast: true } : {}),
                ...(input.agentType ? { agentType: input.agentType } : {}),
                ...(input.model ? { model: input.model } : {}),
            });
        }
        return { warning, updatedFeature };
    }
};
CreateFeatureUseCase = __decorate([
    injectable(),
    __param(0, inject('IFeatureRepository')),
    __param(1, inject('IWorktreeService')),
    __param(2, inject('IFeatureAgentProcessService')),
    __param(3, inject('IAgentRunRepository')),
    __param(4, inject('ISpecInitializerService')),
    __param(5, inject(MetadataGenerator)),
    __param(6, inject(SlugResolver)),
    __param(7, inject('IRepositoryRepository')),
    __param(8, inject('IGitPrService')),
    __param(9, inject('IAttachmentStorageService')),
    __param(10, inject('IAgentValidator')),
    __param(11, inject('ISkillInjectorService')),
    __param(12, inject('ISettingsRepository')),
    __param(13, inject('ILogger')),
    __metadata("design:paramtypes", [Object, Object, Object, Object, Object, MetadataGenerator,
        SlugResolver, Object, Object, Object, Object, Object, Object, Object])
], CreateFeatureUseCase);
export { CreateFeatureUseCase };
