<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 hosts screen: every group, then every host, as cards — and a drawer for whichever one is selected.

    ── IT WAS A 268-PIXEL LIST BESIDE A MOSTLY EMPTY COLUMN. ────────────────────────────────────────────
    That list was doing two jobs at two-thirds size. It had to be narrow so the editor beneath it could be
    a column, and being narrow is what made forty machines a scroll rather than a glance: a host row was a
    name, an address and an auth word stacked in 268 pixels, and the column beside it repeated all three
    for the one that happened to be selected.

    Cards split the two jobs. The grid 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 grid 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 tag filter, a calendar and a share control in
    the toolbar, and offers Serial beside Terminal. None of the five exists: there is one view, no saved
    filters, nothing dated, no sharing outside a team vault, and no serial transport in the SSH layer. Five
    disabled controls would teach nobody anything. See docs/design-import-gaps.md.

    ── 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.

    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,*">

      <!--
        ============ FIND ============
        One wide box, which is what the width bought. It narrows the grid 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 GROUPS. The grid below holds one level of the tree, and typing here
        searches that level and everything under it — every machine in the keychain while nothing is open.
        That is the one thing on this screen that crosses a group boundary, and it has to be: a search that
        looked only where you happen to be standing 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.

        The connect bar that used to be this row — password box, REMEMBER, the note, CONNECT — is in the
        drawer now, beside the host it is about. See HostDrawer.
      -->
      <Border Grid.Row="0" Padding="16,12" Background="{StaticResource Panel}"
              BorderBrush="{StaticResource BorderSubtle}" BorderThickness="0,0,0,1">
        <TextBox x:Name="HostFilter" Text="{Binding HostFilter}"
                 FontFamily="{StaticResource MonoFont}"
                 PlaceholderText="Find a host by name, address or note…   ·   Ctrl+K searches and connects" />
      </Border>

      <!--
        ============ THE TOOLBAR ============
        Only what makes something new. 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 grid of forty is a pair whose subject the user
        has to work out. The group's own EDIT and DELETE sit beside the group cards for the same reason.
      -->
      <Border Grid.Row="1" Padding="16,8" BorderBrush="{StaticResource BorderSubtle}"
              BorderThickness="0,0,0,1">
        <StackPanel Orientation="Horizontal" Spacing="6">
          <Button Classes="accent" Content="+ NEW HOST" Command="{Binding NewHostCommand}" />
          <Button Classes="ghost" Content="+ NEW GROUP" Command="{Binding NewGroupCommand}"
                  ToolTip.Tip="A heading for this grid, and the port, username and key the hosts under it inherit." />
        </StackPanel>
      </Border>

      <StackPanel Grid.Row="2">

        <!--
          ◆ THE TWO HOST KEY PROMPTS WERE HERE, and they are now drawn over the window instead.

          They were banners on this screen, so the shell navigated to this screen before letting the vault
          raise one — because a connection can be started from the palette on any screen, and a question
          behind whatever somebody is looking at is a question nobody can answer. That covered the palette
          and charged everybody else for it: it took the user off the screen they were working on, and for a
          machine typed into the phone's connect box by hand it asked about it on the list it is deliberately
          not on. The decision is over the surface now, both heads, and nothing has to move. See
          HostKeyCard.axaml and MainWindowViewModel.OnVaultConnectionFailed.

          Not copied into both places, deliberately. One prompt in two markups is two copies of the most
          safety-critical wording in the product, and the second is the one that goes stale.
        -->

        <!--
          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. It sits on an Auto row above a star row, and an ItemsControl with no
          ceiling grows without limit — so a pass that merged twenty items would push the grid off the
          bottom of a screen nobody could scroll.
        -->
        <Border Padding="16,10" Background="{StaticResource Panel}"
                BorderBrush="{StaticResource Border}" BorderThickness="0,0,0,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="4">
                      <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>

      </StackPanel>

      <!--
        ============ THE GRID ============

        A drop target that takes nothing itself. Everything the pointer can be over during a drag is inside
        this control, and it has to accept one so that the group cards at the top of it stay reachable from
        the bottom of a long grid: a host dragged off the fortieth card cannot be dropped on a group card
        that has scrolled out of sight, so a drag held at either edge scrolls this while it is in flight.
        See HostsScreen.axaml.cs.
      -->
      <ScrollViewer Grid.Row="3" x:Name="Scroll" HorizontalScrollBarVisibility="Disabled"
                    DragDrop.AllowDrop="True">
        <StackPanel Margin="16,14" Spacing="16">

          <!-- ============ GROUPS ============ -->
          <!--
            Absent entirely from a vault nobody has filed anything in, which is what makes groups cost
            nothing to ignore — the same rule the host list has always followed for its headings.
          -->
          <StackPanel Spacing="8" IsVisible="{Binding HasGroups}">

            <!--
              ============ WHERE YOU ARE ============
              The trail, drawn exactly as the transfers screen draws a directory path, because it is the same
              control answering the same question — and a window with two breadcrumbs that look different has
              two ideas of what a path is. See TransfersScreen.axaml.

              It exists because the cards below are one level of a tree rather than every group at once, and
              a level nobody can name is a grid that has quietly hidden things. The first crumb is always
              there and always goes back to every host, which is what the SHOW ALL button used to be: a
              button that only says "stop" beside a trail that says where you are is the second control for
              the same job, and this one also gets you halfway back rather than all the way.

              ◆ ABOVE THE GROUPS LABEL, and deliberately not under it as the transfers screen's is. That one
              sits below its heading because it describes the one list beneath it; this one describes two.
              Opening a group narrows the hosts as well as the cards, so a trail drawn under GROUPS would
              read as one more thing about the groups while it is also the reason the grid at the bottom of
              the screen is showing eleven machines instead of forty. First in the section, and both headings
              below belong to it.
            -->
            <ItemsControl ItemsSource="{Binding GroupTrail}">
              <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate><WrapPanel Orientation="Horizontal" /></ItemsPanelTemplate>
              </ItemsControl.ItemsPanel>
              <ItemsControl.ItemTemplate>
                <DataTemplate x:DataType="vm:GroupCrumbViewModel">
                  <StackPanel Orientation="Horizontal">
                    <!--
                      The command is the vault's and the item is a crumb, so it is reached through the
                      ItemsControl's data context — the same way every other templated command on the
                      desktop is bound.
                    -->
                    <Button Classes="flat" Padding="3,1"
                            Command="{Binding $parent[ItemsControl].((vm:VaultViewModel)DataContext).OpenGroupCommand}"
                            CommandParameter="{Binding Group}">
                      <TextBlock Classes="mono" Text="{Binding Name}" FontSize="11"
                                 Foreground="{StaticResource TextDim}" />
                    </Button>
                    <TextBlock Classes="mono" Text="›" FontSize="11"
                               Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
                  </StackPanel>
                </DataTemplate>
              </ItemsControl.ItemTemplate>
            </ItemsControl>

            <!--
              The heading and nothing else. This used to be a row with EDIT and DELETE at the far end of it,
              and the card's own menu is where those live now — see the note on it below. They were a second
              control for the job that menu already does, and the harder of the two to read: a button beside
              a heading has to say which card it means, and with none selected it meant the group the trail
              ends with rather than anything on screen. Moving a group is on that menu and never had a button
              here, for the same reason and without the intervening mistake.
            -->
            <TextBlock Classes="label" Text="GROUPS" Foreground="{StaticResource TextDim}" />

            <Border Padding="10" Background="{StaticResource DangerWash}" CornerRadius="6"
                    IsVisible="{Binding IsConfirmingGroupDeletion}">
              <views:ConfirmDeleteCard />
            </Border>

            <!--
              ◆ MOVING THE GROUP TO ANOTHER VAULT. The host drawer's move panel, one level up, and under
              the heading rather than in the drawer because a group has no drawer of its own. 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 it uses. It sits beside that question and never with it: MoveGroup disarms a pending
              deletion and DeleteGroup folds this away, so the section draws at most one of the two.

              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. Saying that here
              rather than only in the status line afterwards is the difference between a warning and a
              surprise.
            -->
            <Border Padding="10" Background="{StaticResource Panel}" CornerRadius="6"
                    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>

            <!--
              A ListBox rather than an ItemsControl of buttons, and that is what marks the chosen group for
              free: selection is a state the control already has, and the tile style draws it the same way
              every other list in this application draws a selected row. Buttons would have needed a
              Classes.active binding per card and a second copy of "which one".

              ◆ ONE CLICK SELECTS, TWO OPEN. SelectedItem is the selection and nothing else now — the
              gesture that opens a group is a double-click, wired in the code-behind beside the one that
              opens a shell on a host. One click used to mean both, and a card was then the only place a
              group could be named while also being the control that threw the rest of the grid away.

              ◆ ONE SELECTION, TWO LISTS. Selecting a card here takes the mark off the host grid below, and
              selecting a host takes it off this one. Two controls each keep their own SelectedItem, so the
              vault is what joins them — see VaultViewModel.OnSelectedHostChanged. Without it both grids
              could be lit at once, which is two chosen things under two pairs of buttons.

              ◆ THIS IS ONE LEVEL, NOT EVERY GROUP. It binds VisibleGroups: what is inside the group the
              trail above ends with, or the outermost groups when it ends at ALL HOSTS. Folded away entirely
              at a group with nothing inside it, which is an ordinary thing to open — the trail stays, and
              it is also the way back to the level where that group has a card of its own to be edited from.

              ◆ THESE CARDS ARE THE DROP TARGET. A host card dragged onto one is filed under that group, and
              that is the whole of what a drag does on this screen. It used to be a heading inside the host
              grid, and the headings are gone — see the note on the host grid below. A group is a thing that
              is drawn once, at the top, where the eye already goes; a target that is also the label of what
              you are aiming at needs no explaining. See the code-behind for the gesture.
            -->
            <ListBox Classes="tiles" x:Name="GroupGrid"
                     DragDrop.AllowDrop="True"
                     IsVisible="{Binding HasVisibleGroups}"
                     ItemsSource="{Binding VisibleGroups}"
                     SelectedItem="{Binding SelectedGroup}"
                     ToolTip.Tip="Press a group to select it, double-press to open it. Drag a host card onto one to file it there.">
              <ListBox.ItemsPanel>
                <ItemsPanelTemplate><WrapPanel /></ItemsPanelTemplate>
              </ListBox.ItemsPanel>

              <!--
                The three things you can do to a group, on the group itself — the same menu the host grid
                below has, for the same reasons. On the list rather than in the item template, because the
                commands are the vault's and a menu inside a DataTemplate would have the row for its data
                context; the code-behind selects whatever was right-clicked before the menu opens, and
                cancels it outright over the space around the cards.

                Open is here as well as on the double-click, which is not a duplicate for its own sake: a
                gesture is unreachable without a pointer. Edit and Delete are here and nowhere else — they
                were also a pair of buttons beside the heading above, which acted on GroupTarget and so with
                no card selected meant the group the trail ends with rather than any card on screen. All
                three act on the card that was right-clicked, which is the whole of what this menu is for.

                Only Open takes a parameter, because OpenGroupCommand's null means ALL HOSTS rather than
                nothing; the other three read GroupTarget, which the selection the code-behind has just made
                is the first half of.

                Moving is above the separator that Delete sits below, exactly as it is in the host pane's
                menu, and for the same reason: it is not a field of the group and it is not a deletion
                either. The two vaults are encrypted under different keys, so it is a re-seal of the whole
                shelf into one and a tombstone per item in the other.
              -->
              <ListBox.ContextMenu>
                <ContextMenu>
                  <MenuItem Header="Open" Command="{Binding OpenGroupCommand}"
                            CommandParameter="{Binding SelectedGroup}" />
                  <MenuItem Header="Edit…" Command="{Binding EditGroupCommand}" />
                  <MenuItem Header="Move to another vault…" Command="{Binding MoveGroupCommand}" />
                  <Separator />
                  <MenuItem Header="Delete…" Command="{Binding DeleteGroupCommand}" />
                </ContextMenu>
              </ListBox.ContextMenu>

              <ListBox.ItemTemplate>
                <DataTemplate x:DataType="vm:HostGroupRowViewModel">
                  <Border Classes="tile">
                    <Grid ColumnDefinitions="Auto,*">
                      <Border Grid.Column="0" Classes="tileicon" Background="{StaticResource Accent}">
                        <TextBlock Text="▤" FontSize="14" Foreground="{StaticResource AccentInk}"
                                   HorizontalAlignment="Center" VerticalAlignment="Center" />
                      </Border>
                      <StackPanel Grid.Column="1" Margin="10,0,0,0" VerticalAlignment="Center"
                                  Spacing="2">
                        <!-- A Grid rather than a horizontal StackPanel; see the note on the host card. -->
                        <Grid ColumnDefinitions="*,Auto">
                          <TextBlock Grid.Column="0" Text="{Binding Label}" FontWeight="Medium"
                                     Foreground="{StaticResource Text}"
                                     TextTrimming="CharacterEllipsis" />
                          <Border Grid.Column="1" 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 Text="{Binding Description}" FontSize="11"
                                   Foreground="{StaticResource TextFaint}" />
                        <!--
                          Which vault this group is in, on the same rule and in the same place as the host
                          card's: only where there is more than one vault to be in. It matters more on a
                          folder than on a machine — two vaults may each hold a "production", and without
                          this the cards are two identical folders side by side, one of which a colleague
                          can read. It is also what says a group is shared at all.
                        -->
                        <TextBlock Classes="mono" Text="{Binding VaultBadge}" FontSize="10"
                                   Foreground="{StaticResource TextFaint}"
                                   TextTrimming="CharacterEllipsis"
                                   IsVisible="{Binding HasVaultBadge}" />
                      </StackPanel>
                    </Grid>
                  </Border>
                </DataTemplate>
              </ListBox.ItemTemplate>
            </ListBox>

          </StackPanel>

          <!-- ============ HOSTS ============ -->
          <StackPanel Spacing="8">

            <Grid ColumnDefinitions="Auto,*,Auto">
              <TextBlock Grid.Column="0" Classes="label" Text="HOSTS"
                         Foreground="{StaticResource TextDim}" VerticalAlignment="Center" />

              <!--
                ============ ◆ WHAT SAYS A SET IS UP ============
                Ctrl, Shift and a band dragged across the grid 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 grid would be a second home for entries the menu already has.

                ◆ THE MENU IS WHERE THE ACTIONS ARE, and the sentence says so because there is no other way
                to find that out. Everything else on this screen can be reached by looking at it.

                A Grid rather than a horizontal StackPanel, for the reason the host card carries at length: a
                horizontal StackPanel measures its children with infinite width, so the sentence would never
                learn it is short of room and would run out over the count at the far end instead of
                trimming. At the window's minimum with the drawer open there is not room for all of it.
              -->
              <Grid Grid.Column="1" Margin="12,0" 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>

              <!-- The collection's own count, so it follows both filters with no second number to keep in step. -->
              <TextBlock Grid.Column="2" Classes="mono" Text="{Binding VisibleHosts.Count}"
                         FontSize="11" Foreground="{StaticResource TextFaint}"
                         VerticalAlignment="Center" />
            </Grid>

            <!--
              ============ ◆ WHAT THE SET'S MENU ASKED ============
              Three panels, at most one of them up, all three above the grid rather than over it — the same
              arrangement the GROUPS section uses and the phone's list uses, for the reason written there: a
              card laid over the cards hides the ticked ones, and which hosts are ticked is the information
              the question exists to give. The grid shortens instead.

              Which of the three is showing is decided in the view model, because each disarms the other two
              on the way up. See MoveChosenHostsToVault, RegroupChosenHosts and DeleteChosenHosts.
            -->

            <!--
              ◆ 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.

              The sentence is not decoration: groups and tags are items of the vault being left, so neither
              can come along, and a host that arrived carrying either would point at something the
              destination does not contain.
            -->
            <Border Padding="10" Background="{StaticResource Panel}" CornerRadius="6"
                    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.
                  See VaultViewModel.BringsTheChosenBindingAlong.
                -->
                <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 dragging the set onto a group card does without the
              picker. Both exist for the reason the single-host gesture and the host editor both do: a
              gesture is unreachable without a pointer, and a card that has scrolled out of sight cannot be
              dragged onto.

              One keychain's groups. A selection spanning two is refused before this panel is drawn at all —
              see VaultViewModel.RegroupChosenHosts, which is the same refusal the drop makes.
            -->
            <Border Padding="10" Background="{StaticResource Panel}" CornerRadius="6"
                    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; see ConfirmDeleteCard.
            -->
            <Border Padding="10" Background="{StaticResource DangerWash}" CornerRadius="6"
                    IsVisible="{Binding IsConfirmingChosenHostDeletion}">
              <views:ConfirmDeleteCard />
            </Border>

            <!--
              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 a ListBox is not by default — Avalonia leaves focus to the items and an empty
              list has none. 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.

              ◆ CARDS ONLY, AND NO HEADINGS. It binds VisibleHosts rather than SidebarRows, which is the
              collection with the group headings folded into it. Those headings were a full-width fold-away
              bar between the cards — a chevron, a name and a count — and in a wrap of cards they read as a
              collapsed dropdown that had somehow been dealt the hosts: the moment a first group existed the
              grid stopped being a grid. Two controls saying "group" on one screen was one too many, and the
              cards above are the better of the two. SidebarRows and the fold are still the phone's, whose
              list has no room for a row of group cards; see Android's HostsScreen.

              What a heading also was is the drop target, and that moved to the group cards rather than
              going away with it — see the note on them above, and the code-behind for the gesture. A host is
              taken back out of a group through its own editor, which is the one place "no group" can be said
              plainly.

              ◆ ONE LEVEL, AS A DIRECTORY PANE IS. A host filed under a group is inside that group and is
              not also here — the cards above are the way into it, and this grid holds what is loose at
              whatever level the trail names. It used to hold every host at once with the groups drawn as
              headings over them, which made opening a group an operation that could only ever subtract.
              See VaultViewModel.Matches, and the note on the find box above for the one thing that crosses
              a group boundary.

              Which group a card is in is on the card, as a chip. That is what the heading above it used to
              say, and one chip per card says it without an accordion — and it is what tells a search result
              from a host that lives at this level, since searching is the one way a card from inside a
              group reaches this grid.

              SelectedItem is still SelectedSidebarRow and deliberately not SelectedHost, though every item
              here is now a host. That property swallows the null the ListBox writes back whenever the
              collection is rebuilt — which is every filter keystroke and every background sync — where
              binding the host selection directly would let that null through, and a null host selection
              closes the drawer and disarms a delete somebody is in the middle of confirming. See the
              property, and RebuildVisibleHosts on where the real clearing is done.
            -->
            <ListBox Classes="tiles" x:Name="HostGrid" Focusable="True"
                     ItemsSource="{Binding VisibleHosts}"
                     SelectedItem="{Binding SelectedSidebarRow}"
                     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>

              <!--
                The three things you can do to a host, on the host itself.

                On the list rather than in the item template, and that is what makes it one menu rather than
                one per card: a ContextMenu inside a DataTemplate would have the row for its data context,
                and the commands here are the vault's. The code-behind selects whatever was right-clicked
                before the menu opens, so the selection-based commands act on the card under the pointer,
                and cancels the menu outright over the space around the cards, where there is no host to act
                on.
              -->
              <!--
                Details is here as well as on the pencil, and that is not a duplicate for its own sake: the
                pencil appears on hover, so it is unreachable without a pointer, and this menu is what a
                keyboard and the context key can get to. It passes no parameter — the code-behind has
                already selected whatever was right-clicked, which is exactly what OpenHostPane falls back
                to.
              -->
              <!--
                ◆ TWO MENUS IN ONE, AND WHICH OF THEM IS DRAWN IS WHETHER ANYTHING IS TICKED.

                Once Ctrl, Shift or a band has ticked cards, this menu is about the set and nothing else —
                and it has to be, because the entries above act on the vault's *selection*, which is one
                host. A Delete… that asked about the card under the pointer while six sat ticked behind the
                menu is the exact mistake the whole of OnContextRequested exists to prevent, arrived at from
                the other direction. Right-clicking a card that is not in the set drops the set first, so
                these entries and the ones above are never both about something; see the code-behind.

                This is the only home for the seven, which is why it says what each of them takes: a set from
                two keychains, a set with a host in it that a newer version wrote, and a set of one are three
                different answers. The three single-host entries are collapsed rather than greyed for the
                reason the phone's sheet collapses them — a terminal, an SFTP session and a form are each
                about one machine, and there is no reading of "edit these six".

                Delete is under a separator, as it is above and as the phone's sheet has it.
              -->
              <ListBox.ContextMenu>
                <ContextMenu>
                  <MenuItem Header="Connect" IsVisible="{Binding !IsChoosingHosts}"
                            Command="{Binding ConnectCommand}" />
                  <MenuItem Header="Details…" IsVisible="{Binding !IsChoosingHosts}"
                            Command="{Binding OpenHostPaneCommand}" />
                  <MenuItem Header="Edit…" IsVisible="{Binding !IsChoosingHosts}"
                            Command="{Binding EditSelectedHostCommand}" />
                  <Separator IsVisible="{Binding !IsChoosingHosts}" />
                  <MenuItem Header="Delete…" IsVisible="{Binding !IsChoosingHosts}"
                            Command="{Binding DeleteHostCommand}" />

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

              <ListBox.ItemTemplate>

                <DataTemplate x:DataType="vm:HostRowViewModel">
                  <!--
                    The address is on the tooltip and nowhere else on the card. It used to be the subtitle;
                    see the note on the summary line below for what took its place and why it is not lost.
                  -->
                  <!--
                    ◆ Classes.chosen is the tick on the card, and it is a class rather than the list's own
                    selection because they are two different things: the list holds one card, which is what
                    the drawer and CONNECT are about, and the set is what Ctrl, Shift and the band build. A
                    card can be in the set without being the selected one, which is what a run of six looks
                    like. See VaultViewModel.ChooseHosts and Border.tile.chosen in App.axaml.
                  -->
                  <Border Classes="tile" Classes.chosen="{Binding IsChosen}"
                          ToolTip.Tip="{Binding Address}">
                    <!--
                      A Panel so the pencil below can be drawn *over* the card rather than in a column of
                      its own. A column would have cost the name 30 pixels of a 232-pixel tile — permanently,
                      for a control that is only there while the pointer is — and the name is the thing the
                      grid exists to be scanned by.
                    -->
                    <Panel>
                      <StackPanel Spacing="6">
                        <Grid ColumnDefinitions="Auto,*,Auto">

                          <Border Grid.Column="0" Classes="tileicon" Background="{StaticResource Chip}">
                            <TextBlock Classes="mono" Text="&gt;_" FontSize="11" FontWeight="Bold"
                                       Foreground="{StaticResource AccentText}"
                                       HorizontalAlignment="Center" VerticalAlignment="Center" />
                          </Border>

                          <StackPanel Grid.Column="1" Margin="10,0,0,0" VerticalAlignment="Center"
                                      Spacing="2">
                            <!--
                              ◆ A GRID AND NOT A HORIZONTAL StackPanel, on both of these rows, and the two
                              look interchangeable until the text is too long for the card.

                              A horizontal StackPanel measures every child with infinite width, so a
                              TextBlock inside one never learns it is short of room and TextTrimming never
                              fires — the text simply runs on past the card's border and over the card beside
                              it. The card is a fixed 248 and a host name is not, so that is the ordinary
                              case rather than the edge one.

                              The star column is the part that gives way and the Auto column is the part that
                              must not: a badge saying "not synced" and the word that says how a host
                              authenticates are both short and both meaningless trimmed, where a hostname
                              with its tail cut is still the machine you were looking for.
                            -->
                            <Grid ColumnDefinitions="*,Auto">
                              <TextBlock Grid.Column="0" Text="{Binding Label}" FontWeight="Medium"
                                         Foreground="{StaticResource Text}"
                                         TextTrimming="CharacterEllipsis" />
                              <Border Grid.Column="1" 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>
                            <!--
                              ◆ ONE COMMA-SEPARATED LINE: the transport, the account, and every tag this host
                              wears. It replaces two things — the address-and-authentication row that was here,
                              and the row of tag chips that used to sit under the whole card.

                              What went is the address, and it went to the tooltip above rather than nowhere.
                              A card is read while scanning forty machines, and at that moment the name and
                              what kind of machine it is are what is being looked for; an address is what you
                              read once you have found it, which is what the drawer is for. The word saying
                              how it authenticates went with it, for a smaller reason: it was there to warn
                              that a password box would be needed, and the box is in the pane beside that
                              warning now.

                              See HostRowViewModel.Summary, and the remark there on why "ssh" is printed at
                              all when every host in this product is one.
                            -->
                            <TextBlock Text="{Binding Summary}" FontSize="10.5"
                                       Foreground="{StaticResource TextFaint}"
                                       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 grid
                              spanning several vaults it is not decoration.
                            -->
                            <TextBlock Classes="mono" Text="{Binding VaultBadge}" FontSize="10.5"
                                       Foreground="{StaticResource TextFaint}"
                                       IsVisible="{Binding HasVaultBadge}" />
                          </StackPanel>

                          <!--
                            Connected, and nothing more. Green means a terminal is open on this host right
                            now; grey means there is not one. It is deliberately not reachability — nothing
                            here pings anything, and a dot that meant "up" would be a claim this application
                            never checks.

                            ◆ The tick sits beside it rather than in place of it, which is the arrangement
                            the phone's row uses and for the same reason: the dot says whether a shell is
                            open on this machine, and swapping it for the tick would make choosing a host
                            hide a fact about it. It takes no width while it is collapsed, so a grid with
                            nothing ticked is laid out exactly as it was.
                          -->
                          <StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="4"
                                      VerticalAlignment="Top" Margin="6,2,0,0">
                            <TextBlock Text="✓" FontSize="11" IsVisible="{Binding IsChosen}"
                                       Foreground="{StaticResource AccentText}"
                                       VerticalAlignment="Center" />
                            <Ellipse Classes="dot" Classes.live="{Binding IsConnected}"
                                     VerticalAlignment="Center" Margin="0,1,0,0" />
                          </StackPanel>
                        </Grid>

                        <!--
                          The group this host is filed under, and only when it is filed under one — which is
                          the same rule the group cards above follow, and the reason a keychain nobody has
                          filed anything in looks exactly as it did before groups existed.

                          This is what the fold-away heading over a run of cards used to say. A chip on the
                          card says it per host instead, which is the shape the rest of this card already is
                          and the shape a wrap of cards can carry: it survives the cards being reflowed, it
                          needs no row of its own, and it is what changes when a card is dropped onto a group.

                          Accent rather than plain, so the one chip that is a *place* is not read as another
                          tag. A group whose name the vault cannot resolve leaves this empty rather than
                          printing an id, for the reason a tag does. See HostRowViewModel.GroupLabel.
                        -->
                        <Border Classes="chip accent" Padding="5,0" HorizontalAlignment="Left"
                                IsVisible="{Binding HasGroup}">
                          <TextBlock Text="{Binding GroupLabel}" FontSize="9.5"
                                     TextTrimming="CharacterEllipsis" />
                        </Border>

                        <!--
                          A wrapped row of tag chips was here. The tags are in the summary line above now,
                          after the account, which is what took the card from three rows and a wrap down to
                          two: a chip and a comma-separated name say the same thing, and only one of them
                          needs a row of its own. Chips are still what a tag looks like where it is *chosen* —
                          the editor's picker in the drawer, and the phone's own list. See
                          HostRowViewModel.Summary.
                        -->
                      </StackPanel>

                    <!--
                      ◆ THE PENCIL, AND THE ONLY WAY THE DRAWER OPENS FROM THIS GRID.

                      Selecting a card no longer opens it. That used to be automatic, which meant the grid
                      gave up 304 pixels the moment anybody touched a card — including every card arrowed
                      past on the way to the one they wanted. Opening the pane is a thing you ask for now,
                      and this is where the asking is. See VaultViewModel.IsHostPaneOpen.

                      Bottom right, under the status dot rather than beside it, and over the card rather
                      than in a column: the two marks a card can carry on that edge are "a shell is open
                      here" and "open this one's pane", and stacking them is what keeps both out of the
                      name's width. It appears on hover and on the selected card; see Button.rowedit in
                      App.axaml for why that is IsVisible rather than an opacity.

                      The row is the command's parameter rather than being left to the selection. A button
                      inside a ListBoxItem takes the press itself, and whether the item is also selected by
                      it is the theme's business — so a command reading SelectedHost would open the pane on
                      whichever card happened to be lit, which on the first click of a session is none of
                      them.
                    -->
                    <Button Classes="flat rowedit" Content="✎"
                            HorizontalAlignment="Right" VerticalAlignment="Bottom"
                            Command="{Binding $parent[ListBox].((vm:VaultViewModel)DataContext).OpenHostPaneCommand}"
                            CommandParameter="{Binding}"
                            ToolTip.Tip="Opens this host's pane beside the grid" />
                    </Panel>
                  </Border>
                </DataTemplate>

              </ListBox.ItemTemplate>
            </ListBox>

            <!--
              Nothing to show, and the two reasons it can happen are different questions. An empty keychain
              is an invitation; a filter that matches nothing is a filter that has gone too far, and saying
              "add a host" to somebody who has thirty would be answering the wrong one.
            -->
            <TextBlock Classes="hint" FontSize="12" TextWrapping="Wrap" MaxWidth="480"
                       HorizontalAlignment="Left" Margin="0,4,0,0"
                       IsVisible="{Binding !HasVisibleHosts}"
                       Text="{Binding NoVisibleHostsMessage}" />

          </StackPanel>

        </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 grid 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 304 pixels only while it has been opened — which is what gives the grid
      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>
