// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.17;

import "../ERC5725.sol";

contract LinearVestingNFT is ERC5725 {
    using SafeERC20 for IERC20;

    struct VestDetails {
        IERC20 payoutToken; /// @dev payout token
        uint256 payout; /// @dev payout token remaining to be paid
        uint128 startTime; /// @dev when vesting starts
        uint128 endTime; /// @dev when vesting end
        uint128 cliff; /// @dev duration in seconds of the cliff in which tokens will be begin releasing
    }
    mapping(uint256 => VestDetails) public vestDetails; /// @dev maps the vesting data with tokenIds

    /// @dev tracker of current NFT id
    uint256 private _tokenIdTracker;

    /**
     * @dev See {IERC5725}.
     */
    constructor(string memory name, string memory symbol) ERC721(name, symbol) {}

    /**
     * @notice Creates a new vesting NFT and mints it
     * @dev Token amount should be approved to be transferred by this contract before executing create
     * @param to The recipient of the NFT
     * @param amount The total assets to be locked over time
     * @param startTime When the vesting starts in epoch timestamp
     * @param duration The vesting duration in seconds
     * @param cliff The cliff duration in seconds
     * @param token The ERC20 token to vest over time
     */
    function create(
        address to,
        uint256 amount,
        uint128 startTime,
        uint128 duration,
        uint128 cliff,
        IERC20 token
    ) public virtual {
        require(startTime >= block.timestamp, "startTime cannot be on the past");
        require(to != address(0), "to cannot be address 0");
        require(cliff <= duration, "duration needs to be more than cliff");

        uint256 newTokenId = _tokenIdTracker;

        vestDetails[newTokenId] = VestDetails({
            payoutToken: token,
            payout: amount,
            startTime: startTime,
            endTime: startTime + duration,
            cliff: startTime + cliff
        });

        _tokenIdTracker++;
        _mint(to, newTokenId);
        IERC20(payoutToken(newTokenId)).safeTransferFrom(msg.sender, address(this), amount);
    }

    /**
     * @dev See {IERC5725}.
     */
    function vestedPayoutAtTime(uint256 tokenId, uint256 timestamp)
        public
        view
        override(ERC5725)
        validToken(tokenId)
        returns (uint256 payout)
    {
        if (timestamp < _cliff(tokenId)) {
            return 0;
        }
        if (timestamp > _endTime(tokenId)) {
            return _payout(tokenId);
        }
        return (_payout(tokenId) * (timestamp - _startTime(tokenId))) / (_endTime(tokenId) - _startTime(tokenId));
    }

    /**
     * @dev See {ERC5725}.
     */
    function _payoutToken(uint256 tokenId) internal view override returns (address) {
        return address(vestDetails[tokenId].payoutToken);
    }

    /**
     * @dev See {ERC5725}.
     */
    function _payout(uint256 tokenId) internal view override returns (uint256) {
        return vestDetails[tokenId].payout;
    }

    /**
     * @dev See {ERC5725}.
     */
    function _startTime(uint256 tokenId) internal view override returns (uint256) {
        return vestDetails[tokenId].startTime;
    }

    /**
     * @dev See {ERC5725}.
     */
    function _endTime(uint256 tokenId) internal view override returns (uint256) {
        return vestDetails[tokenId].endTime;
    }

    /**
     * @dev Internal function to get the cliff time of a given linear vesting NFT
     *
     * @param tokenId to check
     * @return uint256 the cliff time in seconds
     */
    function _cliff(uint256 tokenId) internal view returns (uint256) {
        return vestDetails[tokenId].cliff;
    }
}
