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

/**
 * @title AereIdentity
 * @notice Lightweight on-chain DID registry. Each address controls its own identity:
 *         off-chain signed credentials, profile metadata URI, and revocable claims
 *         issued by trusted attestors (KYC providers, employers, communities).
 * @dev Compatible-in-spirit with W3C DID + ERC-1056. Profile metadata stored as a
 *      content-addressed URI (e.g. ipfs://...) so the chain holds only the pointer.
 */
contract AereIdentity {
    // ── Profiles ────────────────────────────────────────────────
    mapping(address => string) public profileURI;
    mapping(address => uint64) public profileUpdatedAt;

    event ProfileUpdated(address indexed identity, string uri, uint64 timestamp);

    function setProfile(string calldata uri) external {
        profileURI[msg.sender] = uri;
        profileUpdatedAt[msg.sender] = uint64(block.timestamp);
        emit ProfileUpdated(msg.sender, uri, uint64(block.timestamp));
    }

    // ── Attestors ───────────────────────────────────────────────
    mapping(address => bool) public isAttestor;
    address public owner;

    event AttestorAdded(address indexed attestor);
    event AttestorRemoved(address indexed attestor);

    constructor() { owner = msg.sender; isAttestor[msg.sender] = true; }

    modifier onlyOwner() { require(msg.sender == owner, "owner"); _; }

    function addAttestor(address a) external onlyOwner { isAttestor[a] = true; emit AttestorAdded(a); }
    function removeAttestor(address a) external onlyOwner { isAttestor[a] = false; emit AttestorRemoved(a); }
    function transferOwnership(address newOwner) external onlyOwner { require(newOwner != address(0)); owner = newOwner; }

    // ── Claims ──────────────────────────────────────────────────
    /// @dev claimType examples: keccak256("KYC_TIER_1"), keccak256("EMAIL_VERIFIED"),
    ///      keccak256("DAO_MEMBER:ENS"), keccak256("AGE_OVER_18"), etc.
    struct Claim {
        address attestor;
        uint64 issuedAt;
        uint64 expiresAt; // 0 = never
        bytes32 evidence; // optional hash of off-chain document
        bool revoked;
    }

    // subject => claimType => attestor => Claim
    mapping(address => mapping(bytes32 => mapping(address => Claim))) public claims;

    event ClaimIssued(address indexed subject, bytes32 indexed claimType, address indexed attestor, uint64 expiresAt, bytes32 evidence);
    event ClaimRevoked(address indexed subject, bytes32 indexed claimType, address indexed attestor);

    function issueClaim(address subject, bytes32 claimType, uint64 expiresAt, bytes32 evidence) external {
        require(isAttestor[msg.sender], "not attestor");
        require(subject != address(0), "subject");
        claims[subject][claimType][msg.sender] = Claim({
            attestor: msg.sender,
            issuedAt: uint64(block.timestamp),
            expiresAt: expiresAt,
            evidence: evidence,
            revoked: false
        });
        emit ClaimIssued(subject, claimType, msg.sender, expiresAt, evidence);
    }

    function revokeClaim(address subject, bytes32 claimType) external {
        Claim storage c = claims[subject][claimType][msg.sender];
        require(c.attestor == msg.sender, "not issuer");
        require(!c.revoked, "already");
        c.revoked = true;
        emit ClaimRevoked(subject, claimType, msg.sender);
    }

    /// @notice Check if a subject has a non-expired, non-revoked claim of a given type
    ///         from a specific attestor.
    function hasValidClaim(address subject, bytes32 claimType, address attestor) external view returns (bool) {
        Claim memory c = claims[subject][claimType][attestor];
        if (c.attestor == address(0) || c.revoked) return false;
        if (c.expiresAt != 0 && block.timestamp > c.expiresAt) return false;
        return true;
    }
}
