#!/usr/bin/env node
/**
 * check-code.mjs — 代码侧颜色/图标单源闸（分端扫描，零依赖，Node >= 18）
 *
 * 各端规则（违规 exit 1；行内 `ds-ignore: 理由` 豁免）：
 *  desktop/src（jsx/css/js）：禁字面色（hex/rgb/hsl），须 var(--*)；
 *                             禁内联 <path>（图标须经 @dudu/design 的 Icon/icons.js）
 *  ios（swift）             ：禁 Color(red:/UIColor(/Color(0x/#colorLiteral，须 DuduTheme；
 *                             systemName 用的 SF Symbol 须 ∈ design/icon-map.json 的 ios 列
 *  android（kt）            ：禁 Color(0x，须 DuduTheme（生成文件本身豁免）
 *  web（html/css）          ：禁字面色（令牌由 GENERATED tokens.css 提供，页面只允许 var(--*)）
 *
 * 用法：node design-pipeline/check-code.mjs
 */

import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
import { resolve, dirname, join, relative, basename } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dir = dirname(fileURLToPath(import.meta.url));
const ROOT = resolve(__dir, '..');
const errors = [];
const err = (file, line, msg) => errors.push(`${relative(ROOT, file)}:${line}  ${msg}`);

function walk(dir, exts, out = []) {
  if (!existsSync(dir)) return out;
  for (const name of readdirSync(dir)) {
    if (name === 'node_modules' || name === 'dist' || name === 'target' || name.startsWith('.')) continue;
    const p = join(dir, name);
    if (statSync(p).isDirectory()) walk(p, exts, out);
    else if (exts.some((e) => name.endsWith(e))) out.push(p);
  }
  return out;
}

const COLOR_RE = /#[0-9a-fA-F]{3,8}\b|(?:rgba?|hsla?)\(/g;

function scanColors(files, label) {
  for (const f of files) {
    readFileSync(f, 'utf8').split('\n').forEach((line, i) => {
      if (line.includes('ds-ignore:')) return;
      const stripped = line.replace(/var\(--[\w-]+\)/g, ''); // var() 里不含字面色，先剥掉便于报错定位
      const m = stripped.match(COLOR_RE);
      if (m) err(f, i + 1, `${label} 字面色 ${m[0]}（改用设计令牌，或加 ds-ignore: 理由）`);
    });
  }
}

// ─── desktop：字面色 + 内联 <path> ───────────────────────────────────────────

const desktopFiles = walk(join(ROOT, 'desktop/src'), ['.jsx', '.js', '.css', '.html']);
scanColors(desktopFiles, 'desktop');
for (const f of desktopFiles) {
  readFileSync(f, 'utf8').split('\n').forEach((line, i) => {
    if (line.includes('ds-ignore:')) return;
    if (/<path[\s>]/.test(line))
      err(f, i + 1, 'desktop 内联 <path>（图标须登记 design/icons.js 并经 Icon 组件渲染）');
  });
}

// ─── iOS：字面色 + SF Symbol 同集 ────────────────────────────────────────────

const iconMap = JSON.parse(readFileSync(join(ROOT, 'design/icon-map.json'), 'utf8'));
const iosSymbols = new Set(
  Object.entries(iconMap).filter(([k]) => k !== '$schema').map(([, v]) => v.ios).filter(Boolean)
);

for (const f of walk(join(ROOT, 'ios'), ['.swift'])) {
  const isGenerated = basename(f) === 'DuduTheme.swift';
  readFileSync(f, 'utf8').split('\n').forEach((line, i) => {
    if (line.includes('ds-ignore:')) return;
    if (!isGenerated && /Color\(red:|UIColor\(red:|Color\(\s*0x|#colorLiteral/.test(line))
      err(f, i + 1, 'iOS 字面色（改用 DuduTheme.*，或加 ds-ignore: 理由）');
    for (const m of line.matchAll(/systemName:\s*"([^"]+)"/g)) {
      if (!iosSymbols.has(m[1]))
        err(f, i + 1, `iOS SF Symbol "${m[1]}" 未登记 design/icon-map.json（ios 列）`);
    }
  });
}

// ─── Android：字面色 ─────────────────────────────────────────────────────────

for (const f of walk(join(ROOT, 'android/app/src'), ['.kt'])) {
  if (basename(f) === 'DuduTheme.kt') continue; // codegen 产物
  readFileSync(f, 'utf8').split('\n').forEach((line, i) => {
    if (line.includes('ds-ignore:')) return;
    if (/Color\(\s*0x/.test(line))
      err(f, i + 1, 'Android 字面色（改用 DuduTheme / DuduPalette，或加 ds-ignore: 理由）');
  });
}

// ─── web：字面色（tokens.css 为 GENERATED 豁免）──────────────────────────────

scanColors(
  walk(join(ROOT, 'web'), ['.html', '.css']).filter((f) => basename(f) !== 'tokens.css'),
  'web'
);

// ─── 汇总 ────────────────────────────────────────────────────────────────────

if (errors.length) {
  console.error(`❌ check-code：${errors.length} 处违规\n` + errors.map((e) => '  ' + e).join('\n'));
  process.exit(1);
}
console.log('✅ check-code：desktop / iOS / Android / web 单源全绿');
