/**
 * common.js — 机擎 SDK Hook 脚本公共框架
 *
 * 所有平台 Hook 脚本（微信/抖音/小红书等）共用的工具函数和基础设施。
 * 由各平台脚本通过 Frida evaluate 或内联引用使用。
 *
 * @version 1.0.0
 */
'use strict';

// ============================================================
// § 1  日志与事件
// ============================================================

var LOG_LEVEL_MAP = { debug: 0, info: 1, warn: 2, error: 3 };
var _currentLogLevel = 'info';

function setLogLevel(level) {
    if (LOG_LEVEL_MAP[level] !== undefined) _currentLogLevel = level;
}

function log(level, tag, message, extra) {
    if ((LOG_LEVEL_MAP[level] || 0) < (LOG_LEVEL_MAP[_currentLogLevel] || 0)) return;
    send({
        type: 'log',
        level: level,
        tag: String(tag || 'hook'),
        message: String(message || ''),
        extra: extra || {},
        timestamp: Date.now(),
    });
}

function emitEvent(eventType, payload) {
    send({
        type: 'hook_event',
        event_type: String(eventType),
        platform: (typeof PLATFORM !== 'undefined') ? PLATFORM : 'unknown',
        payload: payload || {},
        timestamp: new Date().toISOString(),
    });
}

// ============================================================
// § 2  安全字符串/类型工具
// ============================================================

function safeStr(v) {
    if (v === null || v === undefined) return '';
    try { return v.toString(); } catch (_) { return ''; }
}

function safeInt(v, fallback) {
    if (v === null || v === undefined) return fallback || 0;
    try {
        var n = parseInt(v, 10);
        return isNaN(n) ? (fallback || 0) : n;
    } catch (_) { return fallback || 0; }
}

function safeJSON(v) {
    try { return JSON.parse(v); } catch (_) { return null; }
}

function clamp(val, min, max) {
    return Math.max(min, Math.min(max, val));
}

function generateId(prefix) {
    return (prefix || 'id') + '_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6);
}

// ============================================================
// § 3  Java / Android 辅助
// ============================================================

function javaString(str) {
    return Java.use('java.lang.String').$new(String(str));
}

function getContext() {
    try {
        return Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
    } catch (e) {
        log('warn', 'ctx', '获取 Context 失败: ' + e);
        return null;
    }
}

function getPackageVersion(packageName) {
    try {
        var ctx = getContext();
        if (!ctx) return '';
        var pm = ctx.getPackageManager();
        var info = pm.getPackageInfo(javaString(packageName), 0);
        return safeStr(info.versionName.value);
    } catch (_) {
        return '';
    }
}

function isServiceRunning(serviceClass) {
    try {
        var ctx = getContext();
        if (!ctx) return false;
        var am = Java.cast(
            ctx.getSystemService(javaString('activity')),
            Java.use('android.app.ActivityManager')
        );
        var services = am.getRunningServices(100);
        for (var i = 0; i < services.size(); i++) {
            if (safeStr(services.get(i).service.value.getClassName()) === serviceClass) return true;
        }
    } catch (_) {}
    return false;
}

// ============================================================
// § 4  SQLite 通用查询
// ============================================================

function execSQL(dbPath, sql, limit) {
    limit = limit || 500;
    var results = [];
    try {
        var SQLiteDatabase = Java.use('android.database.sqlite.SQLiteDatabase');
        var db = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY.value);
        var cursor = db.rawQuery(sql, null);
        var count = 0;
        while (cursor.moveToNext() && count < limit) {
            var row = {};
            for (var c = 0; c < cursor.getColumnCount(); c++) {
                row[cursor.getColumnName(c)] = safeStr(cursor.getString(c));
            }
            results.push(row);
            count++;
        }
        cursor.close();
        db.close();
    } catch (e) {
        log('warn', 'db', 'execSQL 失败: ' + e + ' | sql=' + sql);
    }
    return results;
}

function findDatabase(basePath, dbName) {
    try {
        var File = Java.use('java.io.File');
        var dir = File.$new(basePath);
        if (!dir.exists()) return null;
        var subs = dir.listFiles();
        if (!subs) return null;
        for (var i = 0; i < subs.length; i++) {
            var candidate = subs[i].getAbsolutePath() + '/' + dbName;
            var f = File.$new(candidate);
            if (f.exists()) return candidate;
        }
    } catch (_) {}
    return null;
}

// ============================================================
// § 5  Intent 辅助
// ============================================================

function sendBroadcast(action, extras) {
    try {
        var Intent = Java.use('android.content.Intent');
        var ctx = getContext();
        if (!ctx) return false;
        var intent = Intent.$new(action);
        if (extras) {
            var keys = Object.keys(extras);
            for (var i = 0; i < keys.length; i++) {
                intent.putExtra(keys[i], String(extras[keys[i]]));
            }
        }
        ctx.sendBroadcast(intent);
        return true;
    } catch (e) {
        log('error', 'intent', 'sendBroadcast 失败: ' + e);
        return false;
    }
}

