/**
 * src/web-chat/os-commands.ts
 *
 * Cross-platform shell-command builder. Wraps the differences between
 * macOS / Windows / Linux behind a single interface so quick-actions.ts
 * doesn't have to fork per-OS in every action.
 *
 * Platform detection via Node's process.platform:
 *   'darwin' = macOS
 *   'win32'  = Windows (any version)
 *   'linux'  = Linux (any distro)
 *   anything else = best-effort fallback to Linux behavior (BSD, etc.)
 *
 * Each builder returns a shell command STRING, ready to feed to
 * execAsync. They never run anything themselves — that lets callers
 * decide on stdio, timeout, error handling.
 *
 * App identifiers are kept normalized (lowercase keys like "safari",
 * "chrome", "firefox") and resolved to per-OS launch commands at the
 * call site. When an app doesn't exist on a platform, we return null
 * and the caller surfaces a helpful "X is only available on macOS,
 * try Edge instead" message.
 */
const PLATFORM = process.platform;
export const IS_MAC = PLATFORM === 'darwin';
export const IS_WIN = PLATFORM === 'win32';
export const IS_LINUX = PLATFORM === 'linux';
/** Quote a single arg so it survives the shell. */
function shell(arg) {
    if (IS_WIN) {
        // Windows cmd.exe quoting: wrap in double quotes, escape internal
        // double quotes by doubling them. Backslashes are kept verbatim.
        return `"${arg.replace(/"/g, '""')}"`;
    }
    // POSIX single-quote quoting.
    return `'${arg.replace(/'/g, "'\\''")}'`;
}
const APPS = {
    // Browsers — most have all 3 platforms
    'safari': { darwin: 'Safari' },
    'chrome': { darwin: 'Google Chrome', win32: 'chrome', linux: 'google-chrome' },
    'google chrome': { darwin: 'Google Chrome', win32: 'chrome', linux: 'google-chrome' },
    'firefox': { darwin: 'Firefox', win32: 'firefox', linux: 'firefox' },
    'edge': { darwin: 'Microsoft Edge', win32: 'msedge', linux: 'microsoft-edge' },
    'microsoft edge': { darwin: 'Microsoft Edge', win32: 'msedge', linux: 'microsoft-edge' },
    'brave': { darwin: 'Brave Browser', win32: 'brave', linux: 'brave-browser' },
    'arc': { darwin: 'Arc' },
    'opera': { darwin: 'Opera', win32: 'opera', linux: 'opera' },
    // System apps — names differ wildly per OS
    'finder': { darwin: 'Finder' },
    'explorer': { win32: 'explorer' },
    'file manager': { darwin: 'Finder', win32: 'explorer', linux: 'xdg-open' }, // resolved as folder
    'files': { darwin: 'Finder', win32: 'explorer', linux: 'nautilus' },
    // Mail / Calendar — vary
    'mail': { darwin: 'Mail', win32: 'outlookmail', linux: 'thunderbird' },
    'outlook': { darwin: 'Microsoft Outlook', win32: 'outlook' },
    'calendar': { darwin: 'Calendar', win32: 'outlookcal', linux: 'gnome-calendar' },
    'preview': { darwin: 'Preview' },
    'notes': { darwin: 'Notes' },
    'reminders': { darwin: 'Reminders' },
    'photos': { darwin: 'Photos', win32: 'ms-photos:', linux: 'eog' },
    'music': { darwin: 'Music', win32: 'mswindowsmusic:' },
    'maps': { darwin: 'Maps', win32: 'bingmaps:' },
    'messages': { darwin: 'Messages' },
    'facetime': { darwin: 'FaceTime' },
    // Settings / control panel
    'settings': { darwin: 'System Settings', win32: 'ms-settings:', linux: 'gnome-control-center' },
    'system settings': { darwin: 'System Settings', win32: 'ms-settings:', linux: 'gnome-control-center' },
    'system preferences': { darwin: 'System Settings', win32: 'ms-settings:', linux: 'gnome-control-center' },
    'control panel': { win32: 'control' },
    // Dev / terminals
    'terminal': { darwin: 'Terminal', win32: 'wt', linux: 'gnome-terminal' },
    'iterm': { darwin: 'iTerm' },
    'iterm2': { darwin: 'iTerm' },
    'warp': { darwin: 'Warp', linux: 'warp' },
    'cmd': { win32: 'cmd' },
    'powershell': { win32: 'powershell' },
    'vscode': { darwin: 'Visual Studio Code', win32: 'code', linux: 'code' },
    'vs code': { darwin: 'Visual Studio Code', win32: 'code', linux: 'code' },
    'code': { darwin: 'Visual Studio Code', win32: 'code', linux: 'code' },
    'cursor': { darwin: 'Cursor', win32: 'cursor', linux: 'cursor' },
    'xcode': { darwin: 'Xcode' },
    'sublime': { darwin: 'Sublime Text', win32: 'subl', linux: 'subl' },
    'sublime text': { darwin: 'Sublime Text', win32: 'subl', linux: 'subl' },
    // Productivity / chat — most have all 3
    'slack': { darwin: 'Slack', win32: 'slack', linux: 'slack' },
    'discord': { darwin: 'Discord', win32: 'discord', linux: 'discord' },
    'zoom': { darwin: 'zoom.us', win32: 'zoom', linux: 'zoom' },
    'teams': { darwin: 'Microsoft Teams', win32: 'msteams:', linux: 'teams' },
    'whatsapp': { darwin: 'WhatsApp', win32: 'whatsapp', linux: 'whatsapp-for-linux' },
    'telegram': { darwin: 'Telegram', win32: 'telegram', linux: 'telegram-desktop' },
    'spotify': { darwin: 'Spotify', win32: 'spotify', linux: 'spotify' },
    'figma': { darwin: 'Figma', win32: 'figma', linux: 'figma-linux' },
    'notion': { darwin: 'Notion', win32: 'notion', linux: 'notion' },
    'obsidian': { darwin: 'Obsidian', win32: 'obsidian', linux: 'obsidian' },
    // Arabic aliases — same target as English equivalent
    'سفاري': { darwin: 'Safari' },
    'كروم': { darwin: 'Google Chrome', win32: 'chrome', linux: 'google-chrome' },
    'جوجل كروم': { darwin: 'Google Chrome', win32: 'chrome', linux: 'google-chrome' },
    'فايرفوكس': { darwin: 'Firefox', win32: 'firefox', linux: 'firefox' },
    'إيدج': { darwin: 'Microsoft Edge', win32: 'msedge', linux: 'microsoft-edge' },
    'فايندر': { darwin: 'Finder' },
    'مستكشف الملفات': { win32: 'explorer' },
    'البريد': { darwin: 'Mail', win32: 'outlookmail', linux: 'thunderbird' },
    'التقويم': { darwin: 'Calendar', win32: 'outlookcal', linux: 'gnome-calendar' },
    'الملاحظات': { darwin: 'Notes' },
    'الصور': { darwin: 'Photos', win32: 'ms-photos:', linux: 'eog' },
    'الموسيقى': { darwin: 'Music' },
    'الإعدادات': { darwin: 'System Settings', win32: 'ms-settings:', linux: 'gnome-control-center' },
    'إعدادات النظام': { darwin: 'System Settings', win32: 'ms-settings:', linux: 'gnome-control-center' },
    'تيرمنل': { darwin: 'Terminal', win32: 'wt', linux: 'gnome-terminal' },
    'الطرفية': { darwin: 'Terminal', win32: 'wt', linux: 'gnome-terminal' },
    'سلاك': { darwin: 'Slack', win32: 'slack', linux: 'slack' },
    'ديسكورد': { darwin: 'Discord', win32: 'discord', linux: 'discord' },
    'زووم': { darwin: 'zoom.us', win32: 'zoom', linux: 'zoom' },
    'واتساب': { darwin: 'WhatsApp', win32: 'whatsapp', linux: 'whatsapp-for-linux' },
    'تليجرام': { darwin: 'Telegram', win32: 'telegram', linux: 'telegram-desktop' },
};
/**
 * Resolve a normalized app key to its launch command for the current
 * OS. Returns null if the app isn't installed on this platform.
 */
