灵符lingfu

ling-lang

↓ 0 downloads

Ling - The Omniglot Systems Language

Keywords systems, polyglot, ai, crypto, game
lingfu install ling-lang
LicenseApache-2.0 OR MIT
AuthorsLing Lin, Sanny Lin
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (995)

crates/ling-fu/src/lib.rs
use colored::*;
use dialoguer::{Input, Select};
use std::env;

pub mod ignore;
pub mod normalize;
pub mod registry;
pub mod scaffold;
pub mod seed;
use scaffold::{ProjectKind, ProjectScaffold, ScaffoldLang, dir_names};

// ─── Invocation language detection ───────────────────────────────────────────

fn detect_invocation_name() -> InvocationLanguage {
    let args: Vec<String> = env::args().collect();
    let exec_name = args[0].as_str();
    let base_name = std::path::Path::new(exec_name)
        .file_stem()
        .map(|s| s.to_string_lossy().to_string())
        .unwrap_or_else(|| exec_name.to_string());

    match base_name.as_str() {
        "灵符" | "靈符" => InvocationLanguage::Chinese,
        "霊符" | "リンゴフー" | "れいふ" => InvocationLanguage::Japanese,
        "영부" | "링푸" => InvocationLanguage::Korean,
        "линфу" => InvocationLanguage::Russian,
        "لينغفو" => InvocationLanguage::Arabic,
        "लिंगफू" => InvocationLanguage::Hindi,
        "ลิงฟู" => InvocationLanguage::Thai,
        "linhphu" | "linh-phu" => InvocationLanguage::Vietnamese,
        "λινγφυ" => InvocationLanguage::Greek,
        "לינגפו" => InvocationLanguage::Hebrew,
        "lingfu-de" => InvocationLanguage::German,
        "lingfu-fr" => InvocationLanguage::French,
        "lingfu-es" => InvocationLanguage::Spanish,
        "lingfu-tr" => InvocationLanguage::Turkish,
        "lingfu-pl" => InvocationLanguage::Polish,
        _ => InvocationLanguage::English,
    }
}

#[derive(Debug, Clone, PartialEq)]
enum InvocationLanguage {
    English,
    Chinese,
    Japanese,
    Korean,
    Russian,
    Arabic,
    Hindi,
    Thai,
    Vietnamese,
    Greek,
    Hebrew,
    German,
    French,
    Spanish,
    Turkish,
    Polish,
}

impl InvocationLanguage {
    fn cli_name(&self) -> &'static str {
        match self {
            InvocationLanguage::English => "lingfu",
            InvocationLanguage::Chinese => "灵符",
            InvocationLanguage::Japanese => "霊符",
            InvocationLanguage::Korean => "영부",
            InvocationLanguage::Russian => "линфу",
            InvocationLanguage::Arabic => "لينغفو",
            InvocationLanguage::Hindi => "लिंगफू",
            InvocationLanguage::Thai => "ลิงฟู",
            InvocationLanguage::Vietnamese => "linh phù",
            InvocationLanguage::Greek => "λινγφυ",
            InvocationLanguage::Hebrew => "לינגפו",
            InvocationLanguage::German => "lingfu",
            InvocationLanguage::French => "lingfu",
            InvocationLanguage::Spanish => "lingfu",
            InvocationLanguage::Turkish => "lingfu",
            InvocationLanguage::Polish => "lingfu",
        }
    }

    fn commands(&self) -> &'static CommandTranslations {
        match self {
            InvocationLanguage::English => &COMMANDS_EN,
            InvocationLanguage::Chinese => &COMMANDS_ZH,
            InvocationLanguage::Japanese => &COMMANDS_JA,
            InvocationLanguage::Korean => &COMMANDS_KO,
            InvocationLanguage::Russian => &COMMANDS_RU,
            InvocationLanguage::Arabic => &COMMANDS_AR,
            InvocationLanguage::Hindi => &COMMANDS_HI,
            InvocationLanguage::Thai => &COMMANDS_TH,
            InvocationLanguage::Vietnamese => &COMMANDS_VI,
            InvocationLanguage::Greek => &COMMANDS_EL,
            InvocationLanguage::Hebrew => &COMMANDS_HE,
            InvocationLanguage::German => &COMMANDS_DE,
            InvocationLanguage::French => &COMMANDS_FR,
            InvocationLanguage::Spanish => &COMMANDS_ES,
            InvocationLanguage::Turkish => &COMMANDS_TR,
            InvocationLanguage::Polish => &COMMANDS_PL,
        }
    }

    fn welcome_message(&self) -> &'static str {
        match self {
            InvocationLanguage::English => "~ Ling Manifest — Spirit Token Descends ~",
            InvocationLanguage::Chinese => "~ 灵符降世 — 灵境之门已开 ~",
            InvocationLanguage::Japanese => "~ 霊符降臨 — 霊境の門開く ~",
            InvocationLanguage::Korean => "~ 영부강세 — 영경의 문 열림 ~",
            InvocationLanguage::Russian => "~ Линфу Нисходит — Врата Духа Открыты ~",
            InvocationLanguage::Arabic => "~ لينغفو ينزل — بوابة الروح مفتوحة ~",
            InvocationLanguage::Hindi => "~ लिंगफू उतरा — आत्मा का द्वार खुला ~",
            InvocationLanguage::Thai => "~ ลิงฟูเสด็จลง — ประตูวิญญาณเปิดแล้ว ~",
            InvocationLanguage::Vietnamese => "~ Linh Phù Giáng Thế — Cổng Linh Mở ~",
            InvocationLanguage::Greek => "~ Λινγφυ Κατεβαίνει — Η Πύλη του Πνεύματος Ανοίγει ~",
            InvocationLanguage::Hebrew => "~ לינגפו יורד — שער הרוח נפתח ~",
            InvocationLanguage::German => "~ Lingfu Steigt Hinab — Das Geistertor Öffnet Sich ~",
            InvocationLanguage::French => "~ Lingfu Descend — La Porte Spirituelle S'ouvre ~",
            InvocationLanguage::Spanish => "~ Lingfu Desciende — La Puerta Espiritual se Abre ~",
            InvocationLanguage::Turkish => "~ Lingfu İniyor — Ruh Kapısı Açılıyor ~",
            InvocationLanguage::Polish => "~ Lingfu Zstępuje — Brama Ducha Otwarta ~",
        }
    }

    fn help_text(&self) -> &'static str {
        match self {
            InvocationLanguage::English => {
                "Ling Project Manifest Tool — Spirit Token for Your Code"
            },
            InvocationLanguage::Chinese => "灵符 — 灵境项目管理工具",
            InvocationLanguage::Japanese => "霊符 — 霊境プロジェクト管理ツール",
            InvocationLanguage::Korean => "영부 — 영경 프로젝트 관리 도구",
            InvocationLanguage::Russian => "Линфу — Инструмент управления проектами Лин",
            InvocationLanguage::Arabic => "لينغفو — أداة إدارة مشاريع لينغ",
            InvocationLanguage::Hindi => "लिंगफू — लिंग परियोजना प्रबंधन उपकरण",
            InvocationLanguage::Thai => "ลิงฟู — เครื่องมือจัดการโปรเจคลิง",
            InvocationLanguage::Vietnamese => "Linh Phù — Công Cụ Quản Lý Dự Án Linh",
            InvocationLanguage::Greek => "Λινγφυ — Εργαλείο Διαχείρισης Έργων Ling",
            InvocationLanguage::Hebrew => "לינגפו — כלי ניהול פרויקטים של לינג",
            InvocationLanguage::German => "Lingfu — Ling Projektmanagement-Tool",
            InvocationLanguage::French => "Lingfu — Outil de Gestion de Projets Ling",
            InvocationLanguage::Spanish => "Lingfu — Herramienta de Gestión de Proyectos Ling",
            InvocationLanguage::Turkish => "Lingfu — Ling Proje Yönetim Aracı",
            InvocationLanguage::Polish => "Lingfu — Narzędzie do Zarządzania Projektami Ling",
        }
    }

    fn version_text(&self) -> String {
        format!("{} v{}", self.cli_name(), env!("CARGO_PKG_VERSION"))
    }
}

// ─── Command translation tables ───────────────────────────────────────────────

struct CommandTranslations {
    new: &'static [&'static str],
    init: &'static [&'static str],
    build: &'static [&'static str],
    run: &'static [&'static str],
    test: &'static [&'static str],
    check: &'static [&'static str],
    clean: &'static [&'static str],
    doc: &'static [&'static str],
    fmt: &'static [&'static str],
    tree: &'static [&'static str],
    bench: &'static [&'static str],
    update: &'static [&'static str],
    add: &'static [&'static str],
    search: &'static [&'static str],
    publish: &'static [&'static str],
    login: &'static [&'static str],
    logout: &'static [&'static str],
    translate: &'static [&'static str],
    manifest: &'static [&'static str],
    wizard: &'static [&'static str],
    normalize: &'static [&'static str],
}

// English
const COMMANDS_EN: CommandTranslations = CommandTranslations {
    new: &["new", "create"],
    init: &["init", "initialize"],
    build: &["build", "compile", "make"],
    run: &["run", "execute", "start"],
    test: &["test", "verify"],
    check: &["check", "lint", "validate"],
    clean: &["clean", "purge", "clear"],
    doc: &["doc", "docs", "document"],
    fmt: &["fmt", "format", "style"],
    tree: &["tree", "structure", "ls"],
    bench: &["bench", "benchmark", "measure"],
    update: &["update", "upgrade", "refresh"],
    add: &["add", "install", "include"],
    search: &["search", "find", "seek"],
    publish: &["publish", "release", "share"],
    login: &["login", "sign-in", "auth"],
    logout: &["logout", "sign-out", "deauth"],
    translate: &["translate", "trans", "convert"],
    manifest: &["manifest", "show", "info"],
    wizard: &["wizard", "guide", "create-wizard"],
    normalize: &["normalize", "norm", "localize"],
};

// Chinese (中文)
const COMMANDS_ZH: CommandTranslations = CommandTranslations {
    new: &["新", "新生", "创", "创建"],
    init: &["初", "初始", "初始化"],
    build: &["筑", "建", "编译", "构建"],
    run: &["行", "跑", "运行", "执行"],
    test: &["试", "测", "检验", "验证"],
    check: &["查", "核查", "检查", "验"],
    clean: &["清", "净化", "清洁", "扫"],
    doc: &["文", "文档", "说明", "典"],
    fmt: &["整", "格式", "整理", "化"],
    tree: &["树", "结构", "列"],
    bench: &["衡", "基准", "测速", "量"],
    update: &["新", "更新", "升级", "刷新"],
    add: &["添", "加", "安装", "引入"],
    search: &["寻", "搜", "查找", "探"],
    publish: &["发", "发布", "传世", "出版"],
    login: &["入", "登入", "认证"],
    logout: &["出", "退出", "登出"],
    translate: &["译", "翻", "转换", "翻译"],
    manifest: &["显", "现", "显示", "信息"],
    wizard: &["创灵", "向导", "引导"],
    normalize: &["规范", "规范化", "本地化"],
};

