using System;
using System.Net;
using System.Net.Sockets;
using AcDream.Core.Net;
namespace AcDream.App.Net;
///
/// Owns the network-side lifecycle of a live —
/// DNS resolution, endpoint construction, session instantiation, per-frame
/// Tick, and disposal. The post-construction work (event wiring,
/// Connect, character validation, EnterWorld, post-login UI
/// state setup) stays in GameWindow for now because it touches
/// renderer / chat / player-controller state that hasn't been extracted yet.
///
///
///
/// Step 2 of the extraction sequence described in
/// docs/architecture/code-structure.md §4. Future expansions can
/// fold more of TryStartLiveSession into this controller as the
/// surrounding state (event handlers, command bus, settings VM) gets
/// extracted in later steps.
///
///
/// Behavior preservation contract: this class produces
/// byte-identical console output and event-wireup sequencing to the
/// pre-refactor inline code path. The DNS-resolution lines, the
/// "live: connecting to ..." line, and the wiring-vs-Connect ordering
/// all match the previous flow.
///
///
public sealed class LiveSessionController : IDisposable
{
///
/// Active session, or when offline / before
/// succeeded / after .
///
public WorldSession? Session { get; private set; }
///
/// Resolves the endpoint, instantiates the ,
/// hands it to for caller-side event
/// subscriptions, and returns the live session. The caller is
/// responsible for the subsequent Connect /
/// EnterWorld dance.
///
public WorldSession? CreateAndWire(RuntimeOptions options, Action wireEvents)
{
if (options is null) throw new ArgumentNullException(nameof(options));
if (wireEvents is null) throw new ArgumentNullException(nameof(wireEvents));
if (!options.LiveMode) return null;
if (string.IsNullOrEmpty(options.LiveUser) || string.IsNullOrEmpty(options.LivePass))
{
Console.WriteLine("live: ACDREAM_LIVE set but TEST_USER/TEST_PASS missing; skipping");
return null;
}
try
{
var endpoint = ResolveEndpoint(options.LiveHost, options.LivePort);
Console.WriteLine($"live: connecting to {endpoint} as {options.LiveUser}");
Session = new WorldSession(endpoint);
wireEvents(Session);
return Session;
}
catch (Exception ex)
{
Console.WriteLine($"live: session setup failed: {ex.Message}");
Session?.Dispose();
Session = null;
return null;
}
}
///
/// Drains the inbound network queue. Proxies to
/// ; no-op when
/// is .
///
public void Tick() => Session?.Tick();
///
/// Tears down the live session. Safe to call multiple times.
///
public void Dispose()
{
Session?.Dispose();
Session = null;
}
///
/// Resolve a host string (literal IP or DNS name) to an
/// . Pre-refactor logic preserved exactly:
/// try first, fall back to
/// , prefer IPv4 (ACE + retail use
/// IPv4 UDP exclusively), throw on empty resolution.
///
private static IPEndPoint ResolveEndpoint(string host, int port)
{
IPAddress ip;
if (!IPAddress.TryParse(host, out ip!))
{
var addrs = Dns.GetHostAddresses(host);
ip = Array.Find(addrs, a => a.AddressFamily == AddressFamily.InterNetwork)
?? (addrs.Length > 0
? addrs[0]
: throw new Exception($"DNS resolved no addresses for '{host}'"));
Console.WriteLine($"live: resolved {host} → {ip}");
}
return new IPEndPoint(ip, port);
}
}