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

/// @notice Root-owner surface of AereModularAccount used by this module.
interface IAereModularAccountOwner {
    function rootOwner() external view returns (address);
    function setRootOwner(address newOwner) external;
}

/// @title AereSocialRecoveryModule - M-of-N guardian recovery for AereModularAccount
/// @notice Singleton executor module (ERC-7579 module type 2). Per-account state is
///         keyed by the account address. Install this module on the account as
///         module type 2; executeRecovery then calls account.setRootOwner(), which
///         AereModularAccount permits for installed executor modules.
///
///         Flow: guardian initiateRecovery -> other guardians supportRecovery ->
///         after a 48 hour timelock AND >= threshold approvals, any guardian may
///         executeRecovery. The current owner (or the account itself) can
///         cancelRecovery at any time before execution.
///
///         Guardian set and threshold changes are OWNER-ONLY: mutating config
///         functions require msg.sender == the account, and the account's
///         execute() path is gated to EntryPoint / self / root owner.
contract AereSocialRecoveryModule {
    uint256 public constant RECOVERY_DELAY = 48 hours;

    struct Recovery {
        address newOwner;
        uint64 executeAfter; // initiation time + RECOVERY_DELAY
        uint32 approvals;
        bool active;
    }

    /// @notice account => guardian => is guardian.
    mapping(address => mapping(address => bool)) public isGuardian;
    /// @notice account => guardian list (for views + uninstall cleanup).
    mapping(address => address[]) internal _guardianList;
    /// @notice account => M in M-of-N.
    mapping(address => uint256) public thresholdOf;
    /// @notice account => monotonically increasing recovery round id.
    mapping(address => uint256) public recoveryNonce;
    /// @notice account => currently active recovery (if any).
    mapping(address => Recovery) public activeRecovery;
    /// @notice account => round id => guardian => approved.
    mapping(address => mapping(uint256 => mapping(address => bool))) public hasApproved;

    event GuardianAdded(address indexed account, address indexed guardian);
    event GuardianRemoved(address indexed account, address indexed guardian);
    event ThresholdChanged(address indexed account, uint256 threshold);
    event RecoveryInitiated(address indexed account, uint256 indexed round, address indexed newOwner, address guardian, uint64 executeAfter);
    event RecoverySupported(address indexed account, uint256 indexed round, address indexed guardian, uint256 approvals);
    event RecoveryExecuted(address indexed account, uint256 indexed round, address indexed newOwner);
    event RecoveryCancelled(address indexed account, uint256 indexed round, address canceller);

    error NotGuardian();
    error NotOwner();
    error InvalidGuardian();
    error InvalidThreshold();
    error InvalidNewOwner();
    error RecoveryAlreadyActive();
    error NoActiveRecovery();
    error AlreadyApproved();
    error BelowThreshold();
    error TimelockNotElapsed(uint64 executeAfter, uint256 nowTimestamp);

    // ---- ERC-7579-style module base -------------------------------------------

    function isModuleType(uint256 moduleTypeId) external pure returns (bool) {
        return moduleTypeId == 2;
    }

    /// @notice initData layout: abi.encode(address[] guardians, uint256 threshold).
    function onInstall(bytes calldata data) external {
        (address[] memory guardians, uint256 threshold) = abi.decode(data, (address[], uint256));
        address account = msg.sender;
        for (uint256 i = 0; i < guardians.length; i++) {
            _addGuardian(account, guardians[i]);
        }
        if (threshold == 0 || threshold > _guardianList[account].length) revert InvalidThreshold();
        thresholdOf[account] = threshold;
        emit ThresholdChanged(account, threshold);
    }

    /// @notice Full per-account cleanup so a later reinstall starts clean.
    function onUninstall(bytes calldata) external {
        address account = msg.sender;
        address[] storage list = _guardianList[account];
        for (uint256 i = 0; i < list.length; i++) {
            isGuardian[account][list[i]] = false;
        }
        delete _guardianList[account];
        delete thresholdOf[account];
        delete activeRecovery[account];
    }

    // ---- Owner-only guardian management (msg.sender = the smart account) ------

    function addGuardian(address guardian) external {
        _addGuardian(msg.sender, guardian);
    }

    function removeGuardian(address guardian) external {
        address account = msg.sender;
        if (!isGuardian[account][guardian]) revert InvalidGuardian();
        isGuardian[account][guardian] = false;
        address[] storage list = _guardianList[account];
        for (uint256 i = 0; i < list.length; i++) {
            if (list[i] == guardian) {
                list[i] = list[list.length - 1];
                list.pop();
                break;
            }
        }
        if (thresholdOf[account] > list.length) revert InvalidThreshold();
        emit GuardianRemoved(account, guardian);
    }

    function setThreshold(uint256 threshold) external {
        address account = msg.sender;
        if (threshold == 0 || threshold > _guardianList[account].length) revert InvalidThreshold();
        thresholdOf[account] = threshold;
        emit ThresholdChanged(account, threshold);
    }

    function guardiansOf(address account) external view returns (address[] memory) {
        return _guardianList[account];
    }

    function _addGuardian(address account, address guardian) internal {
        if (guardian == address(0) || guardian == account) revert InvalidGuardian();
        if (isGuardian[account][guardian]) revert InvalidGuardian();
        isGuardian[account][guardian] = true;
        _guardianList[account].push(guardian);
        emit GuardianAdded(account, guardian);
    }

    // ---- Recovery flow ----------------------------------------------------------

    function initiateRecovery(address account, address newOwner) external {
        if (!isGuardian[account][msg.sender]) revert NotGuardian();
        if (newOwner == address(0)) revert InvalidNewOwner();
        if (activeRecovery[account].active) revert RecoveryAlreadyActive();
        uint256 round = ++recoveryNonce[account];
        uint64 executeAfter = uint64(block.timestamp + RECOVERY_DELAY);
        activeRecovery[account] = Recovery({
            newOwner: newOwner,
            executeAfter: executeAfter,
            approvals: 1,
            active: true
        });
        hasApproved[account][round][msg.sender] = true;
        emit RecoveryInitiated(account, round, newOwner, msg.sender, executeAfter);
    }

    function supportRecovery(address account) external {
        if (!isGuardian[account][msg.sender]) revert NotGuardian();
        Recovery storage r = activeRecovery[account];
        if (!r.active) revert NoActiveRecovery();
        uint256 round = recoveryNonce[account];
        if (hasApproved[account][round][msg.sender]) revert AlreadyApproved();
        hasApproved[account][round][msg.sender] = true;
        r.approvals += 1;
        emit RecoverySupported(account, round, msg.sender, r.approvals);
    }

    /// @notice Guardian-only. Requires >= threshold approvals AND the 48h timelock.
    ///         Calls account.setRootOwner(newOwner); this module must be installed
    ///         on the account as an executor (module type 2) for that to succeed.
    function executeRecovery(address account) external {
        if (!isGuardian[account][msg.sender]) revert NotGuardian();
        Recovery memory r = activeRecovery[account];
        if (!r.active) revert NoActiveRecovery();
        if (r.approvals < thresholdOf[account]) revert BelowThreshold();
        if (block.timestamp < r.executeAfter) revert TimelockNotElapsed(r.executeAfter, block.timestamp);
        delete activeRecovery[account];
        IAereModularAccountOwner(account).setRootOwner(r.newOwner);
        emit RecoveryExecuted(account, recoveryNonce[account], r.newOwner);
    }

    /// @notice Cancellable by the current root owner or by the account itself.
    function cancelRecovery(address account) external {
        if (msg.sender != account && msg.sender != IAereModularAccountOwner(account).rootOwner()) {
            revert NotOwner();
        }
        if (!activeRecovery[account].active) revert NoActiveRecovery();
        delete activeRecovery[account];
        emit RecoveryCancelled(account, recoveryNonce[account], msg.sender);
    }
}