// Japanese (日本語)
const COMMANDS_JA: CommandTranslations = CommandTranslations {
    new: &["新", "新生", "作成", "新規"],
    init: &["初", "初期化", "はじめ"],
    build: &["築", "建", "ビルド", "コンパイル"],
    run: &["行", "実行", "起動", "ラン"],
    test: &["試", "テスト", "検証", "確認"],
    check: &["査", "チェック", "検査", "確"],
    clean: &["清", "浄化", "クリーン", "掃"],
    doc: &["文", "ドキュメント", "説明", "典"],
    fmt: &["整", "フォーマット", "整理"],
    tree: &["木", "構造", "ツリー", "一覧"],
    bench: &["測", "ベンチ", "計測", "速"],
    update: &["更", "アップデート", "更新", "刷"],
    add: &["添", "追加", "インストール", "導入"],
    search: &["探", "検索", "求", "サーチ"],
    publish: &["発", "公開", "リリース", "発布"],
    login: &["入", "ログイン", "認証"],
    logout: &["出", "ログアウト", "退出"],
    translate: &["訳", "翻訳", "変換", "トランスレート"],
    manifest: &["示", "表示", "情報", "マニフェスト"],
    wizard: &["創霊", "ウィザード", "作成案内"],
    normalize: &["正規化", "言語統一", "ローカライズ"],
};

// Korean (한국어)
const COMMANDS_KO: CommandTranslations = CommandTranslations {
    new: &["새", "생성", "만들기", "신규"],
    init: &["초기화", "초기", "시작"],
    build: &["짓", "빌드", "컴파일", "구축"],
    run: &["달", "실행", "시작", "런"],
    test: &["시", "테스트", "검증", "확인"],
    check: &["검", "체크", "검사", "확"],
    clean: &["청", "정화", "클린", "청소"],
    doc: &["문", "문서", "설명", "전"],
    fmt: &["정", "형식", "정리"],
    tree: &["트리", "구조", "목록"],
    bench: &["측", "벤치", "측정", "속"],
    update: &["갱신", "업데이트", "새로고침"],
    add: &["더", "추가", "설치", "포함"],
    search: &["찾", "검색", "탐", "서치"],
    publish: &["발", "게시", "배포", "공개"],
    login: &["로그인", "인증", "입"],
    logout: &["로그아웃", "퇴장", "출"],
    translate: &["번", "번역", "변환", "트랜슬레이션"],
    manifest: &["보", "표시", "정보", "매니페스트"],
    wizard: &["창령", "마법사", "생성도우미"],
    normalize: &["정규화", "언어통일", "로컬라이즈"],
};

// Russian (русский)
const COMMANDS_RU: CommandTranslations = CommandTranslations {
    new: &["нов", "созд", "создать"],
    init: &["иниц", "инициализировать", "начать"],
    build: &["сбор", "комп", "билд"],
    run: &["зап", "вып", "старт"],
    test: &["тест", "про", "пров"],
    check: &["проверить", "проверка", "провер"],
    clean: &["очист", "чист", "убрать"],
    doc: &["докум", "документация", "доки"],
    fmt: &["форм", "формат", "оформить"],
    tree: &["дер", "структура", "дерево"],
    bench: &["тест_скор", "замер", "бенч"],
    update: &["обнов", "обновить", "апдейт"],
    add: &["доб", "уст", "включ"],
    search: &["поиск", "искать", "найти"],
    publish: &["публ", "опубл", "издать"],
    login: &["вход", "войти", "авт"],
    logout: &["выход", "выйти"],
    translate: &["пер", "перев", "конв"],
    manifest: &["показ", "инфо", "маниф"],
    wizard: &["создДух", "мастер", "гид"],
    normalize: &["нормализовать", "норм", "локализовать"],
};

// Arabic (العربية)
const COMMANDS_AR: CommandTranslations = CommandTranslations {
    new: &["جديد", "إنشاء", "بدء"],
    init: &["تهيئة", "مبادئ", "ابدأ"],
    build: &["بناء", "تجميع", "صنع"],
    run: &["تشغيل", "تنفيذ", "بداية"],
    test: &["اختبار", "تحقق", "تأكيد"],
    check: &["فحص", "مراجعة", "تدقيق"],
    clean: &["تنظيف", "إزالة", "مسح"],
    doc: &["توثيق", "وثائق", "شرح"],
    fmt: &["تنسيق", "ترتيب", "صياغة"],
    tree: &["شجرة", "هيكل", "عرض"],
    bench: &["قياس", "سرعة", "أداء"],
    update: &["تحديث", "تطوير", "تحديث"],
    add: &["إضافة", "تثبيت", "ضمن"],
    search: &["بحث", "إيجاد", "استعلام"],
    publish: &["نشر", "إصدار", "توزيع"],
    login: &["تسجيل_دخول", "دخول", "مصادقة"],
    logout: &["تسجيل_خروج", "خروج"],
    translate: &["ترجمة", "تحويل", "نقل"],
    manifest: &["إظهار", "معلومات", "بيان"],
    wizard: &["ساحر", "دليل", "منشئ"],
    normalize: &["توحيد", "تطبيع", "توطين"],
};

// Hindi (हिन्दी)
const COMMANDS_HI: CommandTranslations = CommandTranslations {
    new: &["नया", "बनाएं", "शुरू"],
    init: &["प्रारंभ", "शुरुआत", "आरंभ"],
    build: &["बनाएं", "संकलन", "निर्माण"],
    run: &["चलाएं", "दौड़ाएं", "प्रारंभ"],
    test: &["परीक्षण", "जांचें", "सत्यापित"],
    check: &["जांच", "परख", "सत्यापन"],
    clean: &["साफ", "शुद्ध", "सफाई"],
    doc: &["दस्तावेज", "प्रलेखन", "टिप्पणी"],
    fmt: &["स्वरूप", "प्रारूप", "सजाएं"],
    tree: &["वृक्ष", "संरचना", "सूची"],
    bench: &["माप", "गति", "बेंचमार्क"],
    update: &["अद्यतन", "अपडेट", "ताजा"],
    add: &["जोड़ें", "स्थापित", "शामिल"],
    search: &["खोजें", "ढूंढें", "तलाश"],
    publish: &["प्रकाशित", "जारी", "वितरित"],
    login: &["लॉगिन", "प्रवेश", "सत्यापित"],
    logout: &["लॉगआउट", "निकास"],
    translate: &["अनुवाद", "रूपांतरित", "बदलें"],
    manifest: &["दिखाएं", "जानकारी", "प्रकट"],
    wizard: &["जादूगर", "मार्गदर्शक", "सृजक"],
    normalize: &["सामान्यीकरण", "नॉर्म", "स्थानीयकरण"],
};

// Thai (ภาษาไทย)
const COMMANDS_TH: CommandTranslations = CommandTranslations {
    new: &["ใหม่", "สร้าง", "เริ่ม"],
    init: &["เริ่มต้น", "กำหนดค่า", "ตั้งค่า"],
    build: &["สร้าง", "คอมไพล์", "ทำ"],
    run: &["เรียก", "รัน", "เริ่มต้น"],
    test: &["ทดสอบ", "ตรวจ", "ยืนยัน"],
    check: &["ตรวจสอบ", "ตรวจ", "ยืนยัน"],
    clean: &["ล้าง", "ทำความสะอาด", "ลบ"],
    doc: &["เอกสาร", "คำอธิบาย", "ไฟล์ช่วย"],
    fmt: &["จัดรูปแบบ", "จัด", "ปรับ"],
    tree: &["ต้นไม้", "โครงสร้าง", "แสดง"],
    bench: &["วัดความเร็ว", "ทดสอบ", "วัด"],
    update: &["อัปเดต", "อัพเกรด", "ปรับปรุง"],
    add: &["เพิ่ม", "ติดตั้ง", "รวม"],
    search: &["ค้นหา", "หา", "สืบค้น"],
    publish: &["เผยแพร่", "ปล่อย", "แจกจ่าย"],
    login: &["เข้าสู่ระบบ", "ลงชื่อเข้า", "ยืนยัน"],
    logout: &["ออกจากระบบ", "ลงชื่อออก"],
    translate: &["แปล", "แปลง", "เปลี่ยน"],
    manifest: &["แสดง", "ข้อมูล", "แจ้ง"],
    wizard: &["ตัวช่วย", "แนะนำ", "สร้าง"],
    normalize: &["ปรับภาษา", "ปรับ", "แปลงภาษา"],
};

// Vietnamese (Tiếng Việt)
const COMMANDS_VI: CommandTranslations = CommandTranslations {
    new: &["mới", "tạo", "khởi_tạo"],
    init: &["khởi_động", "khởi", "bắt_đầu"],
    build: &["xây", "biên_dịch", "dựng"],
    run: &["chạy", "thực_thi", "bắt_đầu"],
    test: &["kiểm_tra", "thử", "xác_nhận"],
    check: &["kiểm", "xác", "tra"],
    clean: &["dọn", "xóa", "sạch"],
    doc: &["tài_liệu", "giải_thích", "hướng_dẫn"],
    fmt: &["định_dạng", "sắp_xếp", "chuẩn"],
    tree: &["cây", "cấu_trúc", "danh_sách"],
    bench: &["đo", "thử_tốc_độ", "hiệu_suất"],
    update: &["cập_nhật", "nâng_cấp", "làm_mới"],
    add: &["thêm", "cài", "bao_gồm"],
    search: &["tìm", "tìm_kiếm", "tra_cứu"],
    publish: &["xuất_bản", "phát_hành", "chia_sẻ"],
    login: &["đăng_nhập", "vào", "xác_thực"],
    logout: &["đăng_xuất", "ra"],
    translate: &["dịch", "chuyển_đổi", "biến_đổi"],
    manifest: &["hiển_thị", "thông_tin", "bày"],
    wizard: &["phù_thủy", "hướng_dẫn", "tạo"],
    normalize: &["chuẩn_hóa", "chuẩn", "bản_địa_hóa"],
};

// Greek (Ελληνικά)
const COMMANDS_EL: CommandTranslations = CommandTranslations {
    new: &["νέο", "δημιουργία", "αρχή"],
    init: &["αρχικοποίηση", "εκκίνηση", "έναρξη"],
    build: &["χτίσε", "μεταγλώττιση", "κάνε"],
    run: &["τρέξε", "εκτέλεσε", "ξεκίνα"],
    test: &["δοκίμασε", "έλεγξε", "επιβεβαίωσε"],
    check: &["έλεγχος", "σκανάρισε", "επαλήθευση"],
    clean: &["καθάρισε", "διέγραψε", "σκούπισε"],
    doc: &["τεκμηρίωση", "εγγραφή", "βοήθεια"],
    fmt: &["μορφοποίηση", "τακτοποίηση", "στυλ"],
    tree: &["δέντρο", "δομή", "λίστα"],
    bench: &["μέτρηση", "ταχύτητα", "απόδοση"],
    update: &["ενημέρωση", "αναβάθμιση", "ανανέωση"],
    add: &["πρόσθεσε", "εγκατάστησε", "συμπεριέλαβε"],
    search: &["αναζήτηση", "βρες", "ανακάλυψε"],
    publish: &["δημοσίευσε", "κυκλοφόρησε", "μοιράσου"],
    login: &["σύνδεση", "είσοδος", "πιστοποίηση"],
    logout: &["αποσύνδεση", "έξοδος"],
    translate: &["μετάφρασε", "μετέτρεψε", "άλλαξε"],
    manifest: &["δείξε", "πληροφορίες", "φανέρωσε"],
    wizard: &["μάγο", "οδηγό", "δημιουργέ"],
    normalize: &["κανονικοποίηση", "νόρμ", "τοπικοποίηση"],
};

