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

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "./AereSpokePool.sol";

/**
 * @title AereERC7683
 * @notice ERC-7683 IOriginSettler implementation for AERE.
 *         Lets standards-compliant intent aggregators (UniswapX, CoW, Across UI,
 *         1inch Fusion) submit cross-chain intents to AERE via a single uniform
 *         interface. The settler delegates execution to AereSpokePool.
 *
 *   Reference: https://eips.ethereum.org/EIPS/eip-7683
 *
 *   Two intent flavours:
 *     - GaslessCrossChainOrder: user signs an off-chain intent, anyone can open it.
 *         The user pays gas indirectly via the filler. Used for true gasless UX.
 *     - OnchainCrossChainOrder: user submits the intent on-chain themselves.
 *         Used by aggregators that prefer simpler flow.
 *
 *   We implement both. Both delegate to the underlying AereSpokePool.
 */

struct OnchainCrossChainOrder {
    uint32 fillDeadline;
    bytes32 orderDataType;
    bytes orderData;
}

struct GaslessCrossChainOrder {
    address originSettler;
    address user;
    uint256 nonce;
    uint256 originChainId;
    uint32 openDeadline;
    uint32 fillDeadline;
    bytes32 orderDataType;
    bytes orderData;
}

struct Output {
    bytes32 token;
    uint256 amount;
    bytes32 recipient;
    uint256 chainId;
}

struct FillInstruction {
    uint64 destinationChainId;
    bytes32 destinationSettler;
    bytes originData;
}

struct ResolvedCrossChainOrder {
    address user;
    uint256 originChainId;
    uint32 openDeadline;
    uint32 fillDeadline;
    bytes32 orderId;
    Output[] maxSpent;
    Output[] minReceived;
    FillInstruction[] fillInstructions;
}

interface IOriginSettler {
    event Open(bytes32 indexed orderId, ResolvedCrossChainOrder resolvedOrder);

    function open(OnchainCrossChainOrder calldata order) external;
    function openFor(GaslessCrossChainOrder calldata order, bytes calldata signature, bytes calldata originFillerData) external;
    function resolve(OnchainCrossChainOrder calldata order) external view returns (ResolvedCrossChainOrder memory);
    function resolveFor(GaslessCrossChainOrder calldata order, bytes calldata originFillerData) external view returns (ResolvedCrossChainOrder memory);
}

