using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace AxCopilot.Services;
public enum PomodoroMode { Idle, Focus, Break }
///
/// Phase L3-9: 뽀모도로 타이머 서비스.
/// 집중(기본 25분) / 휴식(기본 5분) 모드를 관리하며 AppData에 상태를 저장합니다.
///
internal sealed class PomodoroService
{
public static readonly PomodoroService Instance = new();
private static readonly string StateFile = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"AxCopilot", "pomodoro.json");
// ─── 설정 ────────────────────────────────────────────────────────────────
public int FocusMinutes { get; set; } = 25;
public int BreakMinutes { get; set; } = 5;
// ─── 상태 ────────────────────────────────────────────────────────────────
public PomodoroMode Mode { get; private set; } = PomodoroMode.Idle;
public bool IsRunning { get; private set; }
public TimeSpan Remaining { get; private set; }
public event EventHandler? StateChanged;
// ─── 내부 ────────────────────────────────────────────────────────────────
private System.Threading.Timer? _ticker;
private PomodoroService()
{
LoadState();
// 앱 재시작 후 진행 중이던 타이머가 있으면 재개
if (IsRunning && Remaining > TimeSpan.Zero)
StartTicker();
else
IsRunning = false;
}
// ─── 공개 API ────────────────────────────────────────────────────────────
public void StartFocus() => StartMode(PomodoroMode.Focus);
public void StartBreak() => StartMode(PomodoroMode.Break);
public void Stop()
{
StopTicker();
IsRunning = false;
SaveState();
StateChanged?.Invoke(this, EventArgs.Empty);
}
public void Reset()
{
StopTicker();
Mode = PomodoroMode.Idle;
IsRunning = false;
Remaining = TimeSpan.FromMinutes(FocusMinutes);
SaveState();
StateChanged?.Invoke(this, EventArgs.Empty);
}
public void Toggle()
{
if (IsRunning) Stop();
else if (Mode == PomodoroMode.Break) StartBreak();
else StartFocus();
}
// ─── 내부 ────────────────────────────────────────────────────────────────
private void StartMode(PomodoroMode mode)
{
StopTicker();
Mode = mode;
IsRunning = true;
if (Remaining <= TimeSpan.Zero || Mode != mode)
Remaining = TimeSpan.FromMinutes(mode == PomodoroMode.Focus ? FocusMinutes : BreakMinutes);
StartTicker();
SaveState();
StateChanged?.Invoke(this, EventArgs.Empty);
}
private void StartTicker()
{
_ticker = new System.Threading.Timer(_ => Tick(), null, 1000, 1000);
}
private void StopTicker()
{
_ticker?.Dispose();
_ticker = null;
}
private void Tick()
{
if (Remaining <= TimeSpan.Zero)
{
// 타이머 종료
Stop();
var modeLabel = Mode == PomodoroMode.Focus ? "집중" : "휴식";
NotificationCenterService.Show("뽀모도로 타이머", $"{modeLabel} 시간이 종료되었습니다.", NotificationType.Info);
return;
}
Remaining -= TimeSpan.FromSeconds(1);
StateChanged?.Invoke(this, EventArgs.Empty);
}
// ─── 영속성 ──────────────────────────────────────────────────────────────
private void SaveState()
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(StateFile)!);
var dto = new PomodoroStateDto
{
Mode = Mode.ToString(),
IsRunning = IsRunning,
RemainingSeconds = (int)Remaining.TotalSeconds,
FocusMinutes = FocusMinutes,
BreakMinutes = BreakMinutes,
SavedAt = DateTime.Now,
};
File.WriteAllText(StateFile, JsonSerializer.Serialize(dto));
}
catch { /* 무시 */ }
}
private void LoadState()
{
try
{
if (!File.Exists(StateFile)) goto defaults;
var json = File.ReadAllText(StateFile);
var dto = JsonSerializer.Deserialize(json);
if (dto == null) goto defaults;
FocusMinutes = dto.FocusMinutes > 0 ? dto.FocusMinutes : 25;
BreakMinutes = dto.BreakMinutes > 0 ? dto.BreakMinutes : 5;
Mode = Enum.TryParse(dto.Mode, out var m) ? m : PomodoroMode.Idle;
IsRunning = dto.IsRunning;
// 앱이 꺼진 동안 경과 시간 보정
var elapsed = DateTime.Now - dto.SavedAt;
var saved = TimeSpan.FromSeconds(dto.RemainingSeconds);
Remaining = saved - elapsed;
if (Remaining < TimeSpan.Zero) { Remaining = TimeSpan.Zero; IsRunning = false; }
return;
}
catch { /* fall through */ }
defaults:
FocusMinutes = 25;
BreakMinutes = 5;
Mode = PomodoroMode.Idle;
IsRunning = false;
Remaining = TimeSpan.FromMinutes(FocusMinutes);
}
private class PomodoroStateDto
{
[JsonPropertyName("mode")] public string Mode { get; set; } = "Idle";
[JsonPropertyName("isRunning")] public bool IsRunning { get; set; }
[JsonPropertyName("remainingSeconds")] public int RemainingSeconds { get; set; }
[JsonPropertyName("focusMinutes")] public int FocusMinutes { get; set; } = 25;
[JsonPropertyName("breakMinutes")] public int BreakMinutes { get; set; } = 5;
[JsonPropertyName("savedAt")] public DateTime SavedAt { get; set; } = DateTime.Now;
}
}