// Hebrew (עברית)
const COMMANDS_HE: CommandTranslations = CommandTranslations {
    new: &["חדש", "צור", "התחל"],
    init: &["אתחל", "הגדר", "הכן"],
    build: &["בנה", "קמפל", "עשה"],
    run: &["הרץ", "בצע", "התחל"],
    test: &["בדוק", "אמת", "בחן"],
    check: &["בדיקה", "סריקה", "אימות"],
    clean: &["נקה", "מחק", "טהר"],
    doc: &["תיעוד", "הסבר", "עזרה"],
    fmt: &["עיצוב", "סידור", "פרמט"],
    tree: &["עץ", "מבנה", "רשימה"],
    bench: &["מדידה", "מהירות", "ביצועים"],
    update: &["עדכן", "שדרג", "רענן"],
    add: &["הוסף", "התקן", "כלול"],
    search: &["חפש", "מצא", "חקור"],
    publish: &["פרסם", "שחרר", "הפץ"],
    login: &["התחבר", "כניסה", "אמת"],
    logout: &["התנתק", "יציאה"],
    translate: &["תרגם", "המר", "שנה"],
    manifest: &["הצג", "מידע", "גלה"],
    wizard: &["אשף", "מדריך", "יוצר"],
    normalize: &["נרמול", "תקנן", "לוקליזציה"],
};

// German (Deutsch)
const COMMANDS_DE: CommandTranslations = CommandTranslations {
    new: &["neu", "erstellen", "anlegen"],
    init: &["init", "initialisieren", "einrichten"],
    build: &["bauen", "kompilieren", "machen"],
    run: &["ausführen", "starten", "laufen"],
    test: &["testen", "prüfen", "überprüfen"],
    check: &["prüfen", "checken", "validieren"],
    clean: &["bereinigen", "säubern", "löschen"],
    doc: &["dokumentieren", "dokumente", "doku"],
    fmt: &["formatieren", "ordnen", "stilisieren"],
    tree: &["baum", "struktur", "liste"],
    bench: &["messen", "benchmark", "leistung"],
    update: &["aktualisieren", "erneuern", "upgraden"],
    add: &["hinzufügen", "installieren", "einschließen"],
    search: &["suchen", "finden", "erkunden"],
    publish: &["veröffentlichen", "freigeben", "teilen"],
    login: &["einloggen", "anmelden", "auth"],
    logout: &["ausloggen", "abmelden"],
    translate: &["übersetzen", "konvertieren", "trans"],
    manifest: &["zeigen", "info", "manifest"],
    wizard: &["assistent", "führer", "ersteller"],
    normalize: &["normalisieren", "norm", "lokalisieren"],
};

// French (Français)
const COMMANDS_FR: CommandTranslations = CommandTranslations {
    new: &["nouveau", "créer", "init"],
    init: &["initialiser", "configurer", "préparer"],
    build: &["construire", "compiler", "fabriquer"],
    run: &["exécuter", "lancer", "démarrer"],
    test: &["tester", "vérifier", "contrôler"],
    check: &["vérifier", "valider", "analyser"],
    clean: &["nettoyer", "purger", "effacer"],
    doc: &["documenter", "documentation", "docs"],
    fmt: &["formater", "ordonner", "styliser"],
    tree: &["arbre", "structure", "liste"],
    bench: &["mesurer", "benchmark", "performance"],
    update: &["mettre_a_jour", "actualiser", "upgrader"],
    add: &["ajouter", "installer", "inclure"],
    search: &["rechercher", "trouver", "chercher"],
    publish: &["publier", "diffuser", "partager"],
    login: &["connexion", "authentification", "entrer"],
    logout: &["déconnexion", "sortir"],
    translate: &["traduire", "convertir", "trans"],
    manifest: &["afficher", "info", "manifeste"],
    wizard: &["assistant", "guide", "créateur"],
    normalize: &["normaliser", "norm", "localiser"],
};

// Spanish (Español)
const COMMANDS_ES: CommandTranslations = CommandTranslations {
    new: &["nuevo", "crear", "iniciar"],
    init: &["inicializar", "configurar", "preparar"],
    build: &["construir", "compilar", "hacer"],
    run: &["ejecutar", "correr", "iniciar"],
    test: &["probar", "verificar", "testear"],
    check: &["verificar", "validar", "analizar"],
    clean: &["limpiar", "purgar", "borrar"],
    doc: &["documentar", "documentación", "docs"],
    fmt: &["formatear", "ordenar", "estilizar"],
    tree: &["árbol", "estructura", "lista"],
    bench: &["medir", "benchmark", "rendimiento"],
    update: &["actualizar", "renovar", "mejorar"],
    add: &["agregar", "instalar", "incluir"],
    search: &["buscar", "encontrar", "explorar"],
    publish: &["publicar", "lanzar", "compartir"],
    login: &["iniciar_sesión", "autenticar", "entrar"],
    logout: &["cerrar_sesión", "salir"],
    translate: &["traducir", "convertir", "trans"],
    manifest: &["mostrar", "información", "manifiesto"],
    wizard: &["asistente", "crear", "guía"],
    normalize: &["normalizar", "norm", "localizar"],
};

// Turkish (Türkçe)
const COMMANDS_TR: CommandTranslations = CommandTranslations {
    new: &["yeni", "oluştur", "başlat"],
    init: &["başlat", "hazırla", "kur"],
    build: &["inşa", "derle", "yap"],
    run: &["çalıştır", "başlat", "koş"],
    test: &["test", "kontrol", "doğrula"],
    check: &["denetle", "kontrol_et", "doğrula"],
    clean: &["temizle", "sil", "arıt"],
    doc: &["belgele", "belge", "yardım"],
    fmt: &["biçimlendir", "düzenle", "stilize"],
    tree: &["ağaç", "yapı", "liste"],
    bench: &["ölç", "kıyasla", "hız"],
    update: &["güncelle", "yükselt", "tazele"],
    add: &["ekle", "kur", "dahil_et"],
    search: &["ara", "bul", "keşfet"],
    publish: &["yayınla", "dağıt", "paylaş"],
    login: &["giriş", "oturum_aç", "doğrula"],
    logout: &["çıkış", "oturumu_kapat"],
    translate: &["çevir", "dönüştür", "tercüme_et"],
    manifest: &["göster", "bilgi", "manifest"],
    wizard: &["sihirbaz", "rehber", "oluşturucu"],
    normalize: &["normalleştir", "norm", "yerelleştir"],
};

// Polish (Polski)
const COMMANDS_PL: CommandTranslations = CommandTranslations {
    new: &["nowy", "utwórz", "inicjuj"],
    init: &["inicjalizuj", "skonfiguruj", "przygotuj"],
    build: &["zbuduj", "skompiluj", "zrób"],
    run: &["uruchom", "wykonaj", "start"],
    test: &["testuj", "sprawdź", "zweryfikuj"],
    check: &["sprawdź", "zweryfikuj", "zbadaj"],
    clean: &["wyczyść", "usuń", "oczyść"],
    doc: &["dokumentuj", "dokumentacja", "pomoc"],
    fmt: &["formatuj", "ułóż", "ostyluj"],
    tree: &["drzewo", "struktura", "lista"],
    bench: &["mierz", "benchmark", "wydajność"],
    update: &["aktualizuj", "uaktualnij", "odśwież"],
    add: &["dodaj", "zainstaluj", "dołącz"],
    search: &["szukaj", "znajdź", "przeszukaj"],
    publish: &["opublikuj", "wydaj", "udostępnij"],
    login: &["zaloguj", "wejdź", "uwierzytelnij"],
    logout: &["wyloguj", "wyjdź"],
    translate: &["tłumacz", "konwertuj", "przekształć"],
    manifest: &["pokaż", "informacje", "manifest"],
    wizard: &["kreator", "przewodnik", "twórca"],
    normalize: &["normalizuj", "norm", "lokalizuj"],
};

// ─── Localized string helpers ─────────────────────────────────────────────────

fn t<'a>(
    lang: &InvocationLanguage,
    en: &'a str,
    zh: &'a str,
    ja: &'a str,
    ko: &'a str,
    th: &'a str,
    other: &'a str,
) -> &'a str {
    match lang {
        InvocationLanguage::English => en,
        InvocationLanguage::Chinese => zh,
        InvocationLanguage::Japanese => ja,
        InvocationLanguage::Korean => ko,
        InvocationLanguage::Thai => th,
        _ => other,
    }
}

fn say(lang: &InvocationLanguage, en: &str, zh: &str, ja: &str, ko: &str) {
    match lang {
        InvocationLanguage::English => println!("{}", en),
        InvocationLanguage::Chinese => println!("{}", zh),
        InvocationLanguage::Japanese => println!("{}", ja),
        InvocationLanguage::Korean => println!("{}", ko),
        _ => println!("{}", en),
    }
}

// ─── Manifest discovery ───────────────────────────────────────────────────────

const MANIFEST_NAMES: &[&str] = &[
    "Ling.toml",
    "灵符.toml",
    "霊符.toml",
    "영부.toml",
    "ลิงฟู.toml",
];

/// Walk up the directory tree to find a Ling manifest file.
pub(crate) fn find_manifest() -> Option<std::path::PathBuf> {
    let mut dir = std::env::current_dir().ok()?;
    loop {
        for name in MANIFEST_NAMES {
            let candidate = dir.join(name);
            if candidate.exists() {
                return Some(candidate);
            }
        }
        if !dir.pop() {
            break;
        }
    }
    None
}

/// Detect which ScaffoldLang a manifest filename implies.
fn lang_from_manifest(path: &std::path::Path) -> ScaffoldLang {
    let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
    match name {
        "灵符.toml" => ScaffoldLang::Chinese,
        "霊符.toml" => ScaffoldLang::Japanese,
        "영부.toml" => ScaffoldLang::Korean,
        "ลิงฟู.toml" => ScaffoldLang::Thai,
        _ => ScaffoldLang::English,
    }
}

// ─── main ─────────────────────────────────────────────────────────────────────

