import { Parser } from 'i18next-scanner';
import "../../prototype.js";
import { path } from "../../path.js";
import { flist, fread, fwrite, fread_json } from "../../file.js";
import { noprint } from "../../process.js";
import { rethrow } from "../../prototype.js";
import { languages } from "../index.js";
import { RWDict } from "../rwdict.js";
import { parse_trans_from_string_by_babel } from "./parser.js";
/** 扫描源码中的词条，以及收集未翻译的词条，将结果保存到 dict.json 和 untranslateds.json
    - `process.cwd()` rootdir 要扫描根目录
    - config 配置信息 */
export async function scanner(fpd_root, config = {}) {
    const fpd_out = path.resolve(fpd_root, config.output || default_config.output).fpd;
    config = {
        ...default_config,
        ...config,
        output: fpd_out,
        resource: {
            loadPath: '',
            savePath: `${fpd_out}translation/{{lng}}.js`,
            jsonIndent: 4,
            lineEnding: '\n'
        }
    };
    let dict = new RWDict();
    for (const fp_dict of config.dict)
        dict.merge(await try_load_dict(`${fpd_out}${fp_dict}`), { print: false, overwrite: true });
    // 所有语言的扫描统计信息
    let stats = {};
    for (const language of languages)
        stats[language] = {
            translateds: new Set(),
            untranslateds: new Set()
        };
    function on_scanned(text, { language, key, defaultValue, count, context }) {
        // console.log(text, { language, key, defaultValue, count, context })
        text ||= defaultValue;
        if (!key)
            key = context ? `${text}_${context}` : text;
        if (!language) {
            for (const language of languages)
                on_scanned(text, { language, key, count, context });
            return;
        }
        // console.log(text, { language, key, defaultValue, count, context })
        const stat = stats[language];
        // 获取已有翻译
        const translation = dict.get(key, language) ||
            language === 'zh' && text ||
            '';
        if (language === 'zh' && !context)
            return;
        if (translation)
            stat.translateds.add(key);
        else
            stat.untranslateds.add(key);
        if (language === 'en' && count !== undefined)
            on_scanned(text, { language, key: `${key}_plural`, context });
    }
    let parser = new Parser(config);
    let mixed = false;
    (await Promise.all((await flist(fpd_root, {
        print: false,
        deep: true,
        filter: fp => fp.isdir ?
            !(fp.startsWith('.') || excludes.has(fp.fname))
            :
                fp.endsWith('.ts') && !fp.endsWith('.d.ts') || fp.endsWith('.tsx')
    }))
        .filter(fp => !fp.isdir)
        .map(async (fp) => [fp, await fread(`${fpd_root}${fp}`, noprint)]))).forEach(([fp, code]) => {
        // --- 添加代码中扫描到的 t('key') 中的 key 到 parser
        // parser.parseFuncFromString 使用 esprima 来解析代码，esprima 仍然不支持 optional chaining !!
        parser.parseFuncFromString(code.replace(/\?\.\[/g, '[').replace(/\?\.\(/g, '(').replace(/\?\./g, '.'), on_scanned);
        // --- 添加代码中扫描到的 Trans 组件中的 key 到 parser
        if (fp.endsWith('.tsx') && code.includes('<Trans')) {
            // parser.parseTransFromString 使用 acorn 解析代码，不支持 TypeScript，添加 parser.parseTransFromStringByBabel
            if (!mixed) {
                mixed = true;
                parser.parseTransFromStringByBabel = parse_trans_from_string_by_babel;
            }
            parser.parseTransFromStringByBabel(code, { filepath: fp }, on_scanned, rethrow);
        }
    });
    // --- 打印词条统计表
    console.log('扫描完成\n' +
        pad('语言', 0) + pad('未翻译', 1).red + pad('已翻译', 2).green + '\n' +
        Object.entries(stats)
            .filter(([lang, stat]) => lang === 'en' || stat.translateds.size)
            .map(([lang, stat]) => pad(lang, 0) + pad(String(stat.untranslateds.size), 1).red + pad(String(stat.translateds.size), 2).green).join_lines(false));
    const en_untranslateds = stats.en.untranslateds;
    const n_en_untranslateds = en_untranslateds.size;
    if (n_en_untranslateds) {
        console.log('\n缺少英文翻译的词条:'.yellow);
        let i = 0;
        for (const untranslated of en_untranslateds) {
            if (i >= 10)
                break;
            console.log(untranslated);
            i++;
        }
        if (n_en_untranslateds > 10) {
            console.log('...');
            console.log(`--- 共 ${en_untranslateds.size} 个未翻译的英文词条 ---`);
        }
    }
    else
        console.log('\n所有词条都至少含有英文翻译'.green);
    // --- 生成 untranslateds.json，并自动翻译 (扫描到词条还没有英文翻译)
    let untranslateds = {};
    if (n_en_untranslateds) {
        const _en_untranslateds = [...en_untranslateds];
        for (let i = 0; i < _en_untranslateds.length; ++i) {
            const key = _en_untranslateds[i];
            let item = { ...dict.get(key) };
            item.en ||= '';
            item.ja ||= '';
            item.ko ||= '';
            untranslateds[key] = item;
        }
    }
    // --- 保存到 dict.json, untranslateds.json
    const fp_untranslateds = `${fpd_out}untranslateds.json`;
    const fp_dict_ = `${fpd_out}dict.json`;
    await Promise.all([
        fwrite(fp_untranslateds, untranslateds, noprint),
        fwrite(fp_dict_, dict.to_json(true) + '\n', noprint)
    ]);
    console.log((n_en_untranslateds ?
        `${'请手动补全未翻译的词条: '.yellow}${fp_untranslateds.underline.blue}\n` +
            '补全 untranslateds.json 后需要重新运行扫描，会根据 untranslateds.json 更新 dict.json\n'.yellow
        :
            '') +
        `${'请检查词典文件: '}${fp_dict_.underline.blue}\n` +
        '最后词典文件中词条会被打包进 js, 通过 new I18N(<dict.json>) 或 i18n.init(<dict.json>) 加载\n');
    return n_en_untranslateds;
}
function pad(str, index) {
    return str.pad(widths[index], { position: 'left' });
}
const widths = [6, 8, 8];
const excludes = new Set([
    'node_modules/',
    'out/',
    'dist/',
    'test/'
]);
/** 默认 i18next 扫描配置 */
const default_config = {
    debug: false,
    input: [],
    // 相对于根目录
    output: 'i18n/',
    // 若是相对路径，则以 output 为基准进行解析
    dict: ['dict.json', 'untranslateds.json'],
    lngs: ['zh', 'en', 'ja', 'ko'],
    ns: ['translation'],
    defaultLng: 'zh',
    defaultNs: 'translation',
    func: {
        list: ['t'],
        extensions: [], // 避免在 transform 中执行原生的 parseFuncFromString
    },
    trans: {
        extensions: [], // 避免在 transform 中执行原生的 parseTransFromString
        fallbackKey: true,
        babylon: {
            sourceType: 'module',
            allowAwaitOutsideFunction: true,
            // https://babeljs.io/docs/en/babel-parser
            plugins: [
                // Language extensions
                'jsx',
                'typescript',
                // ECMAScript proposals
                'classProperties',
                'classPrivateProperties',
                'classPrivateMethods',
                'classStaticBlock',
                'decimal',
                ['decorators', { decoratorsBeforeExport: true }],
                'doExpressions',
                'exportDefaultFrom',
                'functionBind',
                'importAssertions',
                'moduleBlocks',
                'moduleStringNames',
                'partialApplication',
                ['pipelineOperator', { proposal: 'smart' }],
                'privateIn',
                ['recordAndTuple', { syntaxType: 'bar' }],
                'throwExpressions',
                'topLevelAwait',
            ],
        },
        // 实际并没有用到 acorn, 用了 babel
        acorn: {
            ecmaVersion: 'latest',
            sourceType: 'module', // defaults to 'module'
            // Check out https://github.com/acornjs/acorn/tree/master/acorn#interface for additional options
        }
    },
    // 禁用 : 和 . 作为 seperator
    keySeparator: false, // char to separate keys
    nsSeparator: false, // char to split namespace from key
    // Context Form
    context: true, // whether to add context form key
    contextFallback: true, // whether to add a fallback key as well as the context form key
    contextSeparator: '_', // char to split context from key
    // Plural
    // whether to add plural form key
    plural(language, ns, key, options /** Config */) {
        return language === 'en';
    },
    pluralFallback: true, // whether to add a fallback key as well as the plural form key
    pluralSeparator: '_', // char to split plural from key
    // interpolation options
    interpolation: {
        prefix: '{{', // prefix for interpolation
        suffix: '}}' // suffix for interpolation
    }
};
export async function try_load_dict(fp_dict) {
    try {
        return await fread_json(fp_dict, { print: false });
    }
    catch {
        // console.error('未找到或解析错误，跳过加载：' + modpath)
        return {};
    }
}
//# sourceMappingURL=index.js.map