unit View.Map;

interface

uses
  System.SysUtils, System.Classes, WEBLib.Graphics, WEBLib.Forms, WEBLib.Dialogs,
  Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls, WEBLib.Controls, WEBLib.Grids,
  WEBLib.ExtCtrls, DB, WEBLib.WebCtrls, WEBLib.REST,
  VCL.TMSFNCTypes, VCL.TMSFNCUtils, VCL.TMSFNCGraphics, VCL.TMSFNCGraphicsTypes,
  VCL.TMSFNCCustomControl, VCL.TMSFNCWebBrowser, VCL.TMSFNCMaps, VCL.TMSFNCLeaflet,
  VCL.TMSFNCMapsCommonTypes;

type
  TFViewMap = class(TWebForm)
    btnMenu: TWebButton;
    btnAlerts: TWebButton;
    btnGroups: TWebButton;
    btnLocate: TWebButton;
    btnFilters: TWebButton;
    btnDisplay: TWebButton;
    pnlMap: TWebPanel;
    lfMap: TTMSFNCLeaflet;
    httpReqGeoJson: TWebHttpRequest;

    procedure lfMapMapInitialized(Sender: TObject);
    procedure httpReqGeoJsonResponse(Sender: TObject; AResponse: string);
    procedure httpReqGeoJsonError(Sender: TObject; AError: string);
    procedure lfMapPolyElementMouseEnter(Sender: TObject; AElement: TTMSFNCMapsPolyElement);
    procedure lfMapPolyElementMouseLeave(Sender: TObject; AElement: TTMSFNCMapsPolyElement);
  private
    procedure StyleDistrictsAndFit;
    procedure AssignDistrictNamesFromGeoJSON(const AJson: string);
  public
  end;

var
  FViewMap: TFViewMap;

implementation

uses
  JS, Web;

{$R *.dfm}

procedure TFViewMap.lfMapMapInitialized(Sender: TObject);
begin
   httpReqGeoJson.Execute; // GET assets/bpddistricts.geojson as text
end;

procedure TFViewMap.httpReqGeoJsonResponse(Sender: TObject; AResponse: string);
begin
  lfMap.LoadGeoJSONFromText(AResponse, True, False);
  AssignDistrictNamesFromGeoJSON(AResponse);
  StyleDistrictsAndFit;
end;

procedure TFViewMap.httpReqGeoJsonError(Sender: TObject; AError: string);
begin
  Console.Log('Failed to load bpddistricts.geojson: ' + AError);
end;

procedure TFViewMap.AssignDistrictNamesFromGeoJSON(const AJson: string);
var
  Root, Feature, Props, Geom: JS.TJSObject;
  Features, Coords: JS.TJSArray;
  f, count, i, polyIndex: Integer;
  name, gtype: string;
begin
  Root     := JS.TJSObject(JS.TJSJSON.parse(AJson));
  Features := JS.TJSArray(Root['features']);
  if Features = nil then Exit;

  polyIndex := 0;

  for f := 0 to Features.Length - 1 do
  begin
    Feature := JS.TJSObject(Features[f]);
    Props   := JS.TJSObject(Feature['properties']);
    Geom    := JS.TJSObject(Feature['geometry']);

    if (Props <> nil) and Props.hasOwnProperty('NAME') then
      name := string(Props['NAME'])
    else
      name := '';

    if Geom <> nil then
      gtype := string(Geom['type'])
    else
      gtype := 'Polygon';

    if SameText(gtype, 'Polygon') then
      count := 1
    else if SameText(gtype, 'MultiPolygon') then
    begin
      Coords := JS.TJSArray(Geom['coordinates']); // array of polygons
      if Coords <> nil then
        count := Coords.Length
      else
        count := 1;
    end
    else
      count := 1;

    // tag each created polygon with the district name
    for i := 0 to count - 1 do
    begin
      if polyIndex < lfMap.Polygons.Count then
      begin
        lfMap.Polygons[polyIndex].DisplayName := name;
        Inc(polyIndex);
      end;
    end;
  end;
end;

