/* eslint-disable no-magic-numbers */
/**
 * Unit tests for dynamic slider mark density functionality
 * File is largely generated by Claude
 *
 * Tests the behavior implemented for width-based mark density where:
 * - 330px is the baseline width where 0-100 range with step=5 shows ~10 marks
 * - Wider sliders show more marks, narrower sliders show fewer marks
 * - All marks must be valid step positions (divisible by step value)
 * - Marks are properly spaced to avoid visual overlap
 */

import {SliderMarks} from '../../src/types';
import {sanitizeMarks} from '../../src/utils/computeSliderMarkers';

// Helper function to extract numeric mark positions
const getMarkPositions = (marks: SliderMarks): number[] => {
    if (!marks) {
        return [];
    }
    return Object.keys(marks)
        .map(Number)
        .sort((a, b) => a - b);
};

// Helper function to check if all marks are valid step positions
const areAllMarksValidSteps = (
    marks: SliderMarks,
    min: number,
    step: number
): boolean => {
    if (!marks) {
        return false;
    }
    const positions = getMarkPositions(marks);
    return positions.every(pos => {
        const stepsFromMin = Math.round((pos - min) / step);
        const expectedPosition = min + stepsFromMin * step;
        return Math.abs(pos - expectedPosition) === 0;
    });
};

