"""Heart of Gold locations -- Entry Bay, Corridors, Bridge, Galley, Engine Room,
Pantry, Hatchway, Access Space, Ramp."""

import random

from h2g2.engine.game_object import (
    GameObject, Room, Flag, Direction,
    DirectExit, ConditionalExit, BlockedExit,
)
from h2g2.engine.world import World
from h2g2.engine.state import GameState


# ---- Tool list for Marvin's random request ----

_MARVIN_TOOLS = [
    "screwdriver", "toothbrush", "wrench", "pliers", "rasp", "tweezers",
]


# ---- Object action handlers ----

def _sales_brochure_action(state: GameState) -> bool:
    out = state.output
    if state.prsa in ("read", "examine"):
        out.tell(
            "The brochure reads:\n\n"
            '"The Heart of Gold -- the sleekest, most advanced, most '
            "hip spaceship in the Galaxy. Boasting the revolutionary "
            "Infinite Improbability Drive, the Heart of Gold can take "
            "you anywhere in the Universe almost before you've decided "
            "where you want to go. Seats 300. Complimentary peanuts. "
            'Not liable for loss of limbs, sanity, or probability.\"\n'
        )
        return True
    return False


def _eddie_action(state: GameState) -> bool:
    out = state.output
    if state.prsa == "examine":
        out.tell(
            "Eddie is the shipboard computer. His cheery manner is "
            "almost unbearably irritating.\n"
        )
        return True
    if state.prsa in ("tell", "ask", "talk"):
        out.tell(
            '"Hi there!" says Eddie brightly. "I\'m Eddie, your '
            "shipboard computer, and I'm feeling just great, guys, "
            "and I know I'm just going to get a bundle of joy out of "
            'whatever you do next!"\n'
        )
        return True
    return False


def _zaphod_action(state: GameState) -> bool:
    out = state.output
    if state.prsa == "examine":
        out.tell(
            "Zaphod Beeblebrox, adventurer, ex-hippie, good-timer, "
            "(fraud? quite possibly), manic self-publicist, terribly "
            "bad at personal relationships, often thought to be "
            "completely out to lunch. President of the Galaxy.\n"
        )
        return True
    if state.prsa in ("tell", "ask", "talk"):
        out.tell('"Shut up, Earthman," says Zaphod.\n')
        return True
    return False


def _trillian_action(state: GameState) -> bool:
    out = state.output
    if state.prsa == "examine":
        out.tell(
            "Trillian is a dark-haired woman. She is slim and "
            "attractive, and seems to be the only person on this ship "
            "with any sense.\n"
        )
        return True
    if state.prsa in ("tell", "ask", "talk"):
        out.tell(
            '"Hello, Arthur," says Trillian with a sympathetic smile.\n'
        )
        return True
    return False


def _handbag_action(state: GameState) -> bool:
    out = state.output
    handbag = state.world.get("HANDBAG")

    if state.prsa == "examine":
        out.tell("It's Trillian's handbag.\n")
        return True

    if state.prsa in ("open", "look in"):
        visible = handbag.contents_string()
        if visible:
            descs = [obj.a_desc() for obj in visible]
            out.tell("Inside the handbag you find " + ", ".join(descs) + ".\n")
        else:
            out.tell("The handbag is empty.\n")
        return True

    if state.prsa == "take":
        trillian = state.world.get("TRILLIAN")
        if handbag.parent is trillian:
            out.tell(
                "Trillian gives you a look that suggests you should keep "
                "your hands off her handbag.\n"
            )
            return True
        return False

    return False


def _tweezers_action(state: GameState) -> bool:
    out = state.output
    if state.prsa == "examine":
        out.tell("A small pair of tweezers.\n")
        return True
    return False


def _marvin_action(state: GameState) -> bool:
    out = state.output
    marvin = state.world.get("MARVIN")

    if state.prsa == "examine":
        out.tell(
            "Marvin the Paranoid Android stands here, looking profoundly "
            "depressed. His metal body is the size and shape of a short, "
            "stocky human, and his head hangs at a disconsolate angle.\n"
        )
        return True

    if state.prsa in ("tell", "ask", "talk"):
        mc = state.flags.get("marvin_counter", 0)
        if mc == 0:
            out.tell(
                '"Life," says Marvin. "Don\'t talk to me about life."\n'
            )
        elif mc == 1:
            out.tell(
                '"I could fix the hatch mechanism," Marvin sighs, "but '
                "nobody ever asks me to do anything interesting. Go on "
                'then, I suppose you want me to fix it."\n'
            )
            state.flags["marvin_counter"] = 2
            # Queue Marvin's work timer
            state.clock.queue(_i_marvin, 12, name="I-MARVIN")
        elif mc == 2:
            out.tell(
                '"I\'m working on it," says Marvin gloomily. "Not that '
                'anyone cares."\n'
            )
        elif mc == 3:
            tool = state.flags.get("tool_required", "screwdriver")
            out.tell(
                f'"I need a {tool} to finish the job," says Marvin. '
                '"Not that it matters. Nothing matters."\n'
            )
        elif mc >= 4:
            out.tell(
                '"I\'ve fixed the hatch," says Marvin. "I won\'t pretend '
                'it gives me any satisfaction, because it doesn\'t."\n'
            )
        return True

    if state.prsa in ("give", "show"):
        if state.flags.get("marvin_counter", 0) == 3:
            tool_needed = state.flags.get("tool_required", "screwdriver")
            if state.prso and tool_needed in state.prso.desc.lower():
                state.prso.move_to(marvin)
                state.flags["marvin_counter"] = 4
                state.score += 25
                # Fix the hatch
                hatch = state.world.get("HATCH")
                hatch.fset(Flag.OPENBIT)
                out.tell(
                    "Marvin takes the tool. With agonising slowness and an "
                    "air of infinite depression, he fixes the hatch mechanism.\n\n"
                    '"There," he says. "I\'ve done it. I won\'t pretend it '
                    "gives me any satisfaction, because it doesn't. The hatch "
                    'is now open."\n'
                )
                return True
            else:
                out.tell(
                    f'"That\'s not a {tool_needed}," says Marvin. "Here I am, '
                    "brain the size of a planet, and they can't even bring me "
                    'the right tool."\n'
                )
                return True
        return False

    return False


