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>);
procedure ShowNotificationModal(msg: string);
procedure ShowVersionMismatchAndReload(const ErrorText, ClientVersion: 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;


procedure ShowSpinner(SpinnerID: string);
var
  SpinnerElement: TJSHTMLElement;
begin
  SpinnerElement := TJSHTMLElement(document.getElementById(SpinnerID));
  if Assigned(SpinnerElement) then
  begin
    // Move spinner to the <body> if it's not already there
    asm
      if (SpinnerElement.parentNode !== document.body) {
        document.body.appendChild(SpinnerElement);
      }
    end;

    SpinnerElement.classList.remove('d-none');
    SpinnerElement.classList.add('d-block');
  end;
end;

procedure HideSpinner(SpinnerID: string);
var
  SpinnerElement: TJSHTMLElement;
begin
  SpinnerElement := TJSHTMLElement(document.getElementById(SpinnerID));
  if Assigned(SpinnerElement) then
  begin
    SpinnerElement.classList.remove('d-block');
    SpinnerElement.classList.add('d-none');
  end;
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;


procedure ShowNotificationModal(msg: string);
begin
  asm
    var modal = document.getElementById('main_notification_modal');
    var label = document.getElementById('main_notification_modal_body');
    var closeBtn = document.getElementById('btn_modal_close');

    if (label) label.innerText = msg;

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

    // Button simply closes the modal
    if (closeBtn) {
      closeBtn.onclick = function () {
        var existing = bootstrap.Modal.getInstance(modal);
        if (existing) {
          existing.hide();
        }
      };
    }

    // 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-primary');
    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-primary');
        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;


procedure ShowVersionMismatchAndReload(const ErrorText, ClientVersion: string);
begin
  asm
    var dlg = document.createElement('dialog');
    dlg.style.padding = '1rem';
    dlg.style.maxWidth = '520px';
    dlg.style.border = '1px solid #ccc';
    dlg.style.borderRadius = '8px';

    var title = document.createElement('h3');
    title.textContent = 'template web app';
    title.style.marginTop = '0';

    var msg = document.createElement('pre');
    msg.textContent = ErrorText;
    msg.style.whiteSpace = 'pre-wrap';
    msg.style.fontFamily = 'inherit';

    var btnRow = document.createElement('div');
    btnRow.style.display = 'flex';
    btnRow.style.justifyContent = 'flex-end';
    btnRow.style.marginTop = '1rem';

    var btn = document.createElement('button');
    btn.textContent = 'Reload';
    btn.onclick = function () {
      window.location.reload();
    };

    btnRow.appendChild(btn);
    dlg.appendChild(title);
    dlg.appendChild(msg);
    dlg.appendChild(btnRow);
    document.body.appendChild(dlg);
    dlg.showModal();
  end;
end;

end.

