#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "../include/chroma.h"

// Registry listener
static void registry_global(void *data, struct wl_registry *registry,
                            uint32_t id, const char *interface,
                            uint32_t version) {
  chroma_state_t *state = (chroma_state_t *)data;

  chroma_log("DEBUG", "Registry global: %s (id=%u, version=%u)", interface, id,
             version);
  chroma_log("TRACE", "Checking interface binding for: %s", interface);

  if (strcmp(interface, wl_compositor_interface.name) == 0) {
    state->compositor = wl_registry_bind(registry, id, &wl_compositor_interface,
                                         version < 4 ? version : 4);
    if (!state->compositor) {
      chroma_log("ERROR", "Failed to bind compositor");
    } else {
      chroma_log("INFO", "Bound compositor (version %u)", version);
      chroma_log("TRACE", "Compositor binding successful, interface ready");
    }
  } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
    state->layer_shell =
        wl_registry_bind(registry, id, &zwlr_layer_shell_v1_interface,
                         version < 4 ? version : 4);
    if (!state->layer_shell) {
      chroma_log("ERROR", "Failed to bind layer shell");
    } else {
      chroma_log("INFO", "Bound layer shell (version %u)", version);
      chroma_log(
          "TRACE",
          "wlr-layer-shell protocol available, wallpaper support enabled");
    }
  } else if (strcmp(interface, wl_output_interface.name) == 0) {
    struct wl_output *output = wl_registry_bind(
        registry, id, &wl_output_interface, version < 4 ? version : 4);
    if (!output) {
      chroma_log("ERROR", "Failed to bind output %u", id);
      return;
    }

    if (chroma_output_add(state, id, output) != CHROMA_OK) {
      chroma_log("ERROR", "Failed to add output %u", id);
      wl_output_destroy(output);
    } else {
      chroma_log("INFO", "Added output %u", id);
    }
  }
}

static void registry_global_remove(void *data, struct wl_registry *registry,
                                   uint32_t id) {
  chroma_state_t *state = (chroma_state_t *)data;
  (void)registry;

  chroma_log("DEBUG", "Registry global remove: id=%u", id);
  chroma_log("TRACE", "Global object removal initiated for id=%u", id);
  chroma_output_remove(state, id);
}

const struct wl_registry_listener chroma_registry_listener_impl = {
    .global = registry_global,
    .global_remove = registry_global_remove,
};

// Layer surface event handlers
static void layer_surface_configure(void *data,
                                    struct zwlr_layer_surface_v1 *layer_surface,
                                    uint32_t serial, uint32_t width,
                                    uint32_t height) {
  chroma_output_t *output = (chroma_output_t *)data;
  (void)layer_surface;

  chroma_log("DEBUG", "Layer surface configure: %ux%u, serial=%u", width,
             height, serial);
  chroma_log(
      "TRACE",
      "Configuring layer surface for output %u: dimensions=%ux%u, serial=%u",
      output->id, width, height, serial);

  output->configure_serial = serial;

  // Acknowledge the configure event
  zwlr_layer_surface_v1_ack_configure(output->layer_surface, serial);
  chroma_log("TRACE", "Sent configure acknowledgment for output %u serial %u",
             output->id, serial);

  // Commit the surface to apply the acknowledgment
  wl_surface_commit(output->surface);
  chroma_log("TRACE", "Surface committed for output %u", output->id);

  chroma_log("DEBUG", "Acknowledged layer surface configure for output %u",
             output->id);
}

static void layer_surface_closed(void *data,
                                 struct zwlr_layer_surface_v1 *layer_surface) {
  chroma_output_t *output = (chroma_output_t *)data;
  (void)layer_surface;

  chroma_log("INFO", "Layer surface closed for output %u", output->id);

  // Clean up the surface
  if (output->surface) {
    chroma_surface_destroy(output);
  }
}

const struct zwlr_layer_surface_v1_listener chroma_layer_surface_listener_impl =
    {
        .configure = layer_surface_configure,
        .closed = layer_surface_closed,
};

// Output event handlers
static void output_geometry(void *data, struct wl_output *output, int32_t x,
                            int32_t y, int32_t physical_width,
                            int32_t physical_height, int32_t subpixel,
                            const char *make, const char *model,
                            int32_t transform) {
  chroma_output_t *chroma_output = (chroma_output_t *)data;
  (void)output;
  (void)subpixel;
  (void)make;
  (void)model;

  chroma_output->x = x;
  chroma_output->y = y;
  chroma_output->transform = transform;

  chroma_log("DEBUG", "Output %u geometry: %dx%d at (%d,%d), transform=%d",
             chroma_output->id, physical_width, physical_height, x, y,
             transform);
  chroma_log("TRACE",
             "Output %u geometry details: physical_size=%dx%dmm, "
             "position=(%d,%d), transform=%d",
             chroma_output->id, physical_width, physical_height, x, y,
             transform);
}