def _nutrimat_action(state: GameState) -> bool:
    out = state.output
    if state.prsa == "examine":
        out.tell(
            "The Nutrimatic Drinks Synthesizer is a squat, ugly device "
            "with a single button pad and a slot from which drinks emerge. "
            'A sign reads: "Almost, but not quite, entirely unlike tea."\n'
        )
        return True
    return False


def _pad_action(state: GameState) -> bool:
    out = state.output
    if state.prsa in ("push", "press"):
        if state.flags.get("tea_counter", 0) > 0:
            out.tell(
                "The Nutrimat is already processing your request.\n"
            )
            return True
        state.flags["tea_counter"] = 1
        state.clock.queue(_i_tea, -1, name="I-TEA")
        out.tell(
            'You press the pad. The Nutrimat goes "beeble" and starts '
            "to think about tea.\n"
        )
        return True
    if state.prsa == "examine":
        out.tell("A button pad on the Nutrimatic Drinks Synthesizer.\n")
        return True
    return False


def _slot_action(state: GameState) -> bool:
    out = state.output
    if state.prsa == "examine":
        out.tell("A slot from which drinks emerge.\n")
        return True
    return False


def _tea_action(state: GameState) -> bool:
    out = state.output
    tea = state.world.get("TEA")

    if state.prsa in ("drink", "eat"):
        tea.move_to(state.world.local_globals)
        state.score += 100
        out.tell(
            "You drink the tea. It is the most perfectly normal cup of "
            "tea you have ever tasted. It is exactly what tea should be. "
            "You feel wonderfully refreshed.\n"
        )
        return True

    if state.prsa == "examine":
        out.tell(
            "It is a cup of proper, real tea -- rich, brown, and "
            "reviving.\n"
        )
        return True

    if state.prsa in ("show", "give"):
        door = state.world.get("SCREENING-DOOR")
        if state.prsi is door or (state.here and state.here.id == "AFT-CORRIDOR"):
            state.flags["tea_shown"] = True
            if state.flags.get("no_tea_shown"):
                _open_screening_door(state)
            else:
                out.tell(
                    "The screening door scans the tea but does not open. "
                    "Perhaps it requires something more.\n"
                )
            return True
        return False

    return False


def _substitute_action(state: GameState) -> bool:
    out = state.output
    sub = state.world.get("SUBSTITUTE")

    if state.prsa in ("drink", "eat"):
        sub.move_to(state.world.local_globals)
        state.flags["substitute_drunk"] = True
        state.score -= 30
        out.tell(
            "You drink the Advanced Tea Substitute. It tastes almost, "
            "but not quite, entirely unlike tea. You feel vaguely "
            "dissatisfied.\n"
        )
        return True

    if state.prsa == "examine":
        out.tell(
            "A plastic cup containing a liquid that is almost, but not "
            "quite, entirely unlike tea.\n"
        )
        return True

    return False


def _no_tea_action(state: GameState) -> bool:
    out = state.output

    if state.prsa == "examine":
        out.tell(
            "You are holding no tea. It's the absence of tea -- a "
            "concept given form by your complete lack of tea.\n"
        )
        return True

    if state.prsa in ("show", "give"):
        door = state.world.get("SCREENING-DOOR")
        if state.prsi is door or (state.here and state.here.id == "AFT-CORRIDOR"):
            state.flags["no_tea_shown"] = True
            if state.flags.get("tea_shown"):
                _open_screening_door(state)
            else:
                out.tell(
                    "The screening door scans your absence of tea but does "
                    "not open. Perhaps it requires something more.\n"
                )
            return True
        return False

    if state.prsa == "drop":
        out.tell("How do you drop something you don't have?\n")
        return True

    return False


def _open_screening_door(state: GameState) -> None:
    """Open the screening door when shown both tea and no-tea."""
    out = state.output
    state.flags["screening_door_open"] = True
    door = state.world.get("SCREENING-DOOR")
    door.fset(Flag.OPENBIT)
    state.score += 25
    out.tell(
        "The screening door flickers with confusion as it tries to "
        "reconcile the simultaneous existence of tea and no tea. "
        "Circuits spark. Logic banks overflow. With a defeated whirr, "
        "the door slides open.\n"
    )


def _screening_door_action(state: GameState) -> bool:
    out = state.output
    door = state.world.get("SCREENING-DOOR")

    if state.prsa == "examine":
        if door.fset_q(Flag.OPENBIT):
            out.tell("The screening door is open.\n")
        else:
            out.tell(
                "A gleaming electronic screening door blocks the way "
                "west to the pantry. It appears to require some form of "
                "proof of identity or paradox.\n"
            )
        return True

    if state.prsa == "open":
        if door.fset_q(Flag.OPENBIT):
            out.tell("It's already open.\n")
        else:
            out.tell(
                "The door refuses to open. It seems to want you to "
                "show it something.\n"
            )
        return True

    return False


