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

import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

/**
 * @title ProxyFactory
 * @notice Factory for deploying upgradeable proxies (UUPS and Beacon patterns)
 * @dev Tracks all deployed proxies for the universal bridge system
 */
contract ProxyFactory is AccessControl {
    bytes32 public constant DEPLOYER_ROLE = keccak256("DEPLOYER_ROLE");

    struct ProxyInfo {
        address proxy;
        address implementation;
        ProxyType proxyType;
        uint256 deployedAt;
        address deployer;
        bool isActive;
    }

    enum ProxyType {
        UUPS,
        Beacon,
        Transparent
    }

    // Storage
    mapping(address => ProxyInfo) public proxies;
    address[] public allProxies;
    mapping(address => address[]) public proxiesByImplementation;
    mapping(address => UpgradeableBeacon) public beacons;

    event ProxyDeployed(
        address indexed proxy,
        address indexed implementation,
        ProxyType proxyType,
        address deployer
    );
    
    event BeaconCreated(
        address indexed beacon,
        address indexed implementation
    );

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

    /**
     * @notice Deploy UUPS proxy
     */
    function deployUUPSProxy(
        address implementation,
        bytes calldata initData
    ) external onlyRole(DEPLOYER_ROLE) returns (address proxy) {
        require(implementation != address(0), "Zero implementation");
        require(implementation.code.length > 0, "Not a contract");
        
        // Deploy proxy
        proxy = address(new ERC1967Proxy(implementation, initData));
        
        // Track proxy
        _trackProxy(proxy, implementation, ProxyType.UUPS);
        
        emit ProxyDeployed(proxy, implementation, ProxyType.UUPS, msg.sender);
        
        return proxy;
    }

    /**
     * @notice Deploy beacon proxy
     */
    function deployBeaconProxy(
        address beacon,
        bytes calldata initData
    ) external onlyRole(DEPLOYER_ROLE) returns (address proxy) {
        require(beacon != address(0), "Zero beacon");
        
        // Get implementation from beacon
        address implementation = UpgradeableBeacon(beacon).implementation();
        
        // Deploy beacon proxy
        proxy = address(new BeaconProxy(beacon, initData));
        
        // Track proxy
        _trackProxy(proxy, implementation, ProxyType.Beacon);
        
        emit ProxyDeployed(proxy, implementation, ProxyType.Beacon, msg.sender);
        
        return proxy;
    }

    /**
     * @notice Create beacon for implementation
     */
    function createBeacon(address implementation) external onlyRole(DEPLOYER_ROLE) returns (address beacon) {
        require(implementation != address(0), "Zero implementation");
        require(implementation.code.length > 0, "Not a contract");
        
        UpgradeableBeacon newBeacon = new UpgradeableBeacon(implementation, address(this));
        beacons[implementation] = newBeacon;
        
        emit BeaconCreated(address(newBeacon), implementation);
        
        return address(newBeacon);
    }

    /**
     * @notice Track deployed proxy
     */
    function _trackProxy(
        address proxy,
        address implementation,
        ProxyType proxyType
    ) internal {
        proxies[proxy] = ProxyInfo({
            proxy: proxy,
            implementation: implementation,
            proxyType: proxyType,
            deployedAt: block.timestamp,
            deployer: msg.sender,
            isActive: true
        });
        
        allProxies.push(proxy);
        proxiesByImplementation[implementation].push(proxy);
    }

    // View functions
    
    function getProxyInfo(address proxy) external view returns (ProxyInfo memory) {
        return proxies[proxy];
    }

    function getAllProxies() external view returns (address[] memory) {
        return allProxies;
    }

    function getProxiesByImplementation(address implementation) external view returns (address[] memory) {
        return proxiesByImplementation[implementation];
    }

    function getProxyCount() external view returns (uint256) {
        return allProxies.length;
    }

    function isProxy(address addr) external view returns (bool) {
        return proxies[addr].isActive;
    }
}
