unit Utils;

interface

uses
  System.Classes, SysUtils, JS, Web, WEBLib.Forms, WEBLib.Toast, DateUtils, WebLib.Dialogs;

procedure ShowStatusMessage(const AMessage, AClass: string; const AElementId: string);
procedure HideStatusMessage(const AElementId: string);
procedure ShowSpinner(SpinnerID: string);
procedure HideSpinner(SpinnerID: string);
procedure ShowErrorModal(msg: string);
function CalculateAge(DateOfBirth: TDateTime): Integer;
function FormatPhoneNumber(PhoneNumber: string): string;
procedure ApplyReportTitle(CurrentReportType: string);
procedure ShowToast(const MessageText: string; const ToastType: string = 'success');
procedure ShowConfirmationModal(msg, leftLabel, rightLabel: string; ConfirmProc: TProc<Boolean>);
// function FormatDollarValue(ValueStr: string): string;


implementation

procedure ShowStatusMessage(const AMessage, AClass: string; const AElementId: string);
var
  StatusMessage: TJSHTMLElement;
begin
  StatusMessage := TJSHTMLElement(document.getElementById(AElementId));
  if Assigned(StatusMessage) then
  begin
    if AMessage = '' then
    begin
      StatusMessage.style.setProperty('display', 'none');
      StatusMessage.className := '';
      StatusMessage.innerHTML := '';
    end
    else
    begin
      StatusMessage.innerHTML := AMessage;
      StatusMessage.className := 'alert ' + AClass;
      StatusMessage.style.setProperty('display', 'block');
    end
  end
  else
    console.log('Error: Status message element not found');
end;


procedure HideStatusMessage(const AElementId: string);
var
  StatusMessage: TJSHTMLElement;
begin
  StatusMessage := TJSHTMLElement(document.getElementById(AElementId));
  if Assigned(StatusMessage) then
  begin
    StatusMessage.style.setProperty('display', 'none');
    StatusMessage.className := '';
    StatusMessage.innerHTML := '';
  end
  else
    console.log('Error: Status message element not found');
end;


// ...uses JS, Web...

var
  GIsBusy: Boolean = False;

procedure ShowSpinner(SpinnerID: string);
var
  SpinnerElement, Overlay: TJSHTMLElement;
begin
  SpinnerElement := TJSHTMLElement(document.getElementById(SpinnerID));
  Overlay        := TJSHTMLElement(document.getElementById('screenlock'));

  // ensure overlay exists
  if Overlay = nil then
  begin
    Overlay := TJSHTMLElement(document.createElement('div'));
    Overlay.id := 'screenlock';
    document.body.appendChild(Overlay);
  end;

  // move both to <body> and show
  if Assigned(Overlay) then
  begin
    asm if (Overlay.parentNode !== document.body) { document.body.appendChild(Overlay); } end;
    Overlay.classList.remove('d-none');
    Overlay.classList.add('d-block','position-fixed','top-0','start-0','w-100','h-100');
    Overlay.style.setProperty('z-index','1060');
    Overlay.style.setProperty('background','rgba(0,0,0,.15)');
    Overlay.style.setProperty('pointer-events','auto');
  end;

  if Assigned(SpinnerElement) then
  begin
    asm if (SpinnerElement.parentNode !== document.body) { document.body.appendChild(SpinnerElement); } end;
    SpinnerElement.classList.remove('d-none');
    SpinnerElement.classList.add('d-block','position-fixed','top-50','start-50','translate-middle');
    SpinnerElement.style.setProperty('z-index','1065');
  end;

  document.body.setAttribute('aria-busy','true');
  GIsBusy := True;
end;

procedure HideSpinner(SpinnerID: string);
var
  SpinnerElement, Overlay: TJSHTMLElement;
begin
  SpinnerElement := TJSHTMLElement(document.getElementById(SpinnerID));
  Overlay        := TJSHTMLElement(document.getElementById('screenlock'));

  if Assigned(SpinnerElement) then
  begin
    SpinnerElement.classList.remove('d-block');
    SpinnerElement.classList.add('d-none');
  end;

  if Assigned(Overlay) then
  begin
    Overlay.classList.remove('d-block');
    Overlay.classList.add('d-none');
  end;

  document.body.removeAttribute('aria-busy');
  GIsBusy := False;
end;

function IsBusy: Boolean;
begin
  Result := GIsBusy;
end;



procedure ShowErrorModal(msg: string);
begin
  asm
    var modal = document.getElementById('main_errormodal');
    var label = document.getElementById('main_lblmodal_body');
    var reloadBtn = document.getElementById('btn_modal_restart');

    if (label) label.innerText = msg;

    // Ensure modal is a direct child of <body>
    if (modal && modal.parentNode !== document.body) {
      document.body.appendChild(modal);
    }

    // Bind hard reload to button
    if (reloadBtn) {
      reloadBtn.onclick = function () {
        window.location.reload(true); // hard reload, bypass cache
      };
    }

    // Show the Bootstrap modal
    var bsModal = new bootstrap.Modal(modal, { keyboard: false });
    bsModal.show();
  end;
end;


// ShowConfirmationModal displays a two-button modal with custom labels.
// Params:
// - messageText: text shown in the modal body
// - leftButtonText: label for the left button (e.g., "Cancel")
// - rightButtonText: label for the right button (e.g., "Delete")
// - callback: procedure(confirmed: Boolean); confirmed = True if right button clicked
//
// Example:
// ShowConfirmationModal('Delete this?', 'Cancel', 'Delete',
//   procedure(confirmed: Boolean)
//   begin
//     if confirmed then DeleteOrder();
//   end);

