import stylelint from 'stylelint';
import type { Rule } from 'stylelint';

const ruleName = '@n8n/css-var-naming';

const messages = stylelint.utils.ruleMessages(ruleName, {
	rejected: (variable: string, reason: string) => `Invalid CSS variable "${variable}": ${reason}`,
});

const meta = {
	url: 'https://github.com/n8n-io/n8n',
};

// Reserved vocabulary from proposal.md
// NOTE: color--text, color--background, color--foreground use double dashes
// to separate "color" from the subtype (text/background/foreground)
const PROPERTY_VOCABULARY = new Set([
	'color',
	'color--text',
	'color--background',
	'color--foreground',
	'border-color',
	'border-width',
	'border-top-color',
	'border-bottom-color',
	'border-right-color',
	'border-left-width',
	'border-style',
	'border',
	'height',
	'icon-color',
	'radius',
	'size',
	'stroke-width',
	'shadow',
	'spacing',
	'padding',
	'font-size',
	'font-weight',
	'font-family',
	'line-height',
	'margin',
	'margin-right',
	'margin-left',
	'margin-top',
	'margin-bottom',
	'max-height',
	'max-width',
	'min-height',
	'min-width',
	'z',
	'duration',
	'easing',
	'offset',
	'outline-color',
	'outline-width',
	'width',
	'top',
	'bottom',
	'left',
	'right',
]);

// Properties that can be used as standalone single-group variables (without a value)
const STANDALONE_PROPERTIES = new Set([
	'shadow',
	'radius',
	'border-color',
	'border-style',
	'border-width',
	'border',
	'font-family',
]);

const STATES = new Set([
	'hover',
	'active',
	'focus',
	'focus-visible',
	'visited',
	'disabled',
	'selected',
	'checked',
	'invalid',
	'opened',
	'closed',
	'loading',
]);

const VARIANTS = new Set(['solid', 'outline', 'ghost', 'link', 'soft', 'subtle']);

const MODES = new Set(['light', 'dark', 'hc', 'rtl', 'print']);

const MEDIA = new Set(['sm', 'md', 'lg', 'xl', '2xl']);

// Ignore issues related to these namespaces
const DISABLE_CHECK_FOR_NAMESPACES = new Set(['reka', 'ag', 'chat']);

// Allowed namespaces
const NAMESPACES = new Set(['n8n', 'p', ...DISABLE_CHECK_FOR_NAMESPACES]);

// Semantic values and scales
const SEMANTIC_VALUES = new Set([
	'primary',
	'secondary',
	'success',
	'warning',
	'danger',
	'info',
	'muted',
	'surface',
	'on-primary',
	'on-surface',
]);

const SCALE_VALUES = new Set([
	'5xs',
	'4xs',
	'3xs',
	'2xs',
	'xs',
	'sm',
	'md',
	'lg',
	'xl',
	'2xl',
	'3xl',
	'4xl',
	'5xl',
]);

// Font weight specific values (only valid with font-weight property)
const FONT_WEIGHT_VALUES = new Set(['regular', 'medium', 'semibold', 'bold']);

// Regex for basic validation
// Allows 2-10 groups to accommodate double-dash properties like color--text
const BASIC_PATTERN = /^--[a-z0-9]+(?:-[a-z0-9]+)*(?:--[a-z0-9]+(?:-[a-z0-9]+)*){1,9}$/;

interface ValidationResult {
	valid: boolean;
	reason?: string;
}

function shouldSkip(variable: string) {
	// Split into groups first (drop first empty element from leading --)
	const parts = variable.slice(2).split('-');
	if (DISABLE_CHECK_FOR_NAMESPACES.has(parts[0])) {
		return true;
	}

	return false;
}