def _spare_drive_action(state: GameState) -> bool:
    out = state.output

    if state.prsa == "examine":
        out.tell(
            "It's a portable Infinite Improbability Generator. It has "
            "a large plug at one end, a small plug at the other, and a "
            "switch on the side.\n"
        )
        return True

    return False


def _large_plug_action(state: GameState) -> bool:
    out = state.output

    if state.prsa in ("put", "connect", "plug", "attach"):
        console = state.world.get("CONTROL-CONSOLE")
        if state.prsi is console or (
            state.prsi and state.prsi.id == "CONTROL-CONSOLE"
        ):
            state.flags["drive_to_controls"] = True
            out.tell(
                "You connect the large plug to the control console.\n"
            )
            return True
        return False

    if state.prsa == "examine":
        out.tell("A large plug at one end of the spare drive.\n")
        return True

    return False


def _small_plug_action(state: GameState) -> bool:
    out = state.output

    if state.prsa in ("put", "connect", "plug", "attach"):
        plotter = state.world.get("PLOTTER")
        receptacle = state.world.get("SMALL-RECEPTACLE")
        if state.prsi is plotter or state.prsi is receptacle:
            state.flags["drive_to_plotter"] = True
            out.tell(
                "You connect the small plug to the atomic vector plotter.\n"
            )
            return True
        return False

    if state.prsa == "examine":
        out.tell("A small plug at one end of the spare drive.\n")
        return True

    return False


def _drive_switch_action(state: GameState) -> bool:
    out = state.output

    if state.prsa in ("push", "press", "flip", "turn on", "turn off", "activate"):
        d2c = state.flags.get("drive_to_controls")
        d2p = state.flags.get("drive_to_plotter")
        bsrc = state.flags.get("brownian_source")

        if not d2c or not d2p:
            out.tell(
                "Click. Nothing happens. The drive doesn't seem to be "
                "connected to anything useful.\n"
            )
            return True

        if not bsrc:
            out.tell(
                "The drive hums momentarily then stops. It seems to need "
                "a source of Brownian motion to function.\n"
            )
            return True

        # Success! Trigger improbability event
        out.tell(
            "You flip the switch. The Infinite Improbability Drive "
            "shudders into life. The universe twists. Reality buckles. "
            "For a brief moment, every particle in existence passes "
            "through every other point in the Universe.\n\n"
            "When things settle down, the Heart of Gold is in orbit "
            "around Magrathea.\n"
        )
        state.score += 50
        state.flags["landing_countdown"] = True
        state.clock.queue(_i_landing, 24, name="I-LANDING")
        return True

    if state.prsa == "examine":
        out.tell("A switch on the side of the spare drive.\n")
        return True

    return False


def _plotter_heart_action(state: GameState) -> bool:
    out = state.output
    plotter = state.world.get("PLOTTER")

    if state.prsa == "examine":
        out.tell(
            "It's an atomic vector plotter -- a small, sleek device "
            "of obviously advanced technology. It has a small receptacle "
            "and a dangly bit.\n"
        )
        return True

    return False


def _small_receptacle_action(state: GameState) -> bool:
    out = state.output

    if state.prsa == "examine":
        out.tell("A small receptacle on the atomic vector plotter.\n")
        return True

    return False


def _dangly_bit_action(state: GameState) -> bool:
    out = state.output

    if state.prsa == "examine":
        out.tell(
            "A small dangly bit on the atomic vector plotter. It looks "
            "like it should be suspended in some kind of liquid.\n"
        )
        return True

    if state.prsa in ("put", "dip", "suspend", "submerge"):
        # Check if prsi is tea or substitute (as Brownian motion source)
        if state.prsi and state.prsi.id in ("TEA", "SUBSTITUTE"):
            state.flags["brownian_source"] = state.prsi
            out.tell(
                f"You suspend the dangly bit in {state.prsi.the_desc()}. "
                "Brownian motion of the molecules provides the necessary "
                "random input.\n"
            )
            return True
        return False

    return False


def _control_console_action(state: GameState) -> bool:
    out = state.output

    if state.prsa == "examine":
        out.tell(
            "The main control console of the Heart of Gold. It has an "
            "impressive array of buttons, switches, and screens, most of "
            "which seem to be controlled by Eddie.\n"
        )
        return True

    return False


def _hatch_action(state: GameState) -> bool:
    out = state.output
    hatch = state.world.get("HATCH")

    if state.prsa == "examine":
        if hatch.fset_q(Flag.OPENBIT):
            out.tell("The hatch is open. A ramp leads down.\n")
        else:
            out.tell(
                "The hatch mechanism is jammed. It looks like it needs "
                "to be repaired.\n"
            )
        return True

    if state.prsa == "open":
        if hatch.fset_q(Flag.OPENBIT):
            out.tell("The hatch is already open.\n")
        else:
            out.tell(
                "The hatch mechanism is jammed solid. You can't open it "
                "by hand.\n"
            )
        return True

    return False


def _wrench_action(state: GameState) -> bool:
    out = state.output
    if state.prsa == "examine":
        out.tell("A heavy adjustable wrench.\n")
        return True
    return False


def _pliers_action(state: GameState) -> bool:
    out = state.output
    if state.prsa == "examine":
        out.tell("A sturdy pair of pliers.\n")
        return True
    return False


def _rasp_action(state: GameState) -> bool:
    out = state.output
    if state.prsa == "examine":
        out.tell("A coarse metal rasp.\n")
        return True
    return False


# ---- Timed event handlers ----

