#!/usr/bin/env node
'use strict';

const fs = require('fs');
const path = require('path');

const ROOT_DIR = process.cwd();
const FAIL_UNUSED = process.argv.includes('--fail-unused');

const LOCALE_SPECS = [
  {
    name: 'root',
    label: 'root locales',
    dir: path.join(ROOT_DIR, 'locales'),
    template: 'template.toml',
    langs: ['ko.toml', 'en.toml'],
    ownsKey: (key) => !key.startsWith('ui.common.') && !key.startsWith('msg.common.'),
  },
  {
    name: 'common',
    label: 'common locales',
    dir: path.join(ROOT_DIR, 'common', 'locales'),
    template: 'template.toml',
    langs: ['ko.toml', 'en.toml'],
    ownsKey: (key) => key.startsWith('ui.common.') || key.startsWith('msg.common.'),
  },
];

const SKIP_DIRS = new Set([
  '.git',
  'node_modules',
  'dist',
  'build',
  '.dart_tool',
  '.idea',
  '.vscode',
  'coverage',
  '.next',
  '.cache',
  'tmp',
  'logs',
]);

const CODE_EXTENSIONS = new Set(['.ts', '.tsx', '.dart']);

const CODE_PATTERNS = [
  /\b(?:i18n\.)?t\s*\(\s*['"]([^'"]+)['"]/g,
  /\btr\s*\(\s*['"]([^'"]+)['"]/g,
  /['"]([^'"]+)['"]\s*\.tr\s*\(/g,
];

function readFileRequired(filePath) {
  if (!fs.existsSync(filePath)) {
    return { ok: false, error: `파일이 없습니다: ${filePath}` };
  }
  return { ok: true, value: fs.readFileSync(filePath, 'utf8') };
}

function parseTomlKeys(filePath) {
  const result = readFileRequired(filePath);
  if (!result.ok) {
    return { ok: false, error: result.error, keys: new Set() };
  }

  const keys = new Set();
  const lines = result.value.split(/\r?\n/);
  let currentSection = [];

  for (const rawLine of lines) {
    const line = rawLine.trim();
    if (!line || line.startsWith('#')) {
      continue;
    }

    if (line.startsWith('[[') && line.endsWith(']]')) {
      const sectionName = line.slice(2, -2).trim();
      currentSection = sectionName ? sectionName.split('.').map((p) => p.trim()).filter(Boolean) : [];
      continue;
    }

    if (line.startsWith('[') && line.endsWith(']')) {
      const sectionName = line.slice(1, -1).trim();
      currentSection = sectionName ? sectionName.split('.').map((p) => p.trim()).filter(Boolean) : [];
      continue;
    }

    const eqIndex = line.indexOf('=');
    if (eqIndex === -1) {
      continue;
    }

    let key = line.slice(0, eqIndex).trim();
    if (!key) {
      continue;
    }

    if (key.startsWith('"') && key.endsWith('"')) {
      key = key.slice(1, -1);
    }

    const fullKey = [...currentSection, key].join('.');
    keys.add(fullKey);
  }

  return { ok: true, keys };
}

function walkDir(dirPath, files) {
  const entries = fs.readdirSync(dirPath, { withFileTypes: true });
  for (const entry of entries) {
    if (entry.isDirectory()) {
      if (SKIP_DIRS.has(entry.name)) {
        continue;
      }
      walkDir(path.join(dirPath, entry.name), files);
      continue;
    }

    if (!entry.isFile()) {
      continue;
    }

    const ext = path.extname(entry.name).toLowerCase();
    if (!CODE_EXTENSIONS.has(ext)) {
      continue;
    }

    if (entry.name.includes('.test.') || entry.name.includes('.spec.')) {
      continue;
    }

    files.push(path.join(dirPath, entry.name));
  }
}

function collectCodeKeys() {
  const files = [];
  walkDir(ROOT_DIR, files);

  const keys = new Set();
  for (const filePath of files) {
    const content = fs.readFileSync(filePath, 'utf8');
    for (const pattern of CODE_PATTERNS) {
      let match;
      while ((match = pattern.exec(content)) !== null) {
        if (match[1]) {
          keys.add(match[1]);
        }
      }
    }
  }

  return keys;
}

function shouldIgnoreCodeKey(key) {
  return (
    key.includes('.msg.') ||
    key.includes('.ui.') ||
    key.includes('.err.') ||
    key.includes('.test.') ||
    key.includes('.non.') ||
    key.startsWith('ui.admin.users.list.table.') ||
    key.startsWith('msg.admin.users.detail.') ||
    key.startsWith('msg.dev.clients.') ||
    key.startsWith('ui.admin.users.create.') ||
    key.startsWith('ui.admin.users.detail.') ||
    key.startsWith('ui.dev.clients.') ||
    key.startsWith('ui.dev.session.')
  );
}

function difference(aSet, bSet) {
  const result = [];
  for (const item of aSet) {
    if (!bSet.has(item)) {
      result.push(item);
    }
  }
  return result.sort();
}

function printList(title, items) {
  if (items.length === 0) {
    return;
  }
  console.log(`\n${title}`);
  for (const item of items) {
    console.log(`- ${item}`);
  }
}

function collectSpecResources(spec) {
  const templatePath = path.join(spec.dir, spec.template);
  const templateResult = parseTomlKeys(templatePath);
  if (!templateResult.ok) {
    return { ok: false, error: templateResult.error };
  }

  const langKeyMap = new Map();
  for (const fileName of spec.langs) {
    const langPath = path.join(spec.dir, fileName);
    const langResult = parseTomlKeys(langPath);
    if (!langResult.ok) {
      return { ok: false, error: langResult.error };
    }
    langKeyMap.set(fileName, langResult.keys);
  }

  return {
    ok: true,
    templateKeys: templateResult.keys,
    langKeyMap,
  };
}

function main() {
  const errors = [];
  const warnings = [];

  const rawCodeKeys = Array.from(collectCodeKeys()).filter(
    (key) => !shouldIgnoreCodeKey(key),
  );
  const codeKeys = new Set(rawCodeKeys);

  for (const spec of LOCALE_SPECS) {
    const resources = collectSpecResources(spec);
    if (!resources.ok) {
      errors.push(resources.error);
      continue;
    }

    for (const [fileName, langKeys] of resources.langKeyMap.entries()) {
      const missingInLang = difference(resources.templateKeys, langKeys);
      if (missingInLang.length > 0) {
        errors.push(
          `[Sync Error] ${spec.label} ${fileName} 누락 키 ${missingInLang.length}개`,
        );
        printList(`${spec.label} ${fileName}에 없는 키`, missingInLang);
      }
    }

    const ownedCodeKeys = new Set(
      rawCodeKeys.filter((key) => spec.ownsKey(key)),
    );

    const missingInTemplate = difference(ownedCodeKeys, resources.templateKeys);
    if (missingInTemplate.length > 0) {
      errors.push(
        `[Missing Key] ${spec.label} template.toml 누락 키 ${missingInTemplate.length}개`,
      );
      printList(`${spec.label} template.toml에 없는 코드 사용 키`, missingInTemplate);
    }

    const unusedInTemplate = difference(resources.templateKeys, codeKeys);
    if (unusedInTemplate.length > 0) {
      warnings.push(
        `[Unused Key] ${spec.label} template.toml 미사용 키 ${unusedInTemplate.length}개`,
      );
      printList(`${spec.label} 코드에서 사용되지 않는 키`, unusedInTemplate);
    }
  }

  if (errors.length > 0) {
    console.error('\n요약');
    for (const error of errors) {
      console.error(`- ${error}`);
    }
    process.exit(1);
  }

  if (warnings.length > 0) {
    console.warn('\n요약');
    for (const warning of warnings) {
      console.warn(`- ${warning}`);
    }
    if (FAIL_UNUSED) {
      process.exit(1);
    }
  }

  console.log('\n✅ i18n 검증 완료');
}

main();
