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

import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "./interfaces/ISeason1.sol";

/**
 * @title AereQuestCheckIn — "First Flight" soulbound attestation
 * @notice A SOULBOUND (non-transferable) ERC-721-shaped attestation minted
 *         exactly once per account. It is the on-chain "First Flight" marker of
 *         Season 1 onboarding, intended to be minted via an
 *         AereOnboardingPaymaster-sponsored UserOp so the user pays no gas.
 *
 *   Soulbound: tokenId is deterministically uint256(uint160(account)), so an
 *   account can hold at most one, and it can never be transferred — every
 *   transfer/approval entrypoint HARD-REVERTS (Soulbound).
 *
 *   ERC-721 read surface (ownerOf/balanceOf/tokenURI/supportsInterface) and the
 *   mint Transfer event are provided so wallets and the AERE indexer can render
 *   the badge. There is no burn (the attestation is permanent).
 *
 *   No owner / no admin: the only state transition is a one-shot self check-in.
 *   The referral registry reads `hasCheckedIn` as one of its activation gates.
 */
contract AereQuestCheckIn is IERC165, IAereQuestCheckIn {

    string public constant name = "AERE First Flight";
    string public constant symbol = "FLIGHT";

    /// @notice account => minted.
    mapping(address => bool) private _minted;
    uint256 public totalCheckedIn;

    /// @dev ERC-721 events (mint only; no transfers ever happen).
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event CheckedIn(address indexed account, uint256 tokenId, uint256 timestamp);

    error Soulbound();
    error AlreadyCheckedIn();
    error NotMinted();
    error ZeroAccount();

    /* ------------------------------- check-in ------------------------------- */

    /// @notice Mint the caller's First-Flight attestation. One per account.
    ///         In production this is called by the user's (passkey) smart
    ///         account inside a paymaster-sponsored UserOp, so msg.sender is
    ///         that account and the badge is bound to it.
    function checkIn() external returns (uint256 tokenId) {
        return _checkIn(msg.sender);
    }

    function _checkIn(address account) internal returns (uint256 tokenId) {
        if (account == address(0)) revert ZeroAccount();
        if (_minted[account]) revert AlreadyCheckedIn();
        _minted[account] = true;
        totalCheckedIn += 1;
        tokenId = uint256(uint160(account));
        emit Transfer(address(0), account, tokenId);
        emit CheckedIn(account, tokenId, block.timestamp);
    }

    /* --------------------------------- views -------------------------------- */

    function hasCheckedIn(address account) external view returns (bool) {
        return _minted[account];
    }

    function balanceOf(address account) external view returns (uint256) {
        if (account == address(0)) revert ZeroAccount();
        return _minted[account] ? 1 : 0;
    }

    function ownerOf(uint256 tokenId) public view returns (address) {
        address account = address(uint160(tokenId));
        if (!_minted[account]) revert NotMinted();
        return account;
    }

    function tokenURI(uint256 tokenId) external view returns (string memory) {
        ownerOf(tokenId); // reverts NotMinted if absent
        // Static on-chain metadata; no external hosting dependency.
        return "data:application/json;base64,eyJuYW1lIjoiQUVSRSBGaXJzdCBGbGlnaHQiLCJkZXNjcmlwdGlvbiI6IlNvdWxib3VuZCBTZWFzb24gMSBBbHRpdHVkZSBjaGVjay1pbi4gUG9pbnRzIGFyZSBub3QgYSB0b2tlbiBhbmQgaGF2ZSBubyBndWFyYW50ZWVkIHZhbHVlLiJ9";
    }

    function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
        return
            interfaceId == type(IERC165).interfaceId ||
            interfaceId == 0x80ac58cd || // ERC-721
            interfaceId == 0x5b5e139f;   // ERC-721 Metadata
    }

    /* --------------------------- bricked ERC-721 write ---------------------- */

    function transferFrom(address, address, uint256) external pure { revert Soulbound(); }
    function safeTransferFrom(address, address, uint256) external pure { revert Soulbound(); }
    function safeTransferFrom(address, address, uint256, bytes calldata) external pure { revert Soulbound(); }
    function approve(address, uint256) external pure { revert Soulbound(); }
    function setApprovalForAll(address, bool) external pure { revert Soulbound(); }
    function getApproved(uint256) external pure returns (address) { return address(0); }
    function isApprovedForAll(address, address) external pure returns (bool) { return false; }
}