def _i_tea(state: GameState) -> bool:
    """Nutrimat tea processing and missile attack countdown."""
    out = state.output
    counter = state.flags.get("tea_counter", 0)

    if counter <= 0:
        return False

    counter += 1
    state.flags["tea_counter"] = counter

    if counter == 2:
        out.tell(
            "\nThe Nutrimat hums to itself. It is trying to work out "
            "what tea is.\n"
        )
        return True
    if counter == 3:
        out.tell(
            "\nThe Nutrimat is still thinking. It is exploring the "
            "concept of dried leaves in boiling water.\n"
        )
        return True
    if counter == 4:
        out.tell(
            "\nThe Nutrimat makes some encouraging gurgling noises.\n"
        )
        return True
    if counter == 5:
        out.tell(
            "\nThe Nutrimat whirrs. It seems to be making progress.\n"
        )
        return True
    if counter == 6:
        out.tell(
            "\nThe Nutrimat dings. A cup of something emerges from the "
            "slot. Unfortunately it is a cup of Advanced Tea Substitute.\n"
        )
        sub = state.world.get("SUBSTITUTE")
        galley = state.world.get_room("GALLEY")
        sub.move_to(galley)
        return True
    if counter == 7:
        out.tell(
            "\nThe Nutrimat has become obsessed with the question of "
            "tea. It has connected itself to the main computer to think "
            "about it more deeply.\n"
        )
        return True
    if counter == 8:
        out.tell(
            '\nEddie announces: "Guys! We have a small problem! Two '
            "Magrathean missiles have just locked on to us! I think "
            "they might be planning to destroy us! Isn't that just "
            'wild?"\n'
        )
        return True
    if counter == 9:
        out.tell(
            "\nThe ship shudders. Warning klaxons sound throughout the "
            "corridors.\n"
        )
        return True
    if counter == 10:
        out.tell(
            '\n"The missiles are getting closer!" Eddie reports '
            "cheerfully.\n"
        )
        return True
    if counter == 11:
        out.tell(
            "\nAnother shudder rocks the ship. Something explodes "
            "somewhere aft.\n"
        )
        return True
    if counter == 12:
        out.tell(
            '\n"Impact in about thirty seconds!" says Eddie. "Have a '
            'nice day!"\n'
        )
        return True
    if counter == 13:
        out.tell(
            "\nThe ship lurches violently. Panels spark and shatter.\n"
        )
        return True
    if counter == 14:
        out.tell(
            '\n"Ten seconds to impact! It\'s been great knowing you '
            'guys!" says Eddie.\n'
        )
        return True
    if counter >= 15:
        # Disable this timer
        for entry in state.clock.entries:
            if entry.routine is _i_tea:
                entry.enabled = False
        state.jigs_up(
            "The missiles strike the Heart of Gold amidships. The ship "
            "is blown into a billion tiny pieces, most of which land on "
            "the entirely blameless planet of Bartledan where they cause "
            "untold havoc among the native population who spend the next "
            "three generations wondering what hit them."
        )
        return True

    return False


def _i_marvin(state: GameState) -> bool:
    """After 12 turns, Marvin moves to access space and asks for a tool."""
    out = state.output

    if state.flags.get("marvin_counter", 0) != 2:
        return False

    marvin = state.world.get("MARVIN")
    access = state.world.get_room("ACCESS-SPACE")

    # Select a random tool
    tool = random.choice(_MARVIN_TOOLS)
    state.flags["tool_required"] = tool
    state.flags["marvin_counter"] = 3

    # Move Marvin to the access space
    marvin.move_to(access)

    out.tell(
        "\nMarvin's voice echoes up from somewhere below. \"I've had a "
        "look at the hatch mechanism. It's typical. Shoddy workmanship. "
        f'I need a {tool} to fix it. Not that I expect anyone to '
        'bother bringing me one.\"\n'
    )
    return True


def _i_landing(state: GameState) -> bool:
    """Landing countdown on Magrathea."""
    out = state.output

    state.flags["landed"] = True

    # Disable this timer
    for entry in state.clock.entries:
        if entry.routine is _i_landing:
            entry.enabled = False

    out.tell(
        "\nThe Heart of Gold descends through the atmosphere of "
        "Magrathea. The legendary planet of the planet-builders fills "
        "the viewscreen. The ship settles gently onto a vast grey "
        "plain.\n\n"
        '"We have landed," announces Eddie, completely unnecessarily.\n'
    )

    # Open the ramp path
    hatchway = state.world.get_room("HATCHWAY")
    ramp = state.world.get_room("RAMP")
    hatchway.exits[Direction.DOWN] = ConditionalExit(
        lambda s: _ramp_exit(s)
    )

    return True


# ---- Room action handlers ----

def _entry_bay_action(state: GameState, rarg: str) -> bool:
    out = state.output

    if rarg == "M-LOOK":
        out.tell(
            "This is Entry Bay Number Two of the starship Heart of "
            "Gold. It is a wide, gleaming white chamber. The main "
            "corridor of the ship lies to the south.\n"
        )
        return True

    if rarg == "M-ENTER":
        entry_bay = state.world.get_room("ENTRY-BAY")
        if not entry_bay.fset_q(Flag.TOUCHBIT):
            entry_bay.fset(Flag.TOUCHBIT)
            state.score += 10
            out.tell(
                "\nYou materialise in a wide, white chamber. You look "
                "around, slightly dazed.\n\n"
                "Ford Prefect stumbles in behind you. \"Incredible!\" he "
                "says. \"We've been picked up by the Heart of Gold! The "
                "ship that was stolen by Zaphod Beeblebrox! The most "
                "improbable ship in the whole of the galaxy!\"\n\n"
                "Ford wanders off down the corridor muttering excitedly "
                "to himself.\n"
            )

    return False


