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

import { IPilotAccessControllerV3 } from "../pilot/IPilotAccessControllerV3.sol";

/// @title AccountabilityBondFactoryV3
/// @notice Coordinator-only deployer for accountability bonds.
/// @dev The final governance owner is passed into the child constructor; this
///      factory is never a temporary owner of a deployed bond.
contract AccountabilityBondFactoryV3 {
    uint256 private constant BASE_CHAIN_ID = 8453;
    uint256 private constant BASE_SEPOLIA_CHAIN_ID = 84532;
    error NotCoordinator();
    error ZeroAddress();
    error InitCodeHashMismatch();
    error DeploymentFailed();
    error RuntimeCodeHashMismatch();

    address public immutable coordinator;
    address public immutable bondOwner;
    address public immutable consensus;
    address public immutable disputeRegistry;
    address public immutable pilotController;
    uint256 public immutable maxBondNativePrincipal;
    uint256 public immutable maxAggregateNativeLiability;
    bytes32 public immutable childInitCodeHash;
    bytes32 public immutable expectedChildRuntimeCodeHash;
    bytes private _childInitCode;

    constructor(
        address _coordinator,
        address _bondOwner,
        address _consensus,
        address _disputeRegistry,
        uint256 _maxBondNativePrincipal,
        uint256 _maxAggregateNativeLiability,
        address _pilotController,
        bytes memory childInitCode,
        bytes32 _expectedChildRuntimeCodeHash
    ) {
        if (
            _coordinator == address(0) || _bondOwner == address(0) || _consensus == address(0)
                || childInitCode.length == 0 || _expectedChildRuntimeCodeHash == bytes32(0)
                || ((block.chainid == BASE_CHAIN_ID || block.chainid == BASE_SEPOLIA_CHAIN_ID)
                    && _pilotController == address(0))
        ) revert ZeroAddress();
        if (
            _pilotController != address(0)
                && IPilotAccessControllerV3(_pilotController).requiredChainId() != block.chainid
        ) {
            revert ZeroAddress();
        }
        if (
            _maxBondNativePrincipal == 0 || _maxAggregateNativeLiability == 0
                || _maxBondNativePrincipal > _maxAggregateNativeLiability
        ) revert ZeroAddress();
        coordinator = _coordinator;
        bondOwner = _bondOwner;
        consensus = _consensus;
        _disputeRegistry;
        disputeRegistry = address(0);
        pilotController = _pilotController;
        maxBondNativePrincipal = _maxBondNativePrincipal;
        maxAggregateNativeLiability = _maxAggregateNativeLiability;
        childInitCodeHash = keccak256(childInitCode);
        expectedChildRuntimeCodeHash = _expectedChildRuntimeCodeHash;
        _childInitCode = childInitCode;
    }

    modifier onlyCoordinator() {
        if (msg.sender != coordinator) revert NotCoordinator();
        _;
    }

    /// @notice Deploy the exactly committed child initcode without embedding it
    ///         in this factory's runtime. This keeps the deployer comfortably
    ///         below EIP-170 while preserving constructor semantics.
    function deploy() external onlyCoordinator returns (address bond) {
        bytes memory code = _childInitCode;
        if (keccak256(code) != childInitCodeHash) revert InitCodeHashMismatch();
        assembly ("memory-safe") {
            bond := create(0, add(code, 0x20), mload(code))
        }
        if (bond == address(0)) revert DeploymentFailed();
        if (bond.codehash != expectedChildRuntimeCodeHash) revert RuntimeCodeHashMismatch();
    }
}