describe('Dynamic Slider Mark Density', () => {
    describe('Baseline behavior (330px width)', () => {
        test('should show appropriate marks for 0-100 range with step=5 at 330px', () => {
            const marks = sanitizeMarks({
                min: 0,
                max: 100,
                marks: undefined,
                step: 5,
                sliderWidth: 330,
            });

            expect(marks).toBeDefined();
            const positions = getMarkPositions(marks);

            // With pixel-based algorithm: 3-char labels need ~50px per mark
            // 330px / 50px = ~7 marks (plus min/max adjustment gives 8)
            expect(positions.length).toBe(8);
            expect(areAllMarksValidSteps(marks, 0, 5)).toBe(true);
            expect(positions).toContain(0);
            expect(positions).toContain(100);
        });

        test('should include min and max values in marks', () => {
            const marks = sanitizeMarks({
                min: 0,
                max: 100,
                marks: undefined,
                step: 5,
                sliderWidth: 330,
            });

            const positions = getMarkPositions(marks);

            expect(positions[0]).toBe(0);
            expect(positions[positions.length - 1]).toBe(100);
        });

        test('should show ~11 marks for 0-10 range at baseline width (330px)', () => {
            const marks = sanitizeMarks({
                min: 0,
                max: 10,
                marks: undefined,
                step: 1, // Step of 1 (equivalent to no step constraint)
                sliderWidth: 330,
            });

            expect(marks).toBeDefined();
            const positions = getMarkPositions(marks);

            // Should show ~11 marks regardless of range when at baseline width
            expect(positions.length).toBeGreaterThanOrEqual(9);
            expect(positions.length).toBeLessThanOrEqual(12);
            expect(positions).toContain(0);
            expect(positions).toContain(10);
        });

        test('should reduce density for large numbers to prevent label overlap (0-10000)', () => {
            const smallRange = sanitizeMarks({
                min: 0,
                max: 100,
                marks: undefined,
                step: 10,
                sliderWidth: 330,
            });

            const largeRange = sanitizeMarks({
                min: 0,
                max: 10000,
                marks: undefined,
                step: 1000,
                sliderWidth: 330,
            });

            const smallPositions = getMarkPositions(smallRange);
            const largePositions = getMarkPositions(largeRange);

            // Large numbers (5 chars like "10000") should have fewer marks than small numbers (2-3 chars like "10", "100")
            expect(largePositions.length).toBeLessThanOrEqual(
                smallPositions.length
            );

            // Both should be valid steps
            expect(areAllMarksValidSteps(smallRange, 0, 10)).toBe(true);
            expect(areAllMarksValidSteps(largeRange, 0, 1000)).toBe(true);

            // Large range should still have reasonable number of marks (not too few)
            expect(largePositions.length).toBeGreaterThanOrEqual(3);
        });
    });

    describe('Narrow slider behavior', () => {
        test('should show fewer marks than baseline for 100px width', () => {
            const baseline = sanitizeMarks({
                min: 0,
                max: 100,
                marks: undefined,
                step: 5,
                sliderWidth: 330,
            });

            const narrow = sanitizeMarks({
                min: 0,
                max: 100,
                marks: undefined,
                step: 5,
                sliderWidth: 100,
            });

            const baselinePositions = getMarkPositions(baseline);
            const narrowPositions = getMarkPositions(narrow);

            expect(narrowPositions.length).toBeLessThan(
                baselinePositions.length
            );
            expect(areAllMarksValidSteps(narrow, 0, 5)).toBe(true);
        });

        test('should show only 0, 50, 100 for very narrow slider (100px)', () => {
            const marks = sanitizeMarks({
                min: 0,
                max: 100,
                marks: undefined,
                step: 5,
                sliderWidth: 100,
            });

            const positions = getMarkPositions(marks);

            expect(positions).toEqual([0, 50, 100]);
        });
    });

    describe('Wide slider behavior', () => {
        test('should show more marks than baseline for 660px width', () => {
            const baseline = sanitizeMarks({
                min: 0,
                max: 100,
                marks: undefined,
                step: 5,
                sliderWidth: 330,
            });

            const wide = sanitizeMarks({
                min: 0,
                max: 100,
                marks: undefined,
                step: 5,
                sliderWidth: 660,
            });

            const baselinePositions = getMarkPositions(baseline);
            const widePositions = getMarkPositions(wide);

            expect(widePositions.length).toBeGreaterThan(
                baselinePositions.length
            );
            expect(areAllMarksValidSteps(wide, 0, 5)).toBe(true);
        });
    });

    describe('Different step values', () => {
        test('should work with step=10', () => {
            const marks = sanitizeMarks({
                min: 0,
                max: 100,
                marks: undefined,
                step: 10,
                sliderWidth: 330,
            });

            const positions = getMarkPositions(marks);

            expect(areAllMarksValidSteps(marks, 0, 10)).toBe(true);
            expect(positions).toContain(0);
            expect(positions).toContain(100);

            // All positions should be multiples of 10
            positions.forEach(pos => {
                expect(pos % 10).toBe(0);
            });
        });

        test('should have appropriate density for fractional steps', () => {
            const fractionalStep = sanitizeMarks({
                min: 0,
                max: 10,
                marks: undefined,
                step: 0.5, // Fractional step
                sliderWidth: 1600,
            });

            const fractionalPositions = getMarkPositions(fractionalStep);
            expect(fractionalPositions.length).toBe(21);
            expect(areAllMarksValidSteps(fractionalStep, 0, 0.5)).toBe(true);
        });

        test('should work with fractional step=0.5', () => {
            const marks = sanitizeMarks({
                min: 0,
                max: 5,
                marks: undefined,
                step: 0.5,
                sliderWidth: 330,
            });

            const positions = getMarkPositions(marks);

            expect(areAllMarksValidSteps(marks, 0, 0.5)).toBe(true);
            expect(positions).toContain(0);
            expect(positions).toContain(5);

            // All positions should be multiples of 0.5
            positions.forEach(pos => {
                expect(pos % 0.5).toBeCloseTo(0, 10);
            });
        });
    });

    describe('Edge cases', () => {
        test('should handle undefined sliderWidth', () => {
            const marks = sanitizeMarks({
                min: 0,
                max: 100,
                marks: undefined,
                step: 5,
                sliderWidth: undefined,
            });

            const positions = getMarkPositions(marks);

            expect(areAllMarksValidSteps(marks, 0, 5)).toBe(true);
            expect(positions).toContain(0);
            expect(positions).toContain(100);
            expect(positions.length).toBeGreaterThan(0);
        });
    });

    describe('Width scaling behavior', () => {
        test('marks should increase proportionally with width', () => {
            const width100 = sanitizeMarks({
                min: 0,
                max: 100,
                marks: undefined,
                step: 5,
                sliderWidth: 100,
            });

            const width330 = sanitizeMarks({
                min: 0,
                max: 100,
                marks: undefined,
                step: 5,
                sliderWidth: 330,
            });

            const width660 = sanitizeMarks({
                min: 0,
                max: 100,
                marks: undefined,
                step: 5,
                sliderWidth: 660,
            });

            const positions100 = getMarkPositions(width100);
            const positions330 = getMarkPositions(width330);
            const positions660 = getMarkPositions(width660);

            // Width increases should generally result in more marks
            expect(positions100.length).toBeLessThanOrEqual(
                positions330.length
            );
            expect(positions330.length).toBeLessThanOrEqual(
                positions660.length
            );

            // All should be valid steps
            expect(areAllMarksValidSteps(width100, 0, 5)).toBe(true);
            expect(areAllMarksValidSteps(width330, 0, 5)).toBe(true);
            expect(areAllMarksValidSteps(width660, 0, 5)).toBe(true);
        });
    });

    describe('Extreme ranges with large numbers', () => {
        test('should not create overlapping marks for range -1 to 480256671 WITHOUT width (initial render)', () => {
            const marks = sanitizeMarks({
                min: -1,
                max: 480256671,
                marks: undefined,
                step: undefined, // Let it auto-calculate
                sliderWidth: null, // Simulates initial render before width is measured
            });

            const positions = getMarkPositions(marks);

            // Should have min and max
            expect(positions[0]).toBe(-1);
            expect(positions[positions.length - 1]).toBe(480256671);

            // Should have reasonable number of marks to prevent overlap even without width
            // With ~9-character labels (480256671), we need substantial spacing
            // Labels like "45M", "95M" are ~3-4 chars, so reasonable mark count is 5-7
            expect(positions.length).toBeGreaterThanOrEqual(2); // At least min and max
            expect(positions.length).toBeLessThanOrEqual(11); // Not too many to cause overlap

            // Even without explicit width, assume a reasonable default (330px baseline)
            // and verify spacing would be sufficient
            const estimatedSpacing = 330 / (positions.length - 1);
            expect(estimatedSpacing).toBeGreaterThanOrEqual(30);
        });

        test('should not create overlapping marks for range -1 to 480256671 at 365px width', () => {
            const marks = sanitizeMarks({
                min: -1,
                max: 480256671,
                marks: undefined,
                step: undefined, // Let it auto-calculate
                sliderWidth: 365,
            });

            const positions = getMarkPositions(marks);

            // Should have min and max
            expect(positions[0]).toBe(-1);
            expect(positions[positions.length - 1]).toBe(480256671);

            // Should have reasonable number of marks to prevent overlap
            // With 365px width and ~9-character labels (480256671), we need substantial spacing
            // Estimate: 9 chars * 8px/char = 72px per label, so max ~5 marks for 365px
            expect(positions.length).toBeGreaterThanOrEqual(2); // At least min and max
            expect(positions.length).toBeLessThanOrEqual(7); // Not too many to cause overlap

            // Verify spacing between marks is sufficient
            // With 365px width, marks should be at least 50px apart for long labels
            const estimatedSpacing = 365 / (positions.length - 1);
            expect(estimatedSpacing).toBeGreaterThanOrEqual(50);
        });

        test('should handle very large ranges with appropriate step multipliers', () => {
            const marks = sanitizeMarks({
                min: 0,
                max: 1000000000, // 1 billion
                marks: undefined,
                step: undefined,
                sliderWidth: 330,
            });

            const positions = getMarkPositions(marks);

            // Should have reasonable mark count
            expect(positions.length).toBeGreaterThanOrEqual(2);
            expect(positions.length).toBeLessThanOrEqual(15);

            // Should include min and max
            expect(positions[0]).toBe(0);
            expect(positions[positions.length - 1]).toBe(1000000000);
        });

        test('does not have all marks labeled as "2k" for range 1952 to 2007', () => {
            const marks = sanitizeMarks({
                min: 1952,
                max: 2007,
                marks: undefined,
                step: undefined,
                sliderWidth: 365,
            });

            // Get all the label values (not the keys)
            const labels = Object.values(marks);

            // Count unique labels
            const uniqueLabels = new Set(labels);

            // Should have more than one unique label
            expect(uniqueLabels.size).toBeGreaterThan(1);

            // Should NOT have all labels be "2k"
            const allLabels2k = labels.every(label => label === '2k');
            expect(allLabels2k).toBe(false);
        });
    });
});