def _fore_corridor_action(state: GameState, rarg: str) -> bool:
    out = state.output

    if rarg == "M-LOOK":
        out.tell(
            "You are in a bright corridor near the fore end of the "
            "Heart of Gold. The entry bay lies to the north, the "
            "bridge is up, the galley is to the west, and the corridor "
            "continues aft to the south.\n"
        )
        return True

    return False


def _aft_corridor_action(state: GameState, rarg: str) -> bool:
    out = state.output

    if rarg == "M-LOOK":
        door = state.world.get("SCREENING-DOOR")
        out.tell(
            "You are in the aft section of the corridor. The corridor "
            "continues north. The engine room lies to the south. "
            "A hatchway leads down.\n"
        )
        if door.fset_q(Flag.OPENBIT):
            out.tell("An open doorway leads west to the pantry.\n")
        else:
            out.tell(
                "A gleaming screening door blocks the way west.\n"
            )
        return True

    return False


def _aft_south_exit(state: GameState) -> "Room | None":
    """Exit south from aft corridor to engine room -- requires arguing."""
    out = state.output
    counter = state.flags.get("argument_counter", 0)

    if counter < 3:
        state.flags["argument_counter"] = counter + 1
        responses = [
            "A sign reads: \"Engine Room -- Authorised Personnel Only.\" "
            "You aren't authorised.\n",
            "You still aren't authorised. The sign is quite firm about "
            "this.\n",
            "Oh, all right. Since you're so insistent, the sign gives "
            "up and lets you through.\n",
        ]
        out.tell(responses[counter])
        if counter == 2:
            return state.world.get_room("ENGINE-ROOM")
        return None

    return state.world.get_room("ENGINE-ROOM")


def _aft_west_exit(state: GameState) -> "Room | None":
    """Exit west from aft corridor to pantry -- requires screening door open."""
    out = state.output
    door = state.world.get("SCREENING-DOOR")
    if not door.fset_q(Flag.OPENBIT):
        out.tell("The screening door blocks your way.\n")
        return None
    return state.world.get_room("PANTRY")


def _bridge_action(state: GameState, rarg: str) -> bool:
    out = state.output

    if rarg == "M-LOOK":
        out.tell(
            "This is the bridge of the Heart of Gold. Huge "
            "viewscreens display the stars outside. The main control "
            "console dominates the center of the room. A companionway "
            "leads down.\n"
        )
        return True

    return False


def _galley_action(state: GameState, rarg: str) -> bool:
    out = state.output

    if rarg == "M-LOOK":
        out.tell(
            "This is the ship's galley. Dominating the room is the "
            "Nutrimatic Drinks Synthesizer, a machine designed to "
            "produce the widest possible range of drinks personally "
            "matched to your taste and metabolism. It invariably "
            "produces a liquid almost, but not quite, entirely unlike "
            "tea. The corridor lies to the east.\n"
        )
        return True

    return False


def _engine_room_action(state: GameState, rarg: str) -> bool:
    out = state.output

    if rarg == "M-LOOK":
        look_count = state.flags.get("look_counter", 0)
        look_count += 1
        state.flags["look_counter"] = look_count

        out.tell(
            "This is the engine room of the Heart of Gold. The "
            "Infinite Improbability Drive sits in the middle of the "
            "room, humming gently to itself.\n"
        )

        if look_count == 1:
            out.tell(
                "The room is cluttered with equipment. You can't make "
                "out much detail.\n"
            )
        elif look_count == 2:
            out.tell(
                "Looking more carefully, you notice some equipment "
                "partially hidden behind panels.\n"
            )
        elif look_count >= 3:
            if not state.flags.get("engine_revealed"):
                state.flags["engine_revealed"] = True
                state.score += 25
                # Reveal hidden objects
                spare = state.world.get("SPARE-DRIVE")
                spare.fclear(Flag.INVISIBLE)
                wrench = state.world.get("WRENCH")
                wrench.fclear(Flag.INVISIBLE)
                pliers = state.world.get("PLIERS")
                pliers.fclear(Flag.INVISIBLE)
                rasp = state.world.get("RASP")
                rasp.fclear(Flag.INVISIBLE)
                out.tell(
                    "At last! Careful examination reveals a portable "
                    "Infinite Improbability Generator, a wrench, a pair "
                    "of pliers, and a rasp tucked away behind the "
                    "machinery.\n"
                )
            else:
                out.tell(
                    "You can see the spare drive and various tools.\n"
                )

        return True

    return False


def _pantry_action(state: GameState, rarg: str) -> bool:
    out = state.output

    if rarg == "M-LOOK":
        out.tell(
            "This is a small pantry off the main corridor. It smells "
            "faintly of synthetic food and despair.\n"
        )
        return True

    if rarg == "M-ENTER":
        pantry = state.world.get_room("PANTRY")
        if not pantry.fset_q(Flag.TOUCHBIT):
            pantry.fset(Flag.TOUCHBIT)
            marvin = state.world.get("MARVIN")
            if marvin.parent is pantry:
                state.flags["marvin_counter"] = max(
                    state.flags.get("marvin_counter", 0), 1
                )

    return False


def _hatchway_action(state: GameState, rarg: str) -> bool:
    out = state.output

    if rarg == "M-LOOK":
        hatch = state.world.get("HATCH")
        out.tell(
            "You are in a narrow hatchway. The corridor is above you.\n"
        )
        if hatch.fset_q(Flag.OPENBIT):
            out.tell("The hatch is open. A ramp leads down.\n")
        else:
            out.tell("A hatch below you is jammed shut.\n")
        out.tell("A narrow gap leads east into a cramped access space.\n")
        return True

    return False