export function resolveApp(key) {
    const entry = APPS[key.trim().toLowerCase().replace(/\s+/g, ' ')];
    if (!entry)
        return null;
    if (IS_MAC && entry.darwin) {
        // Windows-style URI schemes (ms-settings:) shouldn't reach macOS,
        // but the table is safe.
        return { command: `open -a ${shell(entry.darwin)}`, displayName: entry.darwin };
    }
    if (IS_WIN && entry.win32) {
        // start "" <thing> handles both ms-* URI schemes and registered app
        // names. The empty title arg is mandatory when the path is quoted.
        return { command: `start "" ${shell(entry.win32)}`, displayName: entry.win32 };
    }
    if (IS_LINUX && entry.linux) {
        // Some entries map to xdg-open with a folder rather than a binary;
        // the caller can detect those and re-route. Most are direct binaries.
        return { command: `${entry.linux} &`, displayName: entry.linux };
    }
    return null;
}
// ─── URL openers ──────────────────────────────────────────────────────
export function openUrl(url) {
    if (IS_MAC)
        return `open ${shell(url)}`;
    if (IS_WIN)
        return `start "" ${shell(url)}`;
    return `xdg-open ${shell(url)}`;
}
/**
 * (Round-5) Open a file path in the default OS application. Used by
 * zatca_invoice / create_pdf_report / create_excel / etc. to actually
 * DISPLAY the result to the user instead of just writing it to disk and
 * leaving them to find it. Works on macOS / Windows / Linux.
 */
