// tools/define-ui-contract/a11y-contract.ts — Accessibility Contract (SPEC-017a)
// Generates WCAG 2.1 AA/AAA contracts per component, ARIA live regions, keyboard patterns.
// === Public API ===
/**
 * Generate an A11y contract for a UI spec.
 * Detects WCAG level from context (defaults to AA).
 */
export function generateA11yContract(components, level = 'AA') {
    const contrastRatios = buildContrastRatios(level);
    const componentContracts = buildComponentContracts(components);
    const ariaLiveRegions = buildAriaLiveRegions(components);
    const keyboardPatterns = buildKeyboardPatterns(components);
    const skipNavLink = components.some((c) => c.toLowerCase().includes('nav') ||
        c.toLowerCase().includes('header') ||
        c.toLowerCase().includes('menu'));
    return {
        level,
        contrastRatios,
        componentContracts,
        ariaLiveRegions,
        keyboardPatterns,
        skipNavLink,
        manualTestingNote: 'Automated axe-core tests detect ~30% of a11y issues. ' +
            'Remaining 70% requires manual testing with a screen reader ' +
            '(NVDA + Firefox on Windows, VoiceOver + Safari on macOS/iOS).',
    };
}
/**
 * Detects the appropriate WCAG level from project context (spec content or tags).
 */
export function detectA11yLevel(specContent) {
    const lower = specContent.toLowerCase();
    if (lower.includes('wcag aaa') || lower.includes('wcag 2.1 aaa') || lower.includes('aaa')) {
        return 'AAA';
    }
    return 'AA';
}
/**
 * Format A11y Contract as Markdown.
 */
