<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:vm="using:DodoSSH.Client.Shell.ViewModels"
             xmlns:views="using:DodoSSH.Client.App.Views"
             x:Class="DodoSSH.Client.App.Views.HostsScreen"
             x:DataType="vm:VaultViewModel">

  <!--
    The monogram converters, as resources rather than through x:Static: MonogramBrushConverter's own remarks
    call for "a StaticResource-shaped reference" naming which half a card wants, and a resource entry is
    what makes that literal — the markup below reads {StaticResource MonogramBg} and
    {StaticResource MonogramFg} rather than a converter parameter that would mean nothing without reading
    that file. Constructed with x:Arguments because the type takes its "which half" as a constructor
    parameter, not a settable property — there is nothing to set once the instance exists.
  -->
  <UserControl.Resources>
    <views:MonogramBrushConverter x:Key="MonogramBg">
      <x:Arguments>
        <x:Boolean>True</x:Boolean>
      </x:Arguments>
    </views:MonogramBrushConverter>
    <views:MonogramBrushConverter x:Key="MonogramFg">
      <x:Arguments>
        <x:Boolean>False</x:Boolean>
      </x:Arguments>
    </views:MonogramBrushConverter>
  </UserControl.Resources>

  <!--
    The hosts screen: every group as a heading, every host under it as a card, and a drawer for whichever one
    is selected.

    ── v5: THE BOARD IS FLAT SECTIONS NOW, NOT A GRID OF GROUP CARDS ONE LEVEL AT A TIME. ──────────────────
    The v4 shape was a directory pane: a wrap of group cards above the hosts, opened by a double-click, with
    a breadcrumb trail above it saying where you were. That model traded a real cost for it — a keychain
    filed under three groups took three clicks to see all of, and "where is everything" had no answer at
    all, only "where am I right now". v5 draws every group at once, in label order, "No group" first, exactly
    the flattening the phone's SidebarRows has always drawn — see VaultViewModel.HostSections and
    VaultViewModel.FlattenIntoSections. A host is filed under a group through its own editor, or through the
    chosen-hosts menu's "Change group…"; dragging a card onto a group card is gone with the cards it used to
    land on. See HostsScreen.axaml.cs for the gestures this shape now needs — a shift-click's run and a
    band's rectangle both have to reach across however many sections are on screen, where a single ListBox
    used to answer both on its own.

    ── ONE SECTION, ONE ListBox, AND "Board" IS THE ItemsControl OF SECTIONS. ────────────────────────────────
    Each HostSectionViewModel draws its own heading and its own ListBox of cards (Classes="sectioncards"),
    which is what lets the wrap of cards stay an ordinary WrapPanel-of-cards-only rather than a single list
    fighting to make a header row pretend to be as wide as the cards it sits above. "Board" is the outer
    ItemsControl holding every section — named because the code-behind needs one focusable ancestor of every
    card to hang KeyDown, tunnelled PointerPressed/ContextRequested and DoubleTapped from; see the remarks on
    HostsScreen for why nothing here can be a single ListBox's own SelectedItem any more.

    ◆ WHY EVERY COMMAND REACHED FROM INSIDE A SECTION OR A CARD GOES THROUGH "#Board" RATHER THAN A PLAIN
    {Binding}. This markup nests two DataTemplates deep by the time it reaches a card — HostSections's own
    item template (x:DataType HostSectionViewModel) and then, inside that, a second ListBox's item template
    (x:DataType HostRowViewModel) — so the ambient DataContext by the time a pencil button or a context menu
    item is declared is a section or a row, never the vault. $parent[ItemsControl] cannot walk past the
    inner ListBox to reach the outer one, because a ListBox answers "is this an ItemsControl" as truly as the
    real target does and $parent stops at the nearest match; and a ContextMenu or MenuFlyout's content sits
    in its own popup, inheriting whatever DataContext the control that opened it already has rather than
    anything further up a visual chain. "#Board" sidesteps both: Avalonia's NameScope is the whole document's,
    not each template's own, so an ElementName reference resolves the same way from inside a WrapPanel's card
    or from inside a MenuFlyout's popup — see #Board.((vm:VaultViewModel)DataContext).SomeCommand throughout
    this file. Kept as one idiom rather than mixing it with $parent[ItemsControl] where a single level of
    nesting would technically allow it (the toolbar's Group ▾ and Tag ▾ flyouts are exactly that case),
    because two techniques answering the same question is one more thing to get right per binding.

    Cards split the two jobs the old 268-pixel list used to do at two-thirds size. The board is about all of
    them — how many groups there are, which machines are in one, which have a shell open — and it gets the
    full width to say so. The drawer is about one, and it is only there when it has been asked for. See
    HostDrawer and VaultViewModel.IsDrawerOpen.

    ◆ ASKED FOR, AND IT USED TO ARRIVE WITH THE SELECTION. Touching any card opened the drawer, so choosing a
    host cost 304 pixels of board every time — including for every card passed over on the way to the one
    somebody wanted. The pencil on a card is what opens it now; selecting is free again. See the item
    template below and VaultViewModel.IsHostPaneOpen.

    ── WHAT IS DELIBERATELY NOT HERE ────────────────────────────────────────────────────────────────────
    The design this was drawn from puts a view-mode switch, a calendar and a share control in the toolbar,
    and offers Serial beside Terminal, and colours the status dot amber for "reachable but not connected".
    None of the four exists: there is one view, nothing dated, no sharing outside a team vault, no serial
    transport in the SSH layer, and nothing here ever pings a host — a third dot colour would be a claim this
    application does not check. See docs/design-import-gaps.md and hosts-v5-design-spec.md's deviations.

    ── ITS DATA CONTEXT IS THE VAULT, and it used to be the shell. ──────────────────────────────────────
    The shell was needed only so that the old sidebar could be handed the vault on its own element while
    everything around it bound Vault.*. The drawer needs no such thing — it is a child of this control and
    inherits what this control has — so the indirection went with the sidebar, and every binding in this
    file is now a property of the vault (directly at the root, or through #Board once a DataTemplate has
    changed what is ambient).

    That is worth more than the tidiness: a control whose data context is the shell cannot be laid out by
    the layout harness without building an entire MainWindowViewModel, and this is the screen with the most
    worth measuring. The window wraps it in a Panel that carries IsHostsScreen, which is the same pattern
    every other screen there already uses — see MainWindow.axaml's note on why the two cannot go on one
    element.
  -->

  <Grid ColumnDefinitions="*,Auto">

    <Grid Grid.Column="0" RowDefinitions="Auto,Auto,Auto,*">

      <!--
        ============ THE TOOLBAR ============
        "Hosts" and the count chip on the left, the two narrowing flyouts and + New host on the right. EDIT
        and DELETE are not here: they act on one host, and one host is what the drawer is about — a pair of
        buttons over a board of forty is a pair whose subject the user has to work out. The group's own
        Edit/Move/Delete sit on its own heading's menu for the same reason.
      -->
      <Grid Grid.Row="0" Margin="16,20,16,16" ColumnDefinitions="Auto,Auto,*,Auto,Auto,Auto">

        <TextBlock Grid.Column="0" Text="Hosts" FontSize="33" FontWeight="Bold" LetterSpacing="-0.5"
                   Foreground="{StaticResource Text}" VerticalAlignment="Center" />

        <!--
          The count chip: every host in a shown vault, unfiltered — see HostBoardTotalCount. Deliberately not
          narrowed by the find box or the two flyouts below, which already say "how many cards are on screen
          right now" on each section's own heading; this says how big the keychain is.
        -->
        <Border Grid.Column="1" Margin="10,0,0,0" MinWidth="34" Height="30" CornerRadius="9" Padding="8,0"
                Background="{StaticResource Chip}" VerticalAlignment="Center">
          <TextBlock Classes="mono" Text="{Binding HostBoardTotalCount}" FontSize="12.5"
                     Foreground="{StaticResource TextDim}" HorizontalAlignment="Center"
                     VerticalAlignment="Center" />
        </Border>

        <!--
          ============ Group ▾ ============
          Chips rather than checkable menu items, the same idiom the tab strip's vault menu uses and for the
          same reason: nothing in this application uses a checkable MenuItem, and a chip that fills when worn
          is this window's own vocabulary for on-and-off already. A host passes by being in a ticked group;
          empty means every group. "New group…" lives at the foot rather than being a control of its own,
          because the mock has no group-creation control and this is the nearest thing to where the design
          put it — see hosts-v5-design-spec.md's deviations.
        -->
        <Button Grid.Column="3" Classes="ghost" Height="40" Margin="0,0,8,0" Content="Group ▾"
                ToolTip.Tip="Narrow the board to one or more groups">
          <Button.Flyout>
            <Flyout Placement="BottomEdgeAlignedLeft">
              <StackPanel Width="240" Spacing="8">
                <TextBlock Classes="label" Text="FILTER BY GROUP" />

                <ItemsControl ItemsSource="{Binding GroupFilterChoices}">
                  <ItemsControl.ItemTemplate>
                    <DataTemplate x:DataType="vm:GroupFilterChoice">
                      <Button Classes="chiptoggle" Classes.worn="{Binding IsChecked}" Margin="0,0,0,4"
                              HorizontalAlignment="Stretch" HorizontalContentAlignment="Left"
                              Command="{Binding #Board.((vm:VaultViewModel)DataContext).ToggleGroupFilterCommand}"
                              CommandParameter="{Binding}">
                        <TextBlock Text="{Binding Label}" FontSize="11" TextTrimming="CharacterEllipsis" />
                      </Button>
                    </DataTemplate>
                  </ItemsControl.ItemTemplate>
                </ItemsControl>

                <Button Classes="ghost" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left"
                        Content="All groups" IsVisible="{Binding HasActiveGroupFilter}"
                        Command="{Binding ResetGroupFilterCommand}" />

                <Border Height="1" Background="{StaticResource BorderSubtle}" />

                <Button Classes="ghost" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left"
                        Content="New group…" Command="{Binding NewGroupCommand}"
                        ToolTip.Tip="A heading for the board, and the port, username and key the hosts under it inherit." />
              </StackPanel>
            </Flyout>
          </Button.Flyout>
        </Button>

        <!--
          ============ Tag ▾ ============
          A host passes by wearing ANY ticked tag, not all of them — the same reading MatchesHostBoardFilters
          gives it. No footer control: a tag is made from a host's own editor, not from this flyout.
        -->
        <Button Grid.Column="4" Classes="ghost" Height="40" Margin="0,0,8,0" Content="Tag ▾"
                ToolTip.Tip="Narrow the board to one or more tags">
          <Button.Flyout>
            <Flyout Placement="BottomEdgeAlignedLeft">
              <StackPanel Width="240" Spacing="8">
                <TextBlock Classes="label" Text="FILTER BY TAG" />

                <ItemsControl ItemsSource="{Binding TagFilterChoices}">
                  <ItemsControl.ItemTemplate>
                    <DataTemplate x:DataType="vm:TagFilterChoice">
                      <Button Classes="chiptoggle" Classes.worn="{Binding IsChecked}" Margin="0,0,0,4"
                              HorizontalAlignment="Stretch" HorizontalContentAlignment="Left"
                              Command="{Binding #Board.((vm:VaultViewModel)DataContext).ToggleTagFilterCommand}"
                              CommandParameter="{Binding}">
                        <TextBlock Text="{Binding Label}" FontSize="11" TextTrimming="CharacterEllipsis" />
                      </Button>
                    </DataTemplate>
                  </ItemsControl.ItemTemplate>
                </ItemsControl>

                <Button Classes="ghost" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left"
                        Content="All tags" IsVisible="{Binding HasActiveTagFilter}"
                        Command="{Binding ResetTagFilterCommand}" />
              </StackPanel>
            </Flyout>
          </Button.Flyout>
        </Button>

        <Button Grid.Column="5" Classes="accent" Height="40" FontSize="13.5" Content="+ New host"
                Command="{Binding NewHostCommand}" />

      </Grid>

      <!--
        ============ FIND ============
        One wide box, restyled but doing exactly what it always has: it narrows the board below and nothing
        else — the connect path, the selection and the pinned host key list all read the unfiltered
        collection — so a filter left in the box can hide a host but can never break one.

        ◆ IT IS ALSO THE WAY OUT OF THE SECTIONS. Typing here searches every section at once, whatever a
        collapse fold or the two flyouts above have done to the board — the one thing on this screen that
        crosses a group boundary, and it has to be: a search that looked only at what happens to be expanded
        would say "no host matches that" about a host this keychain has got. See VaultViewModel.Matches.

        Ctrl+K is named on it because the palette is the other way to reach a host by typing, and somebody
        who has found this box should know about the one that also connects on Enter.
      -->
      <Border Grid.Row="1" Margin="16,0,16,16">
        <TextBox x:Name="HostFilter" Text="{Binding HostFilter}" Height="40" CornerRadius="10"
                 FontFamily="{StaticResource MonoFont}"
                 PlaceholderText="Find a host by name, address or note…   ·   Ctrl+K searches and connects" />
      </Border>

      <!--
        ============ THE PANELS ============
        At most one of these is up at any moment — the view model disarms the others on the way up, see
        MoveChosenHostsToVault, RegroupChosenHosts, DeleteChosenHosts, MoveGroup and DeleteGroup — and every
        one of them sits here, above the board, rather than laid over it: a card over the cards would hide
        the very ticks or the very group it is asking about.
      -->
      <StackPanel Grid.Row="2" Margin="16,0,16,12" Spacing="10">

        <!--
          The conflict log. The merge is only allowed to pick a winner because the value it overrode is kept
          and shown; without this panel it would be last-writer-wins with a longer explanation.

          Bounded and scrollable, on an Auto row rather than growing without limit — a pass that merged
          twenty items must not push the board off the bottom of a screen nobody can scroll to.
        -->
        <Border Padding="14,10" Background="{StaticResource Panel}" CornerRadius="10"
                BorderBrush="{StaticResource Border}" BorderThickness="1" IsVisible="{Binding HasConflicts}">
          <StackPanel Spacing="6">
            <TextBlock Text="Some changes could not be merged automatically."
                       Foreground="{StaticResource Info}" FontWeight="SemiBold" />
            <ScrollViewer MaxHeight="180" HorizontalScrollBarVisibility="Disabled">
              <ItemsControl ItemsSource="{Binding Conflicts}">
                <ItemsControl.ItemTemplate>
                  <DataTemplate x:DataType="vm:ConflictRowViewModel">
                    <Border Margin="0,4" Padding="8" Background="{StaticResource Raised}" CornerRadius="6">
                      <StackPanel Spacing="4">
                        <TextBlock Text="{Binding Summary}" Foreground="{StaticResource Text}"
                                   TextWrapping="Wrap" />
                        <SelectableTextBlock Classes="mono" Text="{Binding Detail}" FontSize="12"
                                             Foreground="{StaticResource TextDim}"
                                             IsVisible="{Binding HasDetail}" />
                      </StackPanel>
                    </Border>
                  </DataTemplate>
                </ItemsControl.ItemTemplate>
              </ItemsControl>
            </ScrollViewer>
            <Button Classes="ghost" Content="DISMISS ALL" HorizontalAlignment="Left"
                    Command="{Binding AcknowledgeAllConflictsCommand}" />
          </StackPanel>
        </Border>

        <!--
          ◆ ONE GROUP'S DELETION QUESTION, arrived at from a heading's own menu rather than from a card — the
          group cards this used to sit under are gone, and Edit/Move/Delete moved to the heading. See
          EditGroupFromHeading, MoveGroupFromHeading and DeleteGroupFromHeading.
        -->
        <Border Padding="10" Background="{StaticResource DangerWash}" CornerRadius="10"
                IsVisible="{Binding IsConfirmingGroupDeletion}">
          <views:ConfirmDeleteCard />
        </Border>

        <!--
          ◆ MOVING THE GROUP TO ANOTHER VAULT. A picker and two buttons rather than a question with a yes:
          what is being asked is which vault, and a move is undone by moving it back — so it is not drawn in
          the danger colours the deletion question above uses. It sits beside that question and never with
          it: MoveGroup disarms a pending deletion and DeleteGroup folds this away, so at most one is drawn.

          The sentence is what the panel is for. A group cannot go anywhere alone: the machines under it are
          items of the vault it is leaving, and so are the groups nested inside it, so all of them are
          re-sealed under the destination's key and all of them take new ids. What cannot come is the group
          it is nested under, which is why it arrives at the top level.
        -->
        <Border Padding="10" Background="{StaticResource Panel}" CornerRadius="10"
                BorderBrush="{StaticResource Border}" BorderThickness="1" IsVisible="{Binding IsMovingGroup}">
          <StackPanel Spacing="8">
            <TextBlock Classes="label" Text="MOVE GROUP TO VAULT" />
            <ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding MoveGroupVaultChoices}"
                      SelectedItem="{Binding SelectedMoveGroupVault}">
              <ComboBox.ItemTemplate>
                <DataTemplate x:DataType="vm:VaultChoiceViewModel">
                  <TextBlock Text="{Binding Display}" FontSize="12" />
                </DataTemplate>
              </ComboBox.ItemTemplate>
            </ComboBox>
            <TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
                       Text="The group goes with the groups inside it and every host filed under them, all re-encrypted with the other vault's key. Any group it is nested under stays behind, so it arrives at the top level, and the hosts' tags stay behind with it." />
            <StackPanel Orientation="Horizontal" Spacing="6">
              <Button Classes="accent" Content="MOVE" Command="{Binding ConfirmMoveGroupCommand}"
                      IsEnabled="{Binding !IsBusy}" />
              <Button Classes="ghost" Content="CANCEL" Command="{Binding CancelMoveGroupCommand}" />
            </StackPanel>
          </StackPanel>
        </Border>

        <!--
          ============ ◆ WHAT SAYS A SET IS UP ============
          Ctrl, Shift and a band dragged across the board tick cards — see HostsScreen.axaml.cs — and this is
          the whole of the chrome that arrives with them: how many, the way out, and where the actions are.
          The phone raises a contextual action bar in the vault header's place for the same state; a window
          with room for a context menu does not need one, and a strip of eight buttons over the board would
          be a second home for entries the menu already has.
        -->
        <Grid ColumnDefinitions="Auto,Auto,*" IsVisible="{Binding IsChoosingHosts}">
          <Border Grid.Column="0" Classes="chip accent" Padding="6,1" VerticalAlignment="Center">
            <TextBlock Text="{Binding ChosenHostsSummary}" FontSize="9.5" />
          </Border>
          <Button Grid.Column="1" Classes="flat" Margin="6,0,0,0" Padding="5,1" FontSize="9.5"
                  Content="CLEAR" Command="{Binding ClearHostChoiceCommand}"
                  ToolTip.Tip="Takes the ticks off every card  ·  Esc, or a click on the space around them" />
          <TextBlock Grid.Column="2" Classes="hint" FontSize="10" Margin="8,0,0,0"
                     VerticalAlignment="Center" TextTrimming="CharacterEllipsis"
                     Text="right-click one of them for what can be done to all of them" />
        </Grid>

        <!--
          ◆ MOVING OR COPYING THEM TO ANOTHER VAULT. One panel for both verbs, because they differ in one
          word and in the sentence under the picker; what varies is bound rather than duplicated. See
          VaultViewModel.ChosenHostVaultPanelTitle and its two siblings.
        -->
        <Border Padding="10" Background="{StaticResource Panel}" CornerRadius="10"
                BorderBrush="{StaticResource Border}" BorderThickness="1"
                IsVisible="{Binding IsSendingChosenHostsToAVault}">
          <StackPanel Spacing="8">
            <Grid ColumnDefinitions="Auto,*">
              <TextBlock Grid.Column="0" Classes="label" Text="{Binding ChosenHostVaultPanelTitle}" />
              <TextBlock Grid.Column="1" Classes="mono" Margin="8,0,0,0" FontSize="11"
                         Foreground="{StaticResource TextFaint}" VerticalAlignment="Center"
                         Text="{Binding ChosenHostsSummary}" />
            </Grid>

            <ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding ChosenHostVaultChoices}"
                      SelectedItem="{Binding SelectedChosenHostVault}">
              <ComboBox.ItemTemplate>
                <DataTemplate x:DataType="vm:VaultChoiceViewModel">
                  <TextBlock Text="{Binding Display}" FontSize="12" />
                </DataTemplate>
              </ComboBox.ItemTemplate>
            </ComboBox>

            <TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
                       Text="{Binding ChosenHostVaultPanelNote}" />

            <!--
              ◆ THE KEY, and only for a move of exactly one host — which key to carry is a fact about one
              machine, and a copy that took it away would leave the original unable to connect. Unticked,
              because moving a key into a team's vault hands it to everybody holding that vault's key.
            -->
            <CheckBox IsVisible="{Binding HasAChosenBindingToBring}"
                      IsChecked="{Binding BringsTheChosenBindingAlong}">
              <TextBlock FontSize="11.5" TextWrapping="Wrap" Text="{Binding ChosenBindingToBringQuestion}" />
            </CheckBox>
            <TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
                       IsVisible="{Binding HasAChosenBindingToBring}"
                       Text="{Binding ChosenBindingToBringNote}" />

            <StackPanel Orientation="Horizontal" Spacing="6">
              <Button Classes="accent" Content="{Binding ChosenHostVaultPanelVerb}"
                      Command="{Binding ConfirmSendChosenHostsToAVaultCommand}"
                      IsEnabled="{Binding !IsBusy}" />
              <Button Classes="ghost" Content="CANCEL" Command="{Binding CancelSendChosenHostsToAVaultCommand}" />
            </StackPanel>
          </StackPanel>
        </Border>

        <!--
          ◆ FILING THEM UNDER A GROUP, which is what "Change group…" on the chosen-hosts menu offers instead
          of a drag now that there is no group card to drop one on.
        -->
        <Border Padding="10" Background="{StaticResource Panel}" CornerRadius="10"
                BorderBrush="{StaticResource Border}" BorderThickness="1"
                IsVisible="{Binding IsRegroupingChosenHosts}">
          <StackPanel Spacing="8">
            <Grid ColumnDefinitions="Auto,*">
              <TextBlock Grid.Column="0" Classes="label" Text="CHANGE GROUP" />
              <TextBlock Grid.Column="1" Classes="mono" Margin="8,0,0,0" FontSize="11"
                         Foreground="{StaticResource TextFaint}" VerticalAlignment="Center"
                         Text="{Binding ChosenHostsSummary}" />
            </Grid>

            <ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding ChosenHostGroupChoices}"
                      SelectedItem="{Binding SelectedChosenHostGroup}">
              <ComboBox.ItemTemplate>
                <DataTemplate x:DataType="vm:GroupChoice">
                  <TextBlock Text="{Binding Label}" FontSize="12" />
                </DataTemplate>
              </ComboBox.ItemTemplate>
            </ComboBox>

            <TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
                       Text="A group lends its port, its account and its key to every host filed under it that says nothing itself, so this can change what these machines dial. Nothing else about them moves." />

            <StackPanel Orientation="Horizontal" Spacing="6">
              <Button Classes="accent" Content="FILE" Command="{Binding ConfirmRegroupChosenHostsCommand}"
                      IsEnabled="{Binding !IsBusy}" />
              <Button Classes="ghost" Content="CANCEL" Command="{Binding CancelRegroupChosenHostsCommand}" />
            </StackPanel>
          </StackPanel>
        </Border>

        <!--
          ◆ THE DELETION QUESTION, naming a count rather than a machine — which is the reason
          DeletionTarget.ChosenHosts exists: six copies of "delete prod-db?" is not a confirmation anybody
          reads. The shared card draws it, as it does for a group above and for one host in the drawer.
        -->
        <Border Padding="10" Background="{StaticResource DangerWash}" CornerRadius="10"
                IsVisible="{Binding IsConfirmingChosenHostDeletion}">
          <views:ConfirmDeleteCard />
        </Border>

      </StackPanel>

      <!--
        ============ THE BOARD ============
        A drop target that takes nothing itself — no group cards left to receive a drag. See
        HostsScreen.axaml.cs.
      -->
      <ScrollViewer Grid.Row="3" x:Name="Scroll" HorizontalScrollBarVisibility="Disabled">
        <StackPanel Margin="16,0,16,20" Spacing="16">

          <!--
            Named because it is where keyboard focus lands when the terminal gives it back, and because
            every gesture on it is wired in the code-behind.

            Focusable, which an ItemsControl is not by default. Without it the release-the-keyboard path is
            a measured no-op: it takes Win32 focus off the terminal's child window and then calls Focus() on
            something that refuses it, leaving the window with nothing focused and the keystrokes going
            nowhere.

            ◆ EVERY GROUP, ONE AFTER ANOTHER, "No group" FIRST. Binds HostSections rather than VisibleHosts:
            the old grid held one level of a tree at a time, and this holds every group's cards at once, the
            way SidebarRows always has for the phone. A keychain nobody has filed anything in draws one
            section with no heading at all — see HostSectionViewModel.HasHeader — which is what makes the
            feature invisible until it is used.
          -->
          <ItemsControl x:Name="Board" Focusable="True" ItemsSource="{Binding HostSections}">
            <ItemsControl.ItemsPanel>
              <ItemsPanelTemplate><StackPanel Spacing="22" /></ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>

            <ItemsControl.ItemTemplate>
              <DataTemplate x:DataType="vm:HostSectionViewModel">
                <StackPanel Spacing="10">

                  <!--
                    ============ THE SECTION HEADING ============
                    "{label} · {count}", the vault name beside it when more than one vault is in play, a
                    per-section collapse toggle, Collapse All / Expand All on the FIRST heading only — see
                    SidebarGroupHeader.IsFirstBoardSection — and a menu for Edit…/Move to another vault…/
                    Delete…, in that order, with no Open: the board draws every group at once now, so there
                    is nothing left to open one INTO. Absent entirely for the headerless section a groupless
                    keychain draws — see HasHeader on the type, and the remark on the item template above.
                  -->
                  <Grid IsVisible="{Binding HasHeader}" ColumnDefinitions="Auto,*,Auto,Auto">

                    <StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
                      <Button Classes="flat" Padding="2"
                              Command="{Binding #Board.((vm:VaultViewModel)DataContext).ToggleGroupCommand}"
                              CommandParameter="{Binding Header}"
                              ToolTip.Tip="{Binding Header.CollapseLabel}">
                        <TextBlock Text="{Binding Header.Chevron}" FontSize="11"
                                   Foreground="{StaticResource TextGhost}" />
                      </Button>
                      <TextBlock FontSize="12.5" FontWeight="Medium" Foreground="{StaticResource TextGhost}">
                        <Run Text="{Binding Header.Label}" /><Run Text=" · " /><Run Text="{Binding Header.Count}" />
                      </TextBlock>
                      <TextBlock Classes="mono" Text="{Binding Header.VaultBadge}" FontSize="10.5"
                                 Foreground="{StaticResource TextFaint}" TextTrimming="CharacterEllipsis"
                                 IsVisible="{Binding Header.HasVaultBadge}" />
                    </StackPanel>

                    <!--
                      Bound on every heading and shown on the first, rather than a control of the board's
                      own above the sections — a virtualised ItemsControl has no clean way for one realized
                      row to ask "am I first", so HostSectionViewModel's own header carries the answer. See
                      VaultViewModel.RebuildHostSections and CollapseAllLabel.
                    -->
                    <Button Grid.Column="2" Classes="flat" Padding="6,2" Margin="0,0,10,0"
                            FontSize="12" Foreground="{StaticResource TextGhost}"
                            IsVisible="{Binding Header.IsFirstBoardSection}"
                            Content="{Binding #Board.((vm:VaultViewModel)DataContext).CollapseAllLabel}"
                            Command="{Binding #Board.((vm:VaultViewModel)DataContext).ToggleAllGroupsCommand}" />

                    <!--
                      Nothing to edit, move or delete about "No group" — it names no item, so the menu is
                      left off that heading altogether rather than opened onto three commands that would
                      each resolve to nothing. See VaultViewModel.GroupOf.
                    -->
                    <Button Grid.Column="3" Classes="flat paneicon" Content="⋯" Width="24" Height="24"
                            IsVisible="{Binding Header.GroupId, Converter={x:Static ObjectConverters.IsNotNull}}"
                            ToolTip.Tip="Edit, move or delete this group">
                      <Button.Flyout>
                        <MenuFlyout>
                          <MenuItem Header="Edit…"
                                    Command="{Binding #Board.((vm:VaultViewModel)DataContext).EditGroupFromHeadingCommand}"
                                    CommandParameter="{Binding Header}" />
                          <MenuItem Header="Move to another vault…"
                                    Command="{Binding #Board.((vm:VaultViewModel)DataContext).MoveGroupFromHeadingCommand}"
                                    CommandParameter="{Binding Header}" />
                          <Separator />
                          <MenuItem Header="Delete…"
                                    Command="{Binding #Board.((vm:VaultViewModel)DataContext).DeleteGroupFromHeadingCommand}"
                                    CommandParameter="{Binding Header}" />
                        </MenuFlyout>
                      </Button.Flyout>
                    </Button>
                  </Grid>

                  <!--
                    ============ THE CARDS ============
                    A ListBox of its own per section — Classes="sectioncards" is what the code-behind finds
                    every card container through, across however many of these are realized. See the type
                    remarks on HostSectionViewModel for why this is a second list per group rather than one
                    list for the whole board.

                    ◆ SELECTED IS ONE-WAY, WRITTEN BY HAND ON THE WAY OUT. See HostsScreen's own remarks:
                    a two-way binding shared by several independently-rebuilt lists would have each list's
                    Reset racing to null the one property every filter keystroke and every sync pass.
                  -->
                  <ListBox Classes="sectioncards" ItemsSource="{Binding Hosts}"
                           SelectedItem="{Binding #Board.((vm:VaultViewModel)DataContext).SelectedSidebarRow, Mode=OneWay}"
                           ToolTip.Tip="Double-press a card for a shell. Ctrl or Shift picks out several, and so does a band dragged across the space between them; right-click any of them for what can be done to the set.">

                    <ListBox.ItemsPanel>
                      <ItemsPanelTemplate><WrapPanel /></ItemsPanelTemplate>
                    </ListBox.ItemsPanel>

                    <!--
                      ◆ TWO MENUS IN ONE, AND WHICH OF THEM IS DRAWN IS WHETHER ANYTHING IS TICKED. See
                      HostsScreen.axaml.cs's OnContextRequested for the half of this rule that lives in code:
                      a right click outside the ticked set drops the set first, so these entries and the
                      ones above are never both meaningful at once.
                    -->
                    <ListBox.ContextMenu>
                      <ContextMenu>
                        <MenuItem Header="Connect" IsVisible="{Binding !#Board.((vm:VaultViewModel)DataContext).IsChoosingHosts}"
                                  Command="{Binding #Board.((vm:VaultViewModel)DataContext).ConnectCommand}" />
                        <MenuItem Header="Details…" IsVisible="{Binding !#Board.((vm:VaultViewModel)DataContext).IsChoosingHosts}"
                                  Command="{Binding #Board.((vm:VaultViewModel)DataContext).OpenHostPaneCommand}" />
                        <MenuItem Header="Edit…" IsVisible="{Binding !#Board.((vm:VaultViewModel)DataContext).IsChoosingHosts}"
                                  Command="{Binding #Board.((vm:VaultViewModel)DataContext).EditSelectedHostCommand}" />
                        <Separator IsVisible="{Binding !#Board.((vm:VaultViewModel)DataContext).IsChoosingHosts}" />
                        <MenuItem Header="Delete…" IsVisible="{Binding !#Board.((vm:VaultViewModel)DataContext).IsChoosingHosts}"
                                  Command="{Binding #Board.((vm:VaultViewModel)DataContext).DeleteHostCommand}" />

                        <MenuItem Header="Connect" IsVisible="{Binding #Board.((vm:VaultViewModel)DataContext).HasOneChosenHost}"
                                  Command="{Binding #Board.((vm:VaultViewModel)DataContext).ConnectToChosenHostCommand}" />
                        <MenuItem Header="Browse files" IsVisible="{Binding #Board.((vm:VaultViewModel)DataContext).HasOneChosenHost}"
                                  Command="{Binding #Board.((vm:VaultViewModel)DataContext).BrowseChosenHostCommand}" />
                        <MenuItem Header="Edit…" IsVisible="{Binding #Board.((vm:VaultViewModel)DataContext).HasOneChosenHost}"
                                  Command="{Binding #Board.((vm:VaultViewModel)DataContext).EditChosenHostCommand}" />
                        <MenuItem Header="Change group…" IsVisible="{Binding #Board.((vm:VaultViewModel)DataContext).IsChoosingHosts}"
                                  Command="{Binding #Board.((vm:VaultViewModel)DataContext).RegroupChosenHostsCommand}" />
                        <MenuItem Header="Move to another vault…" IsVisible="{Binding #Board.((vm:VaultViewModel)DataContext).IsChoosingHosts}"
                                  Command="{Binding #Board.((vm:VaultViewModel)DataContext).MoveChosenHostsToVaultCommand}" />
                        <MenuItem Header="Copy to another vault…" IsVisible="{Binding #Board.((vm:VaultViewModel)DataContext).IsChoosingHosts}"
                                  Command="{Binding #Board.((vm:VaultViewModel)DataContext).CopyChosenHostsToVaultCommand}" />
                        <MenuItem Header="Duplicate" IsVisible="{Binding #Board.((vm:VaultViewModel)DataContext).IsChoosingHosts}"
                                  Command="{Binding #Board.((vm:VaultViewModel)DataContext).DuplicateChosenHostsCommand}" />
                        <Separator IsVisible="{Binding #Board.((vm:VaultViewModel)DataContext).IsChoosingHosts}" />
                        <MenuItem Header="Delete…" IsVisible="{Binding #Board.((vm:VaultViewModel)DataContext).IsChoosingHosts}"
                                  Command="{Binding #Board.((vm:VaultViewModel)DataContext).DeleteChosenHostsCommand}" />
                      </ContextMenu>
                    </ListBox.ContextMenu>

                    <ListBox.ItemTemplate>
                      <DataTemplate x:DataType="vm:HostRowViewModel">

                        <!--
                          The full address is on the tooltip and nowhere else on the card — the design's own
                          rule, kept: a card read while scanning a section is read for its name and whether
                          it is live, and an address is what somebody reads once they have found the machine,
                          which the tooltip and the drawer both still say.

                          MinHeight rather than a fixed Height, unlike the mock's own 120: this codebase's
                          Border.tile is deliberately a fixed width and a free height (see App.axaml), so a
                          card wearing four tags grows a second line rather than clipping the fourth one — a
                          silent truncation is exactly the kind of thing the honesty rule in
                          HANDOFF-hosts-v5.md exists to catch, and it costs nothing here to avoid.
                        -->
                        <Border Classes="tile" Classes.chosen="{Binding IsChosen}" MinHeight="120"
                                ToolTip.Tip="{Binding Address}">
                          <Panel>
                            <Grid RowDefinitions="Auto,*,Auto">

                              <Grid Grid.Row="0" ColumnDefinitions="Auto,*">

                                <!--
                                  The monogram avatar: two letters, stable-hashed into one of the mock's four
                                  hue pairs — see HostRowViewModel.Monogram/MonogramHue and
                                  MonogramBrushConverter, which holds the four pairs themselves. Kept beside
                                  this one screen rather than in the shared palette, because a monogram's
                                  colour is a fact about this card and nothing else in the theme reads it.
                                -->
                                <Border Grid.Column="0" Width="32" Height="32" CornerRadius="9"
                                        Background="{Binding MonogramHue, Converter={StaticResource MonogramBg}}">
                                  <TextBlock Classes="mono" Text="{Binding Monogram}" FontWeight="Bold"
                                             FontSize="12.5" HorizontalAlignment="Center"
                                             VerticalAlignment="Center"
                                             Foreground="{Binding MonogramHue, Converter={StaticResource MonogramFg}}" />
                                </Border>

                                <StackPanel Grid.Column="1" Margin="10,0,0,0" VerticalAlignment="Center"
                                            Spacing="3">

                                  <!--
                                    A Grid rather than a horizontal StackPanel, on both of these rows — a
                                    horizontal StackPanel measures its children with infinite width, so
                                    TextTrimming never fires and the name would run on past the card's own
                                    border instead of trimming. See the longer note this carried in v4.
                                  -->
                                  <Grid ColumnDefinitions="*,Auto,Auto">
                                    <TextBlock Grid.Column="0" Text="{Binding Label}" FontSize="17"
                                               FontWeight="Bold" Foreground="{StaticResource Text}"
                                               TextTrimming="CharacterEllipsis" />

                                    <!--
                                      The tick sits beside the dot rather than in place of it, the same
                                      arrangement the phone's row uses: the dot says whether a shell is open
                                      on this machine, and swapping it for the tick would hide that fact the
                                      moment a card is chosen.
                                    -->
                                    <StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="4"
                                                Margin="6,0,0,0" VerticalAlignment="Center">
                                      <TextBlock Text="✓" FontSize="11" IsVisible="{Binding IsChosen}"
                                                 Foreground="{StaticResource AccentText}" />
                                      <!--
                                        Two states, deliberately: green means a terminal is open on this host
                                        right now, grey means there is not one. Never amber — nothing here
                                        pings anything, and a third colour would be a claim this application
                                        does not check. See Ellipse.dot in App.axaml and
                                        hosts-v5-design-spec.md's deviations.
                                      -->
                                      <Ellipse Classes="dot" Classes.live="{Binding IsConnected}"
                                               Width="9" Height="9" />
                                    </StackPanel>

                                    <Border Grid.Column="2" Classes="chip warn" Padding="4,0" Margin="6,0,0,0"
                                            IsVisible="{Binding Badge, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
                                      <TextBlock Text="{Binding Badge}" FontSize="9.5" />
                                    </Border>
                                  </Grid>

                                  <TextBlock Classes="mono" Text="{Binding Address}" FontSize="12.5"
                                             Foreground="{StaticResource TextGhost}"
                                             TextTrimming="CharacterEllipsis" />

                                  <!--
                                    Which vault this host is in, and only when there is more than one to be
                                    in. It decides who else can see the host and where an edit goes back to,
                                    so on a board spanning several vaults it is not decoration.
                                  -->
                                  <TextBlock Classes="mono" Text="{Binding VaultBadge}" FontSize="10"
                                             Foreground="{StaticResource TextFaint}"
                                             TextTrimming="CharacterEllipsis"
                                             IsVisible="{Binding HasVaultBadge}" />
                                </StackPanel>
                              </Grid>

                              <Grid Grid.Row="2" ColumnDefinitions="*,Auto" VerticalAlignment="Bottom"
                                    Margin="0,8,0,0">

                                <StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="4"
                                            VerticalAlignment="Bottom">

                                  <!--
                                    The pin badge: how many folders are pinned on this host, reading
                                    HostRowViewModel.PinCount — which is Host.PinnedPaths.Count, see that
                                    property's own remarks. Only when there is at least one; a badge reading
                                    zero would be a fact nobody asked for.
                                  -->
                                  <Border Classes="chip" Height="19" CornerRadius="5" Padding="6,2"
                                          IsVisible="{Binding HasPins}">
                                    <StackPanel Orientation="Horizontal" Spacing="3">
                                      <TextBlock Text="&#xE946;" FontFamily="{StaticResource IconFont}"
                                                 FontSize="11" Foreground="{StaticResource TextFaint}" />
                                      <TextBlock Classes="mono" Text="{Binding PinCount}" FontSize="10.5"
                                                 Foreground="{StaticResource TextFaint}" />
                                    </StackPanel>
                                  </Border>

                                  <!--
                                    Tag chips, bottom-left. Templated over plain strings rather than a typed
                                    row, so no x:DataType is declared on this inner template — TagLabels is
                                    already resolved names, not ids, see HostRowViewModel.TagLabels.
                                  -->
                                  <ItemsControl ItemsSource="{Binding TagLabels}" IsVisible="{Binding HasTags}">
                                    <ItemsControl.ItemsPanel>
                                      <ItemsPanelTemplate><WrapPanel /></ItemsPanelTemplate>
                                    </ItemsControl.ItemsPanel>
                                    <ItemsControl.ItemTemplate>
                                      <DataTemplate>
                                        <Border Classes="chip" Height="21" CornerRadius="5" Padding="6,2"
                                                Margin="0,0,4,4">
                                          <TextBlock Text="{Binding}" FontSize="11"
                                                     Foreground="{StaticResource TextFaint}" />
                                        </Border>
                                      </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                  </ItemsControl>
                                </StackPanel>

                                <!--
                                  HostRowViewModel.LastConnectedText is filled from the connection log on
                                  the hosts screen's activation and on SessionEnded, and restrung on a
                                  one-minute tick while this screen is visible — see that property's own
                                  remarks and VaultViewModel.RefreshLastConnectedAsync. This reads it as-is
                                  rather than formatting anything here, and draws nothing while it is empty
                                  rather than a dash that would claim "never connected" — a host never seen
                                  in the log and a host whose vault this has not read yet look the same from
                                  here, and neither is a claim this card can make. A connected host prints
                                  nothing too, per decision 4: the dot above already says "right now".
                                -->
                                <TextBlock Grid.Column="1" Text="{Binding LastConnectedText}" FontSize="12"
                                           Foreground="{StaticResource TextGhost}" VerticalAlignment="Bottom"
                                           IsVisible="{Binding LastConnectedText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
                              </Grid>
                            </Grid>

                            <!--
                              ◆ THE PENCIL, AND THE ONLY WAY THE DRAWER OPENS FROM THIS BOARD. See the note
                              on this in v4's own markup, kept: selecting a card does not open it any more,
                              and this is where the asking is. Bound through #Board rather than the old
                              $parent[ListBox] — see the remark at the top of this file for why a ListBox one
                              level further in than the old grid changes what that path would resolve to.
                            -->
                            <Button Classes="flat rowedit" Content="✎"
                                    HorizontalAlignment="Right" VerticalAlignment="Bottom"
                                    Command="{Binding #Board.((vm:VaultViewModel)DataContext).OpenHostPaneCommand}"
                                    CommandParameter="{Binding}"
                                    ToolTip.Tip="Opens this host's pane beside the board" />
                          </Panel>
                        </Border>
                      </DataTemplate>
                    </ListBox.ItemTemplate>
                  </ListBox>

                </StackPanel>
              </DataTemplate>
            </ItemsControl.ItemTemplate>
          </ItemsControl>

          <!--
            Nothing to show, and the reasons it can happen are different questions — see
            VaultViewModel.NoHostBoardMessage for the one answered here per reason.
          -->
          <TextBlock Classes="hint" FontSize="12" TextWrapping="Wrap" MaxWidth="480"
                     HorizontalAlignment="Left" Margin="0,4,0,0"
                     IsVisible="{Binding !HasHostBoardEntries}"
                     Text="{Binding NoHostBoardMessage}" />

        </StackPanel>
      </ScrollViewer>

      <!--
        ◆ THE BAND, drawn over the scroller rather than inside it.

        It is a rectangle the pointer is dragging out right now, so it belongs in the viewport's coordinates
        and not in the scrolling content's: a band drawn inside the stack would slide away from the pointer
        the moment the board scrolled under it. Same row as the ScrollViewer and after it, which is what puts
        it on top — a Grid cell stacks its children in declaration order.

        IsHitTestVisible="False" is the whole of why the gesture works while it is up. The band is under the
        pointer by definition, and a rectangle that took the pointer would end the drag it is drawing.

        Positioned by Margin from the top left rather than in a Canvas, because the two alignments below make
        the margin mean exactly "where the corner is"; see HostsScreen.axaml.cs, which is the only thing that
        writes to it.
      -->
      <Border Grid.Row="3" x:Name="Band" IsVisible="False" IsHitTestVisible="False"
              HorizontalAlignment="Left" VerticalAlignment="Top"
              Background="{StaticResource AccentWash}" BorderBrush="{StaticResource Accent}"
              BorderThickness="1" CornerRadius="2" />

    </Grid>

    <!--
      The drawer, and it takes its 320 pixels only while it has been opened — which is what gives the board
      the full width for most of the time anybody is looking at it, now including all the time somebody is
      choosing between forty machines.

      Wrapped so that its own IsVisible is the shell's binding and its data context is the vault; see
      MainWindow.axaml's note on why the two cannot be put on one element.
    -->
    <Panel Grid.Column="1" IsVisible="{Binding IsDrawerOpen}">
      <views:HostDrawer x:Name="Drawer" />
    </Panel>

  </Grid>

</UserControl>
