using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using DorkNet.Launcher.Backend;
namespace DorkNet.Launcher;
/// Five-step walkthrough for first-time Photon setup. Opens
/// dashboard.photonengine.com on step 2, walks the user through app
/// creation in steps 3–4, then collects + validates AppIds on step 5.
///
/// Result lives in when
/// is true. Caller (MainWindow) writes it back into AppState.
public partial class PhotonWizard : Window
{
public PhotonResult? Result { get; private set; }
// Photon AppIds are GUIDs — accept with or without hyphens; user
// gets confused if we're strict about formatting. We render the
// canonical form back into AppState.
private static readonly Regex AppIdRegex =
new("^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{12}$",
RegexOptions.Compiled);
private readonly StackPanel[] _steps;
private int _stepIndex;
public PhotonWizard(string? existingRealtime, string? existingVoice, string? existingRegion)
{
InitializeComponent();
_steps = new[] { Step1, Step2, Step3, Step4, Step5 };
// Pre-fill so a user re-opening the wizard sees their existing
// values — they might just want to fix the region or swap apps.
WizRealtime.Text = existingRealtime ?? "";
WizVoice.Text = existingVoice ?? "";
SelectRegion(existingRegion ?? "us");
UpdateAppIdStatuses();
ShowStep(0);
}
private void ShowStep(int index)
{
_stepIndex = Math.Clamp(index, 0, _steps.Length - 1);
for (var i = 0; i < _steps.Length; i++)
_steps[i].Visibility = i == _stepIndex ? Visibility.Visible : Visibility.Collapsed;
StepIndicator.Text = $"STEP {_stepIndex + 1} OF {_steps.Length}";
PrevBtn.Visibility = _stepIndex == 0 ? Visibility.Collapsed : Visibility.Visible;
// Step 4 (Voice) is optional — surface a Skip button so the user
// doesn't feel stuck if they don't want voice chat. Final step
// swaps Next for a Save action.
SkipBtn.Visibility = _stepIndex == 3 ? Visibility.Visible : Visibility.Collapsed;
NextBtn.Content = _stepIndex == _steps.Length - 1 ? "Save" : "Next →";
}
private void OnPrev(object sender, RoutedEventArgs e) => ShowStep(_stepIndex - 1);
private void OnNext(object sender, RoutedEventArgs e)
{
if (_stepIndex < _steps.Length - 1)
{
ShowStep(_stepIndex + 1);
return;
}
TrySave();
}
private void OnClose(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
private void OnOpenPhotonDashboard(object sender, RoutedEventArgs e)
{
try
{
Process.Start(new ProcessStartInfo("https://dashboard.photonengine.com")
{ UseShellExecute = true });
}
catch (Exception ex)
{
MessageBox.Show(this, "Could not open the dashboard: " + ex.Message,
"Photon setup", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
private void OnAppIdChanged(object sender, TextChangedEventArgs e) => UpdateAppIdStatuses();
private void UpdateAppIdStatuses()
{
var realtime = WizRealtime.Text.Trim();
if (string.IsNullOrEmpty(realtime))
{
WizRealtimeStatus.Text = "32-character ID with hyphens, like 12ab34cd-56ef-78ab-90cd-12ef34ab56cd";
WizRealtimeStatus.Foreground = (System.Windows.Media.Brush)FindResource("InkFaint");
}
else if (AppIdRegex.IsMatch(realtime))
{
WizRealtimeStatus.Text = "✓ Looks like a valid AppId.";
WizRealtimeStatus.Foreground = (System.Windows.Media.Brush)FindResource("Ok");
}
else
{
WizRealtimeStatus.Text = "⚠ That doesn't look like a Photon AppId. Should be 32 hex characters (with or without hyphens).";
WizRealtimeStatus.Foreground = (System.Windows.Media.Brush)FindResource("Danger");
}
var voice = WizVoice.Text.Trim();
if (string.IsNullOrEmpty(voice))
{
WizVoiceStatus.Text = "Optional — leave empty if you skipped step 4.";
WizVoiceStatus.Foreground = (System.Windows.Media.Brush)FindResource("InkFaint");
}
else if (AppIdRegex.IsMatch(voice))
{
WizVoiceStatus.Text = "✓ Looks like a valid AppId.";
WizVoiceStatus.Foreground = (System.Windows.Media.Brush)FindResource("Ok");
}
else
{
WizVoiceStatus.Text = "⚠ That doesn't look like a Photon AppId.";
WizVoiceStatus.Foreground = (System.Windows.Media.Brush)FindResource("Danger");
}
}
private void TrySave()
{
var realtime = WizRealtime.Text.Trim();
var voice = WizVoice.Text.Trim();
if (string.IsNullOrEmpty(realtime))
{
MessageBox.Show(this,
"A Realtime AppId is required — without it nobody can connect to your server. Voice is optional.",
"Photon setup", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
if (!AppIdRegex.IsMatch(realtime))
{
MessageBox.Show(this,
"Your Realtime AppId doesn't look right. It should be 32 hex characters (with or without hyphens). Double-check that you copied the entire string from the Photon dashboard.",
"Photon setup", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
if (!string.IsNullOrEmpty(voice) && !AppIdRegex.IsMatch(voice))
{
MessageBox.Show(this,
"Your Voice AppId doesn't look right. Either fix it or clear the field to skip voice.",
"Photon setup", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
var region = WizRegion.SelectedItem is ComboBoxItem item && item.Tag is not null
? item.Tag.ToString() ?? "us"
: "us";
Result = new PhotonResult(Normalise(realtime), Normalise(voice), region);
DialogResult = true;
Close();
}
private void SelectRegion(string tag)
{
foreach (var item in WizRegion.Items.OfType())
{
if (string.Equals(item.Tag?.ToString(), tag, StringComparison.OrdinalIgnoreCase))
{
item.IsSelected = true;
return;
}
}
}
/// Render the AppId in canonical 8-4-4-4-12 form regardless
/// of how the user pasted it — the server's Photon SDK accepts both
/// but normalising keeps the rest of the launcher tidy.
private static string Normalise(string appId)
{
if (string.IsNullOrEmpty(appId)) return "";
var stripped = appId.Replace("-", "").Trim();
if (stripped.Length != 32) return appId;
return $"{stripped[..8]}-{stripped[8..12]}-{stripped[12..16]}-{stripped[16..20]}-{stripped[20..]}";
}
}
public sealed record PhotonResult(string RealtimeAppId, string VoiceAppId, string Region);