static void output_mode(void *data, struct wl_output *output, uint32_t flags,
                        int32_t width, int32_t height, int32_t refresh) {
  chroma_output_t *chroma_output = (chroma_output_t *)data;
  (void)output;

  if (flags & WL_OUTPUT_MODE_CURRENT) {
    chroma_output->width = width;
    chroma_output->height = height;
    chroma_log("DEBUG", "Output %u mode: %dx%d@%d (current)", chroma_output->id,
               width, height, refresh);
    chroma_log("TRACE",
               "Current mode set for output %u: %dx%d@%dHz, flags=0x%x",
               chroma_output->id, width, height, refresh, flags);
  } else {
    chroma_log("TRACE",
               "Non-current mode for output %u: %dx%d@%dHz, flags=0x%x",
               chroma_output->id, width, height, refresh, flags);
  }
}

static void output_scale(void *data, struct wl_output *output, int32_t scale) {
  chroma_output_t *chroma_output = (chroma_output_t *)data;
  (void)output;

  chroma_output->scale = scale;
  chroma_log("DEBUG", "Output %u scale: %d", chroma_output->id, scale);
}

static void output_name(void *data, struct wl_output *output,
                        const char *name) {
  chroma_output_t *chroma_output = (chroma_output_t *)data;
  (void)output;

  free(chroma_output->name);
  chroma_output->name = strdup(name);
  if (!chroma_output->name) {
    chroma_log("ERROR", "Failed to allocate memory for output name");
    return;
  }

  chroma_log("DEBUG", "Output %u name: %s", chroma_output->id, name);
}

static void output_description(void *data, struct wl_output *output,
                               const char *description) {
  chroma_output_t *chroma_output = (chroma_output_t *)data;
  (void)output;

  free(chroma_output->description);
  chroma_output->description = strdup(description);
  if (!chroma_output->description) {
    chroma_log("ERROR", "Failed to allocate memory for output description");
    return;
  }

  chroma_log("DEBUG", "Output %u description: %s", chroma_output->id,
             description);
}

static void output_done(void *data, struct wl_output *output) {
  chroma_output_t *chroma_output = (chroma_output_t *)data;
  (void)output;

  chroma_log("DEBUG", "Output %u done - configuration complete",
             chroma_output->id);
  chroma_log("TRACE",
             "Output %u configuration finalized: %dx%d, scale=%d, name='%s'",
             chroma_output->id, chroma_output->width, chroma_output->height,
             chroma_output->scale,
             chroma_output->name ? chroma_output->name : "unknown");

  // Mark output as active and ready for wallpaper assignment
  chroma_output->active = true;

  // Trigger wallpaper assignment for this output
  if (chroma_output->state) {
    chroma_log("TRACE", "Triggering wallpaper assignment for output %u",
               chroma_output->id);
    handle_output_done(chroma_output->state, chroma_output);
  }
}

const struct wl_output_listener chroma_output_listener_impl = {
    .geometry = output_geometry,
    .mode = output_mode,
    .scale = output_scale,
    .name = output_name,
    .description = output_description,
    .done = output_done,
};

// Wayland connection functions
int chroma_wayland_connect(chroma_state_t *state) {
  if (!state) {
    return CHROMA_ERROR_INIT;
  }

  // Connect to Wayland display
  state->display = wl_display_connect(NULL);
  if (!state->display) {
    chroma_log("ERROR", "Failed to connect to Wayland display: %s",
               strerror(errno));
    return CHROMA_ERROR_WAYLAND;
  }

  // Get registry
  state->registry = wl_display_get_registry(state->display);
  if (!state->registry) {
    chroma_log("ERROR", "Failed to get Wayland registry");
    chroma_wayland_disconnect(state);
    return CHROMA_ERROR_WAYLAND;
  }

  // Add registry listener
  wl_registry_add_listener(state->registry, &chroma_registry_listener_impl,
                           state);

  // Roundtrip to get all globals
  chroma_log("TRACE", "Starting Wayland display roundtrip to discover globals");
  if (wl_display_roundtrip(state->display) == -1) {
    chroma_log("ERROR", "Failed to roundtrip Wayland display");
    chroma_wayland_disconnect(state);
    return CHROMA_ERROR_WAYLAND;
  }
  chroma_log("TRACE", "Wayland display roundtrip completed");

  // Check if we got a compositor
  if (!state->compositor) {
    chroma_log("ERROR", "No compositor available");
    chroma_wayland_disconnect(state);
    return CHROMA_ERROR_WAYLAND;
  }

  // Check if we got layer shell
  if (!state->layer_shell) {
    chroma_log("ERROR", "No layer shell available - compositor may not support "
                        "wlr-layer-shell");
    chroma_wayland_disconnect(state);
    return CHROMA_ERROR_WAYLAND;
  }

  chroma_log("INFO", "Connected to Wayland display, found %d outputs",
             state->output_count);
  return CHROMA_OK;
}

