// 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";

contract AtomicObligationEscrow is AccessControl {
    using SafeERC20 for IERC20;

    bytes32 public constant COORDINATOR_ROLE = keccak256("COORDINATOR_ROLE");

    struct EscrowRecord {
        address token;
        address payer;
        uint256 totalAmount;
        uint256 releasedAmount;
        bool exists;
    }

    mapping(bytes32 => EscrowRecord) public escrows;

    event EscrowFunded(bytes32 indexed obligationId, address indexed token, address indexed payer, uint256 amount);
    event EscrowReleased(bytes32 indexed obligationId, address indexed to, uint256 amount);
    event EscrowRefunded(bytes32 indexed obligationId, address indexed to, uint256 amount);

    error EscrowExists();
    error EscrowMissing();
    error InsufficientEscrow();

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

    function escrowFunds(bytes32 obligationId, address token, address from, uint256 amount)
        external
        onlyRole(COORDINATOR_ROLE)
    {
        if (escrows[obligationId].exists) revert EscrowExists();
        escrows[obligationId] = EscrowRecord({
            token: token,
            payer: from,
            totalAmount: amount,
            releasedAmount: 0,
            exists: true
        });
        IERC20(token).safeTransferFrom(from, address(this), amount);
        emit EscrowFunded(obligationId, token, from, amount);
    }

    function release(bytes32 obligationId, address to, uint256 amount) external onlyRole(COORDINATOR_ROLE) {
        EscrowRecord storage record = escrows[obligationId];
        if (!record.exists) revert EscrowMissing();
        uint256 availableAmount = record.totalAmount - record.releasedAmount;
        if (availableAmount < amount) revert InsufficientEscrow();
        record.releasedAmount += amount;
        IERC20(record.token).safeTransfer(to, amount);
        emit EscrowReleased(obligationId, to, amount);
    }

    function refundRemaining(bytes32 obligationId, address to)
        external
        onlyRole(COORDINATOR_ROLE)
        returns (uint256 refunded)
    {
        EscrowRecord storage record = escrows[obligationId];
        if (!record.exists) revert EscrowMissing();
        refunded = record.totalAmount - record.releasedAmount;
        record.releasedAmount = record.totalAmount;
        IERC20(record.token).safeTransfer(to, refunded);
        emit EscrowRefunded(obligationId, to, refunded);
    }

    function remaining(bytes32 obligationId) external view returns (uint256) {
        EscrowRecord memory record = escrows[obligationId];
        if (!record.exists) revert EscrowMissing();
        return record.totalAmount - record.releasedAmount;
    }
}
