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

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

/**
 * @title GRUVaultIndex
 * @notice Canonical index of GRU vault instances across tiers, tokens, IBAN, and URA policy profiles.
 * @dev Written by VaultFactory (FACTORY_ROLE) or registrar during vault creation.
 */
contract GRUVaultIndex is AccessControl {
    bytes32 public constant FACTORY_ROLE = keccak256("FACTORY_ROLE");
    bytes32 public constant REGISTRAR_ROLE = keccak256("REGISTRAR_ROLE");

    /// @dev GRU monetary tier: 0=M00, 1=M0, 2=M1
    struct VaultRecord {
        address entity;
        address baseToken;
        address depositToken;
        address debtToken;
        uint8 gruTier;
        bytes32 ibanHash;
        bytes32 policyProfileKey;
        uint256 recordedAt;
        bool active;
    }

    mapping(address => VaultRecord) public vaults;
    address[] public allVaults;

    event VaultRecorded(
        address indexed vault,
        address indexed entity,
        address baseToken,
        address depositToken,
        address debtToken,
        uint8 gruTier,
        bytes32 ibanHash,
        bytes32 policyProfileKey
    );

    event PolicyProfileKeyPatched(address indexed vault, bytes32 policyProfileKey);

    event VaultImported(
        address indexed vault,
        address indexed entity,
        address baseToken,
        bytes32 policyProfileKey
    );

    constructor(address admin) {
        require(admin != address(0), "GRUVaultIndex: zero admin");
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(REGISTRAR_ROLE, admin);
    }

    function grantFactoryRole(address factory) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(factory != address(0), "GRUVaultIndex: zero factory");
        _grantRole(FACTORY_ROLE, factory);
    }

    function recordVault(
        address vault,
        address entity,
        address baseToken,
        address depositToken,
        address debtToken,
        uint8 gruTier,
        bytes32 ibanHash,
        bytes32 policyProfileKey
    ) external onlyRole(FACTORY_ROLE) {
        require(vault != address(0), "GRUVaultIndex: zero vault");
        require(!vaults[vault].active, "GRUVaultIndex: exists");

        vaults[vault] = VaultRecord({
            entity: entity,
            baseToken: baseToken,
            depositToken: depositToken,
            debtToken: debtToken,
            gruTier: gruTier,
            ibanHash: ibanHash,
            policyProfileKey: policyProfileKey,
            recordedAt: block.timestamp,
            active: true
        });
        allVaults.push(vault);

        emit VaultRecorded(vault, entity, baseToken, depositToken, debtToken, gruTier, ibanHash, policyProfileKey);
    }

    /// @notice Admin backfill when vaults were indexed before policy profile publish.
    function patchPolicyProfileKey(address vault, bytes32 policyProfileKey)
        external
        onlyRole(DEFAULT_ADMIN_ROLE)
    {
        require(vaults[vault].active, "GRUVaultIndex: unknown");
        vaults[vault].policyProfileKey = policyProfileKey;
        emit PolicyProfileKeyPatched(vault, policyProfileKey);
    }

    /// @notice Registrar migration import (e.g. re-index with policy keys from a prior GRUVaultIndex).
    function importVault(
        address vault,
        address entity,
        address baseToken,
        address depositToken,
        address debtToken,
        uint8 gruTier,
        bytes32 ibanHash,
        bytes32 policyProfileKey,
        uint256 recordedAt
    ) external onlyRole(REGISTRAR_ROLE) {
        require(vault != address(0), "GRUVaultIndex: zero vault");
        require(!vaults[vault].active, "GRUVaultIndex: exists");

        vaults[vault] = VaultRecord({
            entity: entity,
            baseToken: baseToken,
            depositToken: depositToken,
            debtToken: debtToken,
            gruTier: gruTier,
            ibanHash: ibanHash,
            policyProfileKey: policyProfileKey,
            recordedAt: recordedAt,
            active: true
        });
        allVaults.push(vault);

        emit VaultImported(vault, entity, baseToken, policyProfileKey);
    }
}
