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

import {
    ERC721Enumerable
} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { IOptimismMintableERC721 } from "./IOptimismMintableERC721.sol";
import { Semver } from "../universal/Semver.sol";

/// @title OptimismMintableERC721
/// @notice This contract is the remote representation for some token that lives on another network,
///         typically an Optimism representation of an Ethereum-based token. Standard reference
///         implementation that can be extended or modified according to your needs.
contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721, Semver {
    /// @inheritdoc IOptimismMintableERC721
    uint256 public immutable REMOTE_CHAIN_ID;

    /// @inheritdoc IOptimismMintableERC721
    address public immutable REMOTE_TOKEN;

    /// @inheritdoc IOptimismMintableERC721
    address public immutable BRIDGE;

    /// @notice Base token URI for this token.
    string public baseTokenURI;

    /// @notice Modifier that prevents callers other than the bridge from calling the function.
    modifier onlyBridge() {
        require(msg.sender == BRIDGE, "OptimismMintableERC721: only bridge can call this function");
        _;
    }

    /// @custom:semver 1.1.1
    /// @param _bridge        Address of the bridge on this network.
    /// @param _remoteChainId Chain ID where the remote token is deployed.
    /// @param _remoteToken   Address of the corresponding token on the other network.
    /// @param _name          ERC721 name.
    /// @param _symbol        ERC721 symbol.
    constructor(
        address _bridge,
        uint256 _remoteChainId,
        address _remoteToken,
        string memory _name,
        string memory _symbol
    ) ERC721(_name, _symbol) Semver(1, 1, 1) {
        require(_bridge != address(0), "OptimismMintableERC721: bridge cannot be address(0)");
        require(_remoteChainId != 0, "OptimismMintableERC721: remote chain id cannot be zero");
        require(
            _remoteToken != address(0),
            "OptimismMintableERC721: remote token cannot be address(0)"
        );

        REMOTE_CHAIN_ID = _remoteChainId;
        REMOTE_TOKEN = _remoteToken;
        BRIDGE = _bridge;

        // Creates a base URI in the format specified by EIP-681:
        // https://eips.ethereum.org/EIPS/eip-681
        baseTokenURI = string(
            abi.encodePacked(
                "ethereum:",
                Strings.toHexString(uint160(_remoteToken), 20),
                "@",
                Strings.toString(_remoteChainId),
                "/tokenURI?uint256="
            )
        );
    }

    /// @inheritdoc IOptimismMintableERC721
    function remoteChainId() external view returns (uint256) {
        return REMOTE_CHAIN_ID;
    }

    /// @inheritdoc IOptimismMintableERC721
    function remoteToken() external view returns (address) {
        return REMOTE_TOKEN;
    }

    /// @inheritdoc IOptimismMintableERC721
    function bridge() external view returns (address) {
        return BRIDGE;
    }

    /// @inheritdoc IOptimismMintableERC721
    function safeMint(address _to, uint256 _tokenId) external virtual onlyBridge {
        _safeMint(_to, _tokenId);

        emit Mint(_to, _tokenId);
    }

    /// @inheritdoc IOptimismMintableERC721
    function burn(address _from, uint256 _tokenId) external virtual onlyBridge {
        _burn(_tokenId);

        emit Burn(_from, _tokenId);
    }

    /// @notice Checks if a given interface ID is supported by this contract.
    /// @param _interfaceId The interface ID to check.
    /// @return True if the interface ID is supported, false otherwise.
    function supportsInterface(bytes4 _interfaceId)
        public
        view
        override(ERC721Enumerable, IERC165)
        returns (bool)
    {
        bytes4 iface = type(IOptimismMintableERC721).interfaceId;
        return _interfaceId == iface || super.supportsInterface(_interfaceId);
    }

    /// @notice Returns the base token URI.
    /// @return Base token URI.
    function _baseURI() internal view virtual override returns (string memory) {
        return baseTokenURI;
    }
}