def _hatchway_down_exit(state: GameState) -> "Room | None":
    """Exit down from hatchway -- requires hatch open and landed."""
    out = state.output
    hatch = state.world.get("HATCH")
    if not hatch.fset_q(Flag.OPENBIT):
        out.tell("The hatch is jammed shut.\n")
        return None
    if not state.flags.get("landed"):
        out.tell(
            "The hatch is open, but the ship hasn't landed anywhere "
            "yet. Below is the cold vacuum of space.\n"
        )
        return None
    return state.world.get_room("RAMP")


def _access_space_action(state: GameState, rarg: str) -> bool:
    out = state.output

    if rarg == "M-LOOK":
        out.tell(
            "This is a tiny, cramped access space barely large enough "
            "to crouch in. Cables and pipes run along every surface. "
            "The hatchway is to the west.\n"
        )
        return True

    if rarg == "M-BEG":
        # Can only carry 1 item in the access space
        protagonist = state.protagonist
        if state.prsa == "take" and state.prso:
            held = [c for c in protagonist.children
                    if Flag.INVISIBLE not in c.flags]
            if len(held) >= 1:
                out.tell(
                    "There's no room to carry anything more in this "
                    "cramped space. You'll have to drop something first.\n"
                )
                return True
        return False

    return False


def _ramp_exit(state: GameState) -> "Room | None":
    """Conditional ramp access from hatchway."""
    hatch = state.world.get("HATCH")
    if not hatch.fset_q(Flag.OPENBIT):
        state.output.tell("The hatch is jammed shut.\n")
        return None
    if not state.flags.get("landed"):
        state.output.tell("The ship hasn't landed yet.\n")
        return None
    return state.world.get_room("RAMP")


def _ramp_action(state: GameState, rarg: str) -> bool:
    out = state.output

    if rarg == "M-LOOK":
        out.tell(
            "You are standing on the landing ramp of the Heart of Gold. "
            "The vast grey surface of Magrathea stretches away in every "
            "direction beneath a dark sky. You have arrived at the "
            "legendary planet.\n"
        )
        return True

    if rarg == "M-ENTER":
        ramp = state.world.get_room("RAMP")
        if not ramp.fset_q(Flag.TOUCHBIT):
            ramp.fset(Flag.TOUCHBIT)
            state.score += 50
            out.tell(
                "\nYou step out onto the surface of Magrathea. The "
                "air is cold and thin, and the silence is immense.\n\n"
                "You have reached the legendary lost planet of "
                "Magrathea, home of the custom-made luxury planet "
                "industry. This is a great achievement.\n\n"
                "Congratulations.\n"
            )

    return False


def _no_tea_inventory(state: GameState) -> str | None:
    """Inventory extra: show 'no tea' if player has it."""
    no_tea = state.world.get("NO-TEA")
    if no_tea.is_held_by(state.protagonist):
        return "  no tea (the concept)\n"
    return None


# ---- Registration ----