function validateCssVariable(variable: string): ValidationResult {
	if (shouldSkip(variable)) {
		return { valid: true };
	}

	// Split into groups first (drop first empty element from leading --)
	const groups = variable.slice(2).split('--');

	// Check if this is a single-group variable (e.g., --shadow, --radius, --border-color)
	if (groups.length === 1) {
		const singleGroup = groups[0];
		// Allow standalone properties that are in the STANDALONE_PROPERTIES set
		if (STANDALONE_PROPERTIES.has(singleGroup)) {
			return { valid: true };
		}
		return {
			valid: false,
			reason: 'Must have at least 2 groups separated by double dashes (--property--value minimum)',
		};
	}

	// Basic pattern check for multi-group variables
	if (!BASIC_PATTERN.test(variable)) {
		return {
			valid: false,
			reason:
				'Must follow pattern: --[group]--[group]--... with lowercase alphanumerics and single dash within groups',
		};
	}

	// Check group count (2-10 groups to accommodate double-dash properties like color--text)
	if (groups.length < 2) {
		return {
			valid: false,
			reason: 'Must have at least 2 groups separated by double dashes (--property--value minimum)',
		};
	}

	if (groups.length > 10) {
		return {
			valid: false,
			reason: 'Must have at most 10 groups (too many segments)',
		};
	}

	// Check each group for invalid characters
	for (const group of groups) {
		if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(group)) {
			return {
				valid: false,
				reason: `Group "${group}" contains invalid characters. Use only lowercase letters, numbers, and single dash within groups`,
			};
		}
	}

	// Check if first group is a namespace, and if so, validate it
	const firstGroup = groups[0];
	let startIndex = 0;

	// If first group is a valid namespace, skip it for property validation
	if (NAMESPACES.has(firstGroup)) {
		startIndex = 1;
	}

	// Validate property vocabulary (should be in the variable somewhere after namespace)
	const hasValidProperty = groups.slice(startIndex).some((group) => PROPERTY_VOCABULARY.has(group));
	if (!hasValidProperty) {
		return {
			valid: false,
			reason: `Must include a valid property from vocabulary: ${Array.from(PROPERTY_VOCABULARY).join(', ')}`,
		};
	}

	// Find the property index to validate what comes after it
	const propertyIndex = groups
		.slice(startIndex)
		.findIndex((group) => PROPERTY_VOCABULARY.has(group));
	const absolutePropertyIndex = startIndex + propertyIndex;

	// Check if any semantic or scale values appear before the property
	const groupsBeforeProperty = groups.slice(startIndex, absolutePropertyIndex);
	for (const group of groupsBeforeProperty) {
		// Check if this group is a semantic value, scale value, or font-weight value
		if (SEMANTIC_VALUES.has(group) || SCALE_VALUES.has(group) || FONT_WEIGHT_VALUES.has(group)) {
			return {
				valid: false,
				reason: `Value "${group}" appears before the property. Values must come after the property (e.g., --color--${group}, not --${group}--color)`,
			};
		}
	}

	// Get the property name to validate specific property-value combinations
	const propertyName = groups[absolutePropertyIndex];

	// Check if HSL components (h, s, l) appear in non-final positions or as suffixes
	const hslComponents = new Set(['h', 's', 'l']);

	// Check all groups after property for HSL-related issues
	for (let i = absolutePropertyIndex + 1; i < groups.length; i++) {
		const group = groups[i];
		const isLastGroup = i === groups.length - 1;

		// Check if group is exactly h, s, or l (allowed only at the end)
		if (hslComponents.has(group)) {
			if (!isLastGroup) {
				return {
					valid: false,
					reason: `HSL component "${group}" must be at the end of the variable name (e.g., --color--primary--${group}, not --color--${group}--primary)`,
				};
			}
			// If it's the last group and exactly h/s/l, it's valid
			continue;
		}

		// Check if group ends with -h, -s, or -l (never allowed)
		if (group.endsWith('-h') || group.endsWith('-s') || group.endsWith('-l')) {
			return {
				valid: false,
				reason: `HSL component suffix in "${group}" is not allowed. Use standalone HSL components instead (e.g., --color--primary--h, not --color--primary-h)`,
			};
		}
	}

	// The group after property should be a value (semantic or scale)
	if (absolutePropertyIndex + 1 < groups.length) {
		const valueGroup = groups[absolutePropertyIndex + 1];

		// Check if this is a font-weight specific value
		if (FONT_WEIGHT_VALUES.has(valueGroup)) {
			// Font weight values are only valid with font-weight property
			if (propertyName !== 'font-weight') {
				return {
					valid: false,
					reason: `Value "${valueGroup}" can only be used with font-weight property (e.g., --font-weight--${valueGroup})`,
				};
			}
		}

		// Check if this is a known modifier (variant, state, mode, media)
		const isModifier =
			VARIANTS.has(valueGroup) ||
			STATES.has(valueGroup) ||
			MODES.has(valueGroup) ||
			MEDIA.has(valueGroup);

		// If it's not a modifier, validate it's a semantic or scale value
		// We use a permissive approach: reject only clearly invalid patterns
		if (!isModifier) {
			const isValidValue =
				SEMANTIC_VALUES.has(valueGroup) ||
				SCALE_VALUES.has(valueGroup) ||
				FONT_WEIGHT_VALUES.has(valueGroup) ||
				// Allow color shades like "primary-500", "shade-50", "tint-50"
				/^[a-z]+-\d+$/.test(valueGroup) ||
				// Allow descriptive names (3+ chars) - these are likely intentional semantic names
				valueGroup.length >= 3 ||
				// Support hsl css variables (only allowed at the end, checked above)
				hslComponents.has(valueGroup);

			if (!isValidValue) {
				return {
					valid: false,
					reason: `Value "${valueGroup}" is too short. Use semantic values (${Array.from(SEMANTIC_VALUES).slice(0, 5).join(', ')}...) or scale values (${Array.from(SCALE_VALUES).slice(0, 5).join(', ')}...). See proposal for full list.`,
				};
			}
		}
	}

	// Check for states/variants/modes in appropriate positions (optional validation)
	const lastGroup = groups[groups.length - 1];

	// If last group is a state/mode/media, that's valid
	if (STATES.has(lastGroup) || MODES.has(lastGroup) || MEDIA.has(lastGroup)) {
		// Valid pattern
		return { valid: true };
	}

	// Check if we have variants in reasonable positions
	const hasVariant = groups.some((group) => VARIANTS.has(group));
	const hasState = groups.some((group) => STATES.has(group));

	// If we have both variant and state, variant should come before state
	if (hasVariant && hasState) {
		const variantIndex = groups.findIndex((group) => VARIANTS.has(group));
		const stateIndex = groups.findIndex((group) => STATES.has(group));
		if (variantIndex > stateIndex) {
			return {
				valid: false,
				reason:
					'Variant should come before state (e.g., --button--background--primary--solid--hover)',
			};
		}
	}

	return { valid: true };
}