// function ShowConfirmationModal(msg, leftLabel, rightLabel: string;): Boolean;

// if ShowConfirmationModal then
//  doThing()
// else
//  doOtherThing();


procedure ShowConfirmationModal(msg, leftLabel, rightLabel: string; ConfirmProc: TProc<Boolean>);
begin
  asm
    var modal = document.getElementById('main_confirmation_modal');
    var body = document.getElementById('main_modal_body');
    var btnLeft = document.getElementById('btn_confirm_left');
    var btnRight = document.getElementById('btn_confirm_right');
    var bsModal;

    if (body) body.innerText = msg;
    if (btnLeft) btnLeft.innerText = leftLabel;
    if (btnRight) btnRight.innerText = rightLabel;

    if (modal && modal.parentNode !== document.body) {
      document.body.appendChild(modal);
    }

    btnLeft.onclick = null;
    btnRight.onclick = null;

    btnLeft.onclick = function () {
      bsModal.hide();
      ConfirmProc(true); // user confirmed
    };

    btnRight.onclick = function () {
      bsModal.hide();
      ConfirmProc(false); // user canceled
    };

    bsModal = new bootstrap.Modal(modal, { keyboard: false });
    bsModal.show();
  end;
end;


function CalculateAge(DateOfBirth: TDateTime): Integer;
var
  Today, BirthDate: TJSDate;
  Year, Month, Day, BirthYear, BirthMonth, BirthDay: NativeInt;
  DOBString: string;
begin
  Today := TJSDate.New;
  Year := Today.FullYear;
  Month := Today.Month + 1;
  Day := Today.Date;
  // Formats the DateOfBirth as an ISO 8601 date string
  DOBString := FormatDateTime('yyyy-mm-dd', DateOfBirth);
  BirthDate := TJSDate.New(DOBString);
  if BirthDate = nil then
  begin
    Exit(0); // Exit the function with an age of 0 if the date creation fails
  end;

  BirthYear := BirthDate.FullYear;
  BirthMonth := BirthDate.Month + 1;
  BirthDay := BirthDate.Date;

  Result := Year - BirthYear;
  if (Month < BirthMonth) or ((Month = BirthMonth) and (Day < BirthDay)) then
    Dec(Result);
end;


function FormatPhoneNumber(PhoneNumber: string): string;
var
  Digits: string;
begin
  Digits := PhoneNumber.Replace('(', '').Replace(')', '').Replace('-', '').Replace(' ', '');
  case Length(Digits) of
    7:  Result := Format('%s-%s', [Copy(Digits, 1, 3), Copy(Digits, 4, 4)]);
    10: Result := Format('(%s) %s-%s', [Copy(Digits, 1, 3), Copy(Digits, 4, 3), Copy(Digits, 7, 4)]);
  else
    // If the number does not have 7 or 10 digits, whatever they typed is returned
    Result := PhoneNumber;
  end;
end;


procedure ShowToast(const MessageText: string; const ToastType: string = 'success');
var
  ParsedText, ToastKind, MsgPrefix: string;
  Parts: TArray<string>;
begin
  ParsedText := MessageText.Trim;
  ToastKind := ToastType.ToLower;

  // Check for "Success:" or "Failure:" at the start of message
  if ParsedText.Contains(':') then
  begin
    Parts := ParsedText.Split([':'], 2);
    MsgPrefix := Parts[0].Trim.ToLower;

    if (MsgPrefix = 'success') or (MsgPrefix = 'failure') then
    begin
      ParsedText := Parts[1].Trim;

      if MsgPrefix = 'success' then
        ToastKind := 'success'
      else
        ToastKind := 'danger';
    end;
  end;

  asm
    var toastEl = document.getElementById('bootstrapToast');
    var toastBody = document.getElementById('bootstrapToastBody');

    if (!toastEl || !toastBody) return;

    toastBody.innerText = ParsedText;

    toastEl.classList.remove('bg-success', 'bg-danger', 'bg-warning', 'bg-info');
    toastEl.classList.remove('slide-in');

    switch (ToastKind) {
      case 'danger':
        toastEl.classList.add('bg-danger');
        break;
      case 'warning':
        toastEl.classList.add('bg-warning');
        break;
      case 'info':
        toastEl.classList.add('bg-info');
        break;
      default:
        toastEl.classList.add('bg-success');
    }

    // Add slide-in animation
    toastEl.classList.add('slide-in');

    var toast = new bootstrap.Toast(toastEl, { delay: 2500 });
    toast.show();

    // Remove animation class after it's done (so it can be reapplied)
    setTimeout(function() {
      toastEl.classList.remove('slide-in');
    }, 500);
  end;
end;



procedure ApplyReportTitle(CurrentReportType: string);
var
  CrimeTitleElement: TJSHTMLElement;
begin
  CrimeTitleElement := TJSHTMLElement(document.getElementById('crime_title'));
  if Assigned(CrimeTitleElement) then
    CrimeTitleElement.innerText := CurrentReportType
  else
    Console.Log('Element with ID "crime_title" not found.');
end;


// Used html number input type to restrict the input instead of this function

// function FormatDollarValue(ValueStr: string): string;
// var
//   i: Integer;
// begin
//   Result := '';  // Initialize the result

//   // Filter out any characters that are not digits or decimal point
//   for i := 1 to Length(ValueStr) do
//   begin
//     if (Pos(ValueStr[i], '0123456789.') > 0) then
//     begin
//       Result := Result + ValueStr[i];
//     end;
//   end;
// end;

end.
