/**
 * FS-ID Allocator - Intelligent Feature ID allocation with chronological placement
 *
 * Allocates FS-XXX IDs for external work items based on creation date,
 * attempting chronological insertion while preventing collisions.
 *
 * Features:
 * - Chronological ID allocation based on work item creation date
 * - Gap filling (insert FS-011E between FS-010 and FS-020)
 * - Append mode fallback (max ID + 1)
 * - Collision detection (checks both FS-XXX and FS-XXXE)
 * - Archive-aware (scans both active and archived features)
 */
import { type ExternalItemMetadata } from '../core/types/origin-metadata.js';
import type { ExternalContainerContext } from '../core/types/increment-metadata.js';
import { Logger } from '../utils/logger.js';
/**
 * Feature metadata extracted from living docs
 */
export interface FeatureMetadata {
    /** Feature ID (e.g., "FS-042", "FS-042E") */
    id: string;
    /** Feature creation timestamp (ISO 8601) */
    createdAt: string;
    /** Origin (internal | external) */
    origin: 'internal' | 'external';
    /** External ID if external origin */
    externalId?: string;
    /** Path to feature folder */
    path: string;
}
/**
 * External work item to be allocated an FS-ID
 */
export interface ExternalWorkItem {
    /** External ID (e.g., "GH-#638", "JIRA-SPEC-789") */
    externalId: string;
    /** Work item title */
    title: string;
    /** Creation timestamp (ISO 8601) */
    createdAt: string;
    /** External URL */
    externalUrl: string;
}
/**
 * ID allocation result
 */
export interface AllocationResult {
    /** Allocated FS-ID (e.g., "FS-042E") */
    id: string;
    /** Allocation strategy used */
    strategy: 'chronological-insert' | 'append' | 'first';
    /** Reason for allocation decision */
    reason: string;
    /** Numeric ID value */
    number: number;
}
/**
 * FS-ID Allocator Options
 */
export interface FSIdAllocatorOptions {
    /** Enable global collision detection across ALL projects (umbrella mode) */
    globalCollisionDetection?: boolean;
    /**
     * External container context for 2-level directory structure
     *
     * When provided, creates features in 2-level structure:
     * - JIRA: specs/JIRA-{containerId}/{projectId}/FS-XXX/
     * - ADO: specs/{containerId}/{projectId}/FS-XXX/ (no ADO- prefix)
     *
     * When NOT provided (default), creates in 1-level structure:
     * - GitHub: specs/{projectId}/FS-XXX/
     *
     * Collision detection scans both 1-level and 2-level structures when
     * globalCollisionDetection is enabled.
     */
    externalContainer?: ExternalContainerContext;
    /** Logger instance (defaults to consoleLogger) */
    logger?: Logger;
}
/**
 * FS-ID Allocator
 *
 * Intelligently allocates Feature IDs with chronological placement.
 *
 * CRITICAL: In umbrella/multi-repo setups, enable globalCollisionDetection
 * to prevent ID collisions like FS-001 in project-a and FS-001E in project-b.
 */
