// Theme CSS injection and sanitization

import { DISALLOWED_CSS_PATTERNS } from './plugin-types';

const THEME_STYLE_ID = 'active-theme';
const THEME_SKIN_STYLE_ID = 'active-theme-skin';
const THEME_SKIN_BODY_ATTR = 'data-theme-skin';

/**
 * Sanitize theme CSS: strip dangerous patterns like @import, external url(),
 * JavaScript expressions, and -moz-binding. Returns cleaned CSS.
 */
export function sanitizeThemeCSS(css: string): { css: string; warnings: string[] } {
  const warnings: string[] = [];
  let cleaned = css;

  for (const pattern of DISALLOWED_CSS_PATTERNS) {
    if (pattern.test(cleaned)) {
      warnings.push(`Removed disallowed pattern: ${pattern.source}`);
      cleaned = cleaned.replace(new RegExp(pattern.source, 'gi'), '/* [removed] */');
    }
  }

  return { css: cleaned, warnings };
}

/**
 * Validate that theme CSS only targets :root and .dark selectors.
 * Returns warnings for any other selectors found.
 */
export function validateThemeSelectors(css: string): string[] {
  const warnings: string[] = [];

  // Remove comments
  const noComments = css.replace(/\/\*[\s\S]*?\*\//g, '');

  // Find selector blocks (text before { that isn't inside a value)
  const selectorRegex = /([^{}]+)\{/g;
  let match;
  while ((match = selectorRegex.exec(noComments)) !== null) {
    const selector = match[1].trim();
    // Allow :root, .dark, @font-face, @keyframes, @media
    if (
      selector === ':root' ||
      selector === '.dark' ||
      selector.startsWith('@font-face') ||
      selector.startsWith('@keyframes') ||
      selector.startsWith('@media') ||
      selector === ''
    ) {
      continue;
    }

    // Inside @media blocks, also allow :root and .dark
    if (selector === ':root' || selector === '.dark') continue;

    warnings.push(`Non-standard selector "${selector}" - themes should only use :root and .dark`);
  }

  return warnings;
}

/**
 * Inject theme CSS into the document head.
 * Inserted after globals.css so theme variables win specificity.
 */
export function injectThemeCSS(css: string): void {
  if (typeof document === 'undefined') return;

  let styleEl = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null;

  if (!styleEl) {
    styleEl = document.createElement('style');
    styleEl.id = THEME_STYLE_ID;
    document.head.appendChild(styleEl);
  }

  styleEl.textContent = css;
}

/**
 * Remove injected theme CSS, reverting to default.
 */
export function removeThemeCSS(): void {
  if (typeof document === 'undefined') return;

  const styleEl = document.getElementById(THEME_STYLE_ID);
  if (styleEl) {
    styleEl.remove();
  }
}

/**
 * Inject a theme's *skin* CSS - component-level overrides shipped by Theme
 * API v2 themes via `skin.css`. Lives in a separate `<style>` tag so it can
 * be removed cleanly without touching the colour-token block, and is placed
 * AFTER the colour block so component rules win specificity.
 *
 * Also sets `body[data-theme-skin="<themeId>"]` so authors can scope their
 * own `:not(...)` overrides if they want belt-and-braces specificity.
 */
export function injectThemeSkinCSS(css: string, themeId: string): void {
  if (typeof document === 'undefined') return;

  let styleEl = document.getElementById(THEME_SKIN_STYLE_ID) as HTMLStyleElement | null;
  if (!styleEl) {
    styleEl = document.createElement('style');
    styleEl.id = THEME_SKIN_STYLE_ID;
    document.head.appendChild(styleEl);
  }
  styleEl.textContent = css;

  if (document.body) {
    document.body.setAttribute(THEME_SKIN_BODY_ATTR, themeId);
  }
}

export function removeThemeSkinCSS(): void {
  if (typeof document === 'undefined') return;

  const styleEl = document.getElementById(THEME_SKIN_STYLE_ID);
  if (styleEl) styleEl.remove();
  if (document.body) document.body.removeAttribute(THEME_SKIN_BODY_ATTR);
}

/**
 * Sanitize a theme *skin* - looser than `sanitizeThemeCSS` because skins
 * intentionally target real component selectors (toolbars, lists, buttons),
 * not just `:root`/`.dark`. The same script-injection / external-resource
 * prohibitions still apply.
 */
export function sanitizeSkinCSS(css: string): { css: string; warnings: string[] } {
  const warnings: string[] = [];
  let cleaned = css;

  for (const pattern of DISALLOWED_CSS_PATTERNS) {
    if (pattern.test(cleaned)) {
      warnings.push(`Skin: removed disallowed pattern: ${pattern.source}`);
      cleaned = cleaned.replace(new RegExp(pattern.source, 'gi'), '/* [removed] */');
    }
  }

  // `@import` is already covered by DISALLOWED_CSS_PATTERNS, but skins also
  // get an explicit no-`@charset`/`@namespace` policy so they can't change
  // how the host stylesheet parses subsequent rules.
  cleaned = cleaned.replace(/@(charset|namespace)\b[^;]*;?/gi, () => {
    warnings.push('Skin: removed @charset/@namespace directive');
    return '/* [removed] */';
  });

  return { css: cleaned, warnings };
}

/**
 * Check if a theme CSS string is valid and safe.
 */
export function validateThemeCSSSafety(css: string): { valid: boolean; errors: string[] } {
  const errors: string[] = [];

  if (!css.trim()) {
    errors.push('Theme CSS is empty');
    return { valid: false, errors };
  }

  // Check for dangerous patterns
  for (const pattern of DISALLOWED_CSS_PATTERNS) {
    if (pattern.test(css)) {
      errors.push(`Contains disallowed pattern: ${pattern.source}`);
    }
  }

  // Check the CSS actually sets some variables
  if (!css.includes('--color-')) {
    errors.push('Theme CSS should set at least one --color-* variable');
  }

  return { valid: errors.length === 0, errors };
}
