import fs from 'node:fs/promises';
import path from 'node:path';
import { inferSourceChangeScreenTitle } from '../src/services/chat-room-service.js';

const APPLY_FLAG = '--apply';
const repoRootPath = path.resolve(process.cwd(), '../../..');
const codexLiveRootPath = path.join(repoRootPath, 'resource', 'Codex Live');
const genericScreenRootPath = path.join(codexLiveRootPath, 'Codex Live');

type FeaturePlan = {
  featureName: string;
  sourcePath: string;
  targetLabel: string;
  targetPath: string;
  filePaths: string[];
};

function normalizeWhitespace(value: string) {
  return String(value ?? '').replace(/\s+/g, ' ').trim();
}

function getScreenLabelFromTitle(title: string) {
  const segments = String(title ?? '')
    .split('/')
    .map((segment) => segment.trim())
    .filter(Boolean);

  return segments.at(-1) ?? '';
}

function extractSourcePathsFromSpec(text: string) {
  return Array.from(
    new Set(
      Array.from(text.matchAll(/`((?:src|etc|docs|public|scripts)\/[^`]+)`/g), (match) => normalizeWhitespace(match[1])),
    ),
  ).filter(Boolean);
}

function containsPattern(values: string[], pattern: RegExp) {
  return values.some((value) => pattern.test(normalizeWhitespace(value)));
}

function inferScreenLabelFromFeatureMetadata(args: {
  featureName: string;
  filePaths: string[];
  specTexts: string[];
}) {
  const featureName = normalizeWhitespace(args.featureName);
  const filePaths = args.filePaths.map((value) => normalizeWhitespace(value));
  const specTexts = args.specTexts.map((value) => normalizeWhitespace(value));

  if (
    containsPattern(filePaths, /(?:resourceManagerApi|resource-manager-service|resource-manager|ResourceManagementPage)/iu) ||
    containsPattern([featureName], /(?:resource manager|리소스 관리|리소스 경로|리소스 가이드|이미지 생성 CLI)/iu)
  ) {
    return '리소스 관리';
  }

  if (
    containsPattern(filePaths, /(?:ChatSourceChangesPage|chat-room-service)/iu) ||
    containsPattern([featureName], /(?:변경 이력|source change|source-changes)/iu)
  ) {
    return '변경 이력';
  }

  if (
    containsPattern(filePaths, /(?:PreviewAppOverlay|PreviewAppWindow|previewRuntime|appUpdate)/iu) ||
    containsPattern([featureName], /(?:모바일 앱 열기|Preview App)/iu)
  ) {
    return '모바일 앱 열기';
  }

  if (
    containsPattern(filePaths, /(?:MainHeader|HeaderMessageCenter|MainLayout\.css)/iu) ||
    containsPattern([featureName], /(?:헤더)/iu)
  ) {
    return '헤더 표시';
  }

  if (
    containsPattern(
      filePaths,
      /(?:MainChatPanel|ChatConversationView|mainChatPanel|ChatActivityChecklist|chatUtils)/iu,
    ) ||
    containsPattern(
      [featureName],
      /(?:채팅 말풍선|시스템 카드|말풍선|prompt|즉시전송|즉시 접수|답변 이동|활동 로그|첨부 파일|채팅방|MainChatPanel|ChatConversationView|mainChatPanel)/iu,
    )
  ) {
    return '채팅 말풍선';
  }

  return null;
}

function inferScreenLabelFromSpec(args: {
  featureName: string;
  filePaths: string[];
  specTexts: string[];
}) {
  const inferredTitle = inferSourceChangeScreenTitle(args.filePaths, 'Codex Live / Codex Live');
  const inferredLabel = getScreenLabelFromTitle(inferredTitle);

  if (inferredLabel && inferredLabel !== 'Codex Live' && inferredLabel !== '새 대화') {
    return inferredLabel;
  }

  const metadataLabel = inferScreenLabelFromFeatureMetadata(args);

  if (metadataLabel) {
    return metadataLabel;
  }

  const normalizedFeatureName = normalizeWhitespace(args.featureName);
  const hasHeaderSpecificFile = args.filePaths.some((filePath) =>
    /^(?:src\/app\/main\/MainHeader\.(?:ts|tsx)|src\/app\/main\/HeaderMessageCenter\.(?:ts|tsx|css))$/u.test(filePath),
  );
  const hasOnlyHeaderLayoutFiles =
    args.filePaths.length > 0 &&
    args.filePaths.every((filePath) =>
      /^(?:src\/app\/main\/MainLayout\.css|src\/app\/main\/HeaderMessageCenter\.css)$/u.test(filePath),
    );
  const hasPreviewSpecificFile = args.filePaths.some((filePath) =>
    /^(?:src\/app\/main\/PreviewAppOverlay\.(?:ts|tsx)|src\/app\/main\/PreviewAppWindow\.(?:ts|tsx)|src\/app\/main\/previewRuntime\.(?:ts|tsx|js)|src\/app\/main\/appUpdate\.(?:ts|tsx|js))$/u.test(
      filePath,
    ),
  );

  if (/^(?:preview\b|동영상 preview\b)/iu.test(normalizedFeatureName) || hasPreviewSpecificFile) {
    return '모바일 앱 열기';
  }

  if (
    /(?:헤더|테마|앱 설정|알림 뱃지|헤더 표시)/u.test(normalizedFeatureName) &&
    (hasHeaderSpecificFile || hasOnlyHeaderLayoutFiles || args.filePaths.length === 0)
  ) {
    return '헤더 표시';
  }

  return 'Codex Live';
}

async function exists(targetPath: string) {
  try {
    await fs.access(targetPath);
    return true;
  } catch {
    return false;
  }
}

async function readFeaturePlans() {
  if (!(await exists(genericScreenRootPath))) {
    return [] as FeaturePlan[];
  }

  const featureEntries = await fs.readdir(genericScreenRootPath, { withFileTypes: true });
  const plans: FeaturePlan[] = [];

  for (const featureEntry of featureEntries) {
    if (!featureEntry.isDirectory()) {
      continue;
    }

    const featureName = featureEntry.name;
    const featurePath = path.join(genericScreenRootPath, featureName);
    const datedEntries = (await fs.readdir(featurePath, { withFileTypes: true }))
      .filter((entry) => entry.isDirectory())
      .map((entry) => entry.name)
      .sort();

    const specTexts: string[] = [];
    const filePaths = new Set<string>();

    for (const datedEntry of datedEntries) {
      const specPath = path.join(featurePath, datedEntry, 'docs', 'feature-spec.md');

      if (!(await exists(specPath))) {
        continue;
      }

      const specText = await fs.readFile(specPath, 'utf8');
      specTexts.push(specText);

      extractSourcePathsFromSpec(specText).forEach((filePath) => {
        filePaths.add(filePath);
      });
    }

    const targetLabel = inferScreenLabelFromSpec({
      featureName,
      filePaths: Array.from(filePaths),
      specTexts,
    });

    plans.push({
      featureName,
      sourcePath: featurePath,
      targetLabel,
      targetPath: path.join(codexLiveRootPath, targetLabel, featureName),
      filePaths: Array.from(filePaths),
    });
  }

  return plans.sort((left, right) => left.featureName.localeCompare(right.featureName, 'ko'));
}

async function moveDirectoryContents(sourcePath: string, targetPath: string): Promise<void> {
  await fs.mkdir(path.dirname(targetPath), { recursive: true });

  if (!(await exists(targetPath))) {
    await fs.rename(sourcePath, targetPath);
    return;
  }

  const sourceEntries = await fs.readdir(sourcePath, { withFileTypes: true });

  for (const sourceEntry of sourceEntries) {
    const nextSourcePath = path.join(sourcePath, sourceEntry.name);
    const nextTargetPath = path.join(targetPath, sourceEntry.name);

    if (sourceEntry.isDirectory()) {
      await moveDirectoryContents(nextSourcePath, nextTargetPath);
      continue;
    }

    if (await exists(nextTargetPath)) {
      throw new Error(`대상 파일이 이미 존재합니다: ${path.relative(repoRootPath, nextTargetPath)}`);
    }

    await fs.mkdir(path.dirname(nextTargetPath), { recursive: true });
    await fs.rename(nextSourcePath, nextTargetPath);
  }

  await fs.rm(sourcePath, { recursive: false });
}

async function applyMoves(plans: FeaturePlan[]) {
  const applied: Array<{ featureName: string; from: string; to: string }> = [];

  for (const plan of plans) {
    if (plan.targetLabel === 'Codex Live') {
      continue;
    }

    await moveDirectoryContents(plan.sourcePath, plan.targetPath);
    applied.push({
      featureName: plan.featureName,
      from: path.relative(repoRootPath, plan.sourcePath),
      to: path.relative(repoRootPath, plan.targetPath),
    });
  }

  return applied;
}

try {
  const plans = await readFeaturePlans();
  const movablePlans = plans.filter((plan) => plan.targetLabel !== 'Codex Live');

  const summary = {
    mode: process.argv.includes(APPLY_FLAG) ? 'apply' : 'dry-run',
    totalFeatureCount: plans.length,
    movableFeatureCount: movablePlans.length,
    groupedTargets: movablePlans.reduce<Record<string, number>>((accumulator, plan) => {
      accumulator[plan.targetLabel] = (accumulator[plan.targetLabel] ?? 0) + 1;
      return accumulator;
    }, {}),
    moves: movablePlans.map((plan) => ({
      featureName: plan.featureName,
      from: path.relative(repoRootPath, plan.sourcePath),
      to: path.relative(repoRootPath, plan.targetPath),
      filePaths: plan.filePaths,
    })),
  };

  if (!process.argv.includes(APPLY_FLAG)) {
    console.log(JSON.stringify(summary, null, 2));
    process.exit(0);
  }

  const applied = await applyMoves(plans);

  console.log(JSON.stringify({
    ...summary,
    appliedCount: applied.length,
    applied,
  }, null, 2));
} catch (error) {
  console.error(error);
  process.exitCode = 1;
}
