// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { IERC8004IdentityRegistry } from "./IERC8004IdentityRegistry.sol";
import { CanonicalIdentityRegistryV3 } from "./CanonicalIdentityRegistryV3.sol";

/// @title ERC8004IdentityAdapterV3
/// @notice Vaultfire V3 adapter over a pinned ERC-8004 registry. This contract
///         DOES NOT claim to BE ERC-8004. It reads from a configured registry
///         address, and exposes only the fields Vaultfire needs.
/// @dev Owner is intended to be `MultisigGovernanceV3`. Every setter emits an event.
contract ERC8004IdentityAdapterV3 {
    bytes32 public constant CANONICAL_REGISTRY_RUNTIME_CODEHASH =
        keccak256(type(CanonicalIdentityRegistryV3).runtimeCode);
    bytes32 public constant CANONICAL_IDENTITY_SEMANTICS =
        keccak256("Vaultfire.BaseV3.SelfAuthenticatedIdentity.NoBehavioralInputs.v1");
    error NotOwner();
    error ZeroAddress();
    error UnknownAgent(uint256 agentId);
    error InactiveAgent(uint256 agentId);
    error InvalidRegistry();
    error RegistryPinned();

    event OwnerTransferStarted(address indexed currentOwner, address indexed pendingOwner);
    event OwnerTransferAccepted(address indexed previousOwner, address indexed newOwner);
    event RegistrySet(address indexed previous, address indexed current);
    event LegacyKYARegistrySet(address indexed previous, address indexed current);

    IERC8004IdentityRegistry public registry;
    bytes32 public immutable registryCodehash;
    /// @notice Optional legacy Vaultfire KYA registry. Reads only. Labeled clearly.
    address public legacyKYARegistry;

    address public owner;
    address public pendingOwner;

    modifier onlyOwner() {
        if (msg.sender != owner) revert NotOwner();
        _;
    }

    constructor(address _owner, address _registry, bytes32 _registryCodehash) {
        if (_owner == address(0) || _registry == address(0)) revert ZeroAddress();
        if (
            _registry.code.length == 0 || _registryCodehash != CANONICAL_REGISTRY_RUNTIME_CODEHASH
                || _registry.codehash != CANONICAL_REGISTRY_RUNTIME_CODEHASH
        ) {
            revert InvalidRegistry();
        }
        (bool semanticsOk, bytes memory semanticsData) =
            _registry.staticcall(abi.encodeWithSignature("IDENTITY_SEMANTICS()"));
        (bool activeOk, bytes memory activeData) =
            _registry.staticcall(abi.encodeCall(IERC8004IdentityRegistry.isActive, (uint256(0))));
        (bool operatorOk, bytes memory operatorData) = _registry.staticcall(
            abi.encodeCall(IERC8004IdentityRegistry.isAuthorizedOperator, (uint256(0), address(this)))
        );
        (bool agentOk, bytes memory agentData) =
            _registry.staticcall(abi.encodeCall(IERC8004IdentityRegistry.getAgent, (uint256(0))));
        if (
            !semanticsOk || semanticsData.length != 32
                || abi.decode(semanticsData, (bytes32)) != CANONICAL_IDENTITY_SEMANTICS || !activeOk
                || activeData.length != 32 || !operatorOk || operatorData.length != 32 || !agentOk
                || agentData.length != 224
        ) revert InvalidRegistry();
        owner = _owner;
        registry = IERC8004IdentityRegistry(_registry);
        registryCodehash = _registryCodehash;
        emit RegistrySet(address(0), _registry);
    }

    // 2-step ownership (fixes single-step transferOwnership risk)
    function startOwnerTransfer(address _pending) external onlyOwner {
        if (_pending == address(0)) revert ZeroAddress();
        pendingOwner = _pending;
        emit OwnerTransferStarted(owner, _pending);
    }

    function acceptOwnerTransfer() external {
        if (msg.sender != pendingOwner) revert NotOwner();
        address previous = owner;
        owner = pendingOwner;
        pendingOwner = address(0);
        emit OwnerTransferAccepted(previous, owner);
    }

    function setRegistry(address) external pure {
        revert RegistryPinned();
    }

    function setLegacyKYARegistry(address _legacy) external onlyOwner {
        emit LegacyKYARegistrySet(legacyKYARegistry, _legacy);
        legacyKYARegistry = _legacy;
    }

    // --- Reads used by bonds / mandate registry -----------------------------

    function getAgent(uint256 agentId) external view returns (IERC8004IdentityRegistry.Agent memory a) {
        _requirePinnedRegistry();
        a = registry.getAgent(agentId);
        if (a.agentId == 0 || a.agentWallet == address(0)) revert UnknownAgent(agentId);
    }

    function requireActive(uint256 agentId) external view {
        _requirePinnedRegistry();
        if (!registry.isActive(agentId)) revert InactiveAgent(agentId);
    }

    function isActive(uint256 agentId) external view returns (bool) {
        _requirePinnedRegistry();
        return registry.isActive(agentId);
    }

    function isAuthorizedOperator(uint256 agentId, address caller) external view returns (bool) {
        _requirePinnedRegistry();
        return registry.isAuthorizedOperator(agentId, caller);
    }

    /// @notice Sybil / self-bond guard. Returns true if two agents resolve to the
    ///         same principal OR the same agent wallet. Used by bond factories to
    ///         satisfy invariant #15 (no self-bonds, no circular vouches).
    function isSameIdentity(uint256 agentIdA, uint256 agentIdB) external view returns (bool) {
        _requirePinnedRegistry();
        IERC8004IdentityRegistry.Agent memory a = registry.getAgent(agentIdA);
        IERC8004IdentityRegistry.Agent memory b = registry.getAgent(agentIdB);
        if (a.principal != address(0) && a.principal == b.principal) return true;
        if (a.agentWallet != address(0) && a.agentWallet == b.agentWallet) return true;
        return false;
    }

    function _requirePinnedRegistry() private view {
        if (address(registry).codehash != registryCodehash) revert InvalidRegistry();
    }
}
