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

/**
 * @title Vaultfire Name Service (VNS)
 * @notice On-chain registry mapping human-readable .vaultfire names to addresses
 * @dev Simple ENS-style registry for the Vaultfire Protocol ecosystem
 *
 * Features:
 * - Register a .vaultfire name for an address (one name per address)
 * - Look up an address by name
 * - Look up a name by address (reverse resolution)
 * - Names are 3-32 characters, lowercase alphanumeric + hyphens
 * - Free to register — users only pay gas
 * - Owner can transfer name to a new address
 *
 * @custom:security No admin functions — fully permissionless
 * @custom:ethics Privacy-first: no KYC, wallet addresses only
 */
contract VaultfireNameService {

    struct NameRecord {
        address owner;
        uint256 registeredAt;
        bool exists;
    }

    /// @notice name hash => record
    mapping(bytes32 => NameRecord) private _names;

    /// @notice address => name string (reverse lookup)
    mapping(address => string) private _reverseNames;

    /// @notice address => whether they have a registered name
    mapping(address => bool) private _hasName;

    /// @notice Total number of registered names
    uint256 public totalNames;

    // --- Events ---

    event NameRegistered(
        string indexed nameIndexed,
        string name,
        address indexed owner,
        uint256 timestamp
    );

    event NameTransferred(
        string indexed nameIndexed,
        string name,
        address indexed previousOwner,
        address indexed newOwner,
        uint256 timestamp
    );

    event NameReleased(
        string indexed nameIndexed,
        string name,
        address indexed previousOwner,
        uint256 timestamp
    );

    // --- Core Functions ---

    /**
     * @notice Register a .vaultfire name for msg.sender
     * @param name The name to register (without .vaultfire suffix)
     * @dev Name must be 3-32 chars, lowercase alphanumeric + hyphens
     *      One name per address. Name must not already be taken.
     */
    function registerName(string calldata name) external {
        require(!_hasName[msg.sender], "Address already has a name");

        bytes memory nameBytes = bytes(name);
        require(nameBytes.length >= 3 && nameBytes.length <= 32, "Name must be 3-32 characters");

        // Validate characters: lowercase a-z, 0-9, hyphens (not at start/end)
        for (uint256 i = 0; i < nameBytes.length; i++) {
            bytes1 c = nameBytes[i];
            bool isLower = (c >= 0x61 && c <= 0x7A); // a-z
            bool isDigit = (c >= 0x30 && c <= 0x39); // 0-9
            bool isHyphen = (c == 0x2D);              // -
            require(isLower || isDigit || isHyphen, "Invalid character");
            if (i == 0 || i == nameBytes.length - 1) {
                require(!isHyphen, "Cannot start or end with hyphen");
            }
        }

        bytes32 nameHash = keccak256(nameBytes);
        require(!_names[nameHash].exists, "Name already taken");

        _names[nameHash] = NameRecord({
            owner: msg.sender,
            registeredAt: block.timestamp,
            exists: true
        });

        _reverseNames[msg.sender] = name;
        _hasName[msg.sender] = true;
        totalNames++;

        emit NameRegistered(name, name, msg.sender, block.timestamp);
    }

    /**
     * @notice Transfer your name to a new address
     * @param newOwner The address to transfer the name to
     * @dev New owner must not already have a name
     */
    function transferName(address newOwner) external {
        require(_hasName[msg.sender], "No name to transfer");
        require(newOwner != address(0), "Cannot transfer to zero address");
        require(newOwner != msg.sender, "Cannot transfer to self");
        require(!_hasName[newOwner], "New owner already has a name");

        string memory name = _reverseNames[msg.sender];
        bytes32 nameHash = keccak256(bytes(name));

        _names[nameHash].owner = newOwner;
        _reverseNames[newOwner] = name;
        _hasName[newOwner] = true;

        delete _reverseNames[msg.sender];
        _hasName[msg.sender] = false;

        emit NameTransferred(name, name, msg.sender, newOwner, block.timestamp);
    }

    /**
     * @notice Release your name, making it available for others
     */
    function releaseName() external {
        require(_hasName[msg.sender], "No name to release");

        string memory name = _reverseNames[msg.sender];
        bytes32 nameHash = keccak256(bytes(name));

        delete _names[nameHash];
        delete _reverseNames[msg.sender];
        _hasName[msg.sender] = false;
        totalNames--;

        emit NameReleased(name, name, msg.sender, block.timestamp);
    }

    // --- View Functions ---

    /**
     * @notice Look up an address by .vaultfire name
     * @param name The name to look up (without .vaultfire suffix)
     * @return owner The address that owns the name (zero if not registered)
     * @return registeredAt The timestamp when the name was registered
     * @return exists Whether the name is registered
     */
    function resolveNameToAddress(string calldata name)
        external
        view
        returns (address owner, uint256 registeredAt, bool exists)
    {
        bytes32 nameHash = keccak256(bytes(name));
        NameRecord memory record = _names[nameHash];
        return (record.owner, record.registeredAt, record.exists);
    }

    /**
     * @notice Look up a name by address (reverse resolution)
     * @param addr The address to look up
     * @return name The .vaultfire name (empty string if none)
     * @return hasRegistered Whether the address has a registered name
     */
    function resolveAddressToName(address addr)
        external
        view
        returns (string memory name, bool hasRegistered)
    {
        return (_reverseNames[addr], _hasName[addr]);
    }

    /**
     * @notice Check if a name is available
     * @param name The name to check
     * @return available Whether the name can be registered
     */
    function isNameAvailable(string calldata name)
        external
        view
        returns (bool available)
    {
        bytes32 nameHash = keccak256(bytes(name));
        return !_names[nameHash].exists;
    }

    /**
     * @notice Check if an address has a registered name
     * @param addr The address to check
     * @return Whether the address has a name
     */
    function hasRegisteredName(address addr)
        external
        view
        returns (bool)
    {
        return _hasName[addr];
    }
}
