{$mode objfpc}{$H+}
unit uWaypointDrag;

// CC 20260313 waypoint drag state machine — extracted from mainform event handlers
// CC 20260323 adapted for snapshot architecture — no live HC pointers

interface

uses
  Classes,
  TGClasses, Globe4, TGSysUtils, GlobeUtils, TGObjects,
  uHCTypeDefs, uHCUtils, uHCGCI,
  uGroupTypes, uGroupSnapshot, uTracks, uThreadComm;

type
  TWaypointDragState = record
    Active: Boolean;
    Entry: PGroupMapEntry;
    WaypointIdx: Integer;
    OrigPtLL: TPointLL;
  end;

  // CC 20260323 callback signature without PHCGroup parameter
  TGroupCourseVisibleFunc = function(pEntry: PGroupMapEntry): Boolean of object;

procedure WaypointDragBegin(aGlobe: TGlobe4; aLayer: TGlobeLayer;
  aGroupMap: TStringList; X, Y: Integer;
  var ioState: TWaypointDragState;
  aVisibleFunc: TGroupCourseVisibleFunc);

procedure WaypointDragMove(aGlobe: TGlobe4; aLayer: TGlobeLayer;
  X, Y: Integer; var ioState: TWaypointDragState);

procedure WaypointDragCommit(aGlobe: TGlobe4;
  X, Y: Integer; var ioState: TWaypointDragState);

procedure WaypointDragCancel(aGlobe: TGlobe4; aLayer: TGlobeLayer;
  var ioState: TWaypointDragState);

implementation

procedure WaypointDragBegin(aGlobe: TGlobe4; aLayer: TGlobeLayer;
  aGroupMap: TStringList; X, Y: Integer;
  var ioState: TWaypointDragState;
  aVisibleFunc: TGroupCourseVisibleFunc);
const HIT_PX = 8;
var
  lC, idx: Integer;
  lEntry: PGroupMapEntry;
  lDotObj: TGlobeObject;
  lChain: TPointStore;
  ptXY: TPoint;
begin
  for lC := 0 to aGroupMap.Count - 1 do
  begin
    lEntry := PGroupMapEntry(aGroupMap.Objects[lC]);
    if lEntry^.TrackDotObjectID < 0 then Continue;
    if not lEntry^.HasTrack then Continue;                // CC 20260323 snapshot-based check
    if not aVisibleFunc(lEntry) then Continue;             // CC 20260323 no PHCGroup param
    lDotObj := aLayer.ObjectByID(lEntry^.TrackDotObjectID);
    if lDotObj = nil then Continue;
    lChain := TGeoDataObject(lDotObj).Chains[0];
    for idx := 0 to lChain.Count - 1 do
    begin
      if aGlobe.Projection.LLToDeviceXY(lChain.AsLL[idx].iLongX, lChain.AsLL[idx].iLatY, ptXY) then
        if (Sqr(X - ptXY.X) + Sqr(Y - ptXY.Y)) <= Sqr(HIT_PX) then
        begin
          ioState.Active      := True;
          ioState.Entry       := lEntry;
          ioState.WaypointIdx := idx;
          ioState.OrigPtLL    := lChain.AsLL[idx];
          aGlobe.MouseRotateButton := mzbNone;
          Exit;
        end;
    end;
  end;
end;

// CC 20260323 drag preview using snapshot track nodes (no live PHCGroup pointer)
procedure WaypointDragMove(aGlobe: TGlobe4; aLayer: TGlobeLayer;
  X, Y: Integer; var ioState: TWaypointDragState);
var
  ptNow, ptFrom, ptTo, ptGC: TPointLL;
  lDotObj, lLineObj: TGlobeObject;
  lLineChain, lDotChain: TPointStore;
  i, seg: Integer;