function startActivity(componentPkg, componentCls, extras) {
    try {
        var Intent = Java.use('android.content.Intent');
        var ComponentName = Java.use('android.content.ComponentName');
        var ctx = getContext();
        if (!ctx) return false;
        var intent = Intent.$new();
        intent.setComponent(ComponentName.$new(componentPkg, componentCls));
        intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK
        if (extras) {
            var keys = Object.keys(extras);
            for (var i = 0; i < keys.length; i++) {
                intent.putExtra(keys[i], String(extras[keys[i]]));
            }
        }
        ctx.startActivity(intent);
        return true;
    } catch (e) {
        log('error', 'intent', 'startActivity 失败: ' + e);
        return false;
    }
}

// ============================================================
// § 6  Hook 辅助（安全包装 Java.use / overload）
// ============================================================

function tryJavaUse(className) {
    try { return Java.use(className); } catch (_) { return null; }
}

function hookMethod(className, methodName, overloads, callbacks) {
    try {
        var Cls = Java.use(className);
        var method = overloads ? Cls[methodName].overload.apply(Cls[methodName], overloads) : Cls[methodName];
        method.implementation = callbacks.replace || function () {
            if (callbacks.before) callbacks.before.apply(this, arguments);
            var ret = method.apply(this, arguments);
            if (callbacks.after) ret = callbacks.after.call(this, ret, arguments) || ret;
            return ret;
        };
        log('info', 'hook', '已 Hook: ' + className + '.' + methodName);
        return true;
    } catch (e) {
        log('warn', 'hook', 'Hook 失败: ' + className + '.' + methodName + ' → ' + e);
        return false;
    }
}

function hookConstructor(className, overloads, callback) {
    try {
        var Cls = Java.use(className);
        var ctor = overloads ? Cls.$init.overload.apply(Cls.$init, overloads) : Cls.$init;
        ctor.implementation = function () {
            ctor.apply(this, arguments);
            if (callback) callback.apply(this, arguments);
        };
        log('info', 'hook', '已 Hook 构造: ' + className);
        return true;
    } catch (e) {
        log('warn', 'hook', 'Hook 构造失败: ' + className + ' → ' + e);
        return false;
    }
}

// ============================================================
// § 7  文件系统辅助
// ============================================================

function readFile(path) {
    try {
        var File = Java.use('java.io.File');
        var FileInputStream = Java.use('java.io.FileInputStream');
        var BufferedReader = Java.use('java.io.BufferedReader');
        var InputStreamReader = Java.use('java.io.InputStreamReader');
        var f = File.$new(path);
        if (!f.exists()) return null;
        var reader = BufferedReader.$new(InputStreamReader.$new(FileInputStream.$new(f)));
        var sb = [];
        var line;
        while ((line = reader.readLine()) !== null) sb.push(safeStr(line));
        reader.close();
        return sb.join('\n');
    } catch (_) { return null; }
}

function fileExists(path) {
    try { return Java.use('java.io.File').$new(path).exists(); } catch (_) { return false; }
}

// ============================================================
// § 8  缓冲与节流
// ============================================================

function createThrottle(minIntervalMs) {
    var lastFire = 0;
    return function (fn) {
        var now = Date.now();
        if (now - lastFire >= minIntervalMs) {
            lastFire = now;
            fn();
        }
    };
}

function createRingBuffer(capacity) {
    var buf = [];
    return {
        push: function (item) {
            buf.push(item);
            if (buf.length > capacity) buf = buf.slice(-Math.floor(capacity * 0.6));
        },
        slice: function (start, end) { return buf.slice(start, end); },
        get length() { return buf.length; },
        clear: function () { buf = []; },
    };
}

// ============================================================
// § 9  导出（供其它脚本引用）
// ============================================================

var CommonUtils = {
    log: log,
    setLogLevel: setLogLevel,
    emitEvent: emitEvent,
    safeStr: safeStr,
    safeInt: safeInt,
    safeJSON: safeJSON,
    clamp: clamp,
    generateId: generateId,
    javaString: javaString,
    getContext: getContext,
    getPackageVersion: getPackageVersion,
    isServiceRunning: isServiceRunning,
    execSQL: execSQL,
    findDatabase: findDatabase,
    sendBroadcast: sendBroadcast,
    startActivity: startActivity,
    tryJavaUse: tryJavaUse,
    hookMethod: hookMethod,
    hookConstructor: hookConstructor,
    readFile: readFile,
    fileExists: fileExists,
    createThrottle: createThrottle,
    createRingBuffer: createRingBuffer,
};