export async function openPath(filePath) {
    const { exec } = await import('node:child_process');
    const { promisify } = await import('node:util');
    const execAsync = promisify(exec);
    let cmd;
    if (IS_MAC)
        cmd = `open ${shell(filePath)}`;
    else if (IS_WIN)
        cmd = `start "" ${shell(filePath)}`;
    else
        cmd = `xdg-open ${shell(filePath)}`;
    try {
        await execAsync(cmd, { timeout: 5_000 });
        return { ok: true };
    }
    catch (err) {
        return { ok: false, reason: err.message };
    }
}
export function openUrlInBrowser(browserKey, url) {
    const entry = resolveApp(browserKey);
    if (!entry)
        return null;
    if (IS_MAC)
        return `open -a ${shell(entry.displayName)} ${shell(url)}`;
    if (IS_WIN)
        return `start "" ${shell(entry.displayName)} ${shell(url)}`;
    // On Linux, browsers accept the URL as a positional arg.
    return `${entry.displayName} ${shell(url)} &`;
}
// ─── Folder openers ───────────────────────────────────────────────────
export function openFolder(path) {
    if (IS_MAC)
        return `open ${shell(path)}`;
    if (IS_WIN) {
        // Windows users normally type forward-slashes too; let `start` handle.
        return `start "" ${shell(path.replace(/\//g, '\\'))}`;
    }
    return `xdg-open ${shell(path)}`;
}
// ─── Screenshot ───────────────────────────────────────────────────────
export function screenshot(outputPath) {
    if (IS_MAC) {
        return `screencapture -x ${shell(outputPath)}`;
    }
    if (IS_WIN) {
        // PowerShell one-liner: capture all screens, save as PNG. No
        // third-party deps. Run via the powershell.exe interpreter.
        const ps = `Add-Type -AssemblyName System.Windows.Forms,System.Drawing; ` +
            `$b = [System.Windows.Forms.SystemInformation]::VirtualScreen; ` +
            `$bmp = New-Object System.Drawing.Bitmap $b.Width,$b.Height; ` +
            `$g = [System.Drawing.Graphics]::FromImage($bmp); ` +
            `$g.CopyFromScreen($b.Left,$b.Top,0,0,$bmp.Size); ` +
            `$bmp.Save('${outputPath.replace(/'/g, "''")}', [System.Drawing.Imaging.ImageFormat]::Png); ` +
            `$g.Dispose(); $bmp.Dispose()`;
        return `powershell -NoProfile -Command "${ps.replace(/"/g, '\\"')}"`;
    }
    // Linux: try gnome-screenshot, fall back to scrot, then ImageMagick.
    // A sequence of fallbacks via shell `||`.
    return [
        `gnome-screenshot -f ${shell(outputPath)}`,
        `scrot ${shell(outputPath)}`,
        `import -window root ${shell(outputPath)}`,
    ].join(' || ');
}
/**
 * Send a browser-control keystroke to the foreground window. macOS
 * uses AppleScript + System Events; Windows uses PowerShell SendKeys;
 * Linux uses xdotool. On Linux without xdotool we return null and the
 * caller can surface the install hint.
 */
export function browserKeystroke(action) {
    if (IS_MAC) {
        // macOS: AppleScript via osascript. Page Down / Up = key codes
        // 121/116; navigation = Cmd+[ / Cmd+] etc.
        const macMap = {
            scrollDown: 'tell application "System Events" to key code 121',
            scrollUp: 'tell application "System Events" to key code 116',
            top: 'tell application "System Events" to key code 115 using command down',
            bottom: 'tell application "System Events" to key code 119 using command down',
            refresh: 'tell application "System Events" to keystroke "r" using command down',
            back: 'tell application "System Events" to keystroke "[" using command down',
            forward: 'tell application "System Events" to keystroke "]" using command down',
            newTab: 'tell application "System Events" to keystroke "t" using command down',
            closeTab: 'tell application "System Events" to keystroke "w" using command down',
        };
        return `osascript -e ${shell(macMap[action])}`;
    }
    if (IS_WIN) {
        // Windows: PowerShell + System.Windows.Forms.SendKeys. The keys
        // are sent to the foreground window, exactly like a real user
        // pressing them. {PGDN} / {PGUP} / ^r / ^t / ^w / %{LEFT} etc.
        const winMap = {
            scrollDown: '{PGDN}',
            scrollUp: '{PGUP}',
            top: '^{HOME}',
            bottom: '^{END}',
            refresh: '^r',
            back: '%{LEFT}', // Alt+Left = browser back
            forward: '%{RIGHT}', // Alt+Right = browser forward
            newTab: '^t',
            closeTab: '^w',
        };
        const keys = winMap[action];
        const ps = `Add-Type -AssemblyName System.Windows.Forms; ` +
            `[System.Windows.Forms.SendKeys]::SendWait('${keys.replace(/'/g, "''")}')`;
        return `powershell -NoProfile -Command "${ps.replace(/"/g, '\\"')}"`;
    }
    // Linux: use xdotool (most distros ship it; otherwise quickly
    // installable). Returns null if not installed — caller surfaces a
    // clear instruction.
    const linuxMap = {
        scrollDown: 'Page_Down',
        scrollUp: 'Page_Up',
        top: 'ctrl+Home',
        bottom: 'ctrl+End',
        refresh: 'ctrl+r',
        back: 'alt+Left',
        forward: 'alt+Right',
        newTab: 'ctrl+t',
        closeTab: 'ctrl+w',
    };
    return `command -v xdotool >/dev/null 2>&1 && xdotool key ${shell(linuxMap[action])} || (echo "xdotool is required for browser keystrokes on Linux. Install with: sudo apt install xdotool (Debian/Ubuntu) or sudo dnf install xdotool (Fedora)" >&2; exit 1)`;
}
// ─── Type / press / focus URL bar (cross-platform) ───────────────────
/**
 * Send arbitrary text as keystrokes to the foreground window.
 * macOS: AppleScript keystroke
 * Windows: PowerShell SendKeys (escapes the special chars: + ^ % ~ { } ( ))
 * Linux: xdotool type
 */