procedure TFViewMap.StyleDistrictsAndFit;

  function DistrictLetter(const S: string): Char;
  var U: string; k: Integer;
  begin
    U := UpperCase(Trim(S));
    // find last A..Z; works even if there’s extra text/spacing
    Result := #0;
    for k := Length(U) downto 1 do
      if (U[k] >= 'A') and (U[k] <= 'Z') then
      begin
        Result := U[k];
        Break;
      end;
  end;

  procedure ApplyHardCodedColors(const P: TTMSFNCMapsPolygon);
  var L: Char;
  begin
    L := DistrictLetter(P.DisplayName);
    case L of
      'A': begin P.FillColor := $FF60A5FA; P.StrokeColor := $FF1D4ED8; end; // blue
      'B': begin P.FillColor := $FF34D399; P.StrokeColor := $FF047857; end; // emerald
      'C': begin P.FillColor := $FFF59E0B; P.StrokeColor := $FFB45309; end; // amber
      'D': begin P.FillColor := $FFF87171; P.StrokeColor := $FFB91C1C; end; // red
      'E': begin P.FillColor := $FFA78BFA; P.StrokeColor := $FF6D28D9; end; // purple (north)
    else
      // fallback cycle if a name didn’t come through
      case (P.Index mod 5) of
        0: begin P.FillColor := $FF60A5FA; P.StrokeColor := $FF1D4ED8; end;
        1: begin P.FillColor := $FF34D399; P.StrokeColor := $FF047857; end;
        2: begin P.FillColor := $FFF59E0B; P.StrokeColor := $FFB45309; end;
        3: begin P.FillColor := $FFF87171; P.StrokeColor := $FFB91C1C; end;
      else  begin P.FillColor := $FFA78BFA; P.StrokeColor := $FF6D28D9; end;
      end;
    end;

    P.FillOpacity   := 0.42; // bolder than before
    P.StrokeOpacity := 1.0;
    P.StrokeWidth   := 2;
  end;

var
  I, J: Integer;
  P: TTMSFNCMapsPolygon;
  C: TTMSFNCMapsCoordinates;
  minLat, minLng, maxLat, maxLng, lat, lng: Double;
  hasAny: Boolean;
  B: TTMSFNCMapsBoundsRec;
begin
  if lfMap.Polygons.Count = 0 then Exit;

  // Apply hard-coded district colors
  for I := 0 to lfMap.Polygons.Count - 1 do
  begin
    P := lfMap.Polygons[I];
    ApplyHardCodedColors(P);
  end;

  // Build bounds and zoom to fit
  hasAny := False;
  minLat :=  90;  minLng :=  180;
  maxLat := -90;  maxLng := -180;

  for I := 0 to lfMap.Polygons.Count - 1 do
  begin
    P := lfMap.Polygons[I];
    C := P.Coordinates;
    if (C <> nil) and (C.Count > 0) then
      for J := 0 to C.Count - 1 do
      begin
        lat := C.Items[J].Latitude;
        lng := C.Items[J].Longitude;

        if not hasAny then
        begin
          minLat := lat; maxLat := lat;
          minLng := lng; maxLng := lng;
          hasAny := True;
        end
        else
        begin
          if lat < minLat then minLat := lat;
          if lat > maxLat then maxLat := lat;
          if lng < minLng then minLng := lng;
          if lng > maxLng then maxLng := lng;
        end;
      end;
  end;

  if hasAny then
  begin
    B.SouthWest.Latitude  := minLat;
    B.SouthWest.Longitude := minLng;
    B.NorthEast.Latitude  := maxLat;
    B.NorthEast.Longitude := maxLng;
    lfMap.ZoomToBounds(B);
  end;
end;

procedure TFViewMap.lfMapPolyElementMouseEnter(Sender: TObject; AElement: TTMSFNCMapsPolyElement);
begin
  if AElement is TTMSFNCMapsPolygon then
  begin
    TTMSFNCMapsPolygon(AElement).StrokeWidth := 3;
    TTMSFNCMapsPolygon(AElement).FillOpacity := 0.55;
  end;
end;

procedure TFViewMap.lfMapPolyElementMouseLeave(Sender: TObject; AElement: TTMSFNCMapsPolyElement);
begin
  if AElement is TTMSFNCMapsPolygon then
  begin
    TTMSFNCMapsPolygon(AElement).StrokeWidth := 2;
    TTMSFNCMapsPolygon(AElement).FillOpacity := 0.42;
  end;
end;

end.

