Game/EditorSettings.cs
using System.Collections.Generic;

namespace BlockParty;

/// <summary>
/// Editor-only, persisted preferences for the in-editor <see cref="LevelEditorStage"/> — currently
/// just the alignment-grid overlay. Kept separate from the player-facing <see cref="GameSettings"/>
/// (different file, different audience) but stored the same way (JSON in the per-user data folder).
/// </summary>
public sealed class EditorSettings
{
	/// <summary>Draw the white alignment grid over the schematic. Default off.</summary>
	public bool ShowGrid { get; set; } = false;

	/// <summary>Snap dragged geometry to nearby edges and visible grid lines. Whole-pixel placement
	/// remains enforced when this is off. Default on.</summary>
	public bool SnapEnabled { get; set; } = true;

	/// <summary>Grid cell size in logical (arena) pixels. The grid is centred on the arena middle, so
	/// a line always falls on the centre. Clamped to a sane range when drawn. Default 8.</summary>
	public float GridSize { get; set; } = 8f;

	/// <summary>Opacity of the white grid lines, slider-space 0..100 (converted to a 0..1 alpha when
	/// drawn). Default 40.</summary>
	public float GridOpacity { get; set; } = 40f;

	/// <summary>Smart grid: lines are mirrored inward from BOTH inner wall faces at multiples of
	/// <see cref="GridSize"/>, so geometry flush against either wall always lands on a line even though
	/// the playable span (arena minus the 2px walls) doesn't divide evenly by common sizes like 40.
	/// While on, the origin/offset options below are ignored. Default off.</summary>
	public bool GridSmart { get; set; } = false;

	/// <summary>When true the arena centre sits inside a grid CELL (lines offset by half a cell); when
	/// false a line intersection lands on the centre. Default false (intersection on centre).</summary>
	public bool GridCenterCell { get; set; } = false;

	/// <summary>When true and <see cref="GridCenterCell"/> is false, grid lines start at the playable
	/// bottom-left corner just inside the arena walls instead of radiating from the arena centre.</summary>
	public bool GridBottomLeftOrigin { get; set; } = false;

	/// <summary>Signed logical-pixel offsets applied to the grid's selected base origin.</summary>
	public float GridOffsetX { get; set; } = 0f;
	public float GridOffsetY { get; set; } = 0f;

	/// <summary>Level-browser overlay view: true = mapped levels laid out in the level-select map
	/// layout; false = one of the flat non-map grids selected by <see cref="LevelBrowserTestMode"/>.
	/// Persisted so the browser reopens in the last-used mode.</summary>
	public bool LevelBrowserMapMode { get; set; } = false;

	/// <summary>When true, the non-map level browser shows levels explicitly flagged as tests instead
	/// of ordinary non-map levels. Mutually exclusive with <see cref="LevelBrowserMapMode"/>.</summary>
	public bool LevelBrowserTestMode { get; set; } = false;

	/// <summary>When true, the level browser shows the daily-challenge obstacle templates grid instead
	/// of a level view. Mutually exclusive with the map/test flags above (editor picker only).</summary>
	public bool LevelBrowserTemplatesMode { get; set; } = false;

	/// <summary>When true, the level browser's Other/Test view orders levels by newest file timestamp
	/// first; otherwise it uses the stable alphabetical order.</summary>
	public bool LevelBrowserOtherRecentFirst { get; set; } = false;

	/// <summary>Sort order of the level browser's Templates view: 0 = alphabetical, 1 = newest file
	/// first, 2 = highest pick weight first (the sort button cycles through these).</summary>
	public int LevelBrowserTemplateSort { get; set; } = 0;

	/// <summary>Editor-maintained level file timestamps keyed by level id. The game assembly cannot query
	/// physical file times under the sbox whitelist, so the editor assembly refreshes this map.</summary>
	public Dictionary<string, long> LevelBrowserLevelTimes { get; set; } = new();

	/// <summary>Editor-maintained daily-template file timestamps keyed by template id, for the Templates
	/// view's Recent sort (same whitelist workaround as <see cref="LevelBrowserLevelTimes"/>).</summary>
	public Dictionary<string, long> LevelBrowserTemplateTimes { get; set; } = new();

	/// <summary>Id of the last project level successfully opened in the editor. Empty means the scratch
	/// level was edited most recently; a stale id also leaves the editor on a new blank level.</summary>
	public string LastLevelId { get; set; } = "";
}

/// <summary>
/// Static accessor + JSON persistence for <see cref="EditorSettings"/>. Stored in the game's
/// per-user data folder via <see cref="FileSystem.Data"/> (a separate file from the player
/// <see cref="Settings"/>); loaded lazily on first access and re-saved by the editor UI on change.
/// </summary>
public static class EditorPrefs
{
	public const string FilePath = "blockparty_editor_settings.json";

	private static EditorSettings _current;

	public static EditorSettings Current
	{
		get
		{
			if ( _current is null ) Load();
			return _current;
		}
	}

	/// <summary>Load editor settings from disk, falling back to defaults if the file is missing/invalid.</summary>
	public static void Load()
	{
		_current = FileSystem.Data.ReadJsonOrDefault<EditorSettings>( FilePath, new EditorSettings() );
	}

	public static void Save()
	{
		FileSystem.Data.WriteJson( FilePath, Current );
	}
}