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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
 * @title CWVotingEscrow
 * @notice Minimal vote-escrow (veCW) for emission-direction governance.
 */
contract CWVotingEscrow is AccessControl {
    using SafeERC20 for IERC20;

    IERC20 public immutable escrowToken;
    uint256 public constant MAX_LOCK_DURATION = 4 * 365 days;

    struct Lock {
        uint256 amount;
        uint256 unlockAt;
        uint256 votingPower;
    }

    mapping(address => Lock[]) public locks;

    event Locked(address indexed account, uint256 amount, uint256 unlockAt, uint256 votingPower);
    event Withdrawn(address indexed account, uint256 amount);

    error ZeroAddress();
    error InvalidLock();
    error NothingToWithdraw();

    constructor(address admin, address escrowToken_) {
        if (admin == address(0) || escrowToken_ == address(0)) {
            revert ZeroAddress();
        }
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        escrowToken = IERC20(escrowToken_);
    }

    function createLock(uint256 amount, uint256 lockDuration) external {
        if (amount == 0 || lockDuration == 0 || lockDuration > MAX_LOCK_DURATION) {
            revert InvalidLock();
        }
        escrowToken.safeTransferFrom(msg.sender, address(this), amount);

        uint256 unlockAt = block.timestamp + lockDuration;
        uint256 votingPower = (amount * lockDuration) / MAX_LOCK_DURATION;

        locks[msg.sender].push(Lock({amount: amount, unlockAt: unlockAt, votingPower: votingPower}));
        emit Locked(msg.sender, amount, unlockAt, votingPower);
    }

    function withdraw(uint256 lockIndex) external {
        Lock storage entry = locks[msg.sender][lockIndex];
        if (entry.amount == 0 || block.timestamp < entry.unlockAt) {
            revert NothingToWithdraw();
        }
        uint256 amount = entry.amount;
        entry.amount = 0;
        entry.votingPower = 0;
        escrowToken.safeTransfer(msg.sender, amount);
        emit Withdrawn(msg.sender, amount);
    }

    function votingPowerOf(address account) external view returns (uint256 total) {
        Lock[] storage userLocks = locks[account];
        for (uint256 i = 0; i < userLocks.length; i++) {
            if (userLocks[i].amount > 0 && block.timestamp < userLocks[i].unlockAt) {
                total += userLocks[i].votingPower;
            }
        }
    }
}
