// RequestsList — pending contact requests inbox.
//
// Each row shows the requester (identity if known + DC address + fee paid)
// and their intro message. Accept publishes ACCEPT_CONTACT on-chain,
// adds the peer to the local contacts store, and optimistically drops
// the row. Reject (Block) publishes BLOCK_CONTACT; subsequent requests
// from the same sender are refused by the node.

import React, { useState } from 'react';
import { useStore } from '@/lib/store';
import {
  buildAcceptContactTx, buildBlockContactTx, buildLinkDeviceTx,
  submitTx, humanizeTxError,
} from '@/lib/tx';
import { upsertContact as persistContact, markDeviceRegistered, isDeviceRegistered } from '@/lib/storage';
import { getIdentity, fetchDevices, type ContactRequestRaw } from '@/lib/api';
import { shortAddr } from '@/lib/crypto';

export function RequestsList({
  requests, onChanged,
}: {
  requests: ContactRequestRaw[];
  onChanged: () => void;
}): React.ReactElement {
  if (requests.length === 0) {
    return (
      <div style={{
        padding: 32, color: '#6a6a6a', fontSize: 13, textAlign: 'center',
      }}>
        No pending requests. Inbound CONTACT_REQUEST txs will show up here
        for you to accept or block.
      </div>
    );
  }
  return (
    <div>
      {requests.map(r => (
        <RequestRow key={r.tx_id} req={r} onChanged={onChanged} />
      ))}
    </div>
  );
}

function RequestRow({
  req, onChanged,
}: { req: ContactRequestRaw; onChanged: () => void }) {
  const keyFile        = useStore(s => s.keyFile);
  const upsertContact  = useStore(s => s.upsertContact);
  const setSection     = useStore(s => s.setSection);
  const setActiveChat  = useStore(s => s.setActiveChat);

  const [busy, setBusy] = useState<'accept' | 'block' | null>(null);
  const [err,  setErr]  = useState<string | null>(null);

  const act = async (kind: 'accept' | 'block') => {
    if (!keyFile) return;
    setBusy(kind); setErr(null);
    try {
      if (kind === 'accept') {
        // Need the requester's X25519 so a local contact is created
        // with encryption enabled out of the gate — without it the
        // first outgoing message would surface "no key" until we
        // refetched via resolveRecipientKeys.
        const identity = await getIdentity(req.requester_pub);
        const tx = buildAcceptContactTx({
          from:    keyFile.pub_key,
          to:      req.requester_pub,
          privKey: keyFile.priv_key,
        });
        await submitTx(tx);

        // Make sure OUR device is published on-chain too. The
        // useDeviceBootstrap effect tries this on sign-in, but if the
        // user had zero balance then the tx bounced; now that the
        // incoming CONTACT_REQUEST has paid us the contact fee, we
        // have the µT needed. Without this, the peer couldn't encrypt
        // to us — they'd see "recipient has no encryption key" even
        // though we just accepted.
        try {
          const ownDevices = await fetchDevices(keyFile.pub_key);
          const alreadyLinked = ownDevices.some(d => d.x25519_pub_key === keyFile.x25519_pub);
          if (!alreadyLinked && !isDeviceRegistered()) {
            const platform = await window.dchain.app.platform().catch(() => 'unknown');
            const deviceName = platform === 'darwin' ? 'Mac'
              : platform === 'win32' ? 'Windows'
              : platform === 'linux' ? 'Linux'
              : 'Desktop';
            const linkTx = buildLinkDeviceTx({
              from:       keyFile.pub_key,
              x25519Pub:  keyFile.x25519_pub,
              deviceName,
              privKey:    keyFile.priv_key,
            });
            await submitTx(linkTx);
            markDeviceRegistered();
          }
        } catch { /* best-effort — next sign-in retries */ }

        const c = {
          address:   req.requester_pub,
          x25519Pub: identity?.x25519_pub ?? '',
          username:  identity?.nickname || undefined,
          alias:     undefined,
          addedAt:   Date.now(),
        };
        upsertContact(c);
        persistContact(c);
        // Jump the user straight into the new chat — mirrors mobile's
        // router.replace(/chats/<pub>) after accept.
        setActiveChat(req.requester_pub);
        setSection('messages');
      } else {
        const tx = buildBlockContactTx({
          from:    keyFile.pub_key,
          to:      req.requester_pub,
          privKey: keyFile.priv_key,
        });
        await submitTx(tx);
      }
      onChanged();
    } catch (e) {
      setErr(humanizeTxError(e));
    } finally {
      setBusy(null);
    }
  };

  return (
    <div style={{
      padding: 14, borderBottom: '1px solid #1f1f1f',
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8,
      }}>
        <div style={{
          width: 36, height: 36, borderRadius: 18, background: '#1a1a1a',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          color: '#d0d0d0', fontWeight: 700,
        }}>{shortAddr(req.requester_pub, 1).charAt(0).toUpperCase()}</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{
            color: '#fff', fontSize: 13, fontWeight: 700,
            overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
          }}>
            {shortAddr(req.requester_pub, 8)}
          </div>
          <div style={{ color: '#8b8b8b', fontSize: 11, fontFamily: 'monospace' }}>
            {req.requester_addr}
          </div>
        </div>
        <div style={{
          color: '#f0b35a', fontSize: 11, fontWeight: 700,
        }}>
          +{(req.fee_ut / 1_000_000).toFixed(3)} T
        </div>
      </div>

      {req.intro && (
        <div className="selectable" style={{
          padding: 10, borderRadius: 8,
          background: '#000', border: '1px solid #1f1f1f',
          color: '#e0e0e0', fontSize: 12, lineHeight: 1.5,
          whiteSpace: 'pre-wrap', wordBreak: 'break-word',
          marginBottom: 8,
        }}>
          {req.intro}
        </div>
      )}

      <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
        <button
          onClick={() => act('block')}
          disabled={!!busy}
          style={{
            padding: '7px 12px', borderRadius: 999,
            background: 'transparent', border: '1px solid #3a2020',
            color: '#ff6b6b', fontSize: 12, fontWeight: 700,
            cursor: busy ? 'default' : 'pointer',
            opacity: busy ? 0.5 : 1,
          }}
        >{busy === 'block' ? '…' : 'Block'}</button>
        <button
          onClick={() => act('accept')}
          disabled={!!busy}
          style={{
            padding: '7px 14px', borderRadius: 999,
            border: 'none', background: '#1d9bf0', color: '#fff',
            fontSize: 12, fontWeight: 700,
            cursor: busy ? 'default' : 'pointer',
            opacity: busy ? 0.5 : 1,
          }}
        >{busy === 'accept' ? '…' : 'Accept'}</button>
      </div>

      {err && (
        <div style={{
          marginTop: 8, padding: 8, borderRadius: 6,
          background: '#2a1414', color: '#ff9b9b', fontSize: 11,
        }}>{err}</div>
      )}
    </div>
  );
}
