"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.showClientStatus = showClientStatus;
exports.getClientStatus = getClientStatus;
exports.installClient = installClient;
exports.updateClient = updateClient;
exports.launchClient = launchClient;
exports.installMacDmg = installMacDmg;
const node_fs_1 = require("node:fs");
const node_os_1 = __importDefault(require("node:os"));
const node_path_1 = require("node:path");
const node_child_process_1 = require("node:child_process");
const output_1 = require("../output");
const versionCompare_1 = require("../versionCompare");
const MAC_APP_PATH = '/Applications/PlayDrop.app';
const MAC_CLIENT_LATEST_URL = 'https://www.playdrop.ai/api/mac-client/latest';
async function showClientStatus(options = {}) {
    const status = await getClientStatus(options);
    if (options.json) {
        (0, output_1.printJson)(status);
    }
    else {
        printClientStatus(status);
    }
    return status;
}
async function getClientStatus(options = {}) {
    const platform = options.platform ?? process.platform;
    if (platform !== 'darwin') {
        return {
            platform,
            supported: false,
            status: 'unavailable',
            installed: false,
            reason: `PlayDrop native clients are not available for ${platform}.`,
        };
    }
    const appPath = options.appPath ?? MAC_APP_PATH;
    const installed = (0, node_fs_1.existsSync)(appPath);
    const bundle = installed ? readMacBundleInfo(appPath) : {};
    const latest = await (options.fetchLatestManifest ?? fetchLatestMacClientManifest)();
    const updateAvailable = Boolean(installed && (0, versionCompare_1.firstVersionIsNewer)(latest?.version, bundle.version));
    return {
        platform,
        supported: true,
        status: !installed ? 'missing' : updateAvailable ? 'update-available' : 'installed',
        installed,
        appPath,
        version: bundle.version,
        build: bundle.build,
        latestVersion: latest?.version,
        latestBuild: latest?.build,
        updateAvailable,
        nextAction: !installed ? 'playdrop clients install' : updateAvailable ? 'playdrop clients update' : undefined,
    };
}
async function installClient(options = {}) {
    await installOrUpdateClient('install', options);
}
async function updateClient(options = {}) {
    await installOrUpdateClient('update', options);
}
async function launchClient(options = {}) {
    const platform = options.platform ?? process.platform;
    if (platform !== 'darwin') {
        printUnsupportedPlatform(platform);
        return;
    }
    const appPath = options.appPath ?? MAC_APP_PATH;
    if (!(0, node_fs_1.existsSync)(appPath)) {
        console.error(`PlayDrop is not installed at ${appPath}.`);
        console.error('Run "playdrop clients install" first.');
        process.exitCode = 1;
        return;
    }
    const runCommand = options.runCommand ?? runCommandSync;
    const result = runCommand('open', [appPath]);
    if (result.status !== 0) {
        console.error(`Failed to launch PlayDrop from ${appPath}.`);
        process.exitCode = 1;
        return;
    }
    console.log('Launched PlayDrop.');
}
async function installOrUpdateClient(action, options) {
    const platform = options.platform ?? process.platform;
    if (platform !== 'darwin') {
        printUnsupportedPlatform(platform);
        return;
    }
    const appPath = options.appPath ?? MAC_APP_PATH;
    const runCommand = options.runCommand ?? runCommandSync;
    if (isPlayDropRunning(runCommand)) {
        console.error('PlayDrop is running. Quit PlayDrop before installing or updating.');
        process.exitCode = 1;
        return;
    }
    try {
        (0, node_fs_1.accessSync)('/Applications', node_fs_1.constants.W_OK);
    }
    catch {
        console.error('Cannot write to /Applications.');
        console.error('Run this command from a terminal with permission to install apps into /Applications.');
        process.exitCode = 1;
        return;
    }
    const manifest = await (options.fetchLatestManifest ?? fetchLatestMacClientManifest)();
    if (!manifest) {
        console.error('Could not resolve the latest PlayDrop Mac client manifest.');
        process.exitCode = 1;
        return;
    }
    const installed = await installMacDmg(manifest, appPath, runCommand);
    if (installed) {
        console.log(`${action === 'install' ? 'Installed' : 'Updated'} PlayDrop at ${appPath}.`);
    }
}
function printUnsupportedPlatform(platform) {
    console.error(`PlayDrop native clients are not available for ${platform}.`);
    process.exitCode = 1;
}
function printClientStatus(status) {
    if (!status.supported) {
        console.log(`PlayDrop client: unavailable on ${status.platform}`);
        return;
    }
    if (!status.installed) {
        console.log('PlayDrop client: missing');
        console.log('Run: playdrop clients install');
        return;
    }
    const version = status.version ? ` ${status.version}` : '';
    console.log(`PlayDrop client: ${status.status}${version}`);
    if (status.appPath) {
        console.log(`Location: ${status.appPath}`);
    }
    if (status.updateAvailable) {
        console.log('Run: playdrop clients update');
    }
}
async function fetchLatestMacClientManifest() {
    try {
        const response = await fetch(MAC_CLIENT_LATEST_URL, { headers: { Accept: 'application/json' } });
        if (!response.ok) {
            return null;
        }
        const payload = await response.json();
        if (payload.platform !== 'mac' || !payload.version || !payload.build || !payload.downloadUrl || !payload.dmgFilename) {
            return null;
        }
        return {
            platform: 'mac',
            version: payload.version,
            build: payload.build,
            versionCode: payload.versionCode ?? payload.build,
            filename: payload.filename ?? 'PlayDrop.app',
            dmgFilename: payload.dmgFilename,
            downloadUrl: payload.downloadUrl,
        };
    }
    catch {
        return null;
    }
}
function readMacBundleInfo(appPath) {
    const infoPath = (0, node_path_1.join)(appPath, 'Contents', 'Info.plist');
    try {
        const xml = (0, node_fs_1.readFileSync)(infoPath, 'utf8');
        return {
            version: readPlistString(xml, 'CFBundleShortVersionString'),
            build: readPlistString(xml, 'CFBundleVersion'),
        };
    }
    catch {
        return {};
    }
}
function readPlistString(xml, key) {
    const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const match = xml.match(new RegExp(`<key>\\s*${escaped}\\s*</key>\\s*<string>([^<]+)</string>`));
    return match?.[1]?.trim();
}
function isPlayDropRunning(runCommand) {
    const result = (runCommand ?? runCommandSync)('pgrep', ['-x', 'PlayDrop']);
    return result.status === 0;
}
async function installMacDmg(manifest, appPath, runCommand) {
    const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)(node_os_1.default.tmpdir(), 'playdrop-client-'));
    const dmgPath = (0, node_path_1.join)(tempDir, (0, node_path_1.basename)(manifest.dmgFilename || 'PlayDrop.dmg'));
    const mountPath = (0, node_path_1.join)(tempDir, 'mount');
    (0, node_fs_1.mkdirSync)(mountPath, { recursive: true });
    try {
        const response = await fetch(manifest.downloadUrl);
        if (!response.ok) {
            throw new Error(`download_failed:${response.status}`);
        }
        const arrayBuffer = await response.arrayBuffer();
        (0, node_fs_1.writeFileSync)(dmgPath, Buffer.from(arrayBuffer));
        const attach = runCommand('hdiutil', ['attach', dmgPath, '-nobrowse', '-quiet', '-mountpoint', mountPath]);
        if (attach.status !== 0) {
            throw new Error('hdiutil_attach_failed');
        }
        const sourceApp = (0, node_path_1.join)(mountPath, 'PlayDrop.app');
        if (!(0, node_fs_1.existsSync)(sourceApp)) {
            throw new Error('playdrop_app_missing_in_dmg');
        }
        replaceAppBundle(sourceApp, appPath);
        return true;
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        console.error(`Failed to install PlayDrop Mac client: ${message}`);
        process.exitCode = 1;
        return false;
    }
    finally {
        runCommand('hdiutil', ['detach', mountPath, '-quiet']);
        (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
    }
}
function replaceAppBundle(sourceApp, appPath) {
    const parentDirectory = (0, node_path_1.dirname)(appPath);
    const appName = (0, node_path_1.basename)(appPath);
    const uniqueSuffix = `${process.pid}-${Date.now()}`;
    const stagedPath = (0, node_path_1.join)(parentDirectory, `.${appName}.installing-${uniqueSuffix}`);
    const backupPath = (0, node_path_1.join)(parentDirectory, `.${appName}.previous-${uniqueSuffix}`);
    let backupCreated = false;
    try {
        (0, node_fs_1.cpSync)(sourceApp, stagedPath, { recursive: true });
        if ((0, node_fs_1.existsSync)(appPath)) {
            (0, node_fs_1.renameSync)(appPath, backupPath);
            backupCreated = true;
        }
        (0, node_fs_1.renameSync)(stagedPath, appPath);
        if (backupCreated) {
            (0, node_fs_1.rmSync)(backupPath, { recursive: true, force: true });
        }
    }
    catch (error) {
        (0, node_fs_1.rmSync)(stagedPath, { recursive: true, force: true });
        if (backupCreated && !(0, node_fs_1.existsSync)(appPath) && (0, node_fs_1.existsSync)(backupPath)) {
            (0, node_fs_1.renameSync)(backupPath, appPath);
        }
        throw error;
    }
}
function runCommandSync(command, args) {
    const result = (0, node_child_process_1.spawnSync)(command, args, { encoding: 'utf8' });
    return {
        status: result.status,
        stdout: result.stdout ?? '',
        stderr: result.stderr ?? '',
    };
}
