unit View.DeviceManager;

interface

uses
  System.SysUtils, System.Classes, Web, JS, WEBLib.Graphics, WEBLib.Controls,
  WEBLib.Forms, WEBLib.Dialogs, Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls,
  WEBLib.ExtCtrls, XData.Web.Client, ConnectionModule;

type
  TFViewDeviceManager = class(TWebForm)
    XDataWebClient: TXDataWebClient;
    pnlMessage: TWebPanel;
    lblMessage: TWebLabel;
    btnCloseNotification: TWebButton;
    procedure WebFormCreate(Sender: TObject);
    procedure btnCloseNotificationClick(Sender: TObject);
  private
    procedure ShowNotification(const AMsg: string; AIsError: Boolean = True);
    procedure HideNotification;
    procedure ClearTable;
    procedure AddDeviceRow(const ACredentialId, AName, AUserAgent,
      ARegisteredAt, ARevokedAt: string);
    [async] procedure LoadDevices;
    [async] procedure RevokeDevice(const ACredentialId, AName: string);
  public
  end;

var
  FViewDeviceManager: TFViewDeviceManager;

implementation

{$R *.dfm}

procedure TFViewDeviceManager.WebFormCreate(Sender: TObject);
begin
  HideNotification;
  LoadDevices;
end;

procedure TFViewDeviceManager.btnCloseNotificationClick(Sender: TObject);
begin
  HideNotification;
end;

procedure TFViewDeviceManager.ShowNotification(const AMsg: string; AIsError: Boolean);
begin
  lblMessage.Caption := AMsg;
  asm
    var el = document.getElementById('view.devmgr.message');
    if (el) {
      el.classList.remove('alert-danger', 'alert-success');
      el.classList.add(AIsError ? 'alert-danger' : 'alert-success');
      el.classList.remove('d-none');
    }
  end;
end;

procedure TFViewDeviceManager.HideNotification;
begin
  asm
    var el = document.getElementById('view.devmgr.message');
    if (el) el.classList.add('d-none');
  end;
end;

procedure TFViewDeviceManager.ClearTable;
begin
  asm
    var tbody = document.getElementById('view.devmgr.tbody');
    if (tbody) tbody.innerHTML = '';
    var empty = document.getElementById('view.devmgr.empty');
    if (empty) empty.classList.add('d-none');
  end;
end;

procedure TFViewDeviceManager.AddDeviceRow(const ACredentialId, AName, AUserAgent,
  ARegisteredAt, ARevokedAt: string);
var
  tbody, tr, tdName, tdAgent, tdReg, tdStatus, tdAction: TJSHTMLElement;
  btn: TJSHTMLElement;
  isRevoked: Boolean;
  displayDate: string;
begin
  tbody := TJSHTMLElement(document.getElementById('view.devmgr.tbody'));
  if not Assigned(tbody) then
    Exit;

  isRevoked := ARevokedAt <> '';

  tr := TJSHTMLElement(document.createElement('tr'));
  if isRevoked then
    tr.classList.add('table-secondary');

  // Device name
  tdName := TJSHTMLElement(document.createElement('td'));
  if AName <> '' then
    tdName.innerText := AName
  else
    tdName.innerHTML := '<em class="text-muted">unnamed</em>';
  tr.appendChild(tdName);

  // User agent (truncated via CSS)
  tdAgent := TJSHTMLElement(document.createElement('td'));
  tdAgent.setAttribute('title', AUserAgent);
  tdAgent.style.setProperty('max-width', '260px');
  tdAgent.style.setProperty('overflow', 'hidden');
  tdAgent.style.setProperty('text-overflow', 'ellipsis');
  tdAgent.style.setProperty('white-space', 'nowrap');
  tdAgent.innerText := AUserAgent;
  tr.appendChild(tdAgent);

  // Registered at — trim to seconds, replace T with space
  displayDate := ARegisteredAt;
  if Length(displayDate) >= 19 then
    displayDate := Copy(displayDate, 1, 19).Replace('T', ' ');
  tdReg := TJSHTMLElement(document.createElement('td'));
  tdReg.innerText := displayDate;
  tr.appendChild(tdReg);

  // Status badge
  tdStatus := TJSHTMLElement(document.createElement('td'));
  if isRevoked then
    tdStatus.innerHTML := '<span class="badge bg-secondary">Revoked</span>'
  else
    tdStatus.innerHTML := '<span class="badge bg-success">Active</span>';
  tr.appendChild(tdStatus);

  // Action button
  tdAction := TJSHTMLElement(document.createElement('td'));
  if not isRevoked then
  begin
    btn := TJSHTMLElement(document.createElement('button'));
    btn.className := 'btn btn-danger btn-sm';
    btn.innerText := 'Revoke';
    // Capture token and name in a Delphi closure
    btn.addEventListener('click', procedure(Event: TJSMouseEvent)
    begin
      RevokeDevice(ACredentialId, AName);
    end);
    tdAction.appendChild(btn);
  end
  else
    tdAction.innerText := '—';
  tr.appendChild(tdAction);

  tbody.appendChild(tr);
end;

procedure TFViewDeviceManager.LoadDevices;
var
  resp: TXDataClientResponse;
  list: TJSObject;
  data: TJSArray;
  item: TJSObject;
  i, count: Integer;
begin
  ClearTable;
  HideNotification;

  try
    resp  := await(XDataWebClient.RawInvokeAsync('IApiService.GetDeviceList', []));
    list  := TJSObject(resp.Result);
    data  := TJSArray(list['data']);
    count := Integer(list['count']);

    if count = 0 then
    begin
      asm
        var el = document.getElementById('view.devmgr.empty');
        if (el) el.classList.remove('d-none');
      end;
      Exit;
    end;

    for i := 0 to data.Length - 1 do
    begin
      item := TJSObject(data[i]);
      AddDeviceRow(
        string(item['credential_id']),
        string(item['device_name']),
        string(item['user_agent']),
        string(item['registered_at']),
        string(item['revoked_at'])
      );
    end;

  except
    on E: Exception do
      ShowNotification('Failed to load devices: ' + E.Message);
  end;
end;

procedure TFViewDeviceManager.RevokeDevice(const ACredentialId, AName: string);
var
  resp: TXDataClientResponse;
  res: TJSObject;
  status: string;
begin
  try
    resp   := await(XDataWebClient.RawInvokeAsync('IApiService.RevokeDevice', [ACredentialId]));
    res    := TJSObject(resp.Result);
    status := string(res['status']);

    if status = 'ok' then
    begin
      ShowNotification('Device "' + AName + '" access has been revoked.', False);
      LoadDevices;
    end
    else
      ShowNotification(string(res['message']));
  except
    on E: Exception do
      ShowNotification('Revoke failed: ' + E.Message);
  end;
end;

end.