void chroma_wayland_disconnect(chroma_state_t *state) {
  if (!state) {
    return;
  }

  // Clean up all outputs
  for (int i = 0; i < state->output_count; i++) {
    chroma_output_t *output = &state->outputs[i];
    if (output->surface) {
      chroma_surface_destroy(output);
    }

    if (output->wl_output) {
      wl_output_destroy(output->wl_output);
    }

    free(output->name);
    free(output->description);
  }
  state->output_count = 0;

  // Clean up Wayland objects
  if (state->layer_shell) {
    zwlr_layer_shell_v1_destroy(state->layer_shell);
    state->layer_shell = NULL;
  }

  if (state->compositor) {
    wl_compositor_destroy(state->compositor);
    state->compositor = NULL;
  }

  if (state->registry) {
    wl_registry_destroy(state->registry);
    state->registry = NULL;
  }

  if (state->display) {
    wl_display_disconnect(state->display);
    state->display = NULL;
  }

  chroma_log("INFO", "Disconnected from Wayland display");
}

// Output management functions
int chroma_output_add(chroma_state_t *state, uint32_t id,
                      struct wl_output *output) {
  if (!state || !output) {
    return CHROMA_ERROR_INIT;
  }

  if (state->output_count >= MAX_OUTPUTS) {
    chroma_log("ERROR", "Maximum number of outputs (%d) exceeded", MAX_OUTPUTS);
    return CHROMA_ERROR_MEMORY;
  }

  chroma_output_t *chroma_output = &state->outputs[state->output_count];
  memset(chroma_output, 0, sizeof(chroma_output_t));

  chroma_output->wl_output = output;
  chroma_output->id = id;
  chroma_output->scale = 1; // default scale
  chroma_output->active = false;
  chroma_output->state = state;

  // Add output listener
  wl_output_add_listener(output, &chroma_output_listener_impl, chroma_output);
  chroma_log("TRACE", "Output listener attached for output %u", id);

  state->output_count++;

  chroma_log("INFO", "Added output %u (total: %d)", id, state->output_count);
  chroma_log("TRACE",
             "Output %u initialized with default scale=1, active=false", id);
  return CHROMA_OK;
}

void chroma_output_remove(chroma_state_t *state, uint32_t id) {
  if (!state) {
    return;
  }

  chroma_output_t *output = chroma_output_find_by_id(state, id);
  if (!output) {
    chroma_log("WARN", "Attempted to remove non-existent output %u", id);
    return;
  }

  chroma_log("INFO", "Removing output %u (%s)", id,
             output->name ? output->name : "unknown");

  // Clean up surface if it exists
  if (output->surface) {
    chroma_surface_destroy(output);
  }

  // Clean up Wayland output
  if (output->wl_output) {
    wl_output_destroy(output->wl_output);
  }

  // Free allocated strings
  free(output->name);
  free(output->description);

  // Remove from array by shifting remaining elements
  int index = output - state->outputs;
  int remaining = state->output_count - index - 1;
  chroma_log("TRACE", "Removing output %u from array: index=%d, remaining=%d",
             id, index, remaining);
  if (remaining > 0) {
    memmove(output, output + 1, remaining * sizeof(chroma_output_t));
    chroma_log("TRACE", "Shifted %d outputs in array after removal", remaining);
  }

  state->output_count--;
  chroma_log("INFO", "Removed output %u (remaining: %d)", id,
             state->output_count);
}

chroma_output_t *chroma_output_find_by_id(chroma_state_t *state, uint32_t id) {
  if (!state) {
    return NULL;
  }

  for (int i = 0; i < state->output_count; i++) {
    if (state->outputs[i].id == id) {
      return &state->outputs[i];
    }
  }

  return NULL;
}

chroma_output_t *chroma_output_find_by_name(chroma_state_t *state,
                                            const char *name) {
  if (!state || !name) {
    return NULL;
  }

  for (int i = 0; i < state->output_count; i++) {
    chroma_output_t *output = &state->outputs[i];
    if (output->name && strcmp(output->name, name) == 0) {
      return output;
    }
  }

  return NULL;
}