export function typeText(text) {
    if (IS_MAC) {
        const safe = text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
        return `osascript -e ${shell(`tell application "System Events" to keystroke "${safe}"`)}`;
    }
    if (IS_WIN) {
        // SendKeys reserves these — escape with {char}.
        const escaped = text.replace(/[+^%~(){}]/g, m => `{${m}}`);
        const ps = `Add-Type -AssemblyName System.Windows.Forms; ` +
            `[System.Windows.Forms.SendKeys]::SendWait('${escaped.replace(/'/g, "''")}')`;
        return `powershell -NoProfile -Command "${ps.replace(/"/g, '\\"')}"`;
    }
    return `command -v xdotool >/dev/null 2>&1 && xdotool type --delay 30 -- ${shell(text)} || (echo "xdotool required on Linux for typing." >&2; exit 1)`;
}
/** Press Enter / Return in the foreground window. */
export function pressEnter() {
    if (IS_MAC)
        return `osascript -e ${shell('tell application "System Events" to keystroke return')}`;
    if (IS_WIN) {
        return `powershell -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('~')"`;
    }
    return `command -v xdotool >/dev/null 2>&1 && xdotool key Return`;
}
/**
 * Focus the URL/address/search bar in the front browser. Cmd+L on Mac,
 * Ctrl+L on Windows/Linux — works in every major browser.
 */
export function focusUrlBar() {
    if (IS_MAC) {
        return `osascript -e ${shell('tell application "System Events" to keystroke "l" using command down')}`;
    }
    if (IS_WIN) {
        return `powershell -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('^l')"`;
    }
    return `command -v xdotool >/dev/null 2>&1 && xdotool key ctrl+l`;
}
/**
 * Compose a sequence: focus the URL bar, type text, press Enter. Each
 * step waits briefly so the browser keeps up. Used by the "search for X"
 * and "type X in the search bar" fast paths.
 */