pub fn run() -> anyhow::Result<()> {
    let invocation_lang = detect_invocation_name();
    let tr = invocation_lang.commands();

    let args: Vec<String> = env::args().collect();

    if args.len() < 2 {
        print_help(&invocation_lang, tr);
        return Ok(());
    }

    let cmd = &args[1];
    let rest = &args[2..];

    macro_rules! matches_cmd {
        ($field:expr) => {
            $field.contains(&cmd.as_str())
        };
    }

    if cmd == "convert" || cmd == "转换" || cmd == "変換" || cmd == "변환" || cmd == "แปลง"
    {
        // Asset → .ling transcoding lives in the `ling` binary (it owns the
        // glTF/audio/MIDI parsers); forward verbatim. Checked before `translate`
        // because "convert" is also a translate alias.
        let ling_bin = find_ling_binary();
        let status = std::process::Command::new(&ling_bin)
            .arg("convert")
            .args(rest)
            .status()
            .map_err(|e| anyhow::anyhow!("ling: {e}"))?;
        std::process::exit(status.code().unwrap_or(1));
    } else if cmd == "ast" || cmd == "图谱" || cmd == "図譜" || cmd == "도표" || cmd == "ผังต้นไม้"
    {
        // Project-wide AST → SVG. The renderer lives in the `ling` binary (it owns
        // the parser). Treat the whole project as one program: pass the project
        // root (manifest parent, else cwd) unless the user supplied a path.
        let ling_bin = find_ling_binary();
        let has_path = {
            let mut i = 0;
            let mut found = false;
            while i < rest.len() {
                if rest[i] == "--out" {
                    i += 2;
                    continue;
                }
                if rest[i].starts_with('-') {
                    i += 1;
                    continue;
                }
                found = true;
                break;
            }
            found
        };
        let root = find_manifest()
            .and_then(|m| m.parent().map(|p| p.to_path_buf()))
            .unwrap_or_else(|| std::path::PathBuf::from("."));
        let mut command = std::process::Command::new(&ling_bin);
        command.arg("ast");
        if !has_path {
            command.arg(&root);
        }
        let status = command
            .args(rest)
            .status()
            .map_err(|e| anyhow::anyhow!("ling: {e}"))?;
        std::process::exit(status.code().unwrap_or(1));
    } else if cmd == "seed" {
        // Reconstructable encrypted integrity fingerprint of the project —
        // see seed.rs's module doc for the full design.
        seed::run(rest)?;
    } else if matches_cmd!(tr.new) {
        println!("{}", invocation_lang.welcome_message().cyan().bold());
        cmd_new(rest, &invocation_lang)?;
    } else if matches_cmd!(tr.init) {
        println!("{}", invocation_lang.welcome_message().cyan().bold());
        cmd_init(rest, &invocation_lang)?;
    } else if matches_cmd!(tr.build) {
        cmd_build(rest, &invocation_lang)?;
    } else if matches_cmd!(tr.run) {
        cmd_run(rest, &invocation_lang)?;
    } else if matches_cmd!(tr.test) {
        cmd_test(rest, &invocation_lang)?;
    } else if matches_cmd!(tr.check) {
        cmd_check(rest, &invocation_lang)?;
    } else if matches_cmd!(tr.clean) {
        cmd_clean(&invocation_lang)?;
    } else if matches_cmd!(tr.doc) {
        cmd_doc(&invocation_lang)?;
    } else if matches_cmd!(tr.fmt) {
        cmd_fmt(rest, &invocation_lang)?;
    } else if matches_cmd!(tr.tree) {
        cmd_tree(&invocation_lang)?;
    } else if matches_cmd!(tr.bench) {
        cmd_bench(rest, &invocation_lang)?;
    } else if matches_cmd!(tr.update) {
        cmd_update(rest, &invocation_lang)?;
    } else if matches_cmd!(tr.add) {
        cmd_add(rest, &invocation_lang)?;
    } else if matches_cmd!(tr.search) {
        cmd_search(rest, &invocation_lang)?;
    } else if matches_cmd!(tr.publish) {
        cmd_publish(rest, &invocation_lang)?;
    } else if matches_cmd!(tr.login) {
        cmd_login(rest, &invocation_lang)?;
    } else if matches_cmd!(tr.logout) {
        cmd_logout(&invocation_lang)?;
    } else if matches_cmd!(tr.translate) {
        cmd_translate(rest, &invocation_lang)?;
    } else if matches_cmd!(tr.manifest) {
        cmd_manifest(&invocation_lang)?;
    } else if matches_cmd!(tr.wizard) {
        cmd_wizard(&invocation_lang)?;
    } else if matches_cmd!(tr.normalize) {
        cmd_normalize(rest, &invocation_lang)?;
    } else if cmd == "--version" || cmd == "-V" || cmd == "version" {
        println!("{}", invocation_lang.version_text());
    } else if cmd == "--help" || cmd == "-h" || cmd == "help" {
        print_help(&invocation_lang, tr);
    } else {
        eprintln!(
            "{}: {}",
            t(
                &invocation_lang,
                "unknown command",
                "未知命令",
                "不明なコマンド",
                "알 수 없는 명령",
                "คำสั่งที่ไม่รู้จัก",
                "unknown command"
            ),
            cmd.red()
        );
        eprintln!("  {} --help", invocation_lang.cli_name());
        std::process::exit(1);
    }

    Ok(())
}

// ─── print_help ───────────────────────────────────────────────────────────────

fn print_help(lang: &InvocationLanguage, tr: &CommandTranslations) {
    let bin = lang.cli_name();

    println!("\n{}", lang.help_text().cyan().bold());

    let usage_label = t(
        lang,
        "Usage:",
        "用法:",
        "使用法:",
        "사용법:",
        "การใช้งาน:",
        "Usage:",
    );
    println!("\n{}", usage_label.yellow());

    let name_ph = t(lang, "NAME", "名称", "名前", "이름", "ชื่อ", "NAME");
    let pkg_ph = t(
        lang,
        "PACKAGE",
        "包",
        "パッケージ",
        "패키지",
        "แพ็คเกจ",
        "PACKAGE",
    );
    let query_ph = t(lang, "QUERY", "查询", "クエリ", "쿼리", "คิวรี", "QUERY");
    let file_ph = t(lang, "FILE", "文件", "ファイル", "파일", "ไฟล์", "FILE");

    let cmds: &[(&str, &str)] = &[
        (tr.new[0], name_ph),
        (tr.init[0], ""),
        (tr.build[0], ""),
        (tr.run[0], ""),
        (tr.test[0], ""),
        (tr.check[0], ""),
        (tr.clean[0], ""),
        (tr.doc[0], ""),
        (tr.fmt[0], ""),
        (tr.tree[0], ""),
        (tr.bench[0], ""),
        (tr.update[0], ""),
        (tr.add[0], pkg_ph),
        (tr.search[0], query_ph),
        (tr.publish[0], ""),
        (tr.login[0], ""),
        (tr.logout[0], ""),
        (tr.translate[0], file_ph),
        (tr.manifest[0], ""),
        (tr.wizard[0], ""),
        (
            tr.normalize[0],
            t(lang, "LANG", "语言", "言語", "언어", "ภาษา", "LANG"),
        ),
    ];

    for (cmd, arg) in cmds {
        if arg.is_empty() {
            println!("  {} {}", bin, cmd);
        } else {
            println!("  {} {} <{}>", bin, cmd, arg);
        }
    }

    let opts_label = t(
        lang,
        "Options:",
        "选项:",
        "オプション:",
        "옵션:",
        "ตัวเลือก:",
        "Options:",
    );
    println!("\n{}", opts_label.yellow());
    println!(
        "  --lang, -l <LANG>      {}",
        t(
            lang,
            "Scaffolding language (en/zh/ja/ko/th/ru)",
            "支架语言 (en/zh/ja/ko/th/ru)",
            "足場言語 (en/zh/ja/ko/th/ru)",
            "발판 언어 (en/zh/ja/ko/th/ru)",
            "ภาษาโครงสร้าง (en/zh/ja/ko/th/ru)",
            "Scaffolding language (en/zh/ja/ko/th/ru)",
        )
    );
    println!(
        "  --type <TYPE>          {}",
        t(
            lang,
            "Project type (bin/lib/game/ui/ai/crypto/web/polyglot)",
            "项目类型",
            "プロジェクトタイプ",
            "프로젝트 유형",
            "ประเภทโปรเจค",
            "Project type",
        )
    );
    println!(
        "  --crates               {}",
        t(
            lang,
            "With add/search: use crates.io via cargo instead of the ling registry",
            "配合 add/search 使用:通过 cargo 使用 crates.io,而不是 ling 注册处",
            "add/search と併用:ling レジストリではなく cargo 経由で crates.io を使用",
            "add/search와 함께 사용: ling 레지스트리 대신 cargo를 통해 crates.io 사용",
            "ใช้กับ add/search: ใช้ crates.io ผ่าน cargo แทนทะเบียน ling",
            "With add/search: use crates.io via cargo instead of the ling registry",
        )
    );
    println!(
        "  --version, -V          {}",
        t(
            lang,
            "Show version",
            "显示版本",
            "バージョン表示",
            "버전 표시",
            "แสดงเวอร์ชัน",
            "Show version"
        )
    );
    println!(
        "  --help, -h             {}",
        t(
            lang,
            "Show help",
            "显示帮助",
            "ヘルプ表示",
            "도움말 표시",
            "แสดงความช่วยเหลือ",
            "Show help"
        )
    );
    println!();
}

// ─── cmd_new ─────────────────────────────────────────────────────────────────

fn cmd_new(args: &[String], lang: &InvocationLanguage) -> anyhow::Result<()> {
    if args.is_empty() {
        say(
            lang,
            "Please provide a project name",
            "请提供项目名称",
            "プロジェクト名を指定してください",
            "프로젝트 이름을 입력하세요",
        );
        return Ok(());
    }

    let name = &args[0];
    let (kind, primary_lang, root_lang) = parse_new_flags(&args[1..]);
    let root_language = root_lang.unwrap_or_else(|| primary_lang.clone());

    println!(
        "{}",
        format!(
            "{} {}",
            t(
                lang,
                "Creating project:",
                "正在创建项目:",
                "プロジェクトを作成中:",
                "프로젝트 생성 중:",
                "กำลังสร้างโปรเจค:",
                "Creating project:"
            ),
            name
        )
        .green()
    );

    let project = ProjectScaffold {
        name: name.clone(),
        kind,
        language: primary_lang,
        root_language,
        spirit_level: "铁".into(),
    };
    scaffold::scaffold_project(&project)?;
    print_post_create(lang, name);
    Ok(())
}

// ─── cmd_init ────────────────────────────────────────────────────────────────

fn cmd_init(args: &[String], lang: &InvocationLanguage) -> anyhow::Result<()> {
    let cwd = std::env::current_dir()?;
    let name = cwd
        .file_name()
        .map(|n| n.to_string_lossy().to_string())
        .unwrap_or_else(|| "project".into());

    let (kind, primary_lang, root_lang) = parse_new_flags(args);
    let root_language = root_lang.unwrap_or_else(|| primary_lang.clone());

    say(
        lang,
        "Initializing Ling project in current directory...",
        "正在当前目录初始化灵境...",
        "現在のディレクトリに霊境を初期化中...",
        "현재 디렉토리에 영경을 초기화 중...",
    );

    let project = ProjectScaffold {
        name,
        kind,
        language: primary_lang,
        root_language,
        spirit_level: "铁".into(),
    };
    scaffold::scaffold_in_dir(&project, &cwd)?;

    println!(
        "{}",
        t(
            lang,
            "✓ Initialized!",
            "✓ 初始完成!",
            "✓ 初期化完了!",
            "✓ 초기화 완료!",
            "✓ เสร็จสิ้น!",
            "✓ Initialized!"
        )
        .green()
        .bold()
    );
    Ok(())
}

// ─── cmd_build ───────────────────────────────────────────────────────────────

/// First value following `flag` in `args` (e.g. `--path ./app` → `./app`).
fn flag_value(args: &[String], flag: &str) -> Option<String> {
    args.windows(2).find(|w| w[0] == flag).map(|w| w[1].clone())
}

