/*
 * Runtime Web – Unified Serverless Framework for Full-Stack TypeScript Applications
 * Documentation: https://runtimeweb.com/docs/getting-started
 *
 * @template: ai-chat
 */

/**
 * Design Tokens Property Tests
 *
 * Property-based tests for design token completeness.
 * Verifies that all required CSS custom properties are defined
 * for colors, typography, spacing, shadows, and theme variants.
 *
 * **Validates: Requirements 1.1, 1.2, 1.4**
 */

import * as fc from 'fast-check';
import * as fs from 'fs';
import * as path from 'path';

/**
 * Required token categories and their expected tokens.
 * Based on Requirements 1.1, 1.2, 1.4 from the design system specification.
 */
const REQUIRED_TOKENS = {
    /**
     * Color palette tokens - raw color values
     * Requirement 1.1: Define CSS custom properties for all design tokens including colors
     */
    colorPalette: {
        primary: ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
        neutral: ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
        success: ['50', '100', '200', '300', '400', '500', '600', '700'],
        error: ['50', '100', '200', '300', '400', '500', '600', '700'],
        warning: ['50', '100', '200', '300', '400', '500', '600', '700'],
    },

    /**
     * Semantic color tokens - must be defined for both light and dark themes
     * Requirement 1.5: Define semantic color tokens (primary, secondary, success, error, warning, surface, background)
     */
    semanticColors: [
        'color-bg-primary',
        'color-bg-secondary',
        'color-bg-tertiary',
        'color-text-primary',
        'color-text-secondary',
        'color-text-tertiary',
        'color-border',
        'color-border-focus',
        'color-msg-user-bg',
        'color-msg-user-text',
        'color-msg-assistant-bg',
        'color-msg-assistant-text',
        'color-msg-assistant-border',
        'color-state-success-bg',
        'color-state-success-text',
        'color-state-error-bg',
        'color-state-error-text',
        'color-state-warning-bg',
        'color-state-warning-text',
    ],

    /**
     * Typography scale tokens
     * Requirement 1.3: Use a typographic scale with at least 5 distinct sizes (xs, sm, base, lg, xl)
     */
    typography: {
        fontFamily: ['sans', 'mono'],
        fontSize: ['xs', 'sm', 'base', 'lg', 'xl', '2xl'],
        lineHeight: ['tight', 'normal', 'relaxed'],
        fontWeight: ['normal', 'medium', 'semibold', 'bold'],
    },

    /**
     * Spacing scale tokens
     * Requirement 1.4: Define a spacing scale using consistent increments (4px base unit)
     */
    spacing: ['1', '2', '3', '4', '5', '6', '8', '10', '12', '16'],

    /**
     * Border radius tokens
     * Requirement 1.1: Define CSS custom properties for border radii
     */
    borderRadius: ['sm', 'md', 'lg', 'xl', '2xl', 'full'],

    /**
     * Shadow tokens
     * Requirement 1.1: Define CSS custom properties for shadows
     */
    shadows: ['xs', 'sm', 'md', 'lg', 'xl', 'message'],

    /**
     * Animation timing tokens
     * Requirement 1.1: Define CSS custom properties for animations
     */
    animation: {
        duration: ['fast', 'normal', 'slow'],
        easing: ['in', 'out', 'in-out'],
    },
} as const;

/**
 * Parse CSS file and extract all custom property definitions.
 */
function parseCssCustomProperties(cssContent: string): {
    rootTokens: Set<string>;
    darkThemeTokens: Set<string>;
} {
    const rootTokens = new Set<string>();
    const darkThemeTokens = new Set<string>();

    // Match :root block
    const rootMatch = cssContent.match(/:root\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/);
    if (rootMatch) {
        const rootBlock = rootMatch[1];
        const tokenMatches = rootBlock.matchAll(/--([a-zA-Z0-9-]+)\s*:/g);
        for (const match of tokenMatches) {
            rootTokens.add(match[1]);
        }
    }

    // Match @media (prefers-color-scheme: dark) block
    const darkMatch = cssContent.match(/@media\s*\(\s*prefers-color-scheme\s*:\s*dark\s*\)\s*\{[\s\S]*?:root\s*\{([^}]+)\}/);
    if (darkMatch) {
        const darkBlock = darkMatch[1];
        const tokenMatches = darkBlock.matchAll(/--([a-zA-Z0-9-]+)\s*:/g);
        for (const match of tokenMatches) {
            darkThemeTokens.add(match[1]);
        }
    }

    return { rootTokens, darkThemeTokens };
}

/**
 * Load the design-tokens.css file content.
 */
function loadDesignTokensCss(): string {
    const cssPath = path.resolve(__dirname, '../design-tokens.css');
    return fs.readFileSync(cssPath, 'utf-8');
}