def register(world: World, state: GameState) -> None:
    """Create all Heart of Gold rooms and objects."""

    # ---- Initialize state flags ----

    state.flags.setdefault("look_counter", 0)
    state.flags.setdefault("marvin_counter", 0)
    state.flags.setdefault("tool_required", None)
    state.flags.setdefault("tea_counter", 0)
    state.flags.setdefault("tea_shown", False)
    state.flags.setdefault("no_tea_shown", False)
    state.flags.setdefault("substitute_drunk", False)
    state.flags.setdefault("drive_to_controls", False)
    state.flags.setdefault("drive_to_plotter", False)
    state.flags.setdefault("brownian_source", None)
    state.flags.setdefault("landed", False)
    state.flags.setdefault("argument_counter", 0)
    state.flags.setdefault("screening_door_open", False)
    state.flags.setdefault("engine_revealed", False)
    state.flags.setdefault("landing_countdown", False)

    # ---- Global objects for the ship ----

    screening_door = world.register(GameObject(
        "SCREENING-DOOR", desc="screening door",
        synonyms=["door", "screening"],
        adjectives=["screening", "electronic"],
        flags={Flag.DOORBIT, Flag.NDESCBIT},
        action=_screening_door_action,
    ))
    screening_door.move_to(world.local_globals)

    hatch = world.register(GameObject(
        "HATCH", desc="hatch",
        synonyms=["hatch", "hatchway"],
        adjectives=["jammed"],
        flags={Flag.DOORBIT, Flag.NDESCBIT},
        action=_hatch_action,
    ))
    hatch.move_to(world.local_globals)

    # ---- ENTRY BAY ----

    entry_bay = world.register(Room(
        "ENTRY-BAY", desc="Entry Bay Number Two",
        flags={Flag.RLANDBIT, Flag.ONBIT},
        action=_entry_bay_action,
    ))

    sales_brochure = world.register(GameObject(
        "SALES-BROCHURE", desc="Heart of Gold sales brochure",
        synonyms=["brochure", "pamphlet", "leaflet"],
        adjectives=["sales", "heart", "gold"],
        flags={Flag.TAKEBIT, Flag.READBIT},
        size=2,
        action=_sales_brochure_action,
    ))
    sales_brochure.move_to(entry_bay)

    # ---- FORE CORRIDOR ----

    fore_corridor = world.register(Room(
        "FORE-CORRIDOR", desc="Corridor, Fore End",
        flags={Flag.RLANDBIT, Flag.ONBIT},
        action=_fore_corridor_action,
    ))

    # ---- AFT CORRIDOR ----

    aft_corridor = world.register(Room(
        "AFT-CORRIDOR", desc="Corridor, Aft End",
        flags={Flag.RLANDBIT, Flag.ONBIT},
        action=_aft_corridor_action,
        global_objects=[screening_door],
    ))

    # ---- BRIDGE ----

    bridge = world.register(Room(
        "BRIDGE", desc="Bridge",
        flags={Flag.RLANDBIT, Flag.ONBIT},
        action=_bridge_action,
    ))

    eddie = world.register(GameObject(
        "EDDIE", desc="Eddie",
        synonyms=["eddie", "computer"],
        adjectives=["shipboard"],
        flags={Flag.NARTICLEBIT, Flag.ACTORBIT, Flag.NDESCBIT},
        action=_eddie_action,
    ))
    eddie.move_to(bridge)

    zaphod = world.register(GameObject(
        "ZAPHOD", desc="Zaphod Beeblebrox",
        synonyms=["zaphod", "beeblebrox", "president"],
        adjectives=["zaphod"],
        flags={Flag.NARTICLEBIT, Flag.ACTORBIT, Flag.NDESCBIT},
        action=_zaphod_action,
    ))
    zaphod.move_to(bridge)

    trillian = world.register(GameObject(
        "TRILLIAN", desc="Trillian",
        synonyms=["trillian", "woman"],
        adjectives=["dark-haired"],
        flags={Flag.NARTICLEBIT, Flag.ACTORBIT, Flag.NDESCBIT},
        action=_trillian_action,
    ))
    trillian.move_to(bridge)

    handbag = world.register(GameObject(
        "HANDBAG", desc="handbag",
        synonyms=["handbag", "bag", "purse"],
        adjectives=["trillian's"],
        flags={Flag.CONTBIT, Flag.SEARCHBIT, Flag.OPENBIT, Flag.NDESCBIT},
        capacity=10,
        action=_handbag_action,
    ))
    handbag.move_to(trillian)

    tweezers = world.register(GameObject(
        "TWEEZERS", desc="tweezers",
        synonyms=["tweezers"],
        adjectives=["small"],
        flags={Flag.TAKEBIT, Flag.TOOLBIT},
        size=1,
        action=_tweezers_action,
    ))
    tweezers.move_to(handbag)

    control_console = world.register(GameObject(
        "CONTROL-CONSOLE", desc="control console",
        synonyms=["console", "controls"],
        adjectives=["main", "control"],
        flags={Flag.NDESCBIT},
        action=_control_console_action,
    ))
    control_console.move_to(bridge)

    # ---- GALLEY ----

    galley = world.register(Room(
        "GALLEY", desc="Galley",
        flags={Flag.RLANDBIT, Flag.ONBIT},
        action=_galley_action,
    ))

    nutrimat = world.register(GameObject(
        "NUTRIMAT", desc="Nutrimatic Drinks Synthesizer",
        synonyms=["nutrimat", "nutrimatic", "synthesizer", "machine"],
        adjectives=["nutrimatic", "drinks"],
        flags={Flag.NARTICLEBIT, Flag.NDESCBIT},
        action=_nutrimat_action,
    ))
    nutrimat.move_to(galley)

    pad = world.register(GameObject(
        "PAD", desc="button pad",
        synonyms=["pad", "button"],
        adjectives=["button"],
        flags={Flag.NDESCBIT},
        action=_pad_action,
    ))
    pad.move_to(galley)

    slot = world.register(GameObject(
        "SLOT", desc="slot",
        synonyms=["slot"],
        adjectives=["drink", "dispensing"],
        flags={Flag.NDESCBIT},
        action=_slot_action,
    ))
    slot.move_to(galley)

    substitute = world.register(GameObject(
        "SUBSTITUTE", desc="Advanced Tea Substitute",
        synonyms=["substitute", "liquid", "cup"],
        adjectives=["advanced", "tea"],
        flags={Flag.TAKEBIT, Flag.DRINKBIT, Flag.NARTICLEBIT, Flag.INVISIBLE},
        size=3,
        action=_substitute_action,
    ))

    tea = world.register(GameObject(
        "TEA", desc="cup of tea",
        synonyms=["tea", "cup"],
        adjectives=["real", "proper"],
        flags={Flag.TAKEBIT, Flag.DRINKBIT, Flag.INVISIBLE},
        size=3,
        action=_tea_action,
    ))

    no_tea = world.register(GameObject(
        "NO-TEA", desc="no tea",
        synonyms=["no-tea", "absence"],
        adjectives=["no"],
        flags={Flag.TAKEBIT, Flag.NARTICLEBIT},
        size=0,
        action=_no_tea_action,
    ))
    no_tea.move_to(state.protagonist)

    # Register inventory extra for no-tea display
    state.inventory_extras.append(_no_tea_inventory)

    # ---- ENGINE ROOM ----

    engine_room = world.register(Room(
        "ENGINE-ROOM", desc="Engine Room",
        flags={Flag.RLANDBIT, Flag.ONBIT},
        action=_engine_room_action,
    ))

    spare_drive = world.register(GameObject(
        "SPARE-DRIVE", desc="portable Infinite Improbability Generator",
        synonyms=["drive", "generator"],
        adjectives=["spare", "portable", "improbability", "infinite"],
        flags={Flag.TAKEBIT, Flag.INVISIBLE},
        size=8,
        action=_spare_drive_action,
    ))
    spare_drive.move_to(engine_room)

    large_plug = world.register(GameObject(
        "LARGE-PLUG", desc="large plug",
        synonyms=["plug"],
        adjectives=["large"],
        flags={Flag.NDESCBIT, Flag.INTEGRALBIT},
        action=_large_plug_action,
    ))
    large_plug.move_to(spare_drive)

    small_plug = world.register(GameObject(
        "SMALL-PLUG", desc="small plug",
        synonyms=["plug"],
        adjectives=["small"],
        flags={Flag.NDESCBIT, Flag.INTEGRALBIT},
        action=_small_plug_action,
    ))
    small_plug.move_to(spare_drive)

    drive_switch = world.register(GameObject(
        "DRIVE-SWITCH", desc="drive switch",
        synonyms=["switch"],
        adjectives=["drive"],
        flags={Flag.NDESCBIT, Flag.INTEGRALBIT},
        action=_drive_switch_action,
    ))
    drive_switch.move_to(spare_drive)

    wrench = world.register(GameObject(
        "WRENCH", desc="wrench",
        synonyms=["wrench", "spanner"],
        adjectives=["adjustable", "heavy"],
        flags={Flag.TAKEBIT, Flag.TOOLBIT, Flag.INVISIBLE},
        size=4,
        action=_wrench_action,
    ))
    wrench.move_to(engine_room)

    pliers = world.register(GameObject(
        "PLIERS", desc="pliers",
        synonyms=["pliers"],
        adjectives=["sturdy"],
        flags={Flag.TAKEBIT, Flag.TOOLBIT, Flag.INVISIBLE},
        size=3,
        action=_pliers_action,
    ))
    pliers.move_to(engine_room)

    rasp = world.register(GameObject(
        "RASP", desc="rasp",
        synonyms=["rasp", "file"],
        adjectives=["coarse", "metal"],
        flags={Flag.TAKEBIT, Flag.TOOLBIT, Flag.INVISIBLE},
        size=3,
        action=_rasp_action,
    ))
    rasp.move_to(engine_room)

    # ---- PANTRY ----

    pantry = world.register(Room(
        "PANTRY", desc="Pantry",
        flags={Flag.RLANDBIT, Flag.ONBIT},
        action=_pantry_action,
    ))

    marvin = world.register(GameObject(
        "MARVIN", desc="Marvin",
        synonyms=["marvin", "android", "robot"],
        adjectives=["paranoid"],
        flags={Flag.NARTICLEBIT, Flag.ACTORBIT, Flag.NDESCBIT},
        action=_marvin_action,
    ))
    marvin.move_to(pantry)

    # ---- HATCHWAY ----

    hatchway = world.register(Room(
        "HATCHWAY", desc="Hatchway",
        flags={Flag.RLANDBIT, Flag.ONBIT},
        action=_hatchway_action,
        global_objects=[hatch],
    ))

    # ---- ACCESS SPACE ----

    access_space = world.register(Room(
        "ACCESS-SPACE", desc="Access Space",
        flags={Flag.RLANDBIT, Flag.ONBIT},
        action=_access_space_action,
    ))

    # ---- RAMP ----

    ramp = world.register(Room(
        "RAMP", desc="Ramp",
        flags={Flag.RLANDBIT, Flag.ONBIT, Flag.OUTSIDEBIT},
        action=_ramp_action,
    ))

    # Plotter (Heart of Gold version -- may also exist on Vogon ship)
    plotter = world.register(GameObject(
        "PLOTTER-HOG", desc="atomic vector plotter",
        synonyms=["plotter", "device"],
        adjectives=["atomic", "vector"],
        flags={Flag.TAKEBIT},
        size=4,
        action=_plotter_heart_action,
    ))

    small_receptacle = world.register(GameObject(
        "SMALL-RECEPTACLE", desc="small receptacle",
        synonyms=["receptacle"],
        adjectives=["small"],
        flags={Flag.NDESCBIT, Flag.INTEGRALBIT},
        action=_small_receptacle_action,
    ))
    small_receptacle.move_to(plotter)

    dangly_bit = world.register(GameObject(
        "DANGLY-BIT", desc="dangly bit",
        synonyms=["bit", "dangly"],
        adjectives=["dangly", "small"],
        flags={Flag.NDESCBIT, Flag.INTEGRALBIT},
        action=_dangly_bit_action,
    ))
    dangly_bit.move_to(plotter)

    # ---- Connect rooms ----

    entry_bay.exits = {
        Direction.SOUTH: DirectExit(fore_corridor),
        Direction.OUT: DirectExit(fore_corridor),
    }

    fore_corridor.exits = {
        Direction.NORTH: DirectExit(entry_bay),
        Direction.UP: DirectExit(bridge),
        Direction.WEST: DirectExit(galley),
        Direction.SOUTH: DirectExit(aft_corridor),
    }

    aft_corridor.exits = {
        Direction.NORTH: DirectExit(fore_corridor),
        Direction.SOUTH: ConditionalExit(_aft_south_exit),
        Direction.WEST: ConditionalExit(_aft_west_exit),
        Direction.DOWN: DirectExit(hatchway),
    }

    bridge.exits = {
        Direction.DOWN: DirectExit(fore_corridor),
    }

    galley.exits = {
        Direction.EAST: DirectExit(fore_corridor),
        Direction.OUT: DirectExit(fore_corridor),
    }

    engine_room.exits = {
        Direction.NORTH: DirectExit(aft_corridor),
        Direction.OUT: DirectExit(aft_corridor),
    }

    pantry.exits = {
        Direction.EAST: DirectExit(aft_corridor),
        Direction.OUT: DirectExit(aft_corridor),
    }

    hatchway.exits = {
        Direction.UP: DirectExit(aft_corridor),
        Direction.DOWN: ConditionalExit(_hatchway_down_exit),
        Direction.EAST: DirectExit(access_space),
    }

    access_space.exits = {
        Direction.WEST: DirectExit(hatchway),
        Direction.OUT: DirectExit(hatchway),
    }

    ramp.exits = {
        Direction.UP: DirectExit(hatchway),
        Direction.IN: DirectExit(hatchway),
    }
