"use strict";
/**
 * Tailwind / shadcn semantic migration helpers for tidy:
 * theme color discovery, class-string extraction, palette→semantic mapping, and safe replacement.
 * Contrast pairing uses coarse palette bands only (not computed WCAG / dark mode CSS).
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseThemeColorNamesFromCss = parseThemeColorNamesFromCss;
exports.prefixOf = prefixOf;
exports.colorPart = colorPart;
exports.isSemanticColorUtility = isSemanticColorUtility;
exports.mapPaletteUtilityToSemantic = mapPaletteUtilityToSemantic;
exports.approximateBandForColorUtility = approximateBandForColorUtility;
exports.findContrastAndPairingIssues = findContrastAndPairingIssues;
exports.findDarkContrastNormalizationIssues = findDarkContrastNormalizationIssues;
exports.extractClassListStringsFromLine = extractClassListStringsFromLine;
exports.splitClassTokens = splitClassTokens;
exports.replaceNthClassOccurrence = replaceNthClassOccurrence;
/** Known shadcn-style semantic utility suffixes (already themed; do not rewrite). */
const SEMANTIC_COLOR_SUFFIXES = new Set([
    "background",
    "foreground",
    "card",
    "card-foreground",
    "popover",
    "popover-foreground",
    "primary",
    "primary-foreground",
    "secondary",
    "secondary-foreground",
    "muted",
    "muted-foreground",
    "accent",
    "accent-foreground",
    "destructive",
    "destructive-foreground",
    "border",
    "input",
    "ring",
    "chart-1",
    "chart-2",
    "chart-3",
    "chart-4",
    "chart-5",
    "sidebar",
    "sidebar-foreground",
    "sidebar-primary",
    "sidebar-accent",
    "sidebar-border",
    "sidebar-ring",
]);
const NEUTRAL_SCALES = new Set(["gray", "slate", "zinc", "neutral", "stone"]);
/**
 * Collect semantic color names that map to Tailwind color utilities (bg-*, text-*, border-*).
 * Parses --color-<name> from @theme / CSS and legacy shadcn :root variables.
 */
function parseThemeColorNamesFromCss(content) {
    const names = new Set();
    const colorVar = /--color-([\w-]+)\s*:/g;
    let m;
    while ((m = colorVar.exec(content)) !== null) {
        if (m[1])
            names.add(m[1]);
    }
    const legacy = /--(background|foreground|card|card-foreground|popover|popover-foreground|primary|primary-foreground|secondary|secondary-foreground|muted|muted-foreground|accent|accent-foreground|destructive|destructive-foreground|border|input|ring|chart-[1-5]|sidebar|sidebar-foreground|sidebar-primary|sidebar-accent|sidebar-border|sidebar-ring)\s*:/g;
    while ((m = legacy.exec(content)) !== null) {
        if (m[1])
            names.add(m[1]);
    }
    return names;
}
function prefixOf(cls) {
    if (cls.startsWith("bg-"))
        return "bg";
    if (cls.startsWith("text-"))
        return "text";
    if (cls.startsWith("border-"))
        return "border";
    return null;
}
/** Strip utility prefix and return the color part (e.g. bg-white → white, text-muted-foreground → muted-foreground). */
function colorPart(cls) {
    const p = prefixOf(cls);
    if (!p)
        return null;
    return cls.slice(p.length + 1) || null;
}
function isSemanticColorUtility(cls) {
    const part = colorPart(cls);
    if (!part || part.includes("/"))
        return true;
    return SEMANTIC_COLOR_SUFFIXES.has(part);
}
function isAlreadySemanticUtility(cls) {
    const part = colorPart(cls);
    if (!part || part.includes("/"))
        return true;
    if (SEMANTIC_COLOR_SUFFIXES.has(part))
        return true;
    return false;
}
/**
 * Map a default-palette Tailwind class to a semantic utility if theme exposes the target and the mapping is allowed.
 */
