/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
 * Copyright 2025 GNOME Foundation, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *  - Ignacy Kuchciński <ignacykuchcinski@gnome.org>
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
#include "config.h"
#include <glib/gi18n.h>

#define GNOME_DESKTOP_USE_UNSTABLE_API
#include <libgnome-desktop/gnome-wall-clock.h>
#include <adwaita.h>
#include <glib-object.h>
#include <gtk/gtk.h>

#include "cc-time-editor.h"
#include "cc-time-row.h"

#define CLOCK_SCHEMA "org.gnome.desktop.interface"
#define CLOCK_FORMAT_KEY "clock-format"

/**
 * CcTimeRow:
 *
 * An #AdwActionRow used to enter a time, represented as time string.
 *
 * The currently specified time is shown in a label. If the row is activated
 * a popover is shown containing a #CcTimeEditor to edit the time.
 */
struct _CcTimeRow {
  AdwActionRow parent_instance;

  GtkWidget *arrow_box;
  GtkLabel *current;
  GtkPopover *popover;
  CcTimeEditor *editor;
  GSettings *clock_settings;
};

G_DEFINE_TYPE (CcTimeRow, cc_time_row, ADW_TYPE_ACTION_ROW)

typedef enum {
  PROP_TIME = 1,
} CcTimeRowProperty;

static GParamSpec *props[PROP_TIME + 1];

static void cc_time_row_get_property (GObject    *object,
                                      guint       property_id,
                                      GValue     *value,
                                      GParamSpec *pspec);
static void cc_time_row_set_property (GObject      *object,
                                      guint         property_id,
                                      const GValue *value,
                                      GParamSpec   *pspec);
static void cc_time_row_dispose (GObject *object);
static void cc_time_row_finalize (GObject *object);
static void cc_time_row_size_allocate (GtkWidget *widget,
                                       int        width,
                                       int        height,
                                       int        baseline);
static gboolean cc_time_row_focus (GtkWidget        *widget,
                                       GtkDirectionType  direction);
static void cc_time_row_activate (AdwActionRow *row);
static void popover_notify_visible_cb (GObject    *object,
                                       GParamSpec *pspec,
                                       gpointer    user_data);
static void update_current_label (CcTimeRow *self);
static void editor_time_changed_cb (CcTimeEditor *editor,
                                    gpointer      user_data);
static void time_row_clock_changed_cb (CcTimeRow *self);

static void
cc_time_row_class_init (CcTimeRowClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  AdwActionRowClass *row_class = ADW_ACTION_ROW_CLASS (klass);

  object_class->get_property = cc_time_row_get_property;
  object_class->set_property = cc_time_row_set_property;
  object_class->dispose = cc_time_row_dispose;
  object_class->finalize = cc_time_row_finalize;

  widget_class->size_allocate = cc_time_row_size_allocate;
  widget_class->focus = cc_time_row_focus;

  row_class->activate = cc_time_row_activate;

  /**
   * CcTimeRow:time:
   *
   * Time displayed in the row or chosen in the editor,
   * in seconds since midnight.
   */
  props[PROP_TIME] =
    g_param_spec_uint ("time",
                       NULL, NULL,
                       0, G_MAXUINT, 0,
                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

  g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);

  g_type_ensure (CC_TYPE_TIME_EDITOR);

  gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentControl/ui/cc-time-row.ui");

  gtk_widget_class_bind_template_child (widget_class, CcTimeRow, current);
  gtk_widget_class_bind_template_child (widget_class, CcTimeRow, arrow_box);
  gtk_widget_class_bind_template_child (widget_class, CcTimeRow, editor);
  gtk_widget_class_bind_template_child (widget_class, CcTimeRow, popover);
  gtk_widget_class_bind_template_callback (widget_class, popover_notify_visible_cb);
  gtk_widget_class_bind_template_callback (widget_class, editor_time_changed_cb);

  gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_COMBO_BOX);
}

static void
cc_time_row_init (CcTimeRow *self)
{
  gtk_widget_init_template (GTK_WIDGET (self));

  self->clock_settings = g_settings_new (CLOCK_SCHEMA);

  g_signal_connect_object (self->clock_settings, "changed::" CLOCK_FORMAT_KEY,
                           G_CALLBACK (time_row_clock_changed_cb), self,
                           G_CONNECT_SWAPPED);

  update_current_label (self);
}