/// Copy of `args` with `flag` and the value after it removed, so wrapper-only
/// flags (like `--path`) are never forwarded to `cargo` / `ling`.
fn strip_flag(args: &[String], flag: &str) -> Vec<String> {
    let mut out = Vec::with_capacity(args.len());
    let mut skip_value = false;
    for a in args {
        if skip_value {
            skip_value = false;
            continue;
        }
        if a == flag {
            skip_value = true; // also drop the value that follows
            continue;
        }
        out.push(a.clone());
    }
    out
}

fn cmd_build(args: &[String], lang: &InvocationLanguage) -> anyhow::Result<()> {
    say(
        lang,
        "Building Ling project...",
        "正在筑造灵境...",
        "霊境を築造中...",
        "영경 건설 중...",
    );

    // `--path <dir>` pins the project root explicitly. Otherwise we walk up for a
    // manifest, and finally fall back to the current directory so a bare folder of
    // `.ling` files still builds. The flag is consumed here, never forwarded.
    let path_override = flag_value(args, "--path").map(std::path::PathBuf::from);
    let forwarded = strip_flag(args, "--path");

    // Resolve the project root and its scaffolding language (used for entry-file
    // candidates). An explicit --path looks for a manifest inside that folder; a
    // bare folder with none just builds as an English-layout .ling project.
    let (root, slang) = if let Some(p) = path_override {
        let slang = MANIFEST_NAMES
            .iter()
            .map(|n| p.join(n))
            .find(|m| m.exists())
            .as_deref()
            .map(lang_from_manifest)
            .unwrap_or(ScaffoldLang::English);
        (p, slang)
    } else if let Some(manifest) = find_manifest() {
        let slang = lang_from_manifest(&manifest);
        let root = manifest
            .parent()
            .map(std::path::Path::to_path_buf)
            .unwrap_or_else(|| std::path::PathBuf::from("."));
        (root, slang)
    } else {
        (std::path::PathBuf::from("."), ScaffoldLang::English)
    };

    // Resolve the entry file (same candidate order as `cmd_run`) so the native
    // build targets the real entry regardless of project layout.
    let dirs = dir_names(&slang);
    let src_dir = root.join(dirs.src);
    let entry_candidates = [
        src_dir.join(dirs.entry),
        root.join(dirs.entry),
        src_dir.join("main.ling"),
        root.join("main.ling"),
        root.join("start.ling"),
    ];
    let entry = entry_candidates.into_iter().find(|p| p.exists());
    if let Some(e) = &entry {
        say(
            lang,
            &format!("Entry: {}", e.display()),
            &format!("灵源: {}", e.display()),
            &format!("霊源: {}", e.display()),
            &format!("령원: {}", e.display()),
        );
    }

    if root.join("Cargo.toml").exists() {
        // A Cargo project sits alongside the .ling sources — defer to cargo.
        let mut cmd = std::process::Command::new("cargo");
        cmd.arg("build").args(&forwarded).current_dir(&root);
        let status = cmd.status().map_err(|e| anyhow::anyhow!("cargo: {e}"))?;
        if !status.success() {
            anyhow::bail!("build failed");
        }
    } else {
        // Pure .ling project (no Cargo.toml): AOT-compile to a native executable
        // by default (pass --no-aot to embed the interpreter). Target the resolved
        // entry file — `ling build <file>` flattens imports from there — otherwise
        // hand `ling build` the directory and let it auto-detect the entry.
        let target = entry.as_deref().unwrap_or(root.as_path());
        run_aot_build(target, &forwarded, lang)?;
    }
    Ok(())
}

/// The current host's `ling build` platform token.
fn native_platform_name() -> &'static str {
    if cfg!(target_os = "windows") {
        "win"
    } else if cfg!(target_os = "macos") {
        "mac"
    } else {
        "lin"
    }
}

/// Build a native `.ling` project by delegating to the `ling` binary's AOT
/// compiler (`ling build <root> --aot`). AOT is the default; pass `--no-aot` to
/// fall back to the interpreter-embedding build. Extra flags (`--out`, `--icon`,
/// `--pack`, `--platform`) pass straight through; with no `--platform` we default
/// to a native-only build (no web bundle), matching `lingfu build`'s old scope.
fn run_aot_build(
    target: &std::path::Path,
    args: &[String],
    lang: &InvocationLanguage,
) -> anyhow::Result<()> {
    let aot = !args.iter().any(|a| a == "--no-aot");
    say(
        lang,
        if aot {
            "→ AOT-compiling to a native executable…"
        } else {
            "→ building native executable (interpreter-embedded)…"
        },
        if aot {
            "→ 正在 AOT 编译为本地可执行文件…"
        } else {
            "→ 正在构建本地可执行文件(内嵌解释器)…"
        },
        if aot {
            "→ ネイティブ実行ファイルへ AOT コンパイル中…"
        } else {
            "→ ネイティブ実行ファイルを構築中(インタプリタ埋め込み)…"
        },
        if aot {
            "→ 네이티브 실행 파일로 AOT 컴파일 중…"
        } else {
            "→ 네이티브 실행 파일 빌드 중(인터프리터 내장)…"
        },
    );

    let ling_bin = find_ling_binary();
    let mut cmd = std::process::Command::new(&ling_bin);
    cmd.arg("build").arg(target);
    if aot {
        cmd.arg("--aot");
    }
    if !args.iter().any(|a| a == "--platform") {
        cmd.arg("--platform").arg(native_platform_name());
    }
    // Forward every build flag except our own --no-aot opt-out.
    for a in args.iter().filter(|a| a.as_str() != "--no-aot") {
        cmd.arg(a);
    }

    let status = cmd.status().map_err(|e| anyhow::anyhow!("ling: {e}"))?;
    if !status.success() {
        anyhow::bail!("build failed");
    }
    Ok(())
}

// ─── cmd_run ─────────────────────────────────────────────────────────────────

fn cmd_run(args: &[String], lang: &InvocationLanguage) -> anyhow::Result<()> {
    if let Some(manifest) = find_manifest() {
        let slang = lang_from_manifest(&manifest);
        let dirs = dir_names(&slang);
        let root = manifest.parent().unwrap_or(std::path::Path::new("."));
        let src_dir = root.join(dirs.src);

        // Try entry file candidates in order
        let entry_candidates = [
            src_dir.join(dirs.entry),
            root.join(dirs.entry),
            src_dir.join("main.ling"),
            root.join("main.ling"),
        ];

        if let Some(entry) = entry_candidates.iter().find(|p| p.exists()) {
            say(
                lang,
                &format!("Running {}...", entry.display()),
                &format!("正在行灵 {}...", entry.display()),
                &format!("{}を実行中...", entry.display()),
                &format!("{} 실행 중...", entry.display()),
            );
            let ling_bin = find_ling_binary();
            let status = std::process::Command::new(ling_bin)
                .arg("run")
                .arg(entry)
                .args(args)
                .status()
                .map_err(|e| anyhow::anyhow!("ling: {e}"))?;
            if !status.success() {
                anyhow::bail!("run failed");
            }
            return Ok(());
        }
    }

    // Fallback: search cwd for any .ling file
    if let Some(path) = find_ling_entry() {
        say(
            lang,
            &format!("Running {}...", path.display()),
            &format!("正在行灵 {}...", path.display()),
            &format!("{}を実行中...", path.display()),
            &format!("{} 실행 중...", path.display()),
        );
        let ling_bin = find_ling_binary();
        let status = std::process::Command::new(ling_bin)
            .arg("run")
            .arg(&path)
            .args(args)
            .status()
            .map_err(|e| anyhow::anyhow!("ling: {e}"))?;
        if !status.success() {
            anyhow::bail!("run failed");
        }
        return Ok(());
    }

    // Last resort: cargo run
    say(
        lang,
        "Running Ling project...",
        "正在行灵...",
        "霊を実行中...",
        "영 실행 중...",
    );
    let status = std::process::Command::new("cargo")
        .arg("run")
        .args(args)
        .status()
        .map_err(|e| anyhow::anyhow!("cargo: {e}"))?;
    if !status.success() {
        anyhow::bail!("run failed");
    }
    Ok(())
}

// ─── cmd_test ────────────────────────────────────────────────────────────────

fn cmd_test(args: &[String], lang: &InvocationLanguage) -> anyhow::Result<()> {
    say(
        lang,
        "Testing Ling project...",
        "正在试灵...",
        "霊をテスト中...",
        "영 테스트 중...",
    );

    if let Some(manifest) = find_manifest() {
        let slang = lang_from_manifest(&manifest);
        let dirs = dir_names(&slang);
        let root = manifest.parent().unwrap_or(std::path::Path::new("."));
        let tests_dir = root.join(dirs.tests);
        let test_file = root.join(dirs.src).join(dirs.test_file);

        let ling_bin = find_ling_binary();
        let mut ran_any = false;

        // Run dedicated test file if it exists
        if test_file.exists() {
            let status = std::process::Command::new(&ling_bin)
                .arg("run")
                .arg(&test_file)
                .status()
                .map_err(|e| anyhow::anyhow!("ling: {e}"))?;
            if !status.success() {
                anyhow::bail!("tests failed");
            }
            ran_any = true;
        }

        // Run all .ling files under tests directory
        if tests_dir.exists() {
            for entry in walkdir::WalkDir::new(&tests_dir)
                .into_iter()
                .filter_map(|e| e.ok())
                .filter(|e| e.path().is_file())
                .filter(|e| has_ling_ext(e.path()))
            {
                say(
                    lang,
                    &format!("  test: {}", entry.path().display()),
                    &format!("  试验: {}", entry.path().display()),
                    &format!("  テスト: {}", entry.path().display()),
                    &format!("  시험: {}", entry.path().display()),
                );
                let status = std::process::Command::new(&ling_bin)
                    .arg("run")
                    .arg(entry.path())
                    .status()
                    .map_err(|e| anyhow::anyhow!("ling: {e}"))?;
                if !status.success() {
                    anyhow::bail!("test failed: {}", entry.path().display());
                }
                ran_any = true;
            }
        }

        if !ran_any && root.join("Cargo.toml").exists() {
            let status = std::process::Command::new("cargo")
                .arg("test")
                .args(args)
                .current_dir(root)
                .status()
                .map_err(|e| anyhow::anyhow!("cargo: {e}"))?;
            if !status.success() {
                anyhow::bail!("tests failed");
            }
        }
    } else {
        let status = std::process::Command::new("cargo")
            .arg("test")
            .args(args)
            .status()
            .map_err(|e| anyhow::anyhow!("cargo: {e}"))?;
        if !status.success() {
            anyhow::bail!("tests failed");
        }
    }

    println!(
        "{}",
        t(
            lang,
            "✓ All tests passed!",
            "✓ 所有试验通过!",
            "✓ 全テスト通過!",
            "✓ 모든 테스트 통과!",
            "✓ ทดสอบทั้งหมดผ่าน!",
            "✓ All tests passed!"
        )
        .green()
        .bold()
    );
    Ok(())
}

// ─── cmd_check ───────────────────────────────────────────────────────────────