function mapPaletteUtilityToSemantic(cls, themeNames) {
    if (!cls || cls.includes("/"))
        return { kind: "none" };
    if (isAlreadySemanticUtility(cls))
        return { kind: "none" };
    const p = prefixOf(cls);
    if (!p)
        return { kind: "none" };
    const rest = cls.slice(p.length + 1);
    // --- bg-white / bg-black (surface) ---
    if (p === "bg" && rest === "white") {
        const hasBg = themeNames.has("background");
        const hasCard = themeNames.has("card");
        if (hasBg && hasCard) {
            return {
                kind: "ambiguous",
                candidates: ["bg-background", "bg-card"],
                confidence: 0.65,
            };
        }
        if (hasBg)
            return { kind: "replace", replacement: "bg-background", confidence: 0.92 };
        return { kind: "none" };
    }
    // --- text-black ---
    if (p === "text" && rest === "black" && themeNames.has("foreground")) {
        return { kind: "replace", replacement: "text-foreground", confidence: 0.92 };
    }
    // --- text-gray-900 style → text-foreground ---
    if (p === "text") {
        const tm = /^(\w+)-(900|950)$/.exec(rest);
        if (tm && tm[1] && NEUTRAL_SCALES.has(tm[1]) && themeNames.has("foreground")) {
            return { kind: "replace", replacement: "text-foreground", confidence: 0.88 };
        }
    }
    // --- text-*-400..600 neutrals → text-muted-foreground ---
    if (p === "text") {
        const tm = /^(\w+)-(400|500|600)$/.exec(rest);
        if (tm &&
            tm[1] &&
            NEUTRAL_SCALES.has(tm[1]) &&
            themeNames.has("muted-foreground")) {
            return { kind: "replace", replacement: "text-muted-foreground", confidence: 0.9 };
        }
    }
    // --- bg-gray-50 / bg-slate-50 → bg-muted ---
    if (p === "bg") {
        const tm = /^(\w+)-50$/.exec(rest);
        if (tm && tm[1] && NEUTRAL_SCALES.has(tm[1]) && themeNames.has("muted")) {
            return { kind: "replace", replacement: "bg-muted", confidence: 0.85 };
        }
    }
    // --- border-*-200 neutrals → border-border ---
    if (p === "border") {
        const tm = /^(\w+)-200$/.exec(rest);
        if (tm && tm[1] && NEUTRAL_SCALES.has(tm[1]) && themeNames.has("border")) {
            return { kind: "replace", replacement: "border-border", confidence: 0.9 };
        }
    }
    return { kind: "none" };
}
/**
 * Classify bg-* / text-* palette colors into coarse bands for contrast pairing.
 * Semantic shadcn utilities return "semantic".
 */
function approximateBandForColorUtility(cls) {
    if (!cls || cls.includes("/"))
        return "unknown";
    const p = prefixOf(cls);
    if (p !== "bg" && p !== "text")
        return "unknown";
    const rest = cls.slice(p.length + 1);
    if (!rest)
        return "unknown";
    if (SEMANTIC_COLOR_SUFFIXES.has(rest))
        return "semantic";
    if (rest === "white")
        return "light";
    if (rest === "black")
        return "dark";
    const stepped = /^(\w+)-(\d+)$/.exec(rest);
    if (stepped && stepped[1] && stepped[2]) {
        const n = parseInt(stepped[2], 10);
        if (n <= 200)
            return "light";
        if (n >= 800)
            return "dark";
        return "mid";
    }
    return "unknown";
}
function sameNeutralScaleAndShade(bg, text) {
    const b = /^bg-(\w+)-(\d+)$/.exec(bg);
    const t = /^text-(\w+)-(\d+)$/.exec(text);
    if (!b || !t || !b[1] || !t[1] || !b[2] || !t[2])
        return false;
    if (!NEUTRAL_SCALES.has(b[1]) || !NEUTRAL_SCALES.has(t[1]))
        return false;
    return b[1] === t[1] && b[2] === t[2];
}
/** Known good shadcn surface + foreground pairings (do not flag). */
function isSemanticPairOk(bg, text) {
    const pairs = [
        ["bg-background", "text-foreground"],
        ["bg-card", "text-card-foreground"],
        ["bg-popover", "text-popover-foreground"],
        ["bg-primary", "text-primary-foreground"],
        ["bg-secondary", "text-secondary-foreground"],
        ["bg-muted", "text-muted-foreground"],
        ["bg-accent", "text-accent-foreground"],
        ["bg-destructive", "text-destructive-foreground"],
    ];
    return pairs.some(([b, x]) => b === bg && x === text);
}
/**
 * Within one class chunk, find bg/text pairs that likely break contrast or theme pairing.
 * At most one remedial suggestion per text-* token (highest-priority rule wins).
 */