static void
cc_time_row_get_property (GObject    *object,
                          guint       property_id,
                          GValue     *value,
                          GParamSpec *pspec)
{
  CcTimeRow *self = CC_TIME_ROW (object);

  switch ((CcTimeRowProperty) property_id)
    {
    case PROP_TIME:
      g_value_set_uint (value, cc_time_row_get_time (self));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
cc_time_row_set_property (GObject      *object,
                          guint         property_id,
                          const GValue *value,
                          GParamSpec   *pspec)
{
  CcTimeRow *self = CC_TIME_ROW (object);

  switch ((CcTimeRowProperty) property_id)
    {
    case PROP_TIME:
      cc_time_row_set_time (self, g_value_get_uint (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}

static void
cc_time_row_dispose (GObject *object)
{
  gtk_widget_dispose_template (GTK_WIDGET (object), CC_TYPE_TIME_ROW);

  G_OBJECT_CLASS (cc_time_row_parent_class)->dispose (object);
}

static void
cc_time_row_finalize (GObject *object)
{
  CcTimeRow *self = CC_TIME_ROW (object);

  g_clear_object (&self->clock_settings);

  G_OBJECT_CLASS (cc_time_row_parent_class)->finalize (object);
}

static void
cc_time_row_size_allocate (GtkWidget *widget,
                           int        width,
                           int        height,
                           int        baseline)
{
  CcTimeRow *self = CC_TIME_ROW (widget);

  GTK_WIDGET_CLASS (cc_time_row_parent_class)->size_allocate (widget, width, height, baseline);

  gtk_popover_present (self->popover);
}

static gboolean
cc_time_row_focus (GtkWidget        *widget,
                   GtkDirectionType  direction)
{
  CcTimeRow *self = CC_TIME_ROW (widget);

  if (self->popover != NULL && gtk_widget_get_visible (GTK_WIDGET (self->popover)))
    return gtk_widget_child_focus (GTK_WIDGET (self->popover), direction);
  else
    return GTK_WIDGET_CLASS (cc_time_row_parent_class)->focus (widget, direction);
}

static void
cc_time_row_activate (AdwActionRow *row)
{
  CcTimeRow *self = CC_TIME_ROW (row);

  if (gtk_widget_get_visible (self->arrow_box))
    gtk_popover_popup (self->popover);
}

static void
popover_notify_visible_cb (GObject    *object,
                           GParamSpec *pspec,
                           gpointer    user_data)
{
  CcTimeRow *self = CC_TIME_ROW (user_data);

  if (gtk_widget_get_visible (GTK_WIDGET (self->popover)))
    gtk_widget_add_css_class (GTK_WIDGET (self), "has-open-popup");
  else
    gtk_widget_remove_css_class (GTK_WIDGET (self), "has-open-popup");
}

static void
update_current_label (CcTimeRow *self)
{
  GDesktopClockFormat clock_format;
  g_autofree char *time_str = NULL;
  g_autoptr (GDateTime) date_time = NULL;

  clock_format = g_settings_get_enum (self->clock_settings, CLOCK_FORMAT_KEY);
  guint hours = cc_time_editor_get_hour (self->editor);
  guint minutes = cc_time_editor_get_minute (self->editor);

  date_time = g_date_time_new_utc (1, 1, 1, hours % 24, minutes, 0);
  
  if (clock_format == G_DESKTOP_CLOCK_FORMAT_12H)
    {
      /* Translators: This is time format used in 12-hour mode. */
      time_str = g_date_time_format (date_time, _("%-l:%M %p"));
    }
  else
    {
      /* Translators: This is time format used in 24-hour mode. */
      time_str = g_date_time_format (date_time, _("%-k:%M"));
    }

  gtk_label_set_label (self->current, time_str);
}

static void
editor_time_changed_cb (CcTimeEditor *editor,
                        gpointer      user_data)
{
  CcTimeRow *self = CC_TIME_ROW (user_data);

  update_current_label (self);

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TIME]);
}

static void
time_row_clock_changed_cb (CcTimeRow *self)
{
  update_current_label (self);
}

/**
 * cc_time_row_new:
 *
 * Create a new #CcTimeRow.
 *
 * Returns: (transfer full): the new #CcTimeRow
 */
CcTimeRow *
cc_time_row_new (void)
{
  return g_object_new (CC_TYPE_TIME_ROW, NULL);
}

/**
 * cc_time_row_get_time:
 * @self: a #CcTimeRow
 *
 * Get the value of #CcTimeRow:time.
 *
 * Returns: number of minutes since midnight currently specified by the row
 */
guint
cc_time_row_get_time (CcTimeRow *self)
{
  g_return_val_if_fail (CC_IS_TIME_ROW (self), 0);

  guint hours = cc_time_editor_get_hour (self->editor);
  guint minutes = cc_time_editor_get_minute (self->editor);

  return hours * 60 + minutes;
}

/**
 * cc_time_row_set_time:
 * @self: a #CcTimeRow
 * @time: the time, in minutes since midnight
 *
 * Set the value of #CcTimeRow:time.
 */
void
cc_time_row_set_time (CcTimeRow *self,
                      guint      time)
{
  g_return_if_fail (CC_IS_TIME_ROW (self));

  guint hours, minutes;

  hours = time / 60;
  minutes = time % 60;

  cc_time_editor_set_time (self->editor, hours, minutes);

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TIME]);
}