export declare class FSIdAllocator {
    private projectRoot;
    private specsPath;
    private archivePath;
    private projectId;
    private existingFeatures;
    private scanned;
    private globalCollisionDetection;
    private externalContainer;
    private logger;
    constructor(projectRoot: string, projectId?: string, options?: FSIdAllocatorOptions);
    /**
     * Get base directory for features, handling both 1-level and 2-level structures
     *
     * Structure patterns:
     * - 2-level (JIRA): specs/JIRA-{containerId}/{projectId}/
     * - 2-level (ADO): specs/{containerId}/{projectId}/ (no ADO- prefix)
     * - 1-level (GitHub): specs/{projectId}/
     * - Legacy: specs/ (no projectId)
     *
     * IMPORTANT: For 2-level structure, features ALWAYS go in the inner folder (board/area level),
     * even when containerDirName == projectId (e.g., specs/my-project/my-project/).
     * This is correct because the outer folder is PROJECT level, inner is BOARD level.
     */
    private getBaseDirectory;
    /**
     * Scan existing FS-IDs (both active and archived)
     *
     * CRITICAL: Archives are scanned to prevent ID reuse
     *
     * Structure:
     * - With projectId: specs/{projectId}/FS-XXX/
     * - Without projectId: specs/FS-XXX/
     * - 2-level (JIRA): specs/JIRA-{containerId}/{projectId}/FS-XXX/
     * - 2-level (ADO): specs/{containerId}/{projectId}/FS-XXX/
     *
     * When globalCollisionDetection is enabled (umbrella mode):
     * - Scans ALL project folders under specs/
     * - Prevents ID collisions like FS-001 in project-a and FS-001E in project-b
     *
     * CRITICAL FIX: Legacy mode now properly handles 2-level structures
     * by using getBaseDirectory() instead of incorrectly building the path.
     */
    scanExistingIds(): Promise<void>;
    /**
     * Scan ALL projects under specs/ for global collision detection
     *
     * This scans both 1-level and 2-level structures:
     * - specs/FS-XXX/ (root-level features, legacy)
     * - specs/{project}/FS-XXX/ (1-level: all project folders)
     * - specs/{project}/_archive/FS-XXX/ (1-level archived)
     * - specs/JIRA-{container}/{project}/FS-XXX/ (2-level JIRA)
     * - specs/{container}/{project}/FS-XXX/ (2-level ADO - no prefix)
     * - specs/JIRA-{container}/{project}/_archive/FS-XXX/ (2-level archived)
     */
    private scanAllProjects;
    /**
     * Check if folder has 2-level structure (subfolders with FS-XXX inside)
     * Used for ADO folders without ADO- prefix
     */
    private is2LevelFolder;
    /**
     * Scan a 2-level container folder (JIRA-* or ADO-*)
     *
     * Container structure:
     * - specs/JIRA-CORE/
     * -   fe/
     * -     FS-001/
     * -     _archive/
     * -   be/
     * -     FS-002/
     */
    private scanContainerFolder;
    /**
     * Scan directory for FS-XXX folders
     */
    private scanDirectory;
    /**
     * Parse feature metadata from README.md frontmatter
     */
    private parseFeatureMetadata;
    /**
     * Allocate FS-ID for external work item with chronological placement
     *
     * Algorithm:
     * 1. Sort existing IDs by creation date
     * 2. Find chronological insertion point based on work item creation date
     * 3. Check for ID gaps between consecutive features
     * 4. If gap exists, allocate next ID in gap (e.g., FS-011E between FS-010 and FS-020)
     * 5. If no gap, append to end (max ID + 1 with E suffix)
     *
     * CRITICAL (2025-11-26): Unified numeric sequence - FS-XXX and FS-XXXE never share same index
     * - If FS-001 exists (internal), external must use FS-002E
     * - If FS-001E exists (external), internal must use FS-002
     *
     * @param workItem - External work item to allocate ID for
     * @returns Allocation result with ID and strategy
     */
    allocateId(workItem: ExternalWorkItem): Promise<AllocationResult>;
    /**
     * Track a newly allocated ID to prevent duplicates in the same session
     * CRITICAL FIX (2025-12-01): Without this, multiple calls to allocateId()
     * would all return the same ID (e.g., FS-001E) because existingFeatures
     * wasn't updated after allocation.
     */
    private trackAllocatedId;
    /**
     * Check if ID has collision (exact match or variant)
     *
     * @param id - FS-ID to check (e.g., "FS-042" or "FS-042E")
     * @returns True if collision exists
     *
     * @example
     * // Given existing IDs: FS-010, FS-011E, FS-020
     * hasCollision('FS-011')  // true (FS-011E exists)
     * hasCollision('FS-011E') // true (exact match)
     * hasCollision('FS-012')  // false
     */
    hasCollision(id: string): boolean;
    /**
     * Get maximum numeric ID value (ignoring E suffix)
     *
     * @returns Maximum ID number
     */
    getMaxId(): number;
    /**
     * Extract numeric part from FS-ID
     *
     * @param id - FS-ID (e.g., "FS-042" or "FS-042E")
     * @returns Numeric part (e.g., 42)
     */
    private extractNumber;
    /**
     * Get all existing feature IDs (sorted by creation date)
     *
     * @returns Array of feature metadata sorted chronologically
     */
    getExistingFeatures(): FeatureMetadata[];
    /**
     * Get allocation statistics
     */
    getStats(): {
        total: number;
        active: number;
        archived: number;
        internal: number;
        external: number;
        maxId: number;
    };
    /**
     * Find first available numeric index for external feature (unified sequence)
     *
     * CRITICAL: Ensures FS-XXX and FS-XXXE never share the same numeric index
     * - Scans from 1 upward to find first unused numeric index
     * - Checks both FS-XXX (internal) and FS-XXXE (external) for each index
     *
     * @returns Allocation result if gap found, null if should use append strategy
     */
    private findFirstAvailableIndex;
    /**
     * Check if a numeric index is used by ANY feature (internal or external)
     *
     * @param index - Numeric index to check
     * @returns True if either FS-XXX or FS-XXXE exists for this index
     */
    isNumericIndexUsed(index: number): boolean;
    /**
     * Find which feature ID is using a numeric index
     *
     * @param index - Numeric index to look up
     * @returns The existing feature ID using this index (e.g., "FS-001" or "FS-001E"), or "unknown" if not found
     */
    private findCollidingId;
    /**
     * Create feature folder with FEATURE.md and metadata
     *
     * Structure patterns:
     * - 2-level (JIRA): specs/JIRA-{containerId}/{projectId}/FS-XXX/FEATURE.md
     * - 2-level (ADO): specs/{containerId}/{projectId}/FS-XXX/FEATURE.md (no prefix)
     * - 1-level (GitHub): specs/{projectId}/FS-XXX/FEATURE.md
     * - Legacy: specs/FS-XXX/FEATURE.md (not recommended for umbrella)
     *
     * @param fsId - Feature ID (e.g., "FS-042E")
     * @param workItem - External work item metadata
     * @param metadata - External item metadata for origin tracking
     * @returns Path to created feature folder
     */
    createFeatureFolder(fsId: string, workItem: ExternalWorkItem, metadata: ExternalItemMetadata): Promise<string>;
}
//# sourceMappingURL=fs-id-allocator.d.ts.map