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

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

const ROOT_DIR = process.cwd();

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 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 buildReport() {
  const report = {
    generated_at: new Date().toISOString(),
    errors: [],
    warnings: [],
    details: {},
  };

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

  for (const spec of LOCALE_SPECS) {
    const resources = collectSpecResources(spec);
    report.details[spec.name] = {
      missing_in_template: [],
      missing_in_lang: {},
      unused_in_template: [],
    };

    if (!resources.ok) {
      report.errors.push(resources.error);
      continue;
    }

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

    const ownedCodeKeys = new Set(rawCodeKeys.filter((key) => spec.ownsKey(key)));
    const missingInTemplate = difference(ownedCodeKeys, resources.templateKeys);
    if (missingInTemplate.length > 0) {
      report.errors.push(
        `[Missing Key] ${spec.label} template.toml 누락 키 ${missingInTemplate.length}개`,
      );
      report.details[spec.name].missing_in_template = missingInTemplate;
    }

    const unusedInTemplate = difference(resources.templateKeys, codeKeys);
    if (unusedInTemplate.length > 0) {
      report.warnings.push(
        `[Unused Key] ${spec.label} template.toml 미사용 키 ${unusedInTemplate.length}개`,
      );
      report.details[spec.name].unused_in_template = unusedInTemplate;
    }
  }

  return report;
}

function main() {
  const report = buildReport();
  const outDir = path.join(ROOT_DIR, 'reports');
  if (!fs.existsSync(outDir)) {
    fs.mkdirSync(outDir, { recursive: true });
  }
  const outPath = path.join(outDir, 'i18n-report.json');
  fs.writeFileSync(outPath, JSON.stringify(report, null, 2));
  const summaryPath = path.join(outDir, 'i18n-report.txt');

  const lines = [];
  lines.push(`generated_at: ${report.generated_at}`);
  if (report.errors.length > 0) {
    lines.push('errors:');
    report.errors.forEach((err) => lines.push(`- ${err}`));
  } else {
    lines.push('errors: none');
  }
  if (report.warnings.length > 0) {
    lines.push('warnings:');
    report.warnings.forEach((warn) => lines.push(`- ${warn}`));
  } else {
    lines.push('warnings: none');
  }
  fs.writeFileSync(summaryPath, lines.join('\n'));

  if (report.errors.length > 0) {
    console.error('❌ i18n report generated with errors');
    process.exit(1);
  }

  console.log(`✅ i18n report written to ${outPath}`);
  console.log(`✅ i18n summary written to ${summaryPath}`);
}

main();
