using System.Windows;
using SheetMe.Core.Layout;
using SheetMe.Designer.ViewModels;
using SheetMe.Designer.ViewModels.Controls;
namespace SheetMe.Designer.Services;
///
/// 스냅 — 뷰모델과 순수 계산() 사이의 얇은 어댑터.
///
/// 왜 계산이 여기 없는가. 스냅 규칙은 눈으로만 확인되는 종류의 코드다 — 후보 하나,
/// 열거 순서 하나가 바뀌면 붙는 자리가 조용히 달라지고 드래그해 보기 전엔 아무도 모른다.
/// 그래서 판단은 전부 SheetMe.Core 로 옮겨 단위 테스트로 못 박고, 여기에는
/// "어떤 사각형들이 후보인가"와 좌표 변환만 남긴다.
///
/// 허용 오차는 화면 픽셀이다. 월드 고정이면 줌 50%에서 두 배로 끈적이고 200%에서 절반으로
/// 미끄러진다 — 손끝 감각은 화면 거리로 정해진다.
///
public sealed class SnapEngine
{
#region Member Fields
private readonly SnapSolver solver = new();
#endregion
#region Properties
/// 정렬이 실패한 축의 폴백 격자(월드 px)
public double GridSize { get; set; } = 4;
/// 정렬 허용 거리 — 화면 px
public double ScreenTolerance { get; set; } = 6;
/// 간격 균등 스냅 사용
public bool EnableSpacing { get; set; } = true;
#endregion
#region Methods
///
/// 드래그 시작 — 후보를 한 번만 모은다.
///
/// 후보에는 컨테이너 자식까지 넣는다. 히트테스트는 재귀하는데 스냅만 최상위를 보던 것은
/// 규칙 불일치였다 — 패널 안 라벨에 맞추려 해도 붙지 않았다.
/// 잠긴 컨트롤은 후보로 남긴다(고정 기준선으로 쓰는 것이 맞다). 숨긴 것만 뺀다.
///
public void BeginDrag(DesignerViewModel designer, PageViewModel page,
IReadOnlySet moving)
{
var siblings = new List();
foreach (var vm in designer.ControlsOfPage(page))
{
// 끌려가는 컨테이너의 자식은 선택 목록에 없지만 부모를 따라 움직인다 —
// 빼지 않으면 자기 자신에게 붙는다
if (moving.Contains(vm) || vm.Model.Hidden)
{
continue;
}
siblings.Add(ToSnapRect(designer.WorldBoundsOf(vm)));
}
var paper = new SnapRect(0, page.OffsetY, page.WidthDip, page.HeightDip);
solver.Prepare(new SnapContext
{
Pages = new[] { paper },
Siblings = siblings,
// 페이지를 넘는 '균등 간격'은 뜻이 없다
SpacingPage = paper,
});
}
/// 드래그 끝 — 후보를 버린다. 남겨 두면 다음 드래그가 낡은 좌표에 붙는다
public void EndDrag() => solver.Reset();
/// 이동 스냅 — bbox(월드)에 (dx,dy) 를 적용한 자리에서 정렬·간격·격자 순으로 붙인다
public SnapOutcome SnapMove(Rect bbox, double dx, double dy, bool free, double zoom)
=> solver.SolveMove(ToSnapRect(bbox), dx, dy, OptionsFor(zoom), free);
/// 리사이즈 모서리 스냅
public SnapEdgeOutcome SnapEdge(double value, bool isXAxis, bool free, double zoom)
=> solver.SolveEdge(value, isXAxis, OptionsFor(zoom), free);
private SnapOptions OptionsFor(double zoom)
=> SnapOptions.ForZoom(ScreenTolerance, GridSize, zoom, EnableSpacing);
private static SnapRect ToSnapRect(Rect rect)
=> new(rect.X, rect.Y, rect.Width, rect.Height);
#endregion
}