function findContrastAndPairingIssues(tokens, themeNames) {
    const bgTokens = tokens.filter((t) => t.startsWith("bg-"));
    const textTokens = tokens.filter((t) => t.startsWith("text-"));
    if (bgTokens.length === 0 || textTokens.length === 0)
        return [];
    const issues = [];
    const seenText = new Set();
    for (const textToken of textTokens) {
        if (seenText.has(textToken))
            continue;
        for (const bgToken of bgTokens) {
            if (isSemanticPairOk(bgToken, textToken))
                continue;
            const issue = evaluateContrastPair(bgToken, textToken, themeNames);
            if (issue) {
                issues.push(issue);
                seenText.add(textToken);
                break;
            }
        }
    }
    return issues;
}
function evaluateContrastPair(bg, text, theme) {
    const bgBand = approximateBandForColorUtility(bg);
    const textBand = approximateBandForColorUtility(text);
    // --- Semantic surface + inappropriate generic light text ---
    if (text === "text-white") {
        if (bg === "bg-card" && theme.has("card-foreground")) {
            return {
                original: text,
                replacement: "text-card-foreground",
                pairedWith: bg,
                rationale: "Use text-card-foreground on card surfaces so text follows theme and stays readable in light and dark mode.",
                confidence: 0.88,
            };
        }
        if (bg === "bg-muted" && theme.has("muted-foreground")) {
            return {
                original: text,
                replacement: "text-muted-foreground",
                pairedWith: bg,
                rationale: "White text on muted surfaces is often a theming mistake; prefer text-muted-foreground.",
                confidence: 0.87,
            };
        }
        if (bg === "bg-background" && theme.has("foreground")) {
            return {
                original: text,
                replacement: "text-foreground",
                pairedWith: bg,
                rationale: "Prefer text-foreground on the main background for correct theme contrast.",
                confidence: 0.86,
            };
        }
    }
    // --- Same neutral scale and step (e.g. bg-gray-100 text-gray-100) ---
    if (sameNeutralScaleAndShade(bg, text) && theme.has("foreground")) {
        return {
            original: text,
            replacement: "text-foreground",
            pairedWith: bg,
            rationale: "Same background and text shade removes visible contrast between text and surface.",
            confidence: 0.91,
        };
    }
    // --- White on white / black on black ---
    if (bg === "bg-white" && text === "text-white" && theme.has("foreground")) {
        return {
            original: text,
            replacement: "text-foreground",
            pairedWith: bg,
            rationale: "White text on a white background has effectively no contrast.",
            confidence: 0.95,
        };
    }
    if (bg === "bg-black" && text === "text-black" && theme.has("foreground")) {
        return {
            original: text,
            replacement: "text-foreground",
            pairedWith: bg,
            rationale: "Black text on a black background has effectively no contrast.",
            confidence: 0.95,
        };
    }
    const bgPalette = !isSemanticColorUtility(bg);
    const textPalette = !isSemanticColorUtility(text);
    // --- Light palette on light palette ---
    if (theme.has("foreground") &&
        bgBand === "light" &&
        textBand === "light" &&
        bgPalette &&
        textPalette) {
        return {
            original: text,
            replacement: "text-foreground",
            pairedWith: bg,
            rationale: "Light text color on a light background often fails contrast; use a theme foreground token.",
            confidence: 0.78,
        };
    }
    // --- Dark palette on dark palette ---
    if (theme.has("foreground") &&
        bgBand === "dark" &&
        textBand === "dark" &&
        bgPalette &&
        textPalette) {
        return {
            original: text,
            replacement: "text-foreground",
            pairedWith: bg,
            rationale: "Dark-on-dark palette utilities often merge visually; use a theme foreground token for readable text.",
            confidence: 0.78,
        };
    }
    return null;
}
function hasToken(tokens, expected) {
    return tokens.includes(expected);
}
function hasPrefixToken(tokens, prefix) {
    return tokens.some((t) => t.startsWith(prefix));
}
function parseOpacityPercent(token) {
    const m = /\/(\d{1,3})$/.exec(token);
    if (!m || !m[1])
        return null;
    const n = parseInt(m[1], 10);
    return Number.isNaN(n) ? null : n;
}
function isTinyTextToken(token) {
    return /^text-\[(8|9|10)px\]$/.test(token);
}
function pushIssueIfMissing(issues, seenOriginal, tokens, issue) {
    if (issue.appendClass && hasToken(tokens, issue.appendClass))
        return;
    if (seenOriginal.has(issue.original))
        return;
    issues.push(issue);
    seenOriginal.add(issue.original);
}
/**
 * Default-on dark contrast normalization pass.
 * Static heuristic only: this does not compute runtime WCAG contrast.
 */