describe('Design Tokens Property Tests', () => {
    let cssContent: string;
    let rootTokens: Set<string>;
    let darkThemeTokens: Set<string>;

    beforeAll(() => {
        cssContent = loadDesignTokensCss();
        const parsed = parseCssCustomProperties(cssContent);
        rootTokens = parsed.rootTokens;
        darkThemeTokens = parsed.darkThemeTokens;
    });

    /**
     * Feature: ai-chat-ui-redesign, Property 1: Design Token Completeness
     *
     * For any valid design token category (colors, typography, spacing, shadows),
     * the design-tokens.css file SHALL define all required custom properties,
     * and both light and dark theme variants SHALL define all semantic color tokens.
     *
     * **Validates: Requirements 1.1, 1.2, 1.4**
     */
    describe('Property 1: Design Token Completeness', () => {
        describe('Color Palette Tokens', () => {
            it('should define all primary color scale tokens', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.colorPalette.primary),
                        async (shade) => {
                            const tokenName = `color-primary-${shade}`;
                            expect(rootTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.colorPalette.primary.length }
                );
            });

            it('should define all neutral color scale tokens', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.colorPalette.neutral),
                        async (shade) => {
                            const tokenName = `color-neutral-${shade}`;
                            expect(rootTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.colorPalette.neutral.length }
                );
            });

            it('should define all success color scale tokens', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.colorPalette.success),
                        async (shade) => {
                            const tokenName = `color-success-${shade}`;
                            expect(rootTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.colorPalette.success.length }
                );
            });

            it('should define all error color scale tokens', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.colorPalette.error),
                        async (shade) => {
                            const tokenName = `color-error-${shade}`;
                            expect(rootTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.colorPalette.error.length }
                );
            });

            it('should define all warning color scale tokens', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.colorPalette.warning),
                        async (shade) => {
                            const tokenName = `color-warning-${shade}`;
                            expect(rootTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.colorPalette.warning.length }
                );
            });
        });

        describe('Semantic Color Tokens', () => {
            it('should define all semantic color tokens in light theme (:root)', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.semanticColors),
                        async (tokenName) => {
                            expect(rootTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.semanticColors.length }
                );
            });

            it('should define all semantic color tokens in dark theme', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.semanticColors),
                        async (tokenName) => {
                            expect(darkThemeTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.semanticColors.length }
                );
            });
        });

        describe('Typography Tokens', () => {
            it('should define all font family tokens', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.typography.fontFamily),
                        async (family) => {
                            const tokenName = `font-family-${family}`;
                            expect(rootTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.typography.fontFamily.length }
                );
            });

            it('should define all font size tokens (xs, sm, base, lg, xl)', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.typography.fontSize),
                        async (size) => {
                            const tokenName = `font-size-${size}`;
                            expect(rootTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.typography.fontSize.length }
                );
            });

            it('should define all line height tokens', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.typography.lineHeight),
                        async (height) => {
                            const tokenName = `line-height-${height}`;
                            expect(rootTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.typography.lineHeight.length }
                );
            });

            it('should define all font weight tokens', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.typography.fontWeight),
                        async (weight) => {
                            const tokenName = `font-weight-${weight}`;
                            expect(rootTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.typography.fontWeight.length }
                );
            });
        });

        describe('Spacing Tokens', () => {
            it('should define all spacing scale tokens (4px base unit)', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.spacing),
                        async (space) => {
                            const tokenName = `space-${space}`;
                            expect(rootTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.spacing.length }
                );
            });
        });

        describe('Border Radius Tokens', () => {
            it('should define all border radius tokens', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.borderRadius),
                        async (radius) => {
                            const tokenName = `radius-${radius}`;
                            expect(rootTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.borderRadius.length }
                );
            });
        });

        describe('Shadow Tokens', () => {
            it('should define all shadow tokens', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.shadows),
                        async (shadow) => {
                            const tokenName = `shadow-${shadow}`;
                            expect(rootTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.shadows.length }
                );
            });
        });

        describe('Animation Tokens', () => {
            it('should define all animation duration tokens', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.animation.duration),
                        async (duration) => {
                            const tokenName = `duration-${duration}`;
                            expect(rootTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.animation.duration.length }
                );
            });

            it('should define all animation easing tokens', async () => {
                await fc.assert(
                    fc.asyncProperty(
                        fc.constantFrom(...REQUIRED_TOKENS.animation.easing),
                        async (easing) => {
                            const tokenName = `ease-${easing}`;
                            expect(rootTokens.has(tokenName)).toBe(true);
                        }
                    ),
                    { numRuns: REQUIRED_TOKENS.animation.easing.length }
                );
            });
        });

        describe('Theme Support', () => {
            it('should have prefers-color-scheme: dark media query', async () => {
                await fc.assert(
                    fc.asyncProperty(fc.constant(null), async () => {
                        expect(cssContent).toContain('prefers-color-scheme: dark');
                    }),
                    { numRuns: 1 }
                );
            });

            it('should define dark theme tokens for all semantic colors', async () => {
                await fc.assert(
                    fc.asyncProperty(fc.constant(null), async () => {
                        // Verify dark theme has at least the minimum required semantic tokens
                        const requiredDarkTokens = REQUIRED_TOKENS.semanticColors;
                        const missingTokens = requiredDarkTokens.filter((token) => !darkThemeTokens.has(token));

                        expect(missingTokens).toEqual([]);
                    }),
                    { numRuns: 1 }
                );
            });
        });

        describe('Token Value Validity', () => {
            it('should have valid CSS custom property syntax for all tokens', async () => {
                await fc.assert(
                    fc.asyncProperty(fc.constant(null), async () => {
                        // Verify no malformed custom property declarations
                        const malformedPattern = /--[a-zA-Z0-9-]+\s*:\s*;/g;
                        const malformed = cssContent.match(malformedPattern);

                        expect(malformed).toBeNull();
                    }),
                    { numRuns: 1 }
                );
            });

            it('should define tokens in :root selector for global availability', async () => {
                await fc.assert(
                    fc.asyncProperty(fc.constant(null), async () => {
                        expect(cssContent).toContain(':root');
                        expect(rootTokens.size).toBeGreaterThan(0);
                    }),
                    { numRuns: 1 }
                );
            });
        });
    });
});