fn cmd_check(args: &[String], lang: &InvocationLanguage) -> anyhow::Result<()> {
    say(
        lang,
        "Checking Ling source...",
        "正在核查灵源...",
        "霊源を確認中...",
        "령원 확인 중...",
    );

    let ling_bin = find_ling_binary();
    let mut checked = 0u32;

    // Check all .ling files we can find under src/ or current dir
    let src_roots = if let Some(manifest) = find_manifest() {
        let slang = lang_from_manifest(&manifest);
        let dirs = dir_names(&slang);
        let root = manifest
            .parent()
            .unwrap_or(std::path::Path::new("."))
            .to_path_buf();
        vec![root.join(dirs.src), root.join(dirs.tests)]
    } else {
        vec![
            std::path::PathBuf::from("src"),
            std::path::PathBuf::from("."),
        ]
    };

    for root in &src_roots {
        if !root.exists() {
            continue;
        }
        for entry in walkdir::WalkDir::new(root)
            .into_iter()
            .filter_map(|e| e.ok())
            .filter(|e| e.path().is_file())
            .filter(|e| has_ling_ext(e.path()))
        {
            let status = std::process::Command::new(&ling_bin)
                .arg("check")
                .arg(entry.path())
                .args(args)
                .status()
                .map_err(|e| anyhow::anyhow!("ling: {e}"))?;
            if !status.success() {
                anyhow::bail!("check failed: {}", entry.path().display());
            }
            checked += 1;
        }
    }

    if checked == 0 {
        // Try cargo check as fallback
        let status = std::process::Command::new("cargo")
            .arg("check")
            .args(args)
            .status()
            .map_err(|e| anyhow::anyhow!("cargo: {e}"))?;
        if !status.success() {
            anyhow::bail!("check failed");
        }
    } else {
        println!(
            "{}",
            format!(
                "{} {}",
                t(
                    lang,
                    "✓ Checked",
                    "✓ 核查",
                    "✓ 確認",
                    "✓ 확인",
                    "✓ ตรวจสอบ",
                    "✓ Checked"
                ),
                checked
            )
            .green()
            .bold()
        );
    }
    Ok(())
}

// ─── cmd_clean ───────────────────────────────────────────────────────────────

fn cmd_clean(lang: &InvocationLanguage) -> anyhow::Result<()> {
    say(
        lang,
        "Cleaning build artifacts...",
        "正在清洁灵碑...",
        "霊碑を清浄化中...",
        "령비 정화 중...",
    );

    let mut cleaned = false;

    if let Some(manifest) = find_manifest() {
        let slang = lang_from_manifest(&manifest);
        let dirs = dir_names(&slang);
        let root = manifest.parent().unwrap_or(std::path::Path::new("."));

        for dir_name in &[dirs.build, dirs.lock] {
            let dir = root.join(dir_name);
            if dir.exists() {
                std::fs::remove_dir_all(&dir)?;
                println!(
                    "  {} {}",
                    t(lang, "removed", "已清", "削除", "삭제", "ลบ", "removed").red(),
                    dir.display()
                );
                cleaned = true;
            }
        }
    }

    // Also clean target/ if Cargo.toml is present
    if std::path::Path::new("Cargo.toml").exists() || std::path::Path::new("target").exists() {
        let status = std::process::Command::new("cargo")
            .arg("clean")
            .status()
            .map_err(|e| anyhow::anyhow!("cargo: {e}"))?;
        if !status.success() {
            anyhow::bail!("clean failed");
        }
        cleaned = true;
    }

    if cleaned {
        println!(
            "{}",
            t(
                lang,
                "✓ Clean!",
                "✓ 已清洁!",
                "✓ 清浄完了!",
                "✓ 정화 완료!",
                "✓ ล้างเสร็จ!",
                "✓ Clean!"
            )
            .green()
            .bold()
        );
    } else {
        say(
            lang,
            "Nothing to clean.",
            "无需清洁。",
            "清浄するものはありません。",
            "정화할 것이 없습니다.",
        );
    }
    Ok(())
}

// ─── cmd_doc ─────────────────────────────────────────────────────────────────

fn cmd_doc(lang: &InvocationLanguage) -> anyhow::Result<()> {
    say(
        lang,
        "Generating documentation...",
        "正在生成文典...",
        "文典を生成中...",
        "문전 생성 중...",
    );

    // Try mdBook if docs/ or book/ exists
    for docs_dir in &["docs", "book"] {
        if std::path::Path::new(docs_dir).join("book.toml").exists() {
            let status = std::process::Command::new("mdbook")
                .arg("build")
                .arg(docs_dir)
                .status()
                .map_err(|e| anyhow::anyhow!("mdbook: {e}"))?;
            if !status.success() {
                anyhow::bail!("doc failed");
            }
            println!(
                "{}",
                t(
                    lang,
                    "✓ Docs built!",
                    "✓ 文典已生!",
                    "✓ 文典完成!",
                    "✓ 문전 완성!",
                    "✓ เอกสารเสร็จ!",
                    "✓ Docs built!"
                )
                .green()
                .bold()
            );
            return Ok(());
        }
    }

    // Fallback to cargo doc
    let status = std::process::Command::new("cargo")
        .arg("doc")
        .arg("--no-deps")
        .status()
        .map_err(|e| anyhow::anyhow!("cargo: {e}"))?;
    if !status.success() {
        anyhow::bail!("doc failed");
    }
    println!(
        "{}",
        t(
            lang,
            "✓ Docs built!",
            "✓ 文典已生!",
            "✓ 文典完成!",
            "✓ 문전 완성!",
            "✓ เอกสารเสร็จ!",
            "✓ Docs built!"
        )
        .green()
        .bold()
    );
    Ok(())
}

// ─── cmd_fmt ─────────────────────────────────────────────────────────────────

fn cmd_fmt(args: &[String], lang: &InvocationLanguage) -> anyhow::Result<()> {
    say(
        lang,
        "Formatting Ling sources...",
        "正在整理灵源...",
        "霊源を整形中...",
        "령원 정형 중...",
    );

    let ling_bin = find_ling_binary();
    let mut found = false;

    let roots = if let Some(manifest) = find_manifest() {
        let slang = lang_from_manifest(&manifest);
        let dirs = dir_names(&slang);
        let root = manifest
            .parent()
            .unwrap_or(std::path::Path::new("."))
            .to_path_buf();
        vec![root.join(dirs.src)]
    } else {
        vec![
            std::path::PathBuf::from("src"),
            std::path::PathBuf::from("."),
        ]
    };

    for root in &roots {
        if !root.exists() {
            continue;
        }
        for entry in walkdir::WalkDir::new(root)
            .into_iter()
            .filter_map(|e| e.ok())
            .filter(|e| e.path().is_file())
            .filter(|e| has_ling_ext(e.path()))
        {
            let status = std::process::Command::new(&ling_bin)
                .arg("fmt")
                .arg(entry.path())
                .args(args)
                .status()
                .map_err(|e| anyhow::anyhow!("ling: {e}"))?;
            if !status.success() {
                eprintln!("  fmt skipped: {}", entry.path().display());
            }
            found = true;
        }
    }

    if !found {
        // Fallback: rustfmt
        let _ = std::process::Command::new("cargo")
            .arg("fmt")
            .args(args)
            .status();
    }

    println!(
        "{}",
        t(
            lang,
            "✓ Formatted!",
            "✓ 已整理!",
            "✓ 整形完了!",
            "✓ 정형 완료!",
            "✓ จัดรูปแบบเสร็จ!",
            "✓ Formatted!"
        )
        .green()
        .bold()
    );
    Ok(())
}

// ─── cmd_tree ────────────────────────────────────────────────────────────────

fn cmd_tree(lang: &InvocationLanguage) -> anyhow::Result<()> {
    let root_dir = if let Some(manifest) = find_manifest() {
        manifest
            .parent()
            .unwrap_or(std::path::Path::new("."))
            .to_path_buf()
    } else {
        std::env::current_dir()?
    };

    println!(
        "{}",
        t(
            lang,
            "Project tree:",
            "项目结构:",
            "プロジェクト構造:",
            "프로젝트 구조:",
            "โครงสร้างโปรเจค:",
            "Project tree:"
        )
        .yellow()
    );
    print_tree(&root_dir, "", true)?;
    Ok(())
}

fn print_tree(dir: &std::path::Path, prefix: &str, is_root: bool) -> anyhow::Result<()> {
    let name = if is_root {
        dir.file_name()
            .map(|n| n.to_string_lossy().to_string())
            .unwrap_or_else(|| ".".into())
    } else {
        dir.file_name()
            .map(|n| n.to_string_lossy().to_string())
            .unwrap_or_default()
    };

    println!("{}{}", prefix, name.cyan());

    let skip = [".git", "target", "node_modules"];
    let mut entries: Vec<_> = std::fs::read_dir(dir)?
        .filter_map(|e| e.ok())
        .filter(|e| !skip.contains(&e.file_name().to_string_lossy().as_ref()))
        .collect();
    entries.sort_by_key(|e| e.file_name());

    for (i, entry) in entries.iter().enumerate() {
        let is_last = i == entries.len() - 1;
        let connector = if is_last { "└── " } else { "├── " };
        let child_prefix = if is_last { "    " } else { "│   " };
        let path = entry.path();

        if path.is_dir() {
            print_tree(&path, &format!("{}{}", prefix, connector), false)?;
        } else {
            let fname = entry.file_name().to_string_lossy().to_string();
            let colored_name = if has_ling_ext(&path) {
                fname.green().to_string()
            } else if fname.ends_with(".toml") {
                fname.yellow().to_string()
            } else {
                fname
            };
            println!("{}{}{}", prefix, connector, colored_name);
        }

        let _ = child_prefix; // used only for recursion
    }
    Ok(())
}

// ─── cmd_bench ───────────────────────────────────────────────────────────────

fn cmd_bench(args: &[String], lang: &InvocationLanguage) -> anyhow::Result<()> {
    say(
        lang,
        "Running benchmarks...",
        "正在运行基准测试...",
        "ベンチマーク実行中...",
        "벤치마크 실행 중...",
    );
    let status = std::process::Command::new("cargo")
        .arg("bench")
        .args(args)
        .status()
        .map_err(|e| anyhow::anyhow!("cargo: {e}"))?;
    if !status.success() {
        anyhow::bail!("bench failed");
    }
    Ok(())
}

// ─── cmd_update ──────────────────────────────────────────────────────────────

fn cmd_update(args: &[String], lang: &InvocationLanguage) -> anyhow::Result<()> {
    say(
        lang,
        "Updating dependencies...",
        "正在更新依赖...",
        "依存関係を更新中...",
        "의존성 업데이트 중...",
    );
    let status = std::process::Command::new("cargo")
        .arg("update")
        .args(args)
        .status()
        .map_err(|e| anyhow::anyhow!("cargo: {e}"))?;
    if !status.success() {
        anyhow::bail!("update failed");
    }
    println!(
        "{}",
        t(
            lang,
            "✓ Updated!",
            "✓ 已更新!",
            "✓ 更新完了!",
            "✓ 업데이트 완료!",
            "✓ อัปเดตเสร็จ!",
            "✓ Updated!"
        )
        .green()
        .bold()
    );
    Ok(())
}

// ─── cmd_add ─────────────────────────────────────────────────────────────────

