/**
 * 公告 HTML 颜色自适应预处理。
 *
 * 超管写公告时常用暗色主题专用色(`#e0e0e0` 白字 / `#d1d5db` 浅灰 / `#fff` / `white` 等),
 * 这些在浅色背景下糊得看不清;反过来用 `#333` / `#374151` 类深灰文字在深色下也糊。
 *
 * 本函数遍历所有 inline `style`,用算法识别 "灰度系 + 极端亮度"(暗色 OR 浅色专用)
 * → strip 掉对应声明 → 让内容继承容器的主题色(`var(--color-text-primary)`,浅深自适应)。
 *
 * 真彩色(红/绿/蓝/紫/橙/青…,三通道差 ≥ 30)一律保留,因为它们通常双主题都可读。
 *
 * 用法:
 *   const safe = DOMPurify.sanitize(adaptAnnouncementColors(rawHtml));
 *
 * 在 NotificationsPage 展开公告 + AnnouncementModal 强弹两处调用。
 *
 * 实现说明:
 * - 用 DOMParser 解析 HTML(浏览器原生,不依赖 DOMPurify hook)
 * - 用 canvas getContext 解析任意 CSS 颜色值(支持 #hex / rgb / rgba / hsl / 命名色)
 * - SSR 安全:如果 document 不存在,原样返回不处理
 * - 对 color / background-color / border / border-color / border-top 等所有可能含颜色的属性都做识别
 */

let __canvasCtx: CanvasRenderingContext2D | null | undefined = undefined;
function getCanvasCtx(): CanvasRenderingContext2D | null {
  if (__canvasCtx !== undefined) return __canvasCtx;
  if (typeof document === 'undefined') {
    __canvasCtx = null;
    return null;
  }
  __canvasCtx = document.createElement('canvas').getContext('2d');
  return __canvasCtx;
}

/** 把任意 CSS color 值解析成 RGB 三通道(0-255)。失败返回 null。 */
function parseColorToRgb(value: string): [number, number, number] | null {
  const ctx = getCanvasCtx();
  if (!ctx) return null;
  const v = value.trim();
  if (!v) return null;
  try {
    // 先设个已知值,如果解析失败 fillStyle 会保留原值我们能比对
    ctx.fillStyle = '#000000';
    ctx.fillStyle = v;
    const computed = ctx.fillStyle as string;
    // canvas 输出格式:#rrggbb 或 rgba(r,g,b,a)
    if (computed.startsWith('#') && (computed.length === 7 || computed.length === 4)) {
      // 标准化 #abc → #aabbcc
      const hex = computed.length === 4
        ? '#' + computed[1] + computed[1] + computed[2] + computed[2] + computed[3] + computed[3]
        : computed;
      return [
        parseInt(hex.slice(1, 3), 16),
        parseInt(hex.slice(3, 5), 16),
        parseInt(hex.slice(5, 7), 16),
      ];
    }
    const m = computed.match(/\d+/g);
    if (m && m.length >= 3) {
      return [parseInt(m[0], 10), parseInt(m[1], 10), parseInt(m[2], 10)];
    }
  } catch {
    // ignore — 不可解析的丢回 null
  }
  return null;
}

/**
 * 判断颜色是否是"主题感知应被 strip 的灰度色":
 * - 三通道差 < 30 = 灰度系
 * - 亮度 > 200 = 浅色(暗主题专用)
 * - 亮度 < 80  = 深色(浅主题专用)
 * 双向都 strip,让继承主题色。
 *
 * 不 strip 的情况:CSS var、currentColor、inherit、transparent、彩色。
 */
export function shouldStripColor(value: string): boolean {
  const v = value.trim();
  if (!v) return false;
  // CSS var:超管已经主题适配过了,保留
  if (v.includes('var(')) return false;
  // 关键字:本来就是继承/重置,保留
  if (/^(currentColor|inherit|initial|unset|transparent|none)$/i.test(v)) return false;
  const rgb = parseColorToRgb(v);
  if (!rgb) return false;
  const [r, g, b] = rgb;
  const grayDiff = Math.max(r, g, b) - Math.min(r, g, b);
  const brightness = (r + g + b) / 3;
  return grayDiff < 30 && (brightness > 200 || brightness < 80);
}

/** 含可能 color token 的 CSS 属性名集合。border 系列也算 — 因为 `border: 1px solid #xxx`。 */
const COLOR_BEARING_PROPS = new Set([
  'color',
  'background',
  'background-color',
  'border',
  'border-top',
  'border-right',
  'border-bottom',
  'border-left',
  'border-color',
  'border-top-color',
  'border-right-color',
  'border-bottom-color',
  'border-left-color',
  'outline',
  'outline-color',
]);

/**
 * 判断 CSS declaration 是否应整条 strip。
 * 简化规则:把 value 拆成 token,任意 token 是"需要 strip 的颜色"就 strip 整条声明。
 * (border: 1px solid #e0e0e0 → strip 整条,让继承主题 border)
 */
function declHasStripColor(prop: string, value: string): boolean {
  if (!COLOR_BEARING_PROPS.has(prop)) return false;
  // 把 rgb(...)/rgba(...)/hsl(...) 整段当一个 token,避免被空格切碎
  const protectedValue = value.replace(/(rgba?|hsla?)\s*\([^)]+\)/gi, (m) => m.replace(/\s/g, '_'));
  for (const rawTok of protectedValue.split(/\s+/)) {
    const tok = rawTok.replace(/_/g, ' ').replace(/[,;]+$/, '').trim();
    if (!tok) continue;
    if (shouldStripColor(tok)) return true;
  }
  return false;
}

/**
 * 对公告 HTML 做颜色自适应处理,返回处理过的 HTML 字符串。
 * 不做 sanitize,调用方需要再 DOMPurify.sanitize 一次。
 */
export function adaptAnnouncementColors(html: string): string {
  if (!html || typeof document === 'undefined') return html;
  try {
    const doc = new DOMParser().parseFromString(html, 'text/html');
    const styledEls = doc.body.querySelectorAll<HTMLElement>('[style]');
    styledEls.forEach((el) => {
      const style = el.getAttribute('style') || '';
      if (!style.trim()) return;
      const kept: string[] = [];
      for (const declaration of style.split(';')) {
        const trimmed = declaration.trim();
        if (!trimmed) continue;
        const colonIdx = trimmed.indexOf(':');
        if (colonIdx < 0) { kept.push(trimmed); continue; }
        const prop = trimmed.slice(0, colonIdx).trim().toLowerCase();
        const value = trimmed.slice(colonIdx + 1).trim();
        if (declHasStripColor(prop, value)) {
          // strip 整条,让该元素该属性继承主题色
          continue;
        }
        kept.push(`${prop}: ${value}`);
      }
      const newStyle = kept.join('; ');
      if (newStyle) el.setAttribute('style', newStyle);
      else el.removeAttribute('style');
    });
    return doc.body.innerHTML;
  } catch {
    // 解析失败原样返回 — 公告至少不会"消失"
    return html;
  }
}