export function urlBarSearch(text) {
    // Slight delay between keystrokes so the browser has time to focus
    // the URL field before text starts arriving.
    if (IS_MAC) {
        const safe = text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
        return [
            `osascript -e ${shell('tell application "System Events" to keystroke "l" using command down')}`,
            `sleep 0.2`,
            `osascript -e ${shell(`tell application "System Events" to keystroke "${safe}"`)}`,
            `sleep 0.1`,
            `osascript -e ${shell('tell application "System Events" to keystroke return')}`,
        ].join(' && ');
    }
    if (IS_WIN) {
        const escaped = text.replace(/[+^%~(){}]/g, m => `{${m}}`);
        const ps = `Add-Type -AssemblyName System.Windows.Forms; ` +
            `[System.Windows.Forms.SendKeys]::SendWait('^l'); ` +
            `Start-Sleep -Milliseconds 200; ` +
            `[System.Windows.Forms.SendKeys]::SendWait('${escaped.replace(/'/g, "''")}'); ` +
            `Start-Sleep -Milliseconds 100; ` +
            `[System.Windows.Forms.SendKeys]::SendWait('~')`;
        return `powershell -NoProfile -Command "${ps.replace(/"/g, '\\"')}"`;
    }
    return `command -v xdotool >/dev/null 2>&1 && (xdotool key ctrl+l && sleep 0.2 && xdotool type --delay 30 -- ${shell(text)} && sleep 0.1 && xdotool key Return) || (echo "xdotool required" >&2; exit 1)`;
}
// ─── Default folder paths (cross-platform) ────────────────────────────
import { homedir } from 'node:os';
import { join } from 'node:path';
export const FOLDER_KEYS = {
    desktop: ['Desktop'],
    documents: ['Documents'],
    downloads: ['Downloads'],
    pictures: ['Pictures'],
    music: ['Music'],
    videos: ['Videos', 'Movies'], // Mac uses "Movies"
    home: [], // empty path-segments = $HOME
};
export function resolveFolder(key) {
    const norm = key.trim().toLowerCase();
    const segs = FOLDER_KEYS[norm];
    if (!segs)
        return null;
    // On macOS "videos" → ~/Movies, on others → ~/Videos. Pick first
    // segment that exists or fall back to the canonical one.
    const home = homedir();
    if (segs.length === 0)
        return home;
    if (IS_MAC && norm === 'videos')
        return join(home, 'Movies');
    return join(home, segs[0]);
}
// Arabic folder names → same keys
const ARABIC_FOLDERS = {
    'سطح المكتب': 'desktop',
    'المستندات': 'documents',
    'التحميلات': 'downloads',
    'الصور': 'pictures',
    'الموسيقى': 'music',
    'الفيديو': 'videos',
    'الفيديوهات': 'videos',
    'الرئيسية': 'home',
    'البيت': 'home',
};
export function resolveFolderFromAlias(input) {
    const norm = input.trim();
    return resolveFolder(ARABIC_FOLDERS[norm] ?? norm);
}
// ─── System power / lock ──────────────────────────────────────────────
export function lockScreen() {
    if (IS_MAC)
        return 'pmset displaysleepnow';
    if (IS_WIN)
        return 'rundll32.exe user32.dll,LockWorkStation';
    // Try loginctl first (systemd), fall back to xdg-screensaver.
    return 'loginctl lock-session 2>/dev/null || xdg-screensaver lock';
}
export function sleepNow() {
    if (IS_MAC)
        return 'pmset sleepnow';
    if (IS_WIN)
        return 'rundll32.exe powrprof.dll,SetSuspendState 0,1,0';
    return 'systemctl suspend';
}
// ─── Volume ───────────────────────────────────────────────────────────
export function volumeAdjust(deltaPercent) {
    if (IS_MAC) {
        // osascript caps at 100; negative deltas clamp to 0 naturally.
        return `osascript -e 'set volume output volume (output volume of (get volume settings) ${deltaPercent >= 0 ? '+' : '-'} ${Math.abs(deltaPercent)})'`;
    }
    if (IS_WIN) {
        // Windows: each WScript SendKeys volume-up = ~2%. We emit the count
        // needed for the requested delta, capped at 50 keystrokes.
        const steps = Math.min(50, Math.ceil(Math.abs(deltaPercent) / 2));
        const key = deltaPercent >= 0 ? '0xAF' : '0xAE'; // VK_VOLUME_UP / DOWN
        return `powershell -Command "$wshell = New-Object -ComObject wscript.shell; 1..${steps} | ForEach-Object { $wshell.SendKeys([char]${parseInt(key, 16)}) }"`;
    }
    return `amixer -D pulse sset Master ${Math.abs(deltaPercent)}%${deltaPercent >= 0 ? '+' : '-'}`;
}
export function volumeSet(percent) {
    const p = Math.max(0, Math.min(100, Math.round(percent)));
    if (IS_MAC)
        return `osascript -e 'set volume output volume ${p}'`;
    if (IS_WIN) {
        // PowerShell: use the audio session API via NAudio or WMP COM. Keep
        // it simple — set volume to a fraction of max via WScript SendKeys
        // is unreliable, so use the Win32 API via PowerShell.
        return `powershell -Command "[audio]::Volume = ${p / 100}" 2>nul || echo Volume set may require nircmd or audio module`;
    }
    return `amixer -D pulse sset Master ${p}%`;
}
export function volumeMute(mute) {
    if (IS_MAC)
        return `osascript -e 'set volume ${mute ? 'with' : 'without'} output muted'`;
    if (IS_WIN) {
        return `powershell -Command "$wshell = New-Object -ComObject wscript.shell; $wshell.SendKeys([char]0xAD)"`;
    }
    return `amixer -D pulse sset Master ${mute ? 'mute' : 'unmute'}`;
}
// ─── Brightness ───────────────────────────────────────────────────────
export function brightnessAdjust(deltaPercent) {
    if (IS_MAC) {
        // Requires the `brightness` Homebrew CLI: brew install brightness.
        // Fall back to keystroke if not installed.
        const dir = deltaPercent >= 0 ? '+' : '-';
        const step = Math.abs(deltaPercent) / 100;
        return `brightness ${dir}${step} 2>/dev/null || osascript -e 'tell application "System Events" to key code ${deltaPercent >= 0 ? '144' : '145'}'`;
    }
    if (IS_WIN) {
        const target = deltaPercent >= 0 ? 100 : 30;
        return `powershell -Command "(Get-WmiObject -Namespace root/WMI -Class WmiMonitorBrightnessMethods).WmiSetBrightness(0, ${target})"`;
    }
    const step = Math.abs(deltaPercent);
    return `xbacklight ${deltaPercent >= 0 ? '-inc' : '-dec'} ${step}`;
}
export function brightnessSet(percent) {
    const p = Math.max(0, Math.min(100, Math.round(percent)));
    if (IS_MAC)
        return `brightness ${p / 100}`;
    if (IS_WIN) {
        return `powershell -Command "(Get-WmiObject -Namespace root/WMI -Class WmiMonitorBrightnessMethods).WmiSetBrightness(0, ${p})"`;
    }
    return `xbacklight -set ${p}`;
}
// ─── Do Not Disturb / Focus ───────────────────────────────────────────
export function dndToggle(on) {
    if (IS_MAC) {
        // macOS Sonoma+ uses Focus modes via shortcuts CLI — simplest reliable
        // path is to call a user-installed Shortcut named "DND On"/"DND Off".
        // Fall back to the older defaults toggle.
        const focusName = on ? 'DND On' : 'DND Off';
        return `shortcuts run "${focusName}" 2>/dev/null || defaults -currentHost write ~/Library/Preferences/ByHost/com.apple.notificationcenterui doNotDisturb -boolean ${on ? 'true' : 'false'}`;
    }
    if (IS_WIN) {
        // Windows Focus Assist (Quiet hours) toggle via registry — affects new toasts.
        const v = on ? 1 : 0;
        return `powershell -Command "Set-ItemProperty -Path HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Notifications\\Settings -Name NOC_GLOBAL_SETTING_TOASTS_ENABLED -Value ${v}"`;
    }
    return `gsettings set org.gnome.desktop.notifications show-banners ${on ? 'false' : 'true'}`;
}
// ─── Clipboard ────────────────────────────────────────────────────────
export function clipboardRead() {
    if (IS_MAC)
        return 'pbpaste';
    if (IS_WIN)
        return 'powershell -Command "Get-Clipboard"';
    return 'xclip -selection clipboard -o 2>/dev/null || xsel --clipboard --output';
}
export function clipboardWrite(text) {
    // Pipe via stdin to avoid shell escaping issues with the user's text.
    // The caller will run this as a single shell command — for safety we
    // base64-encode the payload.
    const b64 = Buffer.from(text, 'utf-8').toString('base64');
    if (IS_MAC)
        return `printf %s ${shell(b64)} | base64 -d | pbcopy`;
    if (IS_WIN)
        return `powershell -Command "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${b64}')) | Set-Clipboard"`;
    return `printf %s ${shell(b64)} | base64 -d | xclip -selection clipboard 2>/dev/null || printf %s ${shell(b64)} | base64 -d | xsel --clipboard --input`;
}
// ─── Music control ────────────────────────────────────────────────────
// macOS: drives Apple Music (or Spotify if it's the active player).
// Windows: SMTC media keys via SendKeys.
// Linux: playerctl (MPRIS) — works for Spotify, Rhythmbox, etc.
export function musicPlayPause() {
    if (IS_MAC)
        return `osascript -e 'tell application "Music" to playpause' 2>/dev/null || osascript -e 'tell application "Spotify" to playpause'`;
    if (IS_WIN)
        return `powershell -Command "$w = New-Object -ComObject wscript.shell; $w.SendKeys([char]0xB3)"`;
    return 'playerctl play-pause';
}
export function musicNext() {
    if (IS_MAC)
        return `osascript -e 'tell application "Music" to next track' 2>/dev/null || osascript -e 'tell application "Spotify" to next track'`;
    if (IS_WIN)
        return `powershell -Command "$w = New-Object -ComObject wscript.shell; $w.SendKeys([char]0xB0)"`;
    return 'playerctl next';
}
export function musicPrevious() {
    if (IS_MAC)
        return `osascript -e 'tell application "Music" to previous track' 2>/dev/null || osascript -e 'tell application "Spotify" to previous track'`;
    if (IS_WIN)
        return `powershell -Command "$w = New-Object -ComObject wscript.shell; $w.SendKeys([char]0xB1)"`;
    return 'playerctl previous';
}
export function musicNowPlaying() {
    if (IS_MAC) {
        // Try Music first, then Spotify. Returns "Title — Artist" or empty.
        return `osascript -e 'tell application "Music" to if player state is playing then return (name of current track) & " — " & (artist of current track)' 2>/dev/null || osascript -e 'tell application "Spotify" to if player state is playing then return (name of current track) & " — " & (artist of current track)' 2>/dev/null`;
    }
    if (IS_WIN) {
        return `powershell -Command "(Get-Process | Where-Object { $_.MainWindowTitle -match 'Spotify|Music|Groove' } | Select-Object -First 1).MainWindowTitle"`;
    }
    return `playerctl metadata --format '{{title}} — {{artist}}'`;
}
// ─── Device telemetry ─────────────────────────────────────────────────
export function batteryStatus() {
    if (IS_MAC)
        return `pmset -g batt | awk -F';' '/InternalBattery/ {gsub(/^[ \\t]+/,"",$1); print $1, $2}'`;
    if (IS_WIN)
        return `powershell -Command "Get-WmiObject -Class Win32_Battery | Select-Object -First 1 | ForEach-Object { $_.EstimatedChargeRemaining.ToString() + '%, status: ' + (@('Other','Unknown','Discharging','Charging','Full','Low','Critical','High','Charging-High','Charging-Low','Charging-Critical','Undefined','Partially Charged')[$_.BatteryStatus]) }"`;
    return `cat /sys/class/power_supply/BAT0/capacity 2>/dev/null && cat /sys/class/power_supply/BAT0/status`;
}
export function wifiName() {
    if (IS_MAC) {
        // Discover the actual Wi-Fi interface first — on Mac minis with
        // Ethernet, en0 is wired and the Wi-Fi card lives at en1 or en2.
        // Sonoma+ also changed networksetup output ("You are not associated"
        // for non-Wi-Fi interfaces), so we probe ipconfig getsummary too.
        return `IFACE=$(networksetup -listallhardwareports | awk '/^Hardware Port: Wi-Fi$|^Hardware Port: AirPort$/{getline; print $2}'); if [ -n "$IFACE" ]; then SSID=$(networksetup -getairportnetwork "$IFACE" 2>/dev/null | sed 's/^Current Wi-Fi Network: //'); if [ -z "$SSID" ] || echo "$SSID" | grep -q "You are not associated"; then ipconfig getsummary "$IFACE" 2>/dev/null | awk -F ' SSID : ' '/ SSID : / {print $2; exit}'; else echo "$SSID"; fi; else echo ""; fi`;
    }
    if (IS_WIN)
        return `powershell -Command "(netsh wlan show interfaces | Select-String '^\\s*SSID\\s+:').ToString().Split(':')[1].Trim()"`;
    return `iwgetid -r 2>/dev/null || nmcli -t -f active,ssid dev wifi | awk -F: '$1==\"yes\" {print $2}'`;
}
export function diskFree() {
    if (IS_MAC || IS_LINUX)
        return `df -h / | awk 'NR==2 {print $4 " free of " $2 " (" $5 " used)"}'`;
    if (IS_WIN)
        return `powershell -Command "Get-PSDrive C | Select-Object Used,Free | ForEach-Object { '{0:N1} GB free of {1:N1} GB total' -f ($_.Free/1GB), (($_.Used+$_.Free)/1GB) }"`;
    return 'df -h /';
}
export function publicIp() {
    // Same on every platform — depends only on curl being available, which
    // is true on modern macOS, modern Win10+, and any Linux desktop.
    return 'curl -s --max-time 4 https://ifconfig.me 2>/dev/null || curl -s --max-time 4 https://api.ipify.org';
}
export function uptime() {
    if (IS_MAC || IS_LINUX)
        return 'uptime';
    if (IS_WIN)
        return `powershell -Command "(Get-Date) - (gcim Win32_OperatingSystem).LastBootUpTime | ForEach-Object { 'up ' + $_.Days + 'd ' + $_.Hours + 'h ' + $_.Minutes + 'm' }"`;
    return 'uptime';
}
// ─── App control ──────────────────────────────────────────────────────
export function quitApp(appName) {
    if (IS_MAC)
        return `osascript -e 'tell application ${shell(appName)} to quit'`;
    if (IS_WIN)
        return `taskkill /IM ${shell(appName + '.exe')} /F`;
    return `pkill -i ${shell(appName)}`;
}
export function hideApp(appName) {
    if (IS_MAC)
        return `osascript -e 'tell application "System Events" to set visible of process ${shell(appName)} to false'`;
    if (IS_WIN)
        return ''; // Windows has no system-wide "hide app"; pass through to LLM
    return ''; // Linux similarly has no portable hide
}
export function focusApp(appName) {
    if (IS_MAC)
        return `osascript -e 'tell application ${shell(appName)} to activate'`;
    if (IS_WIN)
        return `powershell -Command "(New-Object -ComObject WScript.Shell).AppActivate('${appName.replace(/'/g, "''")}')"`;
    return `wmctrl -a ${shell(appName)} 2>/dev/null || xdotool search --name ${shell(appName)} windowactivate`;
}
// ─── File / Finder operations ────────────────────────────────────────
export function showInFinder(path) {
    if (IS_MAC)
        return `open -R ${shell(path)}`;
    if (IS_WIN)
        return `explorer.exe /select,${shell(path)}`;
    // Most Linux file managers don't have a "select" arg — open the parent.
    return `xdg-open "$(dirname ${shell(path)})"`;
}
export function emptyTrash() {
    if (IS_MAC)
        return `osascript -e 'tell application "Finder" to empty trash'`;
    if (IS_WIN)
        return `powershell -Command "Clear-RecycleBin -Force -Confirm:$false"`;
    return `rm -rf "$HOME/.local/share/Trash/files/"* "$HOME/.local/share/Trash/info/"*`;
}
export function createFolder(path) {
    if (IS_WIN)
        return `mkdir ${shell(path.replace(/\//g, '\\'))}`;
    return `mkdir -p ${shell(path)}`;
}
// ─── Notifications / TTS ──────────────────────────────────────────────
export function notify(title, body) {
    if (IS_MAC) {
        return `osascript -e 'display notification ${shell(body)} with title ${shell(title)}'`;
    }
    if (IS_WIN) {
        // BurntToast is optional. Fall back to msg.exe (every Pro Win has it).
        return `powershell -Command "if (Get-Module -ListAvailable -Name BurntToast) { New-BurntToastNotification -Text '${title.replace(/'/g, "''")}', '${body.replace(/'/g, "''")}' } else { msg * '${title.replace(/'/g, "''")}: ${body.replace(/'/g, "''")}' }"`;
    }
    return `notify-send ${shell(title)} ${shell(body)}`;
}
export function speakText(text, locale) {
    if (IS_MAC) {
        // Maged is the bundled Arabic voice on macOS; Samantha is en-US.
        const voice = locale === 'ar' ? 'Maged' : 'Samantha';
        return `say -v ${shell(voice)} ${shell(text)}`;
    }
    if (IS_WIN) {
        return `powershell -Command "Add-Type -AssemblyName System.Speech; (New-Object System.Speech.Synthesis.SpeechSynthesizer).Speak(${shell(text)})"`;
    }
    // Linux: prefer espeak-ng for Arabic; fall back to espeak.
    return `(command -v espeak-ng >/dev/null && espeak-ng ${shell(text)}) || espeak ${shell(text)}`;
}
// ─── Time / date ──────────────────────────────────────────────────────
export function currentTime() {
    if (IS_WIN)
        return `powershell -Command "Get-Date -Format 'HH:mm'"`;
    return `date +%H:%M`;
}
export function currentDate() {
    if (IS_WIN)
        return `powershell -Command "Get-Date -Format 'yyyy-MM-dd dddd'"`;
    return `date '+%Y-%m-%d %A'`;
}
// ─── Appearance: dark / light mode ───────────────────────────────────
export function setAppearance(mode) {
    if (IS_MAC) {
        const v = mode === 'dark' ? 'true' : 'false';
        return `osascript -e 'tell application "System Events" to tell appearance preferences to set dark mode to ${v}'`;
    }
    if (IS_WIN) {
        // Two registry keys flip together: AppsUseLightTheme + SystemUsesLightTheme.
        // Value 0 = dark, 1 = light. Settings UI uses these immediately.
        const v = mode === 'dark' ? 0 : 1;
        return `powershell -Command "Set-ItemProperty -Path 'HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize' -Name AppsUseLightTheme -Value ${v}; Set-ItemProperty -Path 'HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize' -Name SystemUsesLightTheme -Value ${v}"`;
    }
    // GNOME 42+ uses color-scheme. Older systems use gtk-theme.
    const scheme = mode === 'dark' ? 'prefer-dark' : 'default';
    return `gsettings set org.gnome.desktop.interface color-scheme '${scheme}' 2>/dev/null || gsettings set org.gnome.desktop.interface gtk-theme '${mode === 'dark' ? 'Adwaita-dark' : 'Adwaita'}'`;
}
// ─── Wallpaper ────────────────────────────────────────────────────────
export function setWallpaper(path) {
    if (IS_MAC) {
        // POSIX path → AppleScript file reference. Sets wallpaper on every desktop.
        return `osascript -e 'tell application "System Events" to tell every desktop to set picture to POSIX file ${shell(path)}'`;
    }
    if (IS_WIN) {
        // Update registry + force per-user param refresh so the new wallpaper paints
        // without a reboot. SystemParametersInfoW with SPI_SETDESKWALLPAPER is the
        // cleanest reload, but rundll32 UpdatePerUserSystemParameters is enough for
        // most setups.
        const winPath = path.replace(/\//g, '\\');
        return `powershell -Command "Set-ItemProperty -Path 'HKCU:\\Control Panel\\Desktop' -Name Wallpaper -Value '${winPath.replace(/'/g, "''")}'; rundll32.exe user32.dll, UpdatePerUserSystemParameters"`;
    }
    // GNOME — works on Ubuntu/Fedora/Pop!_OS defaults. Other DEs (KDE, XFCE)
    // need their own command; we let users override via a cli wrapper later.
    return `gsettings set org.gnome.desktop.background picture-uri "file://${path.replace(/"/g, '\\"')}" && gsettings set org.gnome.desktop.background picture-uri-dark "file://${path.replace(/"/g, '\\"')}"`;
}
// ─── Default mail client unread count (cross-platform best-effort) ────
export function mailUnreadCount() {
    if (IS_MAC)
        return `osascript -e 'tell application "Mail" to count of (messages of inbox whose read status is false)'`;
    if (IS_WIN) {
        // Outlook COM. Requires Outlook installed AND running. If it's not,
        // PowerShell throws and the caller falls back to a graceful message.
        return `powershell -Command "try { (New-Object -ComObject Outlook.Application).GetNamespace('MAPI').GetDefaultFolder(6).UnReadItemCount } catch { Write-Output 'OUTLOOK_UNAVAILABLE' }"`;
    }
    // Thunderbird / Evolution don't expose unread counts via a stable CLI.
    // Print a sentinel the caller can detect.
    return `echo NOT_SUPPORTED_LINUX`;
}
export function contactsLookup(name, field) {
    if (IS_MAC) {
        const escName = name.replace(/"/g, '\\"');
        const prop = field === 'email' ? 'value of emails' : 'value of phones';
        const script = `tell application "Contacts" to set theMatches to (people whose name contains "${escName}")
if (count of theMatches) is 0 then
  return "NO_MATCH"
else
  set p to first item of theMatches
  set vals to ${prop} of p
  return (name of p) & "|" & (vals as string)
end if`;
        return { cmd: `osascript -e ${shell(script)}` };
    }
    if (IS_WIN) {
        // Outlook contacts via COM. Filters case-insensitively on FullName.
        const fieldExpr = field === 'email' ? 'Email1Address' : 'MobileTelephoneNumber';
        const ps = `try {
  $ol = New-Object -ComObject Outlook.Application
  $ns = $ol.GetNamespace('MAPI')
  $f = $ns.GetDefaultFolder(10)
  $matches = @($f.Items | Where-Object { $_.FullName -like '*${name.replace(/'/g, "''")}*' })
  if ($matches.Count -eq 0) { Write-Output 'NO_MATCH' }
  else { $m = $matches[0]; Write-Output ($m.FullName + '|' + $m.${fieldExpr}) }
} catch { Write-Output 'NO_MATCH' }`;
        return { cmd: `powershell -Command ${shell(ps)}` };
    }
    // Linux: read a user-maintained CSV at ~/MuassisContacts.csv
    // (format: Name,Phone,Email). Universal grep — no GUI dependency.
    const escName = name.replace(/"/g, '\\"');
    const fieldIdx = field === 'phone' ? 2 : 3;
    return {
        cmd: `awk -F, -v IGNORECASE=1 -v n="${escName}" '$1 ~ n {print $1"|"$${fieldIdx}; found=1; exit} END {if(!found) print "NO_MATCH"}' "$HOME/MuassisContacts.csv" 2>/dev/null || echo NO_MATCH`,
    };
}
/** Build a minimal RFC-5545 VCALENDAR string ready to write to a .ics file. */
export function buildICS(e) {
    const fmt = (d) => d.toISOString().replace(/[:-]|\.\d{3}/g, '');
    const escape = (s) => s.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/,/g, '\\,').replace(/;/g, '\\;');
    const lines = [
        'BEGIN:VCALENDAR',
        'VERSION:2.0',
        'PRODID:-//Muassis//Calendar//EN',
        'CALSCALE:GREGORIAN',
        'METHOD:PUBLISH',
        'BEGIN:VEVENT',
        `UID:${Date.now()}-${Math.random().toString(36).slice(2, 8)}@muassis.ai`,
        `DTSTAMP:${fmt(new Date())}`,
        `DTSTART:${fmt(e.start)}`,
        `DTEND:${fmt(e.end)}`,
        `SUMMARY:${escape(e.summary)}`,
    ];
    if (e.description)
        lines.push(`DESCRIPTION:${escape(e.description)}`);
    if (e.location)
        lines.push(`LOCATION:${escape(e.location)}`);
    lines.push('END:VEVENT', 'END:VCALENDAR');
    return lines.join('\r\n');
}
//# sourceMappingURL=os-commands.js.map