contract AereERC7683 is IOriginSettler, EIP712, ReentrancyGuard {
    using SafeERC20 for IERC20;

    /// AERE-side SpokePool we route deposits through.
    AereSpokePool public immutable spokePool;

    /// Order-data type hash that this settler accepts.
    /// Off-chain code constructs orderData as abi.encode(AereOrder) and uses this constant.
    bytes32 public constant AERE_ORDER_TYPE = keccak256("AereCrossChainOrder(address inputToken,uint256 inputAmount,address outputToken,uint256 outputAmount,uint32 destinationChainId,address recipient)");

    /// EIP-712 type hash for gasless orders.
    bytes32 private constant GASLESS_ORDER_TYPEHASH = keccak256(
        "GaslessCrossChainOrder(address originSettler,address user,uint256 nonce,uint256 originChainId,uint32 openDeadline,uint32 fillDeadline,bytes32 orderDataType,bytes orderData)"
    );

    /// Per-user nonce tracking for gasless orders.
    mapping(address => mapping(uint256 => bool)) public usedNonces;

    struct AereOrderInner {
        address inputToken;
        uint256 inputAmount;
        address outputToken;
        uint256 outputAmount;
        uint32 destinationChainId;
        address recipient;
    }

    constructor(AereSpokePool _spokePool) EIP712("AereERC7683", "1") {
        spokePool = _spokePool;
    }

    // ───────────────────── Onchain order ─────────────────────

    function open(OnchainCrossChainOrder calldata order) external override nonReentrant {
        require(order.orderDataType == AERE_ORDER_TYPE, "ERC7683: unknown orderDataType");
        require(order.fillDeadline > block.timestamp, "ERC7683: deadline passed");

        AereOrderInner memory inner = abi.decode(order.orderData, (AereOrderInner));

        // Pull the input tokens from msg.sender first, then approve SpokePool to take them.
        IERC20(inner.inputToken).safeTransferFrom(msg.sender, address(this), inner.inputAmount);
        IERC20(inner.inputToken).forceApprove(address(spokePool), inner.inputAmount);

        spokePool.deposit(
            inner.inputToken,
            inner.inputAmount,
            inner.outputToken,
            inner.outputAmount,
            inner.destinationChainId,
            inner.recipient,
            order.fillDeadline
        );

        bytes32 orderId = keccak256(abi.encode(msg.sender, order));
        emit Open(orderId, _resolve(msg.sender, order.fillDeadline, 0, inner, orderId));
    }

    function resolve(OnchainCrossChainOrder calldata order) external view override returns (ResolvedCrossChainOrder memory) {
        require(order.orderDataType == AERE_ORDER_TYPE, "ERC7683: unknown orderDataType");
        AereOrderInner memory inner = abi.decode(order.orderData, (AereOrderInner));
        bytes32 orderId = keccak256(abi.encode(msg.sender, order));
        return _resolve(msg.sender, order.fillDeadline, 0, inner, orderId);
    }

    // ───────────────────── Gasless order ─────────────────────

    function openFor(
        GaslessCrossChainOrder calldata order,
        bytes calldata signature,
        bytes calldata /*originFillerData*/
    ) external override nonReentrant {
        require(order.originSettler == address(this), "ERC7683: wrong settler");
        require(order.originChainId == block.chainid, "ERC7683: wrong chain");
        require(order.orderDataType == AERE_ORDER_TYPE, "ERC7683: unknown orderDataType");
        require(order.openDeadline > block.timestamp, "ERC7683: open deadline passed");
        require(order.fillDeadline > block.timestamp, "ERC7683: fill deadline passed");
        require(!usedNonces[order.user][order.nonce], "ERC7683: nonce used");

        // Verify user signature
        bytes32 structHash = keccak256(abi.encode(
            GASLESS_ORDER_TYPEHASH,
            order.originSettler,
            order.user,
            order.nonce,
            order.originChainId,
            order.openDeadline,
            order.fillDeadline,
            order.orderDataType,
            keccak256(order.orderData)
        ));
        bytes32 digest = _hashTypedDataV4(structHash);
        address signer = ECDSA.recover(digest, signature);
        require(signer == order.user, "ERC7683: bad signature");

        usedNonces[order.user][order.nonce] = true;

        AereOrderInner memory inner = abi.decode(order.orderData, (AereOrderInner));

        // Pull input from the USER (they pre-approved this settler).
        IERC20(inner.inputToken).safeTransferFrom(order.user, address(this), inner.inputAmount);
        IERC20(inner.inputToken).forceApprove(address(spokePool), inner.inputAmount);

        spokePool.deposit(
            inner.inputToken,
            inner.inputAmount,
            inner.outputToken,
            inner.outputAmount,
            inner.destinationChainId,
            inner.recipient,
            order.fillDeadline
        );

        bytes32 orderId = keccak256(abi.encode(order));
        emit Open(orderId, _resolve(order.user, order.fillDeadline, order.openDeadline, inner, orderId));
    }

    function resolveFor(
        GaslessCrossChainOrder calldata order,
        bytes calldata /*originFillerData*/
    ) external view override returns (ResolvedCrossChainOrder memory) {
        require(order.orderDataType == AERE_ORDER_TYPE, "ERC7683: unknown orderDataType");
        AereOrderInner memory inner = abi.decode(order.orderData, (AereOrderInner));
        bytes32 orderId = keccak256(abi.encode(order));
        return _resolve(order.user, order.fillDeadline, order.openDeadline, inner, orderId);
    }

    // ───────────────────── Internal ─────────────────────

    function _resolve(
        address user,
        uint32 fillDeadline,
        uint32 openDeadline,
        AereOrderInner memory inner,
        bytes32 orderId
    ) internal view returns (ResolvedCrossChainOrder memory r) {
        r.user = user;
        r.originChainId = block.chainid;
        r.openDeadline = openDeadline;
        r.fillDeadline = fillDeadline;
        r.orderId = orderId;

        r.maxSpent = new Output[](1);
        r.maxSpent[0] = Output({
            token: bytes32(uint256(uint160(inner.inputToken))),
            amount: inner.inputAmount,
            recipient: bytes32(uint256(uint160(address(spokePool)))),
            chainId: block.chainid
        });

        r.minReceived = new Output[](1);
        r.minReceived[0] = Output({
            token: bytes32(uint256(uint160(inner.outputToken))),
            amount: inner.outputAmount,
            recipient: bytes32(uint256(uint160(inner.recipient))),
            chainId: inner.destinationChainId
        });

        r.fillInstructions = new FillInstruction[](1);
        r.fillInstructions[0] = FillInstruction({
            destinationChainId: uint64(inner.destinationChainId),
            destinationSettler: spokePool.remoteSpokePool(inner.destinationChainId),
            originData: abi.encode(
                block.chainid,
                inner,
                fillDeadline,
                user
            )
        });
    }
}
