// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/AccessControl.sol";

contract DBIS_ParticipantRegistry is AccessControl {
    bytes32 public constant PARTICIPANT_ADMIN_ROLE = keccak256("PARTICIPANT_ADMIN");

    enum EntityType { BANK, MSB, CUSTODIAN, TRUSTEE, OPERATOR, AUDITOR }
    enum Status { ACTIVE, SUSPENDED, REVOKED }

    struct Participant {
        bytes32 participantId;
        string legalName;
        string jurisdiction;
        EntityType entityType;
        Status status;
        bytes32[] policyTags;
        address[] operationalWallets;
    }

    mapping(bytes32 => Participant) private _participants;
    mapping(bytes32 => uint8) private _participantStatus;
    mapping(address => bytes32) private _walletToParticipant;

    event ParticipantRegistered(bytes32 indexed participantId, string legalName, string jurisdiction, uint8 entityType);
    event ParticipantStatusChanged(bytes32 indexed participantId, uint8 status);
    event OperationalWalletAdded(bytes32 indexed participantId, address wallet);
    event OperationalWalletRemoved(bytes32 indexed participantId, address wallet);

    constructor(address admin) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(PARTICIPANT_ADMIN_ROLE, admin);
    }

    function registerParticipant(Participant calldata p) external onlyRole(PARTICIPANT_ADMIN_ROLE) {
        require(p.participantId != bytes32(0), "DBIS: zero participantId");
        require(_participants[p.participantId].participantId == bytes32(0), "DBIS: already registered");
        _participants[p.participantId] = Participant({
            participantId: p.participantId,
            legalName: p.legalName,
            jurisdiction: p.jurisdiction,
            entityType: p.entityType,
            status: p.status,
            policyTags: p.policyTags,
            operationalWallets: p.operationalWallets
        });
        _participantStatus[p.participantId] = uint8(p.status);
        _linkWalletsToParticipant(p.participantId, p.operationalWallets);
        emit ParticipantRegistered(p.participantId, p.legalName, p.jurisdiction, uint8(p.entityType));
    }

    function setParticipantStatus(bytes32 participantId, Status s) external onlyRole(PARTICIPANT_ADMIN_ROLE) {
        require(_participants[participantId].participantId != bytes32(0), "DBIS: unknown participant");
        _participants[participantId].status = s;
        _participantStatus[participantId] = uint8(s);
        emit ParticipantStatusChanged(participantId, uint8(s));
    }

    function addOperationalWallet(bytes32 participantId, address wallet) external onlyRole(PARTICIPANT_ADMIN_ROLE) {
        require(_participants[participantId].participantId != bytes32(0), "DBIS: unknown participant");
        require(wallet != address(0), "DBIS: zero wallet");
        _participants[participantId].operationalWallets.push(wallet);
        _walletToParticipant[wallet] = participantId;
        emit OperationalWalletAdded(participantId, wallet);
    }

    function removeOperationalWallet(bytes32 participantId, address wallet) external onlyRole(PARTICIPANT_ADMIN_ROLE) {
        require(_participants[participantId].participantId != bytes32(0), "DBIS: unknown participant");
        address[] storage wallets = _participants[participantId].operationalWallets;
        for (uint256 i = 0; i < wallets.length; i++) {
            if (wallets[i] == wallet) {
                wallets[i] = wallets[wallets.length - 1];
                wallets.pop();
                if (_walletToParticipant[wallet] == participantId) delete _walletToParticipant[wallet];
                emit OperationalWalletRemoved(participantId, wallet);
                return;
            }
        }
        revert("DBIS: wallet not found");
    }

    function isOperationalWallet(address wallet) external view returns (bool) {
        bytes32 pid = _walletToParticipant[wallet];
        if (pid == bytes32(0)) return false;
        return _participantStatus[pid] == uint8(Status.ACTIVE);
    }

    function getParticipantByWallet(address wallet) external view returns (bytes32 participantId) {
        return _walletToParticipant[wallet];
    }

    function getParticipantStatus(bytes32 participantId) external view returns (Status) {
        return _participants[participantId].status;
    }

    function getParticipant(bytes32 participantId) external view returns (Participant memory) {
        return _participants[participantId];
    }

    function _linkWalletsToParticipant(bytes32 participantId, address[] calldata wallets) private {
        for (uint256 i = 0; i < wallets.length; i++) {
            if (wallets[i] != address(0)) _walletToParticipant[wallets[i]] = participantId;
        }
    }
}
