from __future__ import annotations

from concurrent.futures import ThreadPoolExecutor

from textual import on
from textual.app import ComposeResult
from textual.containers import Horizontal, HorizontalGroup, ScrollableContainer, Vertical
from textual.message import Message
from textual.screen import ModalScreen
from textual.widget import Widget
from textual.widgets import Button, Checkbox, Input, Select, Static

from ..domain import (
    build_confirmation_text,
    select_preferred_iso,
    validate_all_steps,
    validate_step,
)
from ..errors import ProxmoxError, ProxmoxPostCreateError
from ..models.workflow import WIZARD_STEPS, DiskConfig, ReferenceData, WorkflowState
from ..services.base import Bridge, ProxmoxService, Storage
from ..settings import AppSettings

GUEST_VERSIONS: dict[str, list[tuple[str, str]]] = {
    "linux": [("6.x - 2.6 Kernel", "l26"), ("2.4 Kernel", "l24"), ("Other Linux", "other")],
    "windows": [
        ("Windows 11/2022+", "win11"),
        ("Windows 10/2019", "win10"),
        ("Windows 8/2012", "win8"),
    ],
    "solaris": [("Solaris", "solaris")],
    "other": [("Other", "other")],
}
NO_DISK_SELECTED = "__no_disk__"


class AutoStartConfirmModal(ModalScreen[bool | None]):
    DEFAULT_CSS = """
    AutoStartConfirmModal {
        align: center middle;
    }

    #auto-start-dialog {
        width: 50;
        height: auto;
        border: round $accent;
        background: $surface;
        padding: 1 2;
    }

    #auto-start-title {
        text-style: bold;
    }

    #auto-start-actions {
        margin-top: 1;
        height: auto;
        width: 1fr;
        align-horizontal: center;
    }

    #auto-start-actions Button {
        width: 12;
        min-width: 12;
        margin-right: 1;
    }
    """

    BINDINGS = [("escape", "cancel", "Cancel")]

    def compose(self) -> ComposeResult:
        with Vertical(id="auto-start-dialog"):
            yield Static("Start VM Automatically?", id="auto-start-title")
            yield Static("Should the VM be started automatically after creation?")
            with HorizontalGroup(id="auto-start-actions"):
                yield Button("Yes", id="auto-start-yes", variant="success")
                yield Button("No", id="auto-start-no")

    def on_mount(self) -> None:
        self.set_focus(self.query_one("#auto-start-yes", Button))

    def action_cancel(self) -> None:
        self.dismiss(None)

    @on(Button.Pressed, "#auto-start-yes")
    def on_yes_pressed(self) -> None:
        self.dismiss(True)

    @on(Button.Pressed, "#auto-start-no")
    def on_no_pressed(self) -> None:
        self.dismiss(False)