const ruleFunction: Rule = (primary, secondaryOptions, context) => {
	return (root, result) => {
		const validOptions = stylelint.utils.validateOptions(result, ruleName, {
			actual: primary,
		});

		if (!validOptions) {
			return;
		}

		root.walkDecls((decl) => {
			const prop = decl.prop;

			// Only check CSS custom properties (variables)
			if (!prop.startsWith('--')) {
				return;
			}

			const validation = validateCssVariable(prop);

			if (!validation.valid) {
				stylelint.utils.report({
					message: messages.rejected(prop, validation.reason!),
					node: decl,
					result,
					ruleName,
				});
			}
		});

		// Also check variable usage in var() functions
		root.walkDecls((decl) => {
			const value = decl.value;

			// Find all var() references
			const varPattern = /var\((--[a-z0-9-]+)/g;
			let match;

			while ((match = varPattern.exec(value)) !== null) {
				const variable = match[1];
				const validation = validateCssVariable(variable);

				if (!validation.valid) {
					stylelint.utils.report({
						message: messages.rejected(variable, validation.reason!),
						node: decl,
						result,
						ruleName,
					});
				}
			}
		});
	};
};

ruleFunction.ruleName = ruleName;
ruleFunction.messages = messages;
ruleFunction.meta = meta;

export default stylelint.createPlugin(ruleName, ruleFunction);
