using System;
using System.Collections.Generic;
using UnityEngine;
namespace PlatypusIdeas.AirPath.Runtime.Modes {
public abstract class PathfindingModeBase : IPathfindingMode {
protected PathfindingModeContext Context;
protected bool _isActive;
protected List<Vector2Int> _coloredCells = new();
public event Action<PathRequest> OnPathRequested;
public event Action OnClearPath;
public abstract string ModeName { get; }
public bool IsActive => _isActive;
protected readonly Color StartColor = Color.green;
protected readonly Color EndColor = Color.red;
protected readonly Color WarningColor = new(1f, 0.5f, 0f);
public void Initialize(PathfindingModeContext context) {
Context = context;
ValidateContext();
}
public virtual void OnActivate() {
_isActive = true;
ClearVisualization();
}
public virtual void OnDeactivate() {
_isActive = false;
ClearVisualization();
}
public abstract void UpdateMode();
public virtual void Cleanup() {
ClearVisualization();
_coloredCells.Clear();
}
public abstract void DrawDebugVisualization();
public abstract string GetStatusInfo();
// Protected methods for event invocation (allows derived classes to trigger events)
/// <summary>
/// Invokes the OnPathRequested event - accessible to derived classes
/// </summary>
protected void InvokePathRequest(PathRequest request) {
OnPathRequested?.Invoke(request);
}
/// <summary>
/// Invokes the OnClearPath event - accessible to derived classes
/// </summary>
protected void InvokeClearPath() {
OnClearPath?.Invoke();
}
// Enhanced helper methods with boundary safety
/// <summary>
/// Request path with automatic boundary clamping
/// </summary>
protected void RequestPath(Vector2Int start, Vector2Int end) {
// Clamp positions to valid grid bounds
var clampedStart = ClampToValidGridPosition(start);
var clampedEnd = ClampToValidGridPosition(end);
// Log if positions were clamped
if (clampedStart != start || clampedEnd != end) {
LogBoundaryClamp(start, clampedStart, "start");
LogBoundaryClamp(end, clampedEnd, "end");
}
var request = new PathRequest(clampedStart, clampedEnd, Context.BirdHeightOffset);
InvokePathRequest(request);
}
/// <summary>
/// Request path with boundary checking and optional warning visualization
/// </summary>
protected void RequestPathSafe(Vector2Int start, Vector2Int end, bool visualizeWarning = true) {
bool startWasClamped = false;
bool endWasClamped = false;
var clampedStart = start;
var clampedEnd = end;
if (!IsValidGridPosition(start)) {
clampedStart = ClampToValidGridPosition(start);
startWasClamped = true;
if (visualizeWarning) {
SetCellColor(clampedStart, WarningColor);
}
}
if (!IsValidGridPosition(end)) {
clampedEnd = ClampToValidGridPosition(end);
endWasClamped = true;
if (visualizeWarning) {
SetCellColor(clampedEnd, WarningColor);
}
}
if (startWasClamped || endWasClamped) {
var warningMsg = $"Path positions were clamped to grid bounds: ";
if (startWasClamped) warningMsg += $"Start ({start}→{clampedStart}) ";
if (endWasClamped) warningMsg += $"End ({end}→{clampedEnd})";
Debug.LogWarning($"[{ModeName}] {warningMsg}");
UpdateInstructionText("Warning: Position was outside grid bounds and was clamped!");
}
var request = new PathRequest(clampedStart, clampedEnd, Context.BirdHeightOffset);
InvokePathRequest(request);
}
protected void ClearCurrentPath() {
InvokeClearPath();
}
/// <summary>
/// Convert world position to grid position with automatic clamping
/// </summary>
protected Vector2Int WorldToGridPosition(Vector3 worldPos) {
return Context.PathfindingService.WorldToGridPosition(worldPos);
}
/// <summary>
/// Convert world position to grid position with out-of-bounds check
/// </summary>
protected Vector2Int WorldToGridPositionSafe(Vector3 worldPos, out bool wasOutOfBounds) {
return Context.PathfindingService.WorldToGridPositionSafe(worldPos, out wasOutOfBounds);
}
protected Vector3 GridToWorldPosition(Vector2Int gridPos, float yOffset = 0) {
return Context.PathfindingService.GridToWorldPosition(gridPos, yOffset);
}
protected bool IsValidGridPosition(Vector2Int pos) {
return Context.PathfindingService.IsValidGridPosition(pos);
}
/// <summary>
/// Clamp a grid position to valid bounds
/// </summary>
protected Vector2Int ClampToValidGridPosition(Vector2Int pos) {
return Context.PathfindingService.ClampToValidGridPosition(pos);
}
protected void SetCellColor(Vector2Int gridPos, Color color) {
// Ensure position is valid before setting color
var clampedPos = ClampToValidGridPosition(gridPos);
if (!Context.TerrainController) return;
Context.TerrainController.SetColor(clampedPos, color);
if (!_coloredCells.Contains(clampedPos)) {
_coloredCells.Add(clampedPos);
}
}
protected void ClearVisualization() {
if (Context?.TerrainController && Application.isPlaying) {
foreach (var cell in _coloredCells) {
try {
Context.TerrainController.ResetCellColor(cell);
} catch (MissingReferenceException) {
}
}
}
_coloredCells.Clear();
}
protected void UpdateInstructionText(string text) {
Context?.UpdateInstructionText?.Invoke(text);
}
protected Vector3 GetAverageBirdPosition() {
Debug.Log("<color=yellow>[PathfindingModeBase] GetAverageBirdPosition called</color>");
Debug.Log($"<color=yellow>[PathfindingModeBase] Context is null? {Context == null}</color>");
if (Context != null) {
Debug.Log($"<color=yellow>[PathfindingModeBase] Context.GetAverageBirdPosition is null? {Context.GetAverageBirdPosition == null}</color>");
if (Context.GetAverageBirdPosition != null) {
Debug.Log("<color=yellow>[PathfindingModeBase] About to invoke callback...</color>");
var result = Context.GetAverageBirdPosition.Invoke();
Debug.Log($"<color=yellow>[PathfindingModeBase] Callback returned: {result}</color>");
return result;
}
}
Debug.LogWarning("<color=red>[PathfindingModeBase] Returning Vector3.zero (fallback)</color>");
return Vector3.zero;
}
protected bool IsPathActive() {
return Context?.IsPathActive?.Invoke() ?? false;
}
private void ValidateContext() {
if (Context == null) {
throw new InvalidOperationException($"[{ModeName}] Context is null");
}
if (!Context.IsValid()) {
var missingComponents = new List<string>();
if (!Context.MainCamera) missingComponents.Add("MainCamera");
if (!Context.TerrainController) missingComponents.Add("TerrainInfo");
if (!Context.PathfindingService) missingComponents.Add("PathfindingService");
var errorDetails = missingComponents.Count > 0
? $"Missing: {string.Join(", ", missingComponents)}"
: "Unknown validation error";
throw new InvalidOperationException($"[{ModeName}] Context is invalid - {errorDetails}");
}
}
/// <summary>
/// Helper for ray casting from camera with boundary check
/// </summary>
protected bool GetTerrainHitFromScreenPoint(Vector2 screenPoint, out RaycastHit hit) {
if (!Context.MainCamera) {
hit = default;
return false;
}
var ray = Context.MainCamera.ScreenPointToRay(screenPoint);
if (!Physics.Raycast(ray, out hit)) return false;
WorldToGridPositionSafe(hit.point, out bool wasOutOfBounds);
if (wasOutOfBounds && Context.ShowClampWarnings) {
Debug.Log($"[{ModeName}] Click was outside grid bounds. Position will be clamped.");
}
return true;
}
/// <summary>
/// Log boundary clamping for debugging
/// </summary>
private void LogBoundaryClamp(Vector2Int original, Vector2Int clamped, string positionName) {
if (original != clamped && Context.ShowClampWarnings) {
Debug.Log($"[{ModeName}] {positionName} position clamped from {original} to {clamped}");
}
}
}
}