class WizardView(Vertical):
    can_focus = False

    class SubmitFinished(Message):
        def __init__(self, success: bool, message: str) -> None:
            self.success = success
            self.message = message
            super().__init__()

    DEFAULT_CSS = """
    WizardView {
        display: none;
        height: 1fr;
        layout: vertical;
        padding: 1 2;
    }

    .section {
        border: round $accent;
        height: 1fr;
        layout: vertical;
        padding: 1 2;
        margin-top: 1;
    }

    .field {
        margin-top: 1;
    }

    WizardView Input {
        height: 1;
        min-height: 1;
        border: none;
        padding: 0 1;
    }

    WizardView Input:focus {
        height: 1;
        min-height: 1;
        border: none;
        background-tint: $foreground 5%;
    }

    WizardView Select {
        height: 1;
        min-height: 1;
    }

    WizardView Select > SelectCurrent {
        height: 1;
        min-height: 1;
        border: none;
        padding: 0 1;
    }

    WizardView Select:focus > SelectCurrent {
        border: none;
        background-tint: $foreground 5%;
    }

    WizardView Button {
        height: 1;
        min-height: 1;
        width: auto;
        min-width: 10;
        padding: 0 1;
        border: none;
        color: $foreground;
    }

    WizardView Button:focus,
    WizardView Button:hover,
    WizardView Button.-active {
        height: 1;
        min-height: 1;
        border: none;
        background-tint: $foreground 8%;
    }

    WizardView Button.-primary,
    WizardView Button.-primary:focus,
    WizardView Button.-primary:hover,
    WizardView Button.-primary.-active,
    WizardView Button.-success,
    WizardView Button.-success:focus,
    WizardView Button.-success:hover,
    WizardView Button.-success.-active {
        color: $button-color-foreground;
        border: none;
    }

    WizardView Checkbox {
        height: 1;
        min-height: 1;
        border: none;
        padding: 0 1;
    }

    WizardView Checkbox:focus {
        height: 1;
        min-height: 1;
        border: none;
        background-tint: $foreground 5%;
    }

    #general-tag-add,
    #general-tag-use,
    #general-tag-remove {
        width: auto;
        margin-top: 1;
    }

    #disks-add,
    #disks-remove {
        width: auto;
        margin-right: 1;
    }

    #disks-select {
        width: 1fr;
    }

    #wizard-actions {
        margin-top: 1;
        height: auto;
    }

    #wizard-errors {
        color: $error;
        margin-top: 1;
    }

    #wizard-status {
        color: $text-muted;
        margin-top: 1;
    }

    #wizard-title {
        text-style: bold;
    }
    """

    def __init__(
        self, settings: AppSettings, workflow: WorkflowState, service: ProxmoxService
    ) -> None:
        super().__init__()
        self._settings = settings
        self._workflow = workflow
        self._service = service
        self._selected_disk_index = 0
        self._node_load_in_flight = False
        self._suppress_node_change = False
        self._suppress_storage_change = False
        self._suppress_disk_selection_change = False
        self._initializing_reference_data = False
        self._loaded_node_reference: str | None = None
        self._loaded_iso_source: tuple[str, str] | None = None

    def _section(self, section_id: str, title: str) -> ScrollableContainer:
        section = ScrollableContainer(id=section_id, classes="section")
        section.border_title = f" {title} "
        return section

    def compose(self):
        yield Static("Wizard", id="wizard-title")
        yield Static("", id="wizard-step")
        yield Static("", id="wizard-errors")
        yield Static("", id="wizard-status")

        with self._section("general-section", "General"):
            yield Select[str](
                [], prompt="Node", id="general-node", allow_blank=True, classes="field"
            )
            yield Input(id="general-vmid", placeholder="VM ID", classes="field")
            yield Input(id="general-name", placeholder="VM Name", classes="field")
            yield Select[str](
                [],
                prompt="Ressource Pool",
                id="general-pool",
                allow_blank=True,
                classes="field",
            )
            yield Input(id="general-tag-input", placeholder="Add tag", classes="field")
            yield Button("Add Tag", id="general-tag-add")
            yield Select[str](
                [],
                prompt="Existing tags",
                id="general-tag-existing",
                allow_blank=True,
                classes="field",
            )
            yield Button("Use Existing", id="general-tag-use")
            yield Select[str](
                [],
                prompt="Current tags",
                id="general-tag-current",
                allow_blank=True,
                classes="field",
            )
            yield Button("Remove Tag", id="general-tag-remove")
            yield Checkbox("High availability", value=True, id="general-ha", classes="field")
            yield Checkbox("Start at boot", value=False, id="general-onboot", classes="field")
            yield Input(id="general-startup-order", placeholder="Startup order", classes="field")
            yield Input(
                id="general-startup-delay", placeholder="Startup delay (seconds)", classes="field"
            )
            yield Input(
                id="general-shutdown-timeout",
                placeholder="Shutdown timeout (seconds)",
                classes="field",
            )

        with self._section("os-section", "Operating System"):
            yield Select[str](
                [("ISO", "iso"), ("Physical disc drive", "physical"), ("No media", "none")],
                value="iso",
                id="os-media-choice",
                allow_blank=False,
                classes="field",
            )
            yield Select[str](
                [], prompt="ISO storage", id="os-storage", allow_blank=True, classes="field"
            )
            yield Select[str](
                [], prompt="ISO image", id="os-iso", allow_blank=True, classes="field"
            )
            yield Input(
                id="os-physical-drive",
                placeholder="Physical drive path",
                value="/dev/sr0",
                classes="field",
            )
            yield Select[str](
                [
                    ("Linux", "linux"),
                    ("Windows", "windows"),
                    ("Solaris", "solaris"),
                    ("Other", "other"),
                ],
                value="linux",
                id="os-guest-type",
                allow_blank=False,
                classes="field",
            )
            yield Select[str](
                GUEST_VERSIONS["linux"],
                value="l26",
                prompt="Guest version",
                id="os-guest-version",
                allow_blank=False,
                classes="field",
            )

        with self._section("system-section", "System"):
            yield Select[str](
                [
                    ("Default", "default"),
                    ("Standard", "std"),
                    ("VirtIO GPU", "virtio"),
                    ("VMware", "vmware"),
                    ("QXL", "qxl"),
                ],
                value="default",
                id="system-graphic-card",
                allow_blank=False,
                classes="field",
            )
            yield Select[str](
                [("q35", "q35"), ("i440fx", "pc")],
                value="q35",
                id="system-machine",
                allow_blank=False,
                classes="field",
            )
            yield Select[str](
                [("OVMF (UEFI)", "ovmf"), ("SeaBIOS", "seabios")],
                value="ovmf",
                id="system-bios",
                allow_blank=False,
                classes="field",
            )
            yield Checkbox("Add EFI disk", value=True, id="system-add-efi", classes="field")
            yield Select[str](
                [], prompt="EFI storage", id="system-efi-storage", allow_blank=True, classes="field"
            )
            yield Checkbox("Pre-enroll keys", value=False, id="system-pre-enroll", classes="field")
            yield Select[str](
                [
                    ("VirtIO SCSI single", "virtio-scsi-single"),
                    ("VirtIO SCSI", "virtio-scsi-pci"),
                    ("LSI", "lsi"),
                    ("MegaSAS", "megasas"),
                    ("PVSCSI", "pvscsi"),
                ],
                value="virtio-scsi-single",
                id="system-scsi-controller",
                allow_blank=False,
                classes="field",
            )
            yield Checkbox("Qemu agent", value=True, id="system-qemu-agent", classes="field")
            yield Checkbox("Add TPM", value=False, id="system-tpm", classes="field")

        with self._section("disks-section", "Disks"):
            with HorizontalGroup(classes="field"):
                yield Button("Add Disk", id="disks-add")
                yield Button("Remove Disk", id="disks-remove")
            with HorizontalGroup(classes="field"):
                yield Select[str](
                    [("No disks configured", NO_DISK_SELECTED)],
                    value=NO_DISK_SELECTED,
                    prompt="Disk",
                    id="disks-select",
                    allow_blank=False,
                )
            yield Static("", id="disks-summary", classes="field")
            yield Select[str](
                [("SCSI", "scsi"), ("VirtIO", "virtio"), ("SATA", "sata"), ("IDE", "ide")],
                value="scsi",
                id="disk-bus",
                allow_blank=False,
                classes="field",
            )
            yield Input(id="disk-device", placeholder="Device index", classes="field")
            yield Select[str](
                [], prompt="Disk storage", id="disk-storage", allow_blank=True, classes="field"
            )
            yield Input(id="disk-size", placeholder="Disk size GiB", classes="field")
            yield Static("Format: raw", id="disk-format", classes="field")
            yield Select[str](
                [
                    ("No cache", "none"),
                    ("Write back", "writeback"),
                    ("Write through", "writethrough"),
                    ("Direct sync", "directsync"),
                    ("Unsafe", "unsafe"),
                ],
                value="none",
                id="disk-cache",
                allow_blank=False,
                classes="field",
            )
            yield Checkbox("Discard", value=False, id="disk-discard", classes="field")
            yield Checkbox("IO thread", value=True, id="disk-io-thread", classes="field")
            yield Checkbox("SSD emulation", value=True, id="disk-ssd", classes="field")
            yield Checkbox("Include in backup", value=True, id="disk-backup", classes="field")
            yield Checkbox(
                "Skip replication", value=False, id="disk-skip-replication", classes="field"
            )
            yield Select[str](
                [("io_uring", "io_uring"), ("native", "native"), ("threads", "threads")],
                value="io_uring",
                id="disk-aio",
                allow_blank=False,
                classes="field",
            )

        with self._section("cpu-section", "CPU"):
            yield Input(id="cpu-cores", value="2", placeholder="Cores", classes="field")
            yield Input(id="cpu-sockets", value="1", placeholder="Sockets", classes="field")
            yield Select[str](
                [("host", "host"), ("kvm64", "kvm64"), ("x86-64-v2-AES", "x86-64-v2-AES")],
                value="host",
                id="cpu-type",
                allow_blank=False,
                classes="field",
            )

        with self._section("memory-section", "Memory"):
            yield Input(id="memory-size", value="2048", placeholder="Memory MiB", classes="field")
            yield Input(
                id="memory-min-size", value="2048", placeholder="Min memory MiB", classes="field"
            )
            yield Checkbox("Ballooning", value=True, id="memory-ballooning", classes="field")
            yield Checkbox("Allow KSM", value=True, id="memory-ksm", classes="field")

        with self._section("network-section", "Network"):
            yield Checkbox("No network device", value=False, id="network-none", classes="field")
            yield Select[str](
                [], prompt="Bridge", id="network-bridge", allow_blank=True, classes="field"
            )
            yield Input(id="network-vlan", placeholder="VLAN tag", classes="field")
            yield Select[str](
                [
                    ("VirtIO", "virtio"),
                    ("E1000", "e1000"),
                    ("VMXNET3", "vmxnet3"),
                    ("RTL8139", "rtl8139"),
                ],
                value="virtio",
                id="network-model",
                allow_blank=False,
                classes="field",
            )
            yield Input(id="network-mac", placeholder="MAC address (blank = auto)", classes="field")
            yield Checkbox("Firewall", value=True, id="network-firewall", classes="field")
            yield Checkbox("Disconnected", value=False, id="network-disconnected", classes="field")
            yield Input(id="network-mtu", placeholder="MTU", classes="field")
            yield Input(id="network-rate", placeholder="Rate limit MB/s", classes="field")
            yield Input(id="network-queues", placeholder="Multiqueue", classes="field")

        with self._section("confirm-section", "Confirm"):
            yield Static("", id="confirm-summary", classes="field")
            yield Static("", id="confirm-result", classes="field")

        with Horizontal(id="wizard-actions"):
            yield Button("Next", id="wizard-next", variant="primary")
            yield Button("Back", id="wizard-back")
            yield Button("Create VM", id="wizard-create", variant="success")

    def on_mount(self) -> None:
        self._set_guest_version_options("linux")
        self._refresh_disk_options()
        self._load_selected_disk_into_widgets()
        self._show_step()
        self._sync_media_visibility()
        self._sync_system_visibility()
        self._sync_memory_visibility()
        self._sync_network_visibility()

    def activate(self) -> None:
        self.styles.display = "block"
        self._show_status("Loading live reference data...")
        self._initializing_reference_data = True
        self.call_after_refresh(self._focus_current_step, True)
        self.run_worker(self._load_initial_data, thread=True, exclusive=True)

    def _load_initial_data(self) -> None:
        with ThreadPoolExecutor(max_workers=4) as executor:
            nodes_future = executor.submit(self._service.load_nodes)
            pools_future = executor.submit(self._service.load_pools)
            tags_future = executor.submit(self._service.load_existing_tags)
            next_vmid_future = executor.submit(self._service.load_next_vmid)

            nodes = nodes_future.result()
            pools = pools_future.result()
            tags = tags_future.result()
            next_vmid = next_vmid_future.result()
        node_names = [node.name for node in nodes]
        preferred_node = (
            self._settings.safety_policy.test_node
            if self._settings.safety_policy.enable_test_mode
            else ""
        ) or (
            "sbx0pve00" if "sbx0pve00" in node_names else (node_names[0] if node_names else "")
        )
        references = ReferenceData(
            nodes=node_names,
            pools=[pool.poolid for pool in pools],
            existing_tags=tags,
        )
        storages: list[Storage] = []
        bridges: list[Bridge] = []
        isos: list[str] = []
        if preferred_node:
            with ThreadPoolExecutor(max_workers=2) as executor:
                storages_future = executor.submit(self._service.load_storages, preferred_node)
                bridges_future = executor.submit(self._service.load_bridges, preferred_node)
                storages = storages_future.result()
                bridges = bridges_future.result()
            iso_storages = sorted(
                storage.storage for storage in storages if "iso" in storage.content
            )
            default_iso_storage = (
                "cephfs" if "cephfs" in iso_storages else (iso_storages[0] if iso_storages else "")
            )
            if default_iso_storage:
                isos = [
                    iso.volid
                    for iso in self._service.load_isos(preferred_node, default_iso_storage)
                ]
        self.app.call_from_thread(
            self._apply_initial_reference_data,
            references,
            next_vmid,
            preferred_node,
            storages,
            bridges,
            isos,
        )

    def _load_node_dependent_data(self, node: str) -> None:
        with ThreadPoolExecutor(max_workers=2) as executor:
            storages_future = executor.submit(self._service.load_storages, node)
            bridges_future = executor.submit(self._service.load_bridges, node)
            storages = storages_future.result()
            bridges = bridges_future.result()
        self.app.call_from_thread(self._apply_node_reference_data, node, storages, bridges)

    def _apply_initial_reference_data(
        self,
        references: ReferenceData,
        next_vmid: int,
        preferred_node: str,
        storages: list[Storage],
        bridges: list[Bridge],
        isos: list[str],
    ) -> None:
        self._workflow.reference_data.nodes = references.nodes
        self._workflow.reference_data.pools = references.pools
        self._workflow.reference_data.existing_tags = references.existing_tags

        self._set_select_options("general-node", references.nodes)
        self._set_select_options("general-pool", references.pools)
        self._set_select_options("general-tag-existing", references.existing_tags)

        if preferred_node:
            self._suppress_node_change = True
            self.query_one("#general-node", Select).value = preferred_node
            self._suppress_node_change = False
            self._workflow.config.general.node = preferred_node
        self.query_one("#general-vmid", Input).value = str(next_vmid)
        self._workflow.config.general.vmid = next_vmid
        if self._settings.safety_policy.enable_test_mode and self._settings.safety_policy.test_pool:
            self.query_one("#general-pool", Select).value = self._settings.safety_policy.test_pool
            self._workflow.config.general.pool = self._settings.safety_policy.test_pool
        if preferred_node:
            self._apply_node_reference_data(preferred_node, storages, bridges)
            if isos:
                storage = self._select("os-storage")
                self._apply_isos(preferred_node, storage, isos)
        self._initializing_reference_data = False
        self._show_status("Loaded general reference data.")
        self._focus_current_step(force=True)

    def _apply_node_reference_data(
        self,
        node: str,
        storages: list[Storage],
        bridges: list[Bridge],
    ) -> None:
        all_storages = sorted(storage.storage for storage in storages)
        iso_storages = sorted(storage.storage for storage in storages if "iso" in storage.content)
        disk_storages = sorted(
            storage.storage for storage in storages if "images" in storage.content
        )
        bridge_names = [bridge.iface for bridge in bridges]

        refs = self._workflow.reference_data
        refs.all_storages = all_storages
        refs.iso_storages = iso_storages
        refs.disk_storages = disk_storages
        refs.bridges = bridge_names
        self._loaded_node_reference = node

        self._set_select_options("os-storage", iso_storages)
        self._set_select_options("system-efi-storage", disk_storages)
        self._set_select_options("disk-storage", disk_storages)
        self._set_select_options("network-bridge", bridge_names)

        default_iso_storage = (
            "cephfs" if "cephfs" in iso_storages else (iso_storages[0] if iso_storages else "")
        )
        default_disk_storage = (
            "ceph-pool"
            if "ceph-pool" in disk_storages
            else (disk_storages[0] if disk_storages else "")
        )
        default_bridge = (
            "vmbr9" if "vmbr9" in bridge_names else (bridge_names[0] if bridge_names else "")
        )

        if default_iso_storage:
            self._suppress_storage_change = True
            self.query_one("#os-storage", Select).value = default_iso_storage
            self._suppress_storage_change = False
            self._workflow.config.os.storage = default_iso_storage
        if default_disk_storage:
            self.query_one("#system-efi-storage", Select).value = default_disk_storage
            self.query_one("#disk-storage", Select).value = default_disk_storage
            self._workflow.config.system.efi_storage = default_disk_storage
            if self._workflow.config.disks:
                self._workflow.config.disks[0].storage = default_disk_storage
                self._load_selected_disk_into_widgets()
        if default_bridge:
            self.query_one("#network-bridge", Select).value = default_bridge
            self._workflow.config.network.bridge = default_bridge
        self._node_load_in_flight = False
        self._show_status(f"Loaded node-specific reference data for {node}.")
        self._focus_current_step(force=True)

    def _load_isos(self, node: str, storage: str) -> None:
        isos = self._service.load_isos(node, storage)
        self.app.call_from_thread(self._apply_isos, node, storage, [iso.volid for iso in isos])

    def _apply_isos(self, node: str, storage: str, iso_values: list[str]) -> None:
        self._workflow.reference_data.isos = iso_values
        self._loaded_iso_source = (node, storage)
        self._set_select_options("os-iso", iso_values)
        preferred = select_preferred_iso(
            iso_values,
            self._settings.default_iso_selector,
        ) or (iso_values[0] if iso_values else "")
        if preferred:
            self.query_one("#os-iso", Select).value = preferred
            self._workflow.config.os.iso = preferred
        self._show_status(f"Loaded {len(iso_values)} ISO image(s) from {storage}.")
        self._focus_current_step(force=True)

    def _show_step(self) -> None:
        for step in WIZARD_STEPS:
            section = self.query_one(f"#{step}-section", ScrollableContainer)
            is_current = step == self._workflow.current_step
            section.display = is_current
            if is_current:
                section.scroll_to(y=0, animate=False, immediate=True, force=True)
        self.query_one("#wizard-title", Static).update("Create Proxmox VM")
        step_label = (
            f"Step {self._workflow.current_step_index + 1}/"
            f"{len(WIZARD_STEPS)}: {self._workflow.step_title}"
        )
        self.query_one("#wizard-step", Static).update(step_label)
        self.query_one("#wizard-back", Button).disabled = self._workflow.current_step_index == 0
        is_confirm = self._workflow.current_step == "confirm"
        self.query_one("#wizard-next", Button).styles.display = "none" if is_confirm else "block"
        self.query_one("#wizard-create", Button).styles.display = "block" if is_confirm else "none"
        if is_confirm:
            self._update_confirmation()
            self._update_confirm_actions()
        self.call_after_refresh(self._focus_current_step, True)

    def _show_errors(self, errors: list[str]) -> None:
        self.query_one("#wizard-errors", Static).update("\n".join(errors))

    def _show_status(self, message: str) -> None:
        self.query_one("#wizard-status", Static).update(message)

    def _focus_current_step(self, force: bool = False) -> None:
        focused = self.app.focused
        if not force and focused is not None and focused is not self:
            return

        target_ids = {
            "general": ["general-name", "general-vmid", "general-node", "wizard-next"],
            "os": ["os-media-choice", "os-storage", "os-iso", "os-physical-drive", "wizard-next"],
            "system": ["system-graphic-card", "system-machine", "wizard-next"],
            "disks": ["disks-select", "disk-size", "wizard-next"],
            "cpu": ["cpu-cores", "cpu-sockets", "wizard-next"],
            "memory": ["memory-size", "memory-min-size", "wizard-next"],
            "network": ["network-bridge", "network-model", "wizard-next"],
            "confirm": ["wizard-create", "wizard-back"],
        }[self._workflow.current_step]

        for target_id in target_ids:
            matches = self.query(f"#{target_id}")
            if not matches:
                continue
            widget = matches.first()
            if not widget.display or widget.disabled:
                continue
            self.app.set_focus(widget)
            return

    def _set_widget_visibility(self, widget_id: str, visible: bool) -> None:
        widget = self.query_one(f"#{widget_id}", Widget)
        if not visible and isinstance(widget, Select):
            widget.expanded = False
        widget.display = visible
        widget.disabled = not visible

    def _set_select_options(self, widget_id: str, values: list[str]) -> None:
        widget = self.query_one(f"#{widget_id}", Select)
        widget.set_options([(value, value) for value in values])

    def _input(self, widget_id: str) -> str:
        return self.query_one(f"#{widget_id}", Input).value.strip()

    def _select(self, widget_id: str) -> str:
        value = self.query_one(f"#{widget_id}", Select).value
        return value if isinstance(value, str) else ""

    def _checked(self, widget_id: str) -> bool:
        return self.query_one(f"#{widget_id}", Checkbox).value

    def _sync_all_from_widgets(self) -> None:
        config = self._workflow.config
        config.general.node = self._select("general-node")
        config.general.vmid = int(self._input("general-vmid") or "0")
        config.general.name = self._input("general-name")
        config.general.pool = self._select("general-pool")
        config.general.ha_enabled = self._checked("general-ha")
        config.general.onboot = self._checked("general-onboot")
        config.general.startup_order = self._input("general-startup-order")
        config.general.startup_delay = self._input("general-startup-delay")
        config.general.shutdown_timeout = self._input("general-shutdown-timeout")

        config.os.media_choice = self._select("os-media-choice") or "iso"
        config.os.storage = self._select("os-storage")
        config.os.iso = self._select("os-iso")
        config.os.physical_drive_path = self._input("os-physical-drive") or "/dev/sr0"
        config.os.guest_type = self._select("os-guest-type") or "linux"
        config.os.guest_version = self._select("os-guest-version") or "l26"

        config.system.graphic_card = self._select("system-graphic-card") or "default"
        config.system.machine = self._select("system-machine") or "q35"
        config.system.bios = self._select("system-bios") or "ovmf"
        config.system.add_efi_disk = self._checked("system-add-efi")
        config.system.efi_storage = self._select("system-efi-storage")
        config.system.pre_enrolled_keys = self._checked("system-pre-enroll")
        config.system.scsi_controller = (
            self._select("system-scsi-controller") or "virtio-scsi-single"
        )
        config.system.qemu_agent = self._checked("system-qemu-agent")
        config.system.tpm_enabled = self._checked("system-tpm")

        self._sync_selected_disk_from_widgets()

        config.cpu.cores = int(self._input("cpu-cores") or "0")
        config.cpu.sockets = int(self._input("cpu-sockets") or "0")
        config.cpu.cpu_type = self._select("cpu-type") or "host"

        config.memory.memory_mib = int(self._input("memory-size") or "0")
        config.memory.min_memory_mib = int(self._input("memory-min-size") or "0")
        config.memory.ballooning = self._checked("memory-ballooning")
        config.memory.allow_ksm = self._checked("memory-ksm")

        config.network.no_network_device = self._checked("network-none")
        config.network.bridge = self._select("network-bridge")
        config.network.vlan_tag = self._input("network-vlan")
        config.network.model = self._select("network-model") or "virtio"
        config.network.mac_address = self._input("network-mac")
        config.network.firewall = self._checked("network-firewall")
        config.network.disconnected = self._checked("network-disconnected")
        config.network.mtu = self._input("network-mtu")
        config.network.rate_limit = self._input("network-rate")
        config.network.multiqueue = self._input("network-queues")

    def _set_guest_version_options(self, guest_type: str) -> None:
        versions = GUEST_VERSIONS.get(guest_type, GUEST_VERSIONS["other"])
        widget = self.query_one("#os-guest-version", Select)
        widget.set_options(versions)
        widget.value = versions[0][1]

    def _update_confirmation(self) -> None:
        self._sync_all_from_widgets()
        summary = build_confirmation_text(self._workflow.config, self._settings)
        self.query_one("#confirm-summary", Static).update(summary)
        result = self._workflow.submission.message
        self.query_one("#confirm-result", Static).update(result)

    def _confirm_action_is_exit(self) -> bool:
        return self._workflow.submission.phase in {"success", "partial"}

    def _update_confirm_actions(self) -> None:
        button = self.query_one("#wizard-create", Button)
        if self._confirm_action_is_exit():
            button.label = "Exit"
            button.variant = "primary"
            button.disabled = False
            return
        button.label = "Create VM"
        button.variant = "success"
        button.disabled = self._workflow.submission.phase == "running"

    def _sync_media_visibility(self) -> None:
        choice = self._select("os-media-choice") or "iso"
        self._set_widget_visibility("os-storage", choice == "iso")
        self._set_widget_visibility("os-iso", choice == "iso")
        self._set_widget_visibility("os-physical-drive", choice == "physical")

    def _sync_system_visibility(self) -> None:
        show_efi_storage = self._checked("system-add-efi") or self._checked("system-tpm")
        self._set_widget_visibility("system-efi-storage", show_efi_storage)
        self._set_widget_visibility("system-pre-enroll", self._checked("system-add-efi"))

    def _sync_memory_visibility(self) -> None:
        show_ballooning_fields = self._checked("memory-ballooning")
        self._set_widget_visibility("memory-min-size", show_ballooning_fields)
        self._set_widget_visibility("memory-ksm", show_ballooning_fields)

    def _sync_network_visibility(self) -> None:
        visible = not self._checked("network-none")
        for widget_id in [
            "network-bridge",
            "network-vlan",
            "network-model",
            "network-mac",
            "network-firewall",
            "network-disconnected",
            "network-mtu",
            "network-rate",
            "network-queues",
        ]:
            self._set_widget_visibility(widget_id, visible)

    def _selected_disk(self) -> DiskConfig | None:
        if not self._workflow.config.disks:
            return None
        self._selected_disk_index = min(
            self._selected_disk_index, len(self._workflow.config.disks) - 1
        )
        return self._workflow.config.disks[self._selected_disk_index]

    def _sync_selected_disk_from_widgets(self, *, refresh_options: bool = True) -> None:
        disk = self._selected_disk()
        if disk is None:
            return
        disk.bus = self._select("disk-bus") or "scsi"
        disk.device = int(self._input("disk-device") or "0")
        disk.storage = self._select("disk-storage")
        disk.size_gib = int(self._input("disk-size") or "0")
        disk.cache = self._select("disk-cache") or "none"
        disk.discard = self._checked("disk-discard")
        disk.io_thread = self._checked("disk-io-thread")
        disk.ssd_emulation = self._checked("disk-ssd")
        disk.backup = self._checked("disk-backup")
        disk.skip_replication = self._checked("disk-skip-replication")
        disk.async_io = self._select("disk-aio") or "io_uring"
        if refresh_options:
            self._refresh_disk_options()

    def _load_selected_disk_into_widgets(self, *, refresh_options: bool = True) -> None:
        disk = self._selected_disk()
        disabled = disk is None
        for widget_id in [
            "disk-bus",
            "disk-device",
            "disk-storage",
            "disk-size",
            "disk-cache",
            "disk-discard",
            "disk-io-thread",
            "disk-ssd",
            "disk-backup",
            "disk-skip-replication",
            "disk-aio",
        ]:
            self.query_one(f"#{widget_id}").disabled = disabled
        if disk is None:
            self.query_one("#disks-summary", Static).update("No disks configured.")
            return
        self.query_one("#disk-bus", Select).value = disk.bus
        self.query_one("#disk-device", Input).value = str(disk.device)
        if disk.storage and disk.storage in self._workflow.reference_data.disk_storages:
            self.query_one("#disk-storage", Select).value = disk.storage
        self.query_one("#disk-size", Input).value = str(disk.size_gib)
        self.query_one("#disk-cache", Select).value = disk.cache
        self.query_one("#disk-discard", Checkbox).value = disk.discard
        self.query_one("#disk-io-thread", Checkbox).value = disk.io_thread
        self.query_one("#disk-ssd", Checkbox).value = disk.ssd_emulation
        self.query_one("#disk-backup", Checkbox).value = disk.backup
        self.query_one("#disk-skip-replication", Checkbox).value = disk.skip_replication
        self.query_one("#disk-aio", Select).value = disk.async_io
        if refresh_options:
            self._refresh_disk_options()

    def _switch_selected_disk(self, new_index: int) -> None:
        self._sync_selected_disk_from_widgets(refresh_options=False)
        self._update_disk_summary()
        self._selected_disk_index = new_index
        self._load_selected_disk_into_widgets(refresh_options=False)
        self.query_one("#disks-select", Select).focus()

    def _refresh_disk_options(self) -> None:
        select = self.query_one("#disks-select", Select)
        self._suppress_disk_selection_change = True
        try:
            if self._workflow.config.disks:
                disk_labels = [
                    f"Disk {index + 1}: {disk.slot_name}"
                    for index, disk in enumerate(self._workflow.config.disks)
                ]
                select.set_options([(label, str(index)) for index, label in enumerate(disk_labels)])
                select.disabled = False
                select.value = str(self._selected_disk_index)
            else:
                select.set_options([("No disks configured", NO_DISK_SELECTED)])
                select.value = NO_DISK_SELECTED
                select.disabled = True
        finally:
            self._suppress_disk_selection_change = False
        self._update_disk_summary()

    def _update_disk_summary(self) -> None:
        if self._workflow.config.disks:
            summary = "\n".join(
                f"{index + 1}. {disk.slot_name} {disk.storage or '-'} {disk.size_gib}GiB"
                for index, disk in enumerate(self._workflow.config.disks)
            )
        else:
            summary = "No disks configured."
        self.query_one("#disks-summary", Static).update(summary)

    @on(Select.Changed, "#general-node")
    def on_general_node_changed(self, event: Select.Changed) -> None:
        if (
            isinstance(event.value, str)
            and event.value
            and not self._node_load_in_flight
            and not self._suppress_node_change
            and not self._initializing_reference_data
            and self._loaded_node_reference != event.value
        ):
            self._node_load_in_flight = True
            self.run_worker(
                lambda: self._load_node_dependent_data(event.value),
                thread=True,
                exclusive=True,
            )

    @on(Select.Changed, "#os-storage")
    def on_os_storage_changed(self, event: Select.Changed) -> None:
        node = self._select("general-node")
        if (
            isinstance(event.value, str)
            and event.value
            and node
            and not self._suppress_storage_change
            and not self._initializing_reference_data
            and self._loaded_iso_source != (node, event.value)
        ):
            self.run_worker(lambda: self._load_isos(node, event.value), thread=True, exclusive=True)

    @on(Select.Changed, "#os-media-choice")
    def on_os_media_changed(self) -> None:
        self._sync_media_visibility()

    @on(Select.Changed, "#os-guest-type")
    def on_os_guest_type_changed(self, event: Select.Changed) -> None:
        if isinstance(event.value, str):
            self._set_guest_version_options(event.value)

    @on(Checkbox.Changed, "#network-none")
    def on_network_none_changed(self) -> None:
        self._sync_network_visibility()

    @on(Checkbox.Changed, "#system-add-efi")
    @on(Checkbox.Changed, "#system-tpm")
    def on_system_dependency_changed(self) -> None:
        self._sync_system_visibility()

    @on(Checkbox.Changed, "#memory-ballooning")
    def on_memory_ballooning_changed(self) -> None:
        self._sync_memory_visibility()

    @on(Select.Changed, "#disks-select")
    def on_disk_selection_changed(self, event: Select.Changed) -> None:
        if (
            not self._suppress_disk_selection_change
            and isinstance(event.value, str)
            and event.value != NO_DISK_SELECTED
            and event.value.isdigit()
        ):
            new_index = int(event.value)
            if new_index == self._selected_disk_index:
                return
            select = self.query_one("#disks-select", Select)
            select.expanded = False
            self.call_after_refresh(self._switch_selected_disk, new_index)

    @on(Button.Pressed, "#general-tag-add")
    def on_add_tag_pressed(self) -> None:
        tag = self._input("general-tag-input")
        if tag and tag not in self._workflow.config.general.tags:
            self._workflow.config.general.tags.append(tag)
            self.query_one("#general-tag-input", Input).value = ""
            self._set_select_options("general-tag-current", self._workflow.config.general.tags)

    @on(Button.Pressed, "#general-tag-use")
    def on_use_existing_tag_pressed(self) -> None:
        tag = self._select("general-tag-existing")
        if tag and tag not in self._workflow.config.general.tags:
            self._workflow.config.general.tags.append(tag)
            self._set_select_options("general-tag-current", self._workflow.config.general.tags)

    @on(Button.Pressed, "#general-tag-remove")
    def on_remove_tag_pressed(self) -> None:
        tag = self._select("general-tag-current")
        if tag in self._workflow.config.general.tags:
            self._workflow.config.general.tags.remove(tag)
            self._set_select_options("general-tag-current", self._workflow.config.general.tags)

    @on(Button.Pressed, "#disks-add")
    def on_add_disk_pressed(self) -> None:
        self._sync_selected_disk_from_widgets()
        next_device = 0
        if self._workflow.config.disks:
            next_device = max(disk.device for disk in self._workflow.config.disks) + 1
        storage = self._select("disk-storage") or self._workflow.config.system.efi_storage
        disk = DiskConfig(device=next_device, storage=storage)
        self._workflow.config.disks.append(disk)
        self._selected_disk_index = len(self._workflow.config.disks) - 1
        self._load_selected_disk_into_widgets()

    @on(Button.Pressed, "#disks-remove")
    def on_remove_disk_pressed(self) -> None:
        if not self._workflow.config.disks:
            return
        self._workflow.config.disks.pop(self._selected_disk_index)
        self._selected_disk_index = max(0, self._selected_disk_index - 1)
        self._load_selected_disk_into_widgets()

    @on(Button.Pressed, "#wizard-back")
    def on_back_pressed(self) -> None:
        self._sync_all_from_widgets()
        self._show_errors([])
        if self._workflow.current_step_index > 0:
            self._workflow.current_step_index -= 1
            self._show_step()

    @on(Button.Pressed, "#wizard-next")
    def on_next_pressed(self) -> None:
        try:
            self._sync_all_from_widgets()
        except ValueError:
            self._show_errors(["Numeric fields contain invalid values."])
            return
        errors = validate_step(
            self._workflow.current_step,
            self._workflow.config,
            self._settings,
            self._workflow.reference_data,
        )
        self._show_errors(errors)
        if errors:
            return
        if self._workflow.current_step_index < len(WIZARD_STEPS) - 1:
            self._workflow.current_step_index += 1
            self._show_step()

    @on(Button.Pressed, "#wizard-create")
    def on_create_pressed(self) -> None:
        if self._confirm_action_is_exit():
            self.app.exit()
            return
        try:
            self._sync_all_from_widgets()
        except ValueError:
            self._show_errors(["Numeric fields contain invalid values."])
            return
        errors = validate_all_steps(
            self._workflow.config, self._settings, self._workflow.reference_data
        )
        self._show_errors(errors)
        if errors:
            return
        self.app.push_screen(
            AutoStartConfirmModal(),
            callback=self._handle_auto_start_choice,
        )

    def _handle_auto_start_choice(self, start_after_create: bool | None) -> None:
        if start_after_create is None:
            self._show_status("VM creation cancelled.")
            self.call_after_refresh(self._focus_current_step, True)
            return
        self._workflow.submission.phase = "running"
        self._workflow.submission.message = "Creating VM..."
        self._update_confirmation()
        self._update_confirm_actions()
        self._show_status("Submitting VM creation request...")
        self.run_worker(
            lambda: self._submit_create(start_after_create),
            thread=True,
            exclusive=True,
        )

    def _submit_create(self, start_after_create: bool) -> None:
        try:
            result = self._service.create_vm(
                self._workflow.config,
                start_after_create=start_after_create,
            )
        except ProxmoxPostCreateError as exc:
            self._workflow.submission.phase = "partial"
            self._workflow.submission.partial_success = True
            self._workflow.submission.node = exc.node
            self._workflow.submission.vmid = exc.vmid
            self._workflow.submission.message = (
                f"VM {exc.vmid} on {exc.node} was created, but {exc.step} failed.\n{exc}"
            )
            self.app.call_from_thread(
                self._finalize_submit, False, self._workflow.submission.message
            )
            return
        except ProxmoxError as exc:
            self._workflow.submission.phase = "error"
            self._workflow.submission.message = f"VM creation failed: {exc}"
            self.app.call_from_thread(
                self._finalize_submit, False, self._workflow.submission.message
            )
            return

        self._workflow.submission.phase = "success"
        self._workflow.submission.node = result.node
        self._workflow.submission.vmid = result.vmid
        self._workflow.submission.message = (
            f"VM {result.vmid} ({result.name}) created on {result.node}."
        )
        self.app.call_from_thread(self._finalize_submit, True, self._workflow.submission.message)

    def _finalize_submit(self, success: bool, message: str) -> None:
        self._update_confirmation()
        self._update_confirm_actions()
        self._show_status(message)
        self.call_after_refresh(self._focus_current_step, True)
        self.post_message(self.SubmitFinished(success, message))