function findDarkContrastNormalizationIssues(tokens, _themeNames) {
    const issues = [];
    const seenOriginal = new Set();
    // 1) Tiny muted text should get dark boost.
    const hasTinyText = tokens.some((t) => isTinyTextToken(t));
    if (hasTinyText && hasToken(tokens, "text-muted-foreground")) {
        pushIssueIfMissing(issues, seenOriginal, tokens, {
            original: "text-muted-foreground",
            replacement: "text-muted-foreground",
            appendClass: "dark:text-muted-foreground/90",
            action: "append",
            rationale: "Tiny muted labels can fade in dark mode; add a dark emphasis variant.",
            confidence: 0.94,
        });
    }
    // 2) Muted surfaces gain dark tint + border support.
    for (const token of tokens) {
        if (token === "bg-muted" || token.startsWith("bg-muted/")) {
            pushIssueIfMissing(issues, seenOriginal, tokens, {
                original: token,
                replacement: token,
                appendClass: "dark:bg-white/5",
                action: "append",
                rationale: "Muted surfaces can disappear in dark mode; add a subtle white tint.",
                confidence: 0.9,
            });
        }
        if (token === "border-border" || token.startsWith("border-border/")) {
            pushIssueIfMissing(issues, seenOriginal, tokens, {
                original: token,
                replacement: token,
                appendClass: "dark:border-white/10",
                action: "append",
                rationale: "Border-border often needs extra dark-mode visibility for separators and cards.",
                confidence: 0.9,
            });
        }
    }
    // 3) Hairline/faint dividers gain stronger dark visibility.
    for (const token of tokens) {
        if (!token.startsWith("bg-border/"))
            continue;
        const opacity = parseOpacityPercent(token);
        if (opacity === null || opacity > 12)
            continue;
        pushIssueIfMissing(issues, seenOriginal, tokens, {
            original: token,
            replacement: token,
            appendClass: "dark:bg-white/15",
            action: "append",
            rationale: "Very faint border dividers tend to vanish in dark mode; add a dark divider floor.",
            confidence: 0.88,
        });
    }
    // 4) Inactive pills/tabs tiny uppercase text with low opacity should be normalized.
    const looksLikePillTab = hasPrefixToken(tokens, "rounded") || hasPrefixToken(tokens, "px-");
    const hasUppercase = hasToken(tokens, "uppercase");
    if (hasTinyText && hasUppercase && looksLikePillTab) {
        for (const token of tokens) {
            if (!token.startsWith("text-"))
                continue;
            const opacity = parseOpacityPercent(token);
            if (opacity === null || opacity >= 70)
                continue;
            if (token.startsWith("text-muted-foreground/")) {
                pushIssueIfMissing(issues, seenOriginal, tokens, {
                    original: token,
                    replacement: "text-muted-foreground",
                    appendClass: "dark:text-muted-foreground/90",
                    action: "append_and_rewrite",
                    rationale: "Tiny uppercase inactive labels need a higher minimum contrast in dark mode.",
                    confidence: 0.92,
                });
            }
            else if (token.startsWith("text-white/") || token.startsWith("text-foreground/")) {
                pushIssueIfMissing(issues, seenOriginal, tokens, {
                    original: token,
                    replacement: "text-foreground",
                    appendClass: "dark:text-foreground",
                    action: "append_and_rewrite",
                    rationale: "Low-opacity tiny uppercase text should be rewritten to a stable foreground contrast token.",
                    confidence: 0.9,
                });
            }
        }
    }
    return issues;
}
/**
 * Extract string contents that hold Tailwind class lists from a line of TS/JS/TSX.
 */
function extractClassListStringsFromLine(line) {
    const chunks = [];
    const patterns = [
        /className\s*=\s*"([^"]+)"/g,
        /className\s*=\s*'([^']+)'/g,
        /className\s*=\s*\{\s*`([^`]+)`\s*\}/g,
    ];
    for (const re of patterns) {
        let m;
        while ((m = re.exec(line)) !== null) {
            if (m[1])
                chunks.push(m[1]);
        }
    }
    for (const fn of ["cn", "clsx", "classNames", "twMerge"]) {
        let pos = 0;
        while (pos < line.length) {
            const needle = `${fn}(`;
            const idx = line.indexOf(needle, pos);
            if (idx === -1)
                break;
            let depth = 1;
            let i = idx + needle.length;
            for (; i < line.length && depth > 0; i++) {
                const c = line[i];
                if (c === "(")
                    depth++;
                else if (c === ")")
                    depth--;
            }
            const args = line.slice(idx + needle.length, i - 1);
            const strRe = /"([^"]*)"|'([^']*)'/g;
            let sm;
            while ((sm = strRe.exec(args)) !== null) {
                const s = sm[1] ?? sm[2];
                if (s)
                    chunks.push(s);
            }
            pos = i;
        }
    }
    return chunks;
}
function splitClassTokens(chunk) {
    return chunk.trim().split(/\s+/).filter(Boolean);
}
/**
 * Replace the nth (0-based) occurrence of a standalone class token in a line.
 * Returns null if the occurrence does not exist.
 */
function replaceNthClassOccurrence(line, fromClass, toClass, occurrenceIndex) {
    const escaped = fromClass.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    // Delimiters: start, whitespace, or quotes/backticks (class lists in JSX)
    const re = new RegExp(`(^|[\\s"'\\\`])${escaped}(?=[\\s"'\\\`]|$)`, "g");
    let n = 0;
    let replaced = false;
    const out = line.replace(re, (match, p1) => {
        if (n === occurrenceIndex) {
            n++;
            replaced = true;
            return `${p1}${toClass}`;
        }
        n++;
        return match;
    });
    return replaced ? out : null;
}
//# sourceMappingURL=tailwindSemantic.js.map