export function formatA11yContract(contract) {
    const lines = [`## Accessibility Contract (WCAG 2.1 ${contract.level})`];
    lines.push('');
    lines.push(`**Required level:** WCAG 2.1 ${contract.level} — Normal text contrast: **${contract.contrastRatios.normalText}:1** minimum, Large text: **${contract.contrastRatios.largeText}:1** minimum`);
    if (contract.skipNavLink) {
        lines.push('');
        lines.push('**Skip Navigation:** Include `<a href="#main-content" class="skip-link">Skip to main content</a>` as first focusable element.');
    }
    if (contract.componentContracts.length > 0) {
        lines.push('');
        lines.push('### Per-Component Requirements');
        lines.push('');
        for (const cc of contract.componentContracts) {
            lines.push(`**${cc.component}:**`);
            for (const req of cc.requirements) {
                lines.push(`  - ${req}`);
            }
        }
    }
    if (contract.ariaLiveRegions.length > 0) {
        lines.push('');
        lines.push('### ARIA Live Regions');
        lines.push('');
        for (const region of contract.ariaLiveRegions) {
            lines.push(`- ${region}`);
        }
    }
    if (contract.keyboardPatterns.length > 0) {
        lines.push('');
        lines.push('### Keyboard Navigation Patterns');
        lines.push('');
        for (const pattern of contract.keyboardPatterns) {
            lines.push(`- ${pattern}`);
        }
    }
    lines.push('');
    lines.push(`> **Manual Testing Required:** ${contract.manualTestingNote}`);
    return lines.join('\n');
}
// === Internal helpers ===
function buildContrastRatios(level) {
    if (level === 'AAA') {
        return { level, normalText: 7, largeText: 4.5 };
    }
    return { level, normalText: 4.5, largeText: 3 };
}
function buildComponentContracts(components) {
    const contracts = [];
    for (const component of components) {
        const lower = component.toLowerCase();
        const requirements = [];
        if (lower.includes('modal') || lower.includes('dialog')) {
            requirements.push('focus trap: focus must stay inside modal while open');
            requirements.push('Escape key closes the modal and returns focus to trigger element');
            requirements.push('role="dialog" + aria-labelledby pointing to modal title');
            requirements.push('aria-modal="true" to prevent screen reader from accessing background');
        }
        if (lower.includes('form') || lower.includes('input')) {
            requirements.push('All inputs associated with labels via htmlFor/id or aria-labelledby');
            requirements.push('Required fields: aria-required="true" + visual indicator (not color alone)');
            requirements.push('Validation errors: aria-live="assertive" region + aria-describedby on input');
            requirements.push('Error messages associated with specific fields via aria-describedby');
        }
        if (lower.includes('table') || lower.includes('grid') || lower.includes('list')) {
            requirements.push('Table: <caption> or aria-label describing table purpose');
            requirements.push('Column headers: <th scope="col">, row headers: <th scope="row">');
            requirements.push('Sortable columns: aria-sort="ascending|descending|none"');
            requirements.push('Keyboard: Tab to navigate, arrow keys within grid cells (if role="grid")');
        }
        if (lower.includes('dropdown') ||
            lower.includes('select') ||
            lower.includes('menu') ||
            lower.includes('nav')) {
            requirements.push('Follow ARIA APG: Listbox, Combobox, or Menu pattern as appropriate');
            requirements.push('Arrow keys navigate options, Enter selects, Escape closes');
            requirements.push('Active option highlighted with aria-activedescendant or focus management');
        }
        if (lower.includes('button') || lower.includes('action')) {
            requirements.push('All buttons have accessible names (visible text or aria-label)');
            requirements.push('Icon-only buttons: aria-label required');
            requirements.push('Toggle buttons: aria-pressed="true|false"');
            requirements.push('Loading state: aria-busy="true" + aria-label="Loading..."');
        }
        if (lower.includes('toast') || lower.includes('notification') || lower.includes('alert')) {
            requirements.push('Errors/critical: role="alert" with aria-live="assertive"');
            requirements.push('Info/success notifications: aria-live="polite"');
            requirements.push('Dismissible: Escape key or visible close button');
        }
        if (requirements.length > 0) {
            contracts.push({ component, requirements });
        }
    }
    // Always add general requirements if no specific components were matched
    if (contracts.length === 0) {
        contracts.push({
            component: 'All interactive elements',
            requirements: [
                'Keyboard accessible: operable via Tab, Enter, Space, Escape as appropriate',
                'Visible focus indicator on all focusable elements',
                'Accessible name for all interactive elements (visible text or aria-label)',
                'Avoid keyboard traps except in intentional modal dialogs',
            ],
        });
    }
    return contracts;
}
function buildAriaLiveRegions(components) {
    const regions = [];
    const componentStr = components.join(' ').toLowerCase();
    if (componentStr.includes('form') || componentStr.includes('input')) {
        regions.push('Form errors: <div role="alert" aria-live="assertive">{error}</div>');
    }
    if (componentStr.includes('toast') ||
        componentStr.includes('notification') ||
        componentStr.includes('success')) {
        regions.push('Notifications/toasts: <div aria-live="polite" aria-atomic="true">{message}</div>');
    }
    if (componentStr.includes('load') ||
        componentStr.includes('fetch') ||
        componentStr.includes('spinner')) {
        regions.push('Loading indicators: <div role="status" aria-busy={isLoading} aria-label="Loading content">');
    }
    if (componentStr.includes('search') || componentStr.includes('filter')) {
        regions.push('Search results count: <div aria-live="polite">{count} results found</div>');
    }
    if (regions.length === 0) {
        regions.push('Status messages: aria-live="polite" for non-urgent updates');
        regions.push('Error alerts: role="alert" for critical issues requiring immediate attention');
    }
    return regions;
}
function buildKeyboardPatterns(components) {
    const patterns = [];
    const componentStr = components.join(' ').toLowerCase();
    if (componentStr.includes('dropdown') || componentStr.includes('select')) {
        patterns.push('Listbox/Combobox: Down/Up arrows navigate, Enter selects, Escape closes');
    }
    if (componentStr.includes('datepicker') || componentStr.includes('calendar')) {
        patterns.push('Date picker: Arrow keys navigate dates, Enter selects, Escape closes');
    }
    if (componentStr.includes('menu') || componentStr.includes('nav')) {
        patterns.push('Navigation menu: Tab between top-level items, Enter/Space opens submenu');
    }
    if (componentStr.includes('tab')) {
        patterns.push('Tabs: Arrow keys switch tabs (automatic activation), Tab moves to panel content');
    }
    if (componentStr.includes('modal') || componentStr.includes('dialog')) {
        patterns.push('Dialog: Tab cycles within, Shift+Tab reverses, Escape closes');
    }
    if (componentStr.includes('slider') || componentStr.includes('range')) {
        patterns.push('Slider: Arrow keys adjust value, Home/End go to min/max');
    }
    if (patterns.length === 0) {
        patterns.push('All interactive elements operable via keyboard without requiring mouse');
        patterns.push('Logical Tab order following visual reading order');
    }
    return patterns;
}
//# sourceMappingURL=a11y-contract.js.map