begin
  aGlobe.Projection.DeviceXYToLL(X, Y, ptNow);
  lDotObj  := aLayer.ObjectByID(ioState.Entry^.TrackDotObjectID);
  lLineObj := aLayer.ObjectByID(ioState.Entry^.TrackLineObjectID);
  if (lDotObj = nil) or (lLineObj = nil) then Exit;

  lLineChain := TGeoDataObject(lLineObj).Chains[0];
  lDotChain  := TGeoDataObject(lDotObj).Chains[0];
  lLineChain.Count := 0;
  lDotChain.Count  := 0;

  // CC 20260323 rebuild chains from SnapTrackNodes with dragged waypoint replaced
  ptFrom := PointLLH(
    DecimalToGlobeUnits(HCLon2Deg(ioState.Entry^.GroupSLon)),
    DecimalToGlobeUnits(HCLat2Deg(ioState.Entry^.GroupSLat)), 0);
  lLineChain.Add(ptFrom);

  for i := 0 to Length(ioState.Entry^.SnapTrackNodes) - 1 do
  begin
    if i = ioState.WaypointIdx then
      ptTo := ptNow
    else
      ptTo := PointLLH(
        DecimalToGlobeUnits(HCLon2Deg(ioState.Entry^.SnapTrackNodes[i].DestLon)),
        DecimalToGlobeUnits(HCLat2Deg(ioState.Entry^.SnapTrackNodes[i].DestLat)), 0);
    for seg := 1 to GC_SEGMENTS_PER_LEG do
    begin
      ptGC := GreatCirclePoint(ptFrom, ptTo, seg / GC_SEGMENTS_PER_LEG);
      lLineChain.Add(ptGC);
    end;
    lDotChain.Add(ptTo);
    ptFrom := ptTo;
  end;

  TGeoDataObject(lDotObj).RedrawObject;
  TGeoDataObject(lLineObj).RedrawObject;
  aGlobe.Invalidate;
end;

// CC 20260323 queue drag commit to HC thread instead of writing PathRec directly
procedure WaypointDragCommit(aGlobe: TGlobe4;
  X, Y: Integer; var ioState: TWaypointDragState);
var
  ptFinal: TPointLL;
  lReq: THCCallbackRequest;
begin
  ioState.Active := False;
  aGlobe.MouseRotateButton := mzbLeft;
  aGlobe.Projection.DeviceXYToLL(X, Y, ptFinal);
  lReq.Kind := cbkDragCommit;
  lReq.GroupUUID := ioState.Entry^.UUID;
  lReq.WaypointIdx := ioState.WaypointIdx;
  lReq.DestLon := HCDegSliverLonF(GlobeUnitsToDecimal(ptFinal.iLongX));
  lReq.DestLat := HCDegSliverLatF(GlobeUnitsToDecimal(ptFinal.iLatY));
  QueueHCCallback(lReq);
  aGlobe.Invalidate;
end;

// CC 20260323 cancel drag — restore from snapshot track nodes
procedure WaypointDragCancel(aGlobe: TGlobe4; aLayer: TGlobeLayer;
  var ioState: TWaypointDragState);
var
  lDotObj, lLineObj: TGlobeObject;
begin
  ioState.Active := False;
  aGlobe.MouseRotateButton := mzbLeft;
  lDotObj := aLayer.ObjectByID(ioState.Entry^.TrackDotObjectID);
  lLineObj := aLayer.ObjectByID(ioState.Entry^.TrackLineObjectID);
  if (lDotObj <> nil) and (lLineObj <> nil) then
  begin
    BuildTrackChainFromSnapshot(ioState.Entry^.GroupSLon, ioState.Entry^.GroupSLat,
      ioState.Entry^.SnapTrackNodes, Length(ioState.Entry^.SnapTrackNodes),
      TGeoDataObject(lLineObj).Chains[0],
      TGeoDataObject(lDotObj).Chains[0]);
    TGeoDataObject(lDotObj).RedrawObject;
    TGeoDataObject(lLineObj).RedrawObject;
    aGlobe.Invalidate;
  end;
end;

end.
