import chalk from 'chalk';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { execFile, spawn } from 'child_process';
import { promisify } from 'util';
import { CH } from '../utils/logger.js';
const execFileAsync = promisify(execFile);
export class DeviceCommand {
    logger;
    constructor(_config, logger) {
        this.logger = logger;
    }
    async status(options = {}) {
        const adbPath = this.findAdbPath();
        const devices = adbPath ? await this.getDevices(adbPath) : [];
        const payload = {
            adbAvailable: Boolean(adbPath),
            adbPath,
            deviceCount: devices.filter((device) => device.state === 'device').length,
            devices,
        };
        if (options.json) {
            console.log(JSON.stringify(payload, null, 2));
            return;
        }
        console.log();
        console.log(chalk.bold.white(`  ${CH.hLine.repeat(3)} Vigthoria Android Developer Bridge ${CH.hLine.repeat(20)}`));
        console.log(chalk.gray('  ADB: ') + (adbPath ? chalk.green(adbPath) : chalk.yellow('not found')));
        if (!adbPath) {
            console.log(chalk.gray('  Install Android SDK Platform Tools, then make adb available on PATH.'));
            console.log();
            return;
        }
        if (devices.length === 0) {
            console.log(chalk.yellow('  No Android devices visible.'));
            console.log(chalk.gray('  Enable Developer Options + USB debugging, then run `vigthoria device list`.'));
            console.log();
            return;
        }
        for (const device of devices) {
            const label = device.model || device.product || 'Android device';
            const state = device.state === 'device' ? chalk.green(device.state) : chalk.yellow(device.state);
            console.log(`  ${chalk.white(device.id)}  ${state}  ${chalk.gray(label)}`);
        }
        console.log();
    }
    async list(options = {}) {
        const adbPath = this.requireAdbPath();
        const devices = await this.getDevices(adbPath);
        if (options.json) {
            console.log(JSON.stringify({ devices }, null, 2));
            return;
        }
        if (devices.length === 0) {
            console.log(chalk.yellow('No Android devices found.'));
            return;
        }
        for (const device of devices) {
            console.log(`${chalk.white(device.id)}\t${device.state}\t${device.model || device.product || ''}`.trim());
        }
    }
    async screenshot(options = {}) {
        const adbPath = this.requireAdbPath();
        const deviceId = await this.resolveDeviceId(adbPath, options.device);
        const output = path.resolve(options.output || `vigthoria-android-screenshot-${this.timestamp()}.png`);
        const args = this.withDevice(deviceId, ['exec-out', 'screencap', '-p']);
        const result = await execFileAsync(adbPath, args, { encoding: 'buffer', maxBuffer: 20 * 1024 * 1024 });
        fs.writeFileSync(output, result.stdout);
        console.log(chalk.green(`Saved screenshot: ${output}`));
    }
    async install(apkPath, options = {}) {
        if (!apkPath)
            throw new Error('APK path is required.');
        const resolvedApk = path.resolve(apkPath);
        if (!fs.existsSync(resolvedApk))
            throw new Error(`APK not found: ${resolvedApk}`);
        const adbPath = this.requireAdbPath();
        const deviceId = await this.resolveDeviceId(adbPath, options.device);
        const result = await this.runAdb(adbPath, this.withDevice(deviceId, ['install', '-r', resolvedApk]), 120000);
        this.printResult(result, options.json);
    }
    async launch(packageName, options = {}) {
        if (!packageName)
            throw new Error('Android package name is required.');
        const adbPath = this.requireAdbPath();
        const deviceId = await this.resolveDeviceId(adbPath, options.device);
        const args = options.activity
            ? ['shell', 'am', 'start', '-n', `${packageName}/${options.activity}`]
            : ['shell', 'monkey', '-p', packageName, '-c', 'android.intent.category.LAUNCHER', '1'];
        const result = await this.runAdb(adbPath, this.withDevice(deviceId, args));
        this.printResult(result, options.json);
    }
    async logs(options = {}) {
        const adbPath = this.requireAdbPath();
        const deviceId = await this.resolveDeviceId(adbPath, options.device);
        const lines = String(options.lines || '250');
        const args = this.withDevice(deviceId, options.follow ? ['logcat'] : ['logcat', '-d', '-t', lines]);
        if (options.follow) {
            await new Promise((resolve, reject) => {
                const child = spawn(adbPath, args, { stdio: 'inherit' });
                child.on('error', reject);
                child.on('exit', (code) => {
                    if (code && code !== 0)
                        reject(new Error(`adb logcat exited with code ${code}`));
                    else
                        resolve();
                });
            });
            return;
        }
        const result = await this.runAdb(adbPath, args, 30000, 8 * 1024 * 1024);
        this.printResult(result, options.json);
    }
    async tcpip(options = {}) {
        const adbPath = this.requireAdbPath();
        const deviceId = await this.resolveDeviceId(adbPath, options.device);
        const port = String(options.port || '5555');
        const result = await this.runAdb(adbPath, this.withDevice(deviceId, ['tcpip', port]));
        this.printResult(result, options.json);
    }
    async connect(address, options = {}) {
        if (!address)
            throw new Error('Device address is required, for example 192.168.1.30:5555.');
        const adbPath = this.requireAdbPath();
        const result = await this.runAdb(adbPath, ['connect', address]);
        this.printResult(result, options.json);
    }
    async disconnect(address, options = {}) {
        const adbPath = this.requireAdbPath();
        const result = await this.runAdb(adbPath, address ? ['disconnect', address] : ['disconnect']);
        this.printResult(result, options.json);
    }
    async getDevices(adbPath) {
        const result = await this.runAdb(adbPath, ['devices', '-l']);
        if (!result.success)
            return [];
        return result.stdout
            .split(/\r?\n/)
            .slice(1)
            .map((line) => line.trim())
            .filter(Boolean)
            .map((line) => this.parseDeviceLine(line));
    }
    parseDeviceLine(line) {
        const parts = line.split(/\s+/);
        const id = parts[0] || '';
        const state = parts[1] || 'unknown';
        const metadata = new Map();
        for (const token of parts.slice(2)) {
            const separator = token.indexOf(':');
            if (separator > 0)
                metadata.set(token.slice(0, separator), token.slice(separator + 1));
        }
        return {
            id,
            state,
            model: metadata.get('model'),
            product: metadata.get('product'),
            transport: metadata.get('transport_id'),
            raw: line,
        };
    }
    async resolveDeviceId(adbPath, requested) {
        if (requested && requested.trim())
            return requested.trim();
        const devices = (await this.getDevices(adbPath)).filter((device) => device.state === 'device');
        if (devices.length === 0)
            throw new Error('No authorized Android device found. Run `vigthoria device status`.');
        if (devices.length > 1)
            throw new Error('Multiple Android devices found. Pass --device <id>.');
        return devices[0].id;
    }
    withDevice(deviceId, args) {
        return deviceId ? ['-s', deviceId, ...args] : args;
    }
    async runAdb(adbPath, args, timeout = 30000, maxBuffer = 1024 * 1024) {
        try {
            const result = await execFileAsync(adbPath, args, { timeout, maxBuffer });
            return { success: true, stdout: String(result.stdout || '').trim(), stderr: String(result.stderr || '').trim() };
        }
        catch (error) {
            const failure = error;
            return {
                success: false,
                stdout: String(failure.stdout || '').trim(),
                stderr: String(failure.stderr || failure.message || '').trim(),
            };
        }
    }
    printResult(result, json = false) {
        if (json) {
            console.log(JSON.stringify(result, null, 2));
            return;
        }
        if (result.stdout)
            console.log(result.stdout);
        if (result.stderr)
            console.error(result.success ? chalk.gray(result.stderr) : chalk.red(result.stderr));
        if (!result.success)
            process.exitCode = 1;
    }
    requireAdbPath() {
        const adbPath = this.findAdbPath();
        if (!adbPath) {
            throw new Error('ADB not found. Install Android SDK Platform Tools and make adb available on PATH.');
        }
        return adbPath;
    }
    findAdbPath() {
        const executable = process.platform === 'win32' ? 'adb.exe' : 'adb';
        const candidates = [
            process.env.VIGTHORIA_ADB_PATH,
            process.env.ADB_PATH,
            process.env.ANDROID_HOME ? path.join(process.env.ANDROID_HOME, 'platform-tools', executable) : undefined,
            process.env.ANDROID_SDK_ROOT ? path.join(process.env.ANDROID_SDK_ROOT, 'platform-tools', executable) : undefined,
            path.join(os.homedir(), 'Android', 'Sdk', 'platform-tools', executable),
            process.platform === 'win32' ? path.join(process.env.LOCALAPPDATA || '', 'Android', 'Sdk', 'platform-tools', executable) : undefined,
            process.platform === 'darwin' ? path.join(os.homedir(), 'Library', 'Android', 'sdk', 'platform-tools', executable) : undefined,
            '/usr/bin/adb',
            '/usr/local/bin/adb',
            '/opt/android-sdk/platform-tools/adb',
        ].filter((candidate) => Boolean(candidate));
        for (const candidate of candidates) {
            if (fs.existsSync(candidate))
                return candidate;
        }
        const pathDirs = String(process.env.PATH || '').split(path.delimiter).filter(Boolean);
        for (const pathDir of pathDirs) {
            const candidate = path.join(pathDir, executable);
            if (fs.existsSync(candidate))
                return candidate;
        }
        return null;
    }
    timestamp() {
        return new Date().toISOString().replace(/[:.]/g, '-');
    }
}
