/**
 * TFDS 规范契约轻量自检（只读，不改写用户任何配置文件）
 *
 * 在对方项目根目录执行：
 *   node node_modules/@tfdesign/b-end/scripts/check-tfds-contract.mjs
 *
 * 目标：在未接入 @tfdesign/b-end/eslint 时，提供一层“最低成本”的漂移提示。
 * 注意：这是启发式扫描，不等价于 ESLint 的完整硬门禁。
 */
import fs from 'node:fs';
import path from 'node:path';
import {
  collectScanTargets,
  exists,
  printContractReport,
  read,
  rel,
} from './lib/contract-scan-core.mjs';

const projectRoot = path.resolve(process.env.TFDS_PROJECT_ROOT || process.cwd());

function main() {
  const issues = [];
  const ok = [];

  const pkgPath = path.join(projectRoot, 'package.json');
  if (!exists(pkgPath)) {
    issues.push(`未找到 package.json（当前检测根目录：${projectRoot}）。请在对方项目根目录执行本脚本。`);
    printContractReport(projectRoot, issues, ok);
    process.exit(1);
  }

  const depsRaw = read(pkgPath);
  if (depsRaw) {
    try {
      const pkg = JSON.parse(depsRaw);
      const deps = { ...pkg.dependencies, ...pkg.devDependencies };
      const eslintCfg =
        read(path.join(projectRoot, 'eslint.config.js')) ||
        read(path.join(projectRoot, 'eslint.config.mjs'));
      if (eslintCfg?.includes('@tfdesign/b-end/eslint')) {
        ok.push('检测到 @tfdesign/b-end/eslint preset（推荐作为 CI 硬门禁）');
      } else if (deps['@tfds/eslint-config']) {
        ok.push('检测到 @tfds/eslint-config（已废弃，请迁移至 @tfdesign/b-end/eslint）');
      }
    } catch {
      // ignore
    }
  }

  const { scanRoots, targets } = collectScanTargets(projectRoot);
  if (scanRoots.length === 0) {
    issues.push('未找到可扫描的源码目录（候选：src, app, pages, components）。跳过规则扫描。');
    printContractReport(projectRoot, issues, ok);
    process.exit(0);
  }
  ok.push(`将扫描：${scanRoots.map((r) => rel(projectRoot, r)).join(', ')}`);
  if (targets.length === 0) {
    issues.push('未找到可扫描的 .js/.jsx/.ts/.tsx/.css 文件。');
    printContractReport(projectRoot, issues, ok);
    process.exit(0);
  }

  // Heuristics:
  // - Tailwind 任意值：[...]（强烈建议禁止）
  // - CSS / JSX 内硬编码 hex：#fff / #ffffff（建议走 token/theme）
  // - 明显的 spacing 任意值：p-[..] / m-[..] / gap-[..]
  // - 伪 FormTitle：原生 h2/h3/h4 或自绘左竖条标题（应改用 <FormTitle />）
  const reArbitrary = /\[[^\]]+\]/g;
  const reHex = /#[0-9a-fA-F]{3,8}\b/g;
  const reArbSpacing = /\b(?:p|px|py|pt|pr|pb|pl|m|mx|my|mt|mr|mb|ml|gap|gap-x|gap-y)-\[[^\]]+\]\b/g;
  const reNativeHeading = /<h[2-4]\b[^>]*>/g;
  const reClassAttr = /\b(?:className|class)\s*=\s*["']([^"']+)["']/g;

  const hit = new Map(); // file -> collected heuristic hits

  function collectPseudoTitleHits(text) {
    const hits = [];
    hits.push(...(text.match(reNativeHeading) || []).slice(0, 6));

    for (const m of text.matchAll(reClassAttr)) {
      const cls = m[1];
      const hasTinyRailWidth = /\bw-1\b|\bw-\[(?:3|4|6)px\]/.test(cls);
      const hasRailHeight = /\bh-(?:3\.5|4|5|6|8)\b|\bh-\[(?:14|16|18|20|24|32)px\]/.test(cls);
      const hasAccentBg = /\bbg-(?:brand|teal|cyan|emerald|green|primary|sky|blue)-/.test(cls);
      const hasLeftBorderTitle = /\bborder-l(?:-\d+)?\b/.test(cls) && /\b(?:font-medium|font-semibold|text-base|text-lg|text-xl)\b/.test(cls);
      if ((hasTinyRailWidth && hasRailHeight && hasAccentBg) || hasLeftBorderTitle) {
        hits.push(`className="${cls}"`);
      }
      if (hits.length >= 10) break;
    }
    return hits.slice(0, 10);
  }

  function collectAiEntryWhiteWrapperHits(text) {
    const looksLikeAiEntry =
      /<ChatInput\b/.test(text) &&
      /<Card\b/.test(text) &&
      /(今天想做什么|从模板|模板广场|模板中心|搜索助手|助手广场|Agent|AI\s*入口|AI\s*首页|AI\s*工作台|智能分析入口)/i.test(text);

    if (!looksLikeAiEntry) return [];

    const hits = [];
    const classWrapper = /<(?:main|section|div)\b[^>]*(?:className|class)\s*=\s*["'][^"']*(?:bg-surface|bg-white)[^"']*(?:rounded|rounded-)[^"']*["'][^>]*>/gi;
    const classWrapperReverse = /<(?:main|section|div)\b[^>]*(?:className|class)\s*=\s*["'][^"']*(?:rounded|rounded-)[^"']*(?:bg-surface|bg-white)[^"']*["'][^>]*>/gi;
    const styleWrapper = /<(?:main|section|div)\b[^>]*style=\{\{[^}]*background\s*:\s*['"`](?:var\(--color-surface\)|#fff(?:fff)?|white)[^}]*\}\}[^>]*>/gi;

    hits.push(...(text.match(classWrapper) || []).slice(0, 4));
    hits.push(...(text.match(classWrapperReverse) || []).slice(0, 4));
    hits.push(...(text.match(styleWrapper) || []).slice(0, 4));
    return [...new Set(hits)].slice(0, 8);
  }

  function collectMainSpacingHits(text) {
    const hits = [];
    const reMainSpacing = /<main\b[^>]*(?:className|class)\s*=\s*["'][^"']*\b(?:p-2|p-6|p-8|px-6|px-8|py-6|py-8)\b[^"']*["'][^>]*>/gi;
    hits.push(...(text.match(reMainSpacing) || []).slice(0, 8));
    return hits;
  }

  function collectCenteredShellHits(text) {
    const hits = [];
    const reCentered =
      /<(?:main|section|div)\b[^>]*(?:className|class)\s*=\s*["'][^"']*\bmax-w-(?:screen|[0-9a-zA-Z_-]+)[^"']*\bmx-auto\b[^"']*["'][^>]*>/gi;
    const reCenteredReverse =
      /<(?:main|section|div)\b[^>]*(?:className|class)\s*=\s*["'][^"']*\bmx-auto\b[^"']*\bmax-w-(?:screen|[0-9a-zA-Z_-]+)[^"']*["'][^>]*>/gi;
    hits.push(...(text.match(reCentered) || []).slice(0, 4));
    hits.push(...(text.match(reCenteredReverse) || []).slice(0, 4));
    return [...new Set(hits)].slice(0, 8);
  }

  function collectSurfaceChromeHits(text) {
    const hits = [];
    const classChrome =
      /<(?:main|section|div|Card)\b[^>]*(?:className|class)\s*=\s*["'][^"']*(?:bg-surface|bg-white)[^"']*\b(?:border|ring|shadow)\b[^"']*["'][^>]*>/gi;
    const styleChrome =
      /<(?:main|section|div)\b[^>]*style=\{\{[^}]*background\s*:\s*['"`](?:var\(--color-surface\)|white|#fff(?:fff)?)[^}]*\b(?:border|borderColor|boxShadow)\b[^}]*\}\}[^>]*>/gi;
    hits.push(...(text.match(classChrome) || []).slice(0, 4));
    hits.push(...(text.match(styleChrome) || []).slice(0, 4));
    return [...new Set(hits)].slice(0, 8);
  }

  function collectAiEntryCardColorHits(text) {
    const looksLikeAiEntry =
      /<ChatInput\b/.test(text) &&
      /<Card\b/.test(text) &&
      /(今天想做什么|从模板|模板广场|模板中心|搜索助手|助手广场|Agent|AI\s*入口|AI\s*首页|AI\s*工作台|智能分析入口)/i.test(text);
    if (!looksLikeAiEntry) return [];
    return (text.match(/<Card\b[^>]*\bcolor\s*=\s*["']grey["'][^>]*>/gi) || []).slice(0, 8);
  }

  function collectTabsSizeDriftHits(text) {
    const looksLikeContentTabs =
      /(筛选|filter|FilterBar|模板|卡片|Card|panel|Panel|Playground|工作区|白卡|section|Section)/i.test(text);
    if (!looksLikeContentTabs) return [];
    const hits = [];
    const reTabsLarge = /<Tabs\b[^>]*\bsize\s*=\s*["'](?:md|lg)["'][^>]*>/gi;
    for (const m of text.matchAll(reTabsLarge)) {
      const before = text.slice(Math.max(0, m.index - 360), m.index);
      const match = m[0];
      const allowedTopNavContext = /(TopBar|Header|PAGE_TABS|页面级|顶导|一级导航|顶部\s*header)/i.test(before);
      if (allowedTopNavContext) continue;
      hits.push(match);
      if (hits.length >= 8) break;
    }
    return hits;
  }

  function collectFilterFullWidthHits(text) {
    const looksLikeFilterBar = /(function|const)\s+\w*Filter\w*|筛选栏|FilterBar|filter bar/i.test(text);
    if (!looksLikeFilterBar) return [];
    const hits = [];
    const inputFullWidth = /<Input\b[^>]*(?:className|class)\s*=\s*["'][^"']*\bw-full\b[^"']*["'][^>]*>/gi;
    const inputStyleFull = /<Input\b[^>]*style=\{\{[^}]*(?:width\s*:\s*['"`]100%|flex\s*:\s*['"`]1)[^}]*\}\}[^>]*>/gi;
    hits.push(...(text.match(inputFullWidth) || []).slice(0, 4));
    hits.push(...(text.match(inputStyleFull) || []).slice(0, 4));
    return [...new Set(hits)].slice(0, 8);
  }

  function collectRemoteAvatarHits(text) {
    const hits = [];
    const componentRemote =
      /<(?:Avatar|ChatBubble|NavBar|ChatMessage)\b[^>]*(?:src|avatarSrc)\s*=\s*["']https?:\/\/[^"']+["'][^>]*>/gi;
    const objectRemote =
      /\b(?:avatarSrc|avatarUrl|avatarURL|profileImage|profilePic)\s*:\s*["']https?:\/\/[^"']+["']/gi;
    const roundedImgRemote =
      /<img\b(?=[^>]*src\s*=\s*["']https?:\/\/[^"']+["'])(?=[^>]*(?:rounded-full|avatar|头像))[^>]*>/gi;

    hits.push(...(text.match(componentRemote) || []).slice(0, 4));
    hits.push(...(text.match(objectRemote) || []).slice(0, 4));
    hits.push(...(text.match(roundedImgRemote) || []).slice(0, 4));
    return [...new Set(hits)].slice(0, 8);
  }

  for (const f of targets) {
    const t = read(f);
    if (!t) continue;
    const arbitrary = (t.match(reArbitrary) || []).slice(0, 10);
    const hex = (t.match(reHex) || []).slice(0, 10);
    const arbSpacing = (t.match(reArbSpacing) || []).slice(0, 10);
    const pseudoTitle = collectPseudoTitleHits(t);
    const aiEntryWhiteWrapper = collectAiEntryWhiteWrapperHits(t);
    const mainSpacing = collectMainSpacingHits(t);
    const centeredShell = collectCenteredShellHits(t);
    const surfaceChrome = collectSurfaceChromeHits(t);
    const aiEntryCardColor = collectAiEntryCardColorHits(t);
    const tabsSizeDrift = collectTabsSizeDriftHits(t);
    const filterFullWidth = collectFilterFullWidthHits(t);
    const remoteAvatar = collectRemoteAvatarHits(t);
    if (
      arbitrary.length ||
      hex.length ||
      arbSpacing.length ||
      pseudoTitle.length ||
      aiEntryWhiteWrapper.length ||
      mainSpacing.length ||
      centeredShell.length ||
      surfaceChrome.length ||
      aiEntryCardColor.length ||
      tabsSizeDrift.length ||
      filterFullWidth.length ||
      remoteAvatar.length
    ) {
      hit.set(f, {
        arbitrary,
        hex,
        arbSpacing,
        pseudoTitle,
        aiEntryWhiteWrapper,
        mainSpacing,
        centeredShell,
        surfaceChrome,
        aiEntryCardColor,
        tabsSizeDrift,
        filterFullWidth,
        remoteAvatar,
      });
    }
  }

  if (hit.size === 0) {
    ok.push('未发现明显的 Tailwind 任意值 / hex 硬编码（启发式）');
    printContractReport(projectRoot, issues, ok);
    process.exit(0);
  }

  for (const [f, v] of hit.entries()) {
    if (v.arbSpacing.length) {
      issues.push(`[${rel(projectRoot, f)}] 发现 spacing 任意值（示例）：${v.arbSpacing.join(', ')}`);
    }
    // 注意：arbitrary 会包含很多场景（如 grid-cols-[...]），这里不全禁，但先提示
    if (v.arbitrary.length) {
      issues.push(`[${rel(projectRoot, f)}] 发现 Tailwind 任意值（示例）：${v.arbitrary.join(', ')}`);
    }
    if (v.hex.length) {
      issues.push(`[${rel(projectRoot, f)}] 发现 hex 硬编码颜色（示例）：${v.hex.join(', ')}`);
    }
    if (v.pseudoTitle.length) {
      issues.push(`[${rel(projectRoot, f)}] 发现疑似手写标题 / 伪 FormTitle（示例）：${v.pseudoTitle.join(', ')}。白卡、面板、卡片、section 标题必须改用 <FormTitle />。`);
    }
    if (v.aiEntryWhiteWrapper.length) {
      issues.push(`[${rel(projectRoot, f)}] AI 入口页疑似被大白卡包住（示例）：${v.aiEntryWhiteWrapper.join(', ')}。ChatHomePagePattern 应直接坐浅灰底，Hero / ChatInput / Tabs / 模板 Card 网格外层不要包 bg-surface / 白色圆角大容器；模板卡自身用 <Card color="white" />。`);
    }
    if (v.mainSpacing.length) {
      issues.push(`[${rel(projectRoot, f)}] main 外框间距疑似不符合 TFDS（示例）：${v.mainSpacing.join(', ')}。通用白卡工作区 main 应使用 p-4（16px），禁止 p-2 / p-6 / p-8 / px-6 / px-8。`);
    }
    if (v.centeredShell.length) {
      issues.push(`[${rel(projectRoot, f)}] 主工作区疑似被 max-w + mx-auto 居中收窄（示例）：${v.centeredShell.join(', ')}。TFDS B 端页应撑满浏览器可用宽度，禁止在 main/主内容外层套居中容器。`);
    }
    if (v.surfaceChrome.length) {
      issues.push(`[${rel(projectRoot, f)}] 白色工作卡疑似带外层描边/阴影（示例）：${v.surfaceChrome.join(', ')}。最外层 WorkSurface 只允许纯白底 + 12px 圆角，不加 border/ring/shadow；内部控件描边可保留。`);
    }
    if (v.aiEntryCardColor.length) {
      issues.push(`[${rel(projectRoot, f)}] AI 入口页模板卡疑似使用 Card color="grey"（示例）：${v.aiEntryCardColor.join(', ')}。ChatHome 父级是浅灰底，模板卡必须用 <Card color="white" />。`);
    }
    if (v.tabsSizeDrift.length) {
      issues.push(`[${rel(projectRoot, f)}] 内容区 Tabs 疑似使用 md/lg 尺寸（示例）：${v.tabsSizeDrift.join(', ')}。白卡内、筛选维度、卡片内、Playground 面板内 Tabs 默认 size="sm"；md/lg 只用于平台顶部一级导航。`);
    }
    if (v.filterFullWidth.length) {
      issues.push(`[${rel(projectRoot, f)}] 筛选栏 Input 疑似误用全宽（示例）：${v.filterFullWidth.join(', ')}。FilterBar 搜索框默认约 240px，枚举/状态/时间控件 160-200px；白卡正文单列字段才默认 w-full。`);
    }
    if (v.remoteAvatar.length) {
      issues.push(`[${rel(projectRoot, f)}] 头像疑似使用随机外链或手写 img（示例）：${v.remoteAvatar.join(', ')}。TFDS 人像类头像默认应使用包内本地成员头像素材；Avatar/ChatBubble/NavBar/Table avatar/avatarText 缺省会自动取本地成员头像，只有业务真实接口返回头像 URL 时才传远程 src。`);
    }
  }

  issues.push('建议：在 eslint.config.js 接入 `@tfdesign/b-end/eslint` 并在 CI 执行 `npx eslint .`（比本脚本更准确）。');
  printContractReport(projectRoot, issues, ok);
  process.exit(1);
}

main();