fn cmd_add(args: &[String], lang: &InvocationLanguage) -> anyhow::Result<()> {
    let use_crates = args.iter().any(|a| a == "--crates");

    if use_crates {
        // `--crates` opts back into the original behavior: shell out to
        // `cargo add` against crates.io/ambient Cargo config, for the Ling
        // projects that really do need a Rust/Cargo dependency. Everything
        // below this branch is unchanged from before fu.ling-lang.org support
        // existed, other than stripping the `--crates` flag itself out of the
        // args forwarded to cargo (which doesn't know that flag).
        let cargo_args: Vec<String> =
            args.iter().filter(|a| a.as_str() != "--crates").cloned().collect();
        if cargo_args.is_empty() {
            say(
                lang,
                "Please specify a package to add",
                "请指定要添加的包",
                "追加するパッケージを指定してください",
                "추가할 패키지를 지정하세요",
            );
            return Ok(());
        }
        let package = &cargo_args[0];
        say(
            lang,
            &format!("Adding package: {}", package),
            &format!("正在添包: {}", package),
            &format!("パッケージを追加中: {}", package),
            &format!("패키지 추가 중: {}", package),
        );
        let status = std::process::Command::new("cargo")
            .arg("add")
            .args(&cargo_args)
            .status()
            .map_err(|e| anyhow::anyhow!("cargo: {e}"))?;
        if !status.success() {
            anyhow::bail!("add failed");
        }
        println!(
            "{}",
            t(
                lang,
                "✓ Added!",
                "✓ 已添!",
                "✓ 追加完了!",
                "✓ 추가 완료!",
                "✓ เพิ่มเสร็จ!",
                "✓ Added!"
            )
            .green()
            .bold()
        );
        return Ok(());
    }

    if args.is_empty() {
        say(
            lang,
            "Please specify a package to add",
            "请指定要添加的包",
            "追加するパッケージを指定してください",
            "추가할 패키지를 지정하세요",
        );
        return Ok(());
    }
    let package = &args[0];
    say(
        lang,
        &format!("Installing package: {} (from {})", package, registry::active_registry()),
        &format!("正在安装包: {}", package),
        &format!("パッケージをインストール中: {}", package),
        &format!("패키지 설치 중: {}", package),
    );
    let result = registry::install(package)?;
    println!(
        "{}",
        t(
            lang,
            "✓ Installed!",
            "✓ 已安装!",
            "✓ インストール完了!",
            "✓ 설치 완료!",
            "✓ ติดตั้งเสร็จ!",
            "✓ Installed!"
        )
        .green()
        .bold()
    );
    let dir_display = result.dir.display().to_string().replace('\\', "/");
    println!("  {} v{} -> {}", result.name, result.version, dir_display);
    match &result.entry {
        Some(entry) => {
            let entry_path = format!("{}/{}", dir_display, entry.display()).replace('\\', "/");
            println!(
                "  {}",
                t(
                    lang,
                    "entry file:",
                    "入口文件:",
                    "エントリファイル:",
                    "진입 파일:",
                    "ไฟล์เริ่มต้น:",
                    "entry file:"
                )
            );
            println!("    {}", entry_path);
            println!(
                "  {}",
                t(
                    lang,
                    "add this to your project:",
                    "把这行加到你的项目里:",
                    "プロジェクトに追加してください:",
                    "프로젝트에 추가하세요:",
                    "เพิ่มบรรทัดนี้ในโปรเจคของคุณ:",
                    "add this to your project:"
                )
            );
            println!("    use \"{}\";", entry_path);
        },
        None => {
            println!(
                "  {}",
                t(
                    lang,
                    "note: no .ling entry file was found — check the package's own README",
                    "注意:未找到 .ling 入口文件——请查看包自带的 README",
                    "注意:.ling のエントリファイルが見つかりませんでした — パッケージの README を確認してください",
                    "참고: .ling 진입 파일을 찾지 못했습니다 — 패키지 자체 README를 확인하세요",
                    "หมายเหตุ: ไม่พบไฟล์เริ่มต้น .ling — โปรดดู README ของแพ็กเกจ",
                    "note: no .ling entry file was found — check the package's own README"
                )
            );
        },
    }
    Ok(())
}

// ─── cmd_search ──────────────────────────────────────────────────────────────

fn cmd_search(args: &[String], lang: &InvocationLanguage) -> anyhow::Result<()> {
    let use_crates = args.iter().any(|a| a == "--crates");

    if use_crates {
        // `--crates` opts back into the original behavior: shell out to
        // `cargo search` against crates.io. Unchanged other than stripping
        // the `--crates` flag itself before forwarding to cargo.
        let cargo_args: Vec<String> =
            args.iter().filter(|a| a.as_str() != "--crates").cloned().collect();
        if cargo_args.is_empty() {
            say(
                lang,
                "Please provide a search query",
                "请提供搜索词",
                "検索クエリを入力してください",
                "검색어를 입력하세요",
            );
            return Ok(());
        }
        say(
            lang,
            &format!("Searching: {}", cargo_args[0]),
            &format!("正在寻找: {}", cargo_args[0]),
            &format!("検索中: {}", cargo_args[0]),
            &format!("검색 중: {}", cargo_args[0]),
        );
        let status = std::process::Command::new("cargo")
            .arg("search")
            .args(&cargo_args)
            .status()
            .map_err(|e| anyhow::anyhow!("cargo: {e}"))?;
        if !status.success() {
            anyhow::bail!("search failed");
        }
        return Ok(());
    }

    if args.is_empty() {
        say(
            lang,
            "Please provide a search query",
            "请提供搜索词",
            "検索クエリを入力してください",
            "검색어를 입력하세요",
        );
        return Ok(());
    }
    let query = &args[0];
    say(
        lang,
        &format!("Searching {}: {}", registry::active_registry(), query),
        &format!("正在寻找: {}", query),
        &format!("検索中: {}", query),
        &format!("검색 중: {}", query),
    );
    let results = registry::search(query)?;
    if results.is_empty() {
        println!(
            "{}",
            t(
                lang,
                "No packages found.",
                "未找到匹配的包。",
                "パッケージが見つかりませんでした。",
                "패키지를 찾을 수 없습니다.",
                "ไม่พบแพ็กเกจที่ตรงกัน",
                "No packages found."
            )
        );
        return Ok(());
    }
    for pkg in &results {
        println!(
            "{:<24} {:>8} {}  {}",
            pkg.name.green(),
            pkg.downloads,
            t(
                lang,
                "downloads",
                "下载",
                "ダウンロード",
                "다운로드",
                "ดาวน์โหลด",
                "downloads"
            ),
            pkg.description
        );
    }
    Ok(())
}

// ─── cmd_publish ─────────────────────────────────────────────────────────────

fn cmd_publish(args: &[String], lang: &InvocationLanguage) -> anyhow::Result<()> {
    say(
        lang,
        &format!("Publishing to {}...", registry::active_registry()),
        "正在发布到注册处...",
        "レジストリに公開中...",
        "레지스트리에 게시 중...",
    );
    registry::publish(args)?;
    println!(
        "{}",
        t(
            lang,
            "✓ Published!",
            "✓ 已发布!",
            "✓ 公開完了!",
            "✓ 게시 완료!",
            "✓ เผยแพร่เสร็จ!",
            "✓ Published!"
        )
        .green()
        .bold()
    );
    Ok(())
}

// ─── cmd_login / logout ───────────────────────────────────────────────────────

fn cmd_login(args: &[String], lang: &InvocationLanguage) -> anyhow::Result<()> {
    say(
        lang,
        &format!("Saving API key for {}...", registry::active_registry()),
        "正在登入注册处...",
        "レジストリにログイン中...",
        "레지스트리 로그인 중...",
    );
    registry::login(args)?;
    println!(
        "{}",
        t(
            lang,
            "✓ Logged in!",
            "✓ 已登入!",
            "✓ ログイン完了!",
            "✓ 로그인 완료!",
            "✓ เข้าสู่ระบบเสร็จ!",
            "✓ Logged in!"
        )
        .green()
        .bold()
    );
    Ok(())
}

fn cmd_logout(lang: &InvocationLanguage) -> anyhow::Result<()> {
    say(
        lang,
        "Logging out from registry...",
        "正在退出注册处...",
        "レジストリからログアウト中...",
        "레지스트리 로그아웃 중...",
    );
    registry::logout()?;
    println!(
        "{}",
        t(
            lang,
            "✓ Logged out!",
            "✓ 已退出!",
            "✓ ログアウト完了!",
            "✓ 로그아웃 완료!",
            "✓ ออกจากระบบเสร็จ!",
            "✓ Logged out!"
        )
        .green()
        .bold()
    );
    Ok(())
}

// ─── cmd_translate ───────────────────────────────────────────────────────────

fn cmd_translate(args: &[String], lang: &InvocationLanguage) -> anyhow::Result<()> {
    say(
        lang,
        "Translate source keywords between lexicons (use --from <lang> --to <lang> <file>)",
        "翻译源文件关键字 (使用 --from <语言> --to <语言> <文件>)",
        "ソースキーワードを翻訳 (--from <言語> --to <言語> <ファイル>)",
        "소스 키워드 번역 (--from <언어> --to <언어> <파일>)",
    );

    let mut from_lang = String::new();
    let mut to_lang = String::new();
    let mut file_path = String::new();

    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--from" if i + 1 < args.len() => {
                from_lang = args[i + 1].clone();
                i += 1;
            },
            "--to" if i + 1 < args.len() => {
                to_lang = args[i + 1].clone();
                i += 1;
            },
            s if !s.starts_with('-') && file_path.is_empty() => {
                file_path = s.to_string();
            },
            _ => {},
        }
        i += 1;
    }

    if file_path.is_empty() || from_lang.is_empty() || to_lang.is_empty() {
        say(
            lang,
            "Usage: translate --from <lang> --to <lang> <file>",
            "用法: 译 --from <语言> --to <语言> <文件>",
            "使用法: 訳 --from <言語> --to <言語> <ファイル>",
            "사용법: 번 --from <언어> --to <언어> <파일>",
        );
        return Ok(());
    }

    say(
        lang,
        &format!(
            "Translating {} from {} to {}...",
            file_path, from_lang, to_lang
        ),
        &format!("正在翻译 {} 从 {} 到 {}...", file_path, from_lang, to_lang),
        &format!(
            "{} を {} から {} へ翻訳中...",
            file_path, from_lang, to_lang
        ),
        &format!(
            "{} 를 {} 에서 {} 로 번역 중...",
            file_path, from_lang, to_lang
        ),
    );

    // Invoke ling binary with translate subcommand
    let ling_bin = find_ling_binary();
    let status = std::process::Command::new(&ling_bin)
        .args([
            "translate",
            "--from",
            &from_lang,
            "--to",
            &to_lang,
            &file_path,
        ])
        .status()
        .map_err(|e| anyhow::anyhow!("ling: {e}"))?;
    if !status.success() {
        anyhow::bail!("translate failed");
    }
    Ok(())
}

// ─── cmd_manifest ────────────────────────────────────────────────────────────

