/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 2 -*-
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 * SPDX-FileCopyrightText: Michael Terry
 */

using GLib;

[GtkTemplate (ui = "/org/gnome/DejaDup/SnapshotsPage.ui")]
public class SnapshotsPage : Adw.NavigationPage
{
  public signal void browse(DejaDup.Operation.State state, string tag);

  public void scan(DejaDup.Backend? backend = null)
  {
    saved_state = null;

    try {
      var op = OperationLauncher.get_instance().create_list_snapshots(backend);
      operation.set(op);

      op.collection_dates.connect(handle_collection_dates);
      status_page.connect_to_operation(op);

      op.start.begin();
    } catch (Error e) {
      status_page.set_error_state(e.message);
    }

    cancel_mount.reset();
    stack.visible_child_name = "operation";
  }

  [GtkChild]
  unowned Gtk.Stack stack;
  [GtkChild]
  unowned Gtk.ListBox list_view;
  [GtkChild]
  unowned OperationStatusPage status_page;
  [GtkChild]
  unowned Gtk.ProgressBar progress;

  WeakRef operation;
  DejaDup.Operation.State saved_state;
  Cancellable cancel_mount;

  construct {
    var op_watcher = DejaDup.OperationWatcher.get_instance();
    op_watcher.bind_property(
      "active", list_view, "sensitive",
      BindingFlags.SYNC_CREATE | BindingFlags.INVERT_BOOLEAN
    );

    cancel_mount = new Cancellable();
  }

  [GtkCallback]
  void handle_hidden()
  {
    cancel_mount.cancel();
    var op = operation.get() as DejaDup.Operation;
    if (op != null)
      op.stop();
    operation.set(null);
  }

  void handle_collection_dates(DejaDup.Operation op, List<DejaDup.SnapshotInfo> dates)
  {
    saved_state = op.get_state();

    if (dates.length() == 0) {
      stack.visible_child_name = "no-snapshots";
      return;
    }

    dates.sort((a, b) => {return a.time.compare(b.time);});

    list_view.remove_all();
    dates.foreach((info) => {
      info.time = info.time.to_local();

      // TODO: find a better way to format the date time.
      // %c is the correct format to use. But in en_US, %c has:
      // - an unnecessary timezone (we normalize the timezone)
      // - uses zero-padding for the hours, but space-padding might feel more
      //   natural
      // Other locales might have similar issues.
      //
      // And regardless of locale, the width of the string is quite flexible
      // in non-monospace fonts. So a vertical page of dates (like we have
      // here) can look a little horizontally-bouncy. But monospace makes it
      // look too tech-y. Is there a typographic trick to play there?
      var user_str = info.time.format("%c");
      list_view.prepend(make_row(user_str, info.external_description, info.tag));
    });

    stack.visible_child_name = "list";
  }

  Gtk.Widget make_row(string title, string? desc, string tag)
  {
    var row = new Adw.ActionRow();
    row.activatable = true;
    row.title = title;
    row.subtitle = desc;
    var icon_name = "go-next-symbolic";
    if (saved_state.tool.supports_mount())
      icon_name = "external-link-symbolic";
    row.add_suffix(new Gtk.Image.from_icon_name(icon_name));
    row.set_data_full("tag", tag.dup(), free);
    return row;
  }

  async void mount(string tag)
  {
    list_view.sensitive = false;

    var pulse_id = Timeout.add(250, () => {
      progress.visible = true;
      progress.pulse();
      return Source.CONTINUE;
    });

    string error_msg = null;
    try {
      var dir = yield DejaDup.MountManager.get_instance().mount(saved_state, tag, cancel_mount);
      if (dir != null) {
        var launcher = new Gtk.FileLauncher(File.new_for_path(dir));
        yield launcher.launch(this.root as Gtk.Window, cancel_mount);
      }
    } catch (IOError.CANCELLED e) {
      // ignore
    } catch (DejaDup.MountError e) {
      error_msg = e.message;
    } catch (Error e) {
      error_msg = e.message + "\n\n" + _("Try closing any open file manager windows and trying again.");
    }

    Source.remove(pulse_id);
    progress.visible = false;
    list_view.sensitive = true;

    if (error_msg != null) {
      // Ubuntu has an issue between apparmor and the way we are using FUSE from inside flatpak.
      // Until that is resolved, allow detecting that (this error message is from libfuse, bubbling
      // up to restic, and then to us, and is not translated) and falling back to the old browser.
      // See https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/2120439
      // Full error message is something like "file descriptor 3 is not a socket, can't send fuse
      // fd": https://github.com/libfuse/libfuse/blob/ecfb204/util/fusermount.c#L1695
      // Or we've also seen "Socket operation on non-socket" - report here:
      // https://gitlab.gnome.org/World/deja-dup/-/issues/619#note_2728684
      if (error_msg.contains("is not a socket, can't send fuse fd") ||
          error_msg.contains("Socket operation on non-socket")) {
        browse(saved_state, tag);
      } else {
        ErrorDialog.create(this, _("Failed to Open Backup"), error_msg);
      }
    }
  }

  [GtkCallback]
  void row_activated(Gtk.ListBoxRow row)
  {
    var tag = row.get_data<string>("tag");
    if (saved_state.tool.supports_mount())
      mount.begin(tag);
    else
      browse(saved_state, tag);
  }
}
