// Standalone tray ring gauge for Claude's 5-hour usage window.
// Build: csc /target:winexe /out:TrayGauge.exe TrayGauge.cs
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;

class TrayGauge : ApplicationContext {
    static readonly string Dir = Path.GetDirectoryName(Application.ExecutablePath);
    static readonly string Log = Path.Combine(Dir, "tray.log");
    readonly NotifyIcon icon = new NotifyIcon();
    string detail = "";
    [System.Runtime.InteropServices.DllImport("user32.dll")] static extern bool DestroyIcon(IntPtr h);

    static void Note(string s) {
        try { File.AppendAllText(Log, DateTime.Now.ToString("MM-dd HH:mm:ss ") + s + Environment.NewLine); } catch { }
    }

    TrayGauge() {
        var menu = new ContextMenuStrip();
        menu.Items.Add("새로고침", null, (s, e) => Update());
        menu.Items.Add("종료", null, (s, e) => { Note("exit: menu"); icon.Visible = false; ExitThread(); });
        icon.ContextMenuStrip = menu;
        icon.BalloonTipTitle = "Claude 사용량";
        icon.MouseClick += (s, e) => {
            if (e.Button != MouseButtons.Left) return;
            Update();
            icon.BalloonTipText = detail;
            icon.ShowBalloonTip(5000);
        };
        Update();
        icon.Visible = true;
        var t = new System.Windows.Forms.Timer { Interval = 60000 };
        t.Tick += (s, e) => Update();
        t.Start();
        AppDomain.CurrentDomain.ProcessExit += (s, e) => Note("exit: process");
        Note("start pid=" + Process.GetCurrentProcess().Id);
    }

    void Update() {
        int pct = 0; string tip = "usage unavailable"; Color color = Color.Gray;
        detail = "사용량을 가져오지 못했습니다 (tray.log 확인)";
        try {
            var psi = new ProcessStartInfo("node", "\"" + Path.Combine(Dir, "usage.js") + "\"") {
                UseShellExecute = false, RedirectStandardOutput = true, CreateNoWindow = true, WorkingDirectory = Dir,
            };
            string json;
            using (var p = Process.Start(psi)) { json = p.StandardOutput.ReadToEnd(); p.WaitForExit(); }
            Func<string, long> num = k => long.Parse(Regex.Match(json, "\"" + k + "\":(-?[0-9]+)").Groups[1].Value);
            long real = num("pct"), week = num("pct7d"), mins = num("minsLeft");
            pct = (int)Math.Min(100, Math.Max(0, real));
            color = real >= 80 ? Color.OrangeRed : real >= 50 ? Color.Gold : Color.LimeGreen;
            tip = string.Format("5h {0}%   7d {1}%   reset in {2}h{3}m", real, week, mins / 60, mins % 60);
            detail = string.Format("5시간 한도: {0}%{4}7일 한도: {1}%{4}리셋까지 {2}시간 {3}분", real, week, mins / 60, mins % 60, Environment.NewLine);
        } catch (Exception ex) { Note("update failed: " + ex.Message); }
        Note("tick " + pct + "%");   // heartbeat: a log that stops dead means we were killed

        var bmp = new Bitmap(32, 32);
        using (var g = Graphics.FromImage(bmp)) {
            g.SmoothingMode = SmoothingMode.AntiAlias;
            var rect = new RectangleF(4, 4, 24, 24);
            using (var track = new Pen(Color.FromArgb(110, 128, 128, 128), 6)) g.DrawEllipse(track, rect);
            if (pct > 0) using (var arc = new Pen(color, 6) { StartCap = LineCap.Round, EndCap = LineCap.Round })
                g.DrawArc(arc, rect, -90, pct * 3.6f);
        }
        IntPtr h = bmp.GetHicon();
        var old = icon.Icon;
        icon.Icon = Icon.FromHandle(h);
        icon.Text = tip.Length > 62 ? tip.Substring(0, 62) : tip;   // Windows caps the tooltip
        if (old != null) { DestroyIcon(old.Handle); old.Dispose(); }
        bmp.Dispose();
    }

    [STAThread]
    static void Main() {
        bool fresh;
        using (var mutex = new Mutex(true, "ClaudeTokenTray", out fresh)) {
            if (!fresh) { Note("exit: already running"); return; }
            Application.Run(new TrayGauge());
        }
    }
}