fn cmd_manifest(lang: &InvocationLanguage) -> anyhow::Result<()> {
    if let Some(path) = find_manifest() {
        say(
            lang,
            &format!("Manifest: {}", path.display()),
            &format!("灵符清单: {}", path.display()),
            &format!("マニフェスト: {}", path.display()),
            &format!("매니페스트: {}", path.display()),
        );
        let content = std::fs::read_to_string(&path)?;
        println!("{}", content);
    } else if std::path::Path::new("Cargo.toml").exists() {
        println!("{}", std::fs::read_to_string("Cargo.toml")?);
    } else {
        say(
            lang,
            "No manifest found (Ling.toml / 灵符.toml / 霊符.toml / 영부.toml / ลิงฟู.toml)",
            "未找到灵符清单",
            "マニフェストが見つかりません",
            "매니페스트를 찾을 수 없습니다",
        );
    }
    Ok(())
}

// ─── cmd_wizard ──────────────────────────────────────────────────────────────

fn cmd_wizard(lang: &InvocationLanguage) -> anyhow::Result<()> {
    println!("{}", lang.welcome_message().magenta().bold());

    let name_prompt = t(
        lang,
        "Project Name:",
        "项目名称:",
        "プロジェクト名:",
        "프로젝트 이름:",
        "ชื่อโปรเจค:",
        "Project Name:",
    );
    let name: String = Input::new().with_prompt(name_prompt).interact_text()?;

    let type_options: Vec<&str> = match lang {
        InvocationLanguage::Chinese => vec![
            "独行 (Binary)",
            "共修 (Library)",
            "游灵 (Game)",
            "显灵 (UI)",
            "智灵 (AI/ML)",
            "密灵 (Crypto)",
            "网灵 (Web/WASM)",
            "万言 (Polyglot)",
        ],
        InvocationLanguage::Japanese => vec![
            "単独 (Binary)",
            "共修 (Library)",
            "游霊 (Game)",
            "表霊 (UI)",
            "智霊 (AI/ML)",
            "密霊 (Crypto)",
            "網霊 (Web)",
            "万言 (Polyglot)",
        ],
        InvocationLanguage::Korean => vec![
            "독립 (Binary)",
            "라이브러리 (Library)",
            "게임 (Game)",
            "인터페이스 (UI)",
            "인공지능 (AI)",
            "암호 (Crypto)",
            "웹 (Web)",
            "다국어 (Polyglot)",
        ],
        InvocationLanguage::Thai => vec![
            "โปรแกรม (Binary)",
            "ไลบรารี (Library)",
            "เกม (Game)",
            "ส่วนติดต่อ (UI)",
            "ปัญญา (AI)",
            "การเข้ารหัส (Crypto)",
            "เว็บ (Web)",
            "หลายภาษา (Polyglot)",
        ],
        _ => vec![
            "Binary", "Library", "Game", "UI", "AI/ML", "Crypto", "Web/WASM", "Polyglot",
        ],
    };

    let type_prompt = t(
        lang,
        "Spirit Realm Type:",
        "灵境类型:",
        "霊境タイプ:",
        "영경 유형:",
        "ประเภทวิญญาณ:",
        "Project Type:",
    );
    let selection = Select::new()
        .with_prompt(type_prompt)
        .items(&type_options)
        .default(0)
        .interact()?;

    let project_kind = match selection {
        0 => ProjectKind::Bin,
        1 => ProjectKind::Lib,
        2 => ProjectKind::Game,
        3 => ProjectKind::Ui,
        4 => ProjectKind::Ai,
        5 => ProjectKind::Crypto,
        6 => ProjectKind::Web,
        _ => ProjectKind::Polyglot,
    };

    let lang_options = &["en", "zh", "ja", "ko", "th", "ru"];
    let lang_prompt = t(
        lang,
        "Scaffolding language:",
        "支架语言:",
        "足場言語:",
        "발판 언어:",
        "ภาษาโครงสร้าง:",
        "Scaffolding language:",
    );
    let lang_sel = Select::new()
        .with_prompt(lang_prompt)
        .items(lang_options)
        .default(0)
        .interact()?;
    let primary_lang = lang_options[lang_sel].to_string();

    println!(
        "{}",
        format!(
            "{} {}",
            t(
                lang,
                "Creating project:",
                "正在创建项目:",
                "プロジェクトを作成中:",
                "프로젝트 생성 중:",
                "กำลังสร้างโปรเจค:",
                "Creating project:"
            ),
            name
        )
        .green()
    );

    let project = ProjectScaffold {
        name: name.clone(),
        kind: project_kind,
        language: primary_lang.clone(),
        root_language: primary_lang,
        spirit_level: "铁".into(),
    };
    scaffold::scaffold_project(&project)?;
    print_post_create(lang, &name);
    Ok(())
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

fn parse_new_flags(args: &[String]) -> (ProjectKind, String, Option<String>) {
    let mut kind = ProjectKind::Bin;
    let mut primary_lang = "en".to_string();
    let mut root_lang: Option<String> = None;
    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--type" | "-t" if i + 1 < args.len() => {
                kind = ProjectKind::from_name(&args[i + 1]);
                i += 1;
            },
            "--lang" | "-l" if i + 1 < args.len() => {
                primary_lang = args[i + 1].clone();
                i += 1;
            },
            "--root-lang" if i + 1 < args.len() => {
                root_lang = Some(args[i + 1].clone());
                i += 1;
            },
            _ => {},
        }
        i += 1;
    }
    (kind, primary_lang, root_lang)
}

fn print_post_create(lang: &InvocationLanguage, name: &str) {
    println!(
        "\n{}",
        t(
            lang,
            "✓ Spirit Realm created!",
            "✓ 灵境已成!",
            "✓ 霊境が完成しました!",
            "✓ 영경이 완성되었습니다!",
            "✓ วิญญาณเสร็จสิ้น!",
            "✓ Spirit Realm created!"
        )
        .green()
        .bold()
    );
    println!("  cd {}", name);
    println!("  {} build", "lingfu".cyan());
    println!("  {} run", "lingfu".cyan());
}

fn find_ling_entry() -> Option<std::path::PathBuf> {
    let candidates = [
        "main.ling",
        "src/main.ling",
        "src/start.ling",
        "start.ling",
        "启.灵",
        "灵源/启.灵",
        "啓.霊",
        "霊源/啓.霊",
        "시작.령",
        "령원/시작.령",
        "เริ่ม.ลิง",
    ];
    for c in &candidates {
        let p = std::path::Path::new(c);
        if p.exists() {
            return Some(p.to_path_buf());
        }
    }
    if let Ok(entries) = std::fs::read_dir(".") {
        for entry in entries.flatten() {
            if has_ling_ext(&entry.path()) {
                return Some(entry.path());
            }
        }
    }
    None
}

fn find_ling_binary() -> std::path::PathBuf {
    if let Ok(exe) = std::env::current_exe() {
        let parent = exe.parent().unwrap_or(std::path::Path::new("."));
        for name in &["ling", "ling.exe"] {
            let p = parent.join(name);
            if p.exists() {
                return p;
            }
        }
    }
    std::path::PathBuf::from("ling")
}

fn has_ling_ext(path: &std::path::Path) -> bool {
    matches!(
        path.extension().and_then(|e| e.to_str()),
        Some("ling" | "灵" | "霊" | "령" | "ลิง")
    )
}

// ─── cmd_normalize ────────────────────────────────────────────────────────────

fn cmd_normalize(args: &[String], lang: &InvocationLanguage) -> anyhow::Result<()> {
    // Parse flags
    let dry_run = args.iter().any(|a| a == "--dry-run" || a == "-n");
    let content_only = args.iter().any(|a| a == "--content-only");
    let files_only = args.iter().any(|a| a == "--files-only");

    // Find target language: positional or --lang / -l flag
    let mut target_lang_str: Option<&str> = None;
    let mut i = 0;
    while i < args.len() {
        match args[i].as_str() {
            "--lang" | "-l" if i + 1 < args.len() => {
                target_lang_str = Some(&args[i + 1]);
                i += 2;
            },
            s if !s.starts_with('-') => {
                target_lang_str = Some(s);
                i += 1;
            },
            _ => {
                i += 1;
            },
        }
    }

    let target = match target_lang_str.and_then(normalize::Lang::from_name) {
        Some(l) => l,
        None => {
            // Interactive prompt if no language specified
            let opts = &[
                "en  — English",
                "zh  — 中文",
                "ja  — 日本語",
                "ko  — 한국어",
                "th  — ภาษาไทย",
            ];
            let prompt = t(
                lang,
                "Target language:",
                "目标语言:",
                "対象言語:",
                "대상 언어:",
                "ภาษาเป้าหมาย:",
                "Target language:",
            );
            let sel = dialoguer::Select::new()
                .with_prompt(prompt)
                .items(opts)
                .default(0)
                .interact()?;
            [
                normalize::Lang::En,
                normalize::Lang::Zh,
                normalize::Lang::Ja,
                normalize::Lang::Ko,
                normalize::Lang::Th,
            ][sel]
        },
    };

    println!(
        "{} {} {}{}",
        t(
            lang,
            "Normalizing to",
            "规范化至",
            "正規化:",
            "정규화:",
            "ปรับภาษาเป็น:",
            "Normalizing to"
        ),
        target.name().cyan().bold(),
        if dry_run {
            " [dry-run]".yellow().to_string()
        } else {
            String::new()
        },
        if content_only {
            " [content only]".dimmed().to_string()
        } else if files_only {
            " [files only]".dimmed().to_string()
        } else {
            String::new()
        },
    );

    let root = std::path::Path::new(".");
    match normalize::normalize_project(root, target, dry_run, content_only, files_only) {
        Ok(stats) => {
            println!();
            if dry_run {
                println!(
                    "{}",
                    t(
                        lang,
                        "Dry run complete — no files changed.",
                        "模拟完成 — 未修改任何文件。",
                        "ドライランが完了しました — 変更なし。",
                        "드라이 런 완료 — 변경 없음.",
                        "ทดลองเสร็จสิ้น — ไม่มีไฟล์ถูกเปลี่ยน",
                        "Dry run complete — no files changed.",
                    )
                    .dimmed()
                );
            }
            println!(
                "  {} {} {}",
                "✓".green(),
                stats.files_rewritten,
                t(
                    lang,
                    "file(s) rewritten",
                    "文件已重写",
                    "ファイル書き換え済み",
                    "파일 재작성됨",
                    "ไฟล์ถูกเขียนใหม่",
                    "file(s) rewritten"
                ),
            );
            println!(
                "  {} {} {}",
                "~".yellow(),
                stats.files_renamed,
                t(
                    lang,
                    "file(s) renamed",
                    "文件已重命名",
                    "ファイル名変更済み",
                    "파일 이름 변경됨",
                    "ไฟล์ถูกเปลี่ยนชื่อ",
                    "file(s) renamed"
                ),
            );
            println!(
                "  {} {} {}",
                "↕".magenta(),
                stats.dirs_renamed,
                t(
                    lang,
                    "folder(s) renamed",
                    "文件夹已重命名",
                    "フォルダ名変更済み",
                    "폴더 이름 변경됨",
                    "โฟลเดอร์ถูกเปลี่ยนชื่อ",
                    "folder(s) renamed"
                ),
            );
        },
        Err(e) => {
            eprintln!("{}: {}", "error".red(), e);
            std::process::exit(1);
        },
    }
    Ok(())
}

← Back to index