#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

#include "../include/chroma.h"
#include "vendor/tomlc17.h"

// Match output name/description against a config pattern
// Supports:
//   - Exact name match: "DP-1" matches wl_output.name == "DP-1"
//   - Description prefix match: "desc:Samsung" matches if description starts
//   with "Samsung"
static bool match_output(const char *pattern, const char *output_name,
                         const char *output_description) {
  if (!pattern || !output_name) {
    return false;
  }

  // Try exact name match first
  if (strcmp(pattern, output_name) == 0) {
    return true;
  }

  // Check for description prefix match: "desc:<prefix>"
  if (strncmp(pattern, "desc:", 5) == 0) {
    const char *desc_prefix = pattern + 5;
    size_t prefix_len = strlen(desc_prefix);

    if (output_description && prefix_len > 0) {
      // Match if description starts with the prefix (case-insensitive)
      if (strncasecmp(output_description, desc_prefix, prefix_len) == 0) {
        return true;
      }
    }
  }

  return false;
}

// Parse scaling mode from string
chroma_scale_mode_t parse_scale_mode(const char *value) {
  if (!value)
    return CHROMA_SCALE_FILL; // default

  if (strcasecmp(value, "fill") == 0) {
    return CHROMA_SCALE_FILL;
  } else if (strcasecmp(value, "fit") == 0) {
    return CHROMA_SCALE_FIT;
  } else if (strcasecmp(value, "stretch") == 0) {
    return CHROMA_SCALE_STRETCH;
  } else if (strcasecmp(value, "center") == 0) {
    return CHROMA_SCALE_CENTER;
  }

  chroma_log("WARN", "Unknown scaling mode: %s (using fill)", value);
  return CHROMA_SCALE_FILL;
}

// Parse filter quality from string
chroma_filter_quality_t parse_filter_quality(const char *value) {
  if (!value)
    return CHROMA_FILTER_LINEAR; // default

  if (strcasecmp(value, "nearest") == 0) {
    return CHROMA_FILTER_NEAREST;
  } else if (strcasecmp(value, "linear") == 0) {
    return CHROMA_FILTER_LINEAR;
  } else if (strcasecmp(value, "bilinear") == 0) {
    return CHROMA_FILTER_BILINEAR;
  } else if (strcasecmp(value, "trilinear") == 0) {
    return CHROMA_FILTER_TRILINEAR;
  }

  chroma_log("WARN", "Unknown filter quality: %s (using linear)", value);
  return CHROMA_FILTER_LINEAR;
}

// Get string representation of scaling mode
static const char *scale_mode_to_string(chroma_scale_mode_t mode) {
  switch (mode) {
  case CHROMA_SCALE_FILL:
    return "fill";
  case CHROMA_SCALE_FIT:
    return "fit";
  case CHROMA_SCALE_STRETCH:
    return "stretch";
  case CHROMA_SCALE_CENTER:
    return "center";
  default:
    return "unknown";
  }
}

// Get string representation of filter quality
static const char *filter_quality_to_string(chroma_filter_quality_t quality) {
  switch (quality) {
  case CHROMA_FILTER_NEAREST:
    return "nearest";
  case CHROMA_FILTER_LINEAR:
    return "linear";
  case CHROMA_FILTER_BILINEAR:
    return "bilinear";
  case CHROMA_FILTER_TRILINEAR:
    return "trilinear";
  default:
    return "unknown";
  }
}

// Parse anchor position from string
chroma_anchor_t parse_anchor(const char *value) {
  if (!value)
    return CHROMA_ANCHOR_CENTER;

  if (strcasecmp(value, "center") == 0) {
    return CHROMA_ANCHOR_CENTER;
  } else if (strcasecmp(value, "top") == 0) {
    return CHROMA_ANCHOR_TOP;
  } else if (strcasecmp(value, "bottom") == 0) {
    return CHROMA_ANCHOR_BOTTOM;
  } else if (strcasecmp(value, "left") == 0) {
    return CHROMA_ANCHOR_LEFT;
  } else if (strcasecmp(value, "right") == 0) {
    return CHROMA_ANCHOR_RIGHT;
  } else if (strcasecmp(value, "top-left") == 0 ||
             strcasecmp(value, "topleft") == 0) {
    return CHROMA_ANCHOR_TOP_LEFT;
  } else if (strcasecmp(value, "top-right") == 0 ||
             strcasecmp(value, "topright") == 0) {
    return CHROMA_ANCHOR_TOP_RIGHT;
  } else if (strcasecmp(value, "bottom-left") == 0 ||
             strcasecmp(value, "bottomleft") == 0) {
    return CHROMA_ANCHOR_BOTTOM_LEFT;
  } else if (strcasecmp(value, "bottom-right") == 0 ||
             strcasecmp(value, "bottomright") == 0) {
    return CHROMA_ANCHOR_BOTTOM_RIGHT;
  }

  chroma_log("WARN", "Unknown anchor: %s (using center)", value);
  return CHROMA_ANCHOR_CENTER;
}

// Get string representation of anchor position
static const char *anchor_to_string(chroma_anchor_t anchor) {
  switch (anchor) {
  case CHROMA_ANCHOR_CENTER:
    return "center";
  case CHROMA_ANCHOR_TOP:
    return "top";
  case CHROMA_ANCHOR_BOTTOM:
    return "bottom";
  case CHROMA_ANCHOR_LEFT:
    return "left";
  case CHROMA_ANCHOR_RIGHT:
    return "right";
  case CHROMA_ANCHOR_TOP_LEFT:
    return "top-left";
  case CHROMA_ANCHOR_TOP_RIGHT:
    return "top-right";
  case CHROMA_ANCHOR_BOTTOM_LEFT:
    return "bottom-left";
  case CHROMA_ANCHOR_BOTTOM_RIGHT:
    return "bottom-right";
  default:
    return "unknown";
  }
}

// Output-to-image mapping
int add_output_mapping(chroma_config_t *config, const char *output_name,
                       const char *image_path, chroma_scale_mode_t scale_mode,
                       chroma_filter_quality_t filter_quality,
                       chroma_anchor_t anchor, float anchor_x, float anchor_y) {
  if (!config || !output_name || !image_path) {
    return CHROMA_ERROR_INIT;
  }

  if (config->mapping_count >= MAX_OUTPUTS) {
    chroma_log("ERROR", "Maximum number of output mappings reached (%d)",
               MAX_OUTPUTS);
    return CHROMA_ERROR_MEMORY;
  }

  // XXX: Validate string lengths to prevent buffer overflow
  size_t output_len = strlen(output_name);
  size_t path_len = strlen(image_path);

  if (output_len >= sizeof(config->mappings[0].output_name)) {
    chroma_log("ERROR", "Output name too long: %s (max %zu)", output_name,
               sizeof(config->mappings[0].output_name) - 1);
    return CHROMA_ERROR_CONFIG;
  }

  if (path_len >= sizeof(config->mappings[0].image_path)) {
    chroma_log("ERROR", "Image path too long: %s (max %zu)", image_path,
               sizeof(config->mappings[0].image_path) - 1);
    return CHROMA_ERROR_CONFIG;
  }

  chroma_config_mapping_t *mapping = &config->mappings[config->mapping_count];

  strcpy(mapping->output_name, output_name);
  strcpy(mapping->image_path, image_path);
  mapping->scale_mode = scale_mode;
  mapping->filter_quality = filter_quality;
  mapping->anchor = anchor;
  mapping->anchor_x = anchor_x;
  mapping->anchor_y = anchor_y;

  config->mapping_count++;

  chroma_log(
      "DEBUG",
      "Added mapping: %s -> %s (scale: %s, filter: %s, anchor: %s @ %.1f,%.1f)",
      output_name, image_path, scale_mode_to_string(scale_mode),
      filter_quality_to_string(filter_quality), anchor_to_string(anchor),
      (double)anchor_x, (double)anchor_y);
  chroma_log("TRACE", "Output mapping %d: '%s' -> '%s' (path length: %zu)",
             config->mapping_count, output_name, image_path, path_len);
  return CHROMA_OK;
}

// Parse TOML configuration file
int chroma_config_load_toml(chroma_config_t *config, const char *config_file) {
  if (!config || !config_file) {
    return CHROMA_ERROR_INIT;
  }

  toml_result_t result = toml_parse_file_ex(config_file);
  if (!result.ok) {
    chroma_log("DEBUG", "TOML parse failed: %s", result.errmsg);
    toml_free(result);
    return CHROMA_ERROR_CONFIG;
  }

  chroma_log("INFO", "Loading TOML configuration from: %s", config_file);

  // Parse default_image
  toml_datum_t default_image = toml_seek(result.toptab, "default_image");
  if (default_image.type == TOML_STRING) {
    char *expanded_path = chroma_expand_path(default_image.u.s);
    const char *path_to_use = expanded_path ? expanded_path : default_image.u.s;
    if (strlen(path_to_use) < sizeof(config->default_image)) {
      strcpy(config->default_image, path_to_use);
    } else {
      chroma_log(
          "WARN", "default_image path too long (%zu >= %zu), truncating: %s",
          strlen(path_to_use), sizeof(config->default_image), path_to_use);
      snprintf(config->default_image, sizeof(config->default_image), "%s",
               path_to_use);
    }
    if (expanded_path) {
      free(expanded_path);
    }
  }

  // Parse daemon_mode
  toml_datum_t daemon_mode = toml_seek(result.toptab, "daemon_mode");
  if (daemon_mode.type == TOML_BOOLEAN) {
    config->daemon_mode = daemon_mode.u.boolean;
  }

  // Parse solid_color (optional global background color)
  toml_datum_t solid_color = toml_seek(result.toptab, "solid_color");
  if (solid_color.type == TOML_STRING) {
    strncpy(config->solid_color, solid_color.u.s,
            sizeof(config->solid_color) - 1);
  }

  // Parse transition section
  toml_datum_t transition = toml_seek(result.toptab, "transition");
  if (transition.type == TOML_TABLE) {
    toml_datum_t t_enable = toml_seek(transition, "enable");
    if (t_enable.type == TOML_BOOLEAN) {
      config->transition_enabled = t_enable.u.boolean;
    }
    toml_datum_t t_duration = toml_seek(transition, "duration_ms");
    if (t_duration.type == TOML_INT64) {
      config->transition_duration_ms = (int)t_duration.u.int64;
    }
  }

  // Parse animation section
  toml_datum_t animation = toml_seek(result.toptab, "animation");
  if (animation.type == TOML_TABLE) {
    toml_datum_t a_enable = toml_seek(animation, "enable");
    if (a_enable.type == TOML_BOOLEAN) {
      config->animation_enabled = a_enable.u.boolean;
    }
    toml_datum_t a_fps = toml_seek(animation, "max_fps");
    if (a_fps.type == TOML_INT64) {
      config->animation_max_fps = (int)a_fps.u.int64;
    }
  }

  // Parse IPC section
  toml_datum_t ipc = toml_seek(result.toptab, "ipc");
  if (ipc.type == TOML_TABLE) {
    toml_datum_t i_enable = toml_seek(ipc, "enable");
    if (i_enable.type == TOML_BOOLEAN) {
      config->ipc_enabled = i_enable.u.boolean;
    }
    toml_datum_t i_socket = toml_seek(ipc, "socket_path");
    if (i_socket.type == TOML_STRING) {
      char *expanded = chroma_expand_path(i_socket.u.s);
      if (expanded) {
        strncpy(config->ipc_socket_path, expanded,
                sizeof(config->ipc_socket_path) - 1);
        free(expanded);
      } else {
        strncpy(config->ipc_socket_path, i_socket.u.s,
                sizeof(config->ipc_socket_path) - 1);
      }
    }
  }

  // Parse scale_mode
  toml_datum_t scale_mode = toml_seek(result.toptab, "scale_mode");
  if (scale_mode.type == TOML_STRING) {
    config->default_scale_mode = parse_scale_mode(scale_mode.u.s);
  }

  // Parse filter_quality
  toml_datum_t filter_quality = toml_seek(result.toptab, "filter_quality");
  if (filter_quality.type == TOML_STRING) {
    config->default_filter_quality = parse_filter_quality(filter_quality.u.s);
  }

  // Parse anchor
  toml_datum_t anchor = toml_seek(result.toptab, "anchor");
  if (anchor.type == TOML_STRING) {
    config->default_anchor = parse_anchor(anchor.u.s);
  }

  // Parse anchor_x
  toml_datum_t anchor_x = toml_seek(result.toptab, "anchor_x");
  if (anchor_x.type == TOML_INT64) {
    config->default_anchor_x = (float)anchor_x.u.int64;
  } else if (anchor_x.type == TOML_FP64) {
    config->default_anchor_x = (float)anchor_x.u.fp64;
  }

  // Parse anchor_y
  toml_datum_t anchor_y = toml_seek(result.toptab, "anchor_y");
  if (anchor_y.type == TOML_INT64) {
    config->default_anchor_y = (float)anchor_y.u.int64;
  } else if (anchor_y.type == TOML_FP64) {
    config->default_anchor_y = (float)anchor_y.u.fp64;
  }

  // Parse downsampling section
  toml_datum_t downsampling = toml_seek(result.toptab, "downsampling");
  if (downsampling.type == TOML_TABLE) {
    toml_datum_t ds_tab = downsampling;

    toml_datum_t enable = toml_seek(ds_tab, "enable");
    if (enable.type == TOML_BOOLEAN) {
      config->enable_downsampling = enable.u.boolean;
    }

    toml_datum_t max_width = toml_seek(ds_tab, "max_output_width");
    if (max_width.type == TOML_INT64) {
      config->max_output_width = (int)max_width.u.int64;
    }

    toml_datum_t max_height = toml_seek(ds_tab, "max_output_height");
    if (max_height.type == TOML_INT64) {
      config->max_output_height = (int)max_height.u.int64;
    }

    toml_datum_t min_scale = toml_seek(ds_tab, "min_scale_factor");
    if (min_scale.type == TOML_FP64) {
      config->min_scale_factor = (float)min_scale.u.fp64;
    } else if (min_scale.type == TOML_INT64) {
      config->min_scale_factor = (float)min_scale.u.int64;
    }
  }

  // Parse output mappings array
  toml_datum_t outputs = toml_seek(result.toptab, "output");
  if (outputs.type == TOML_ARRAY) {
    for (int i = 0; i < outputs.u.arr.size; i++) {
      toml_datum_t output = outputs.u.arr.elem[i];
      if (output.type != TOML_TABLE) {
        continue;
      }

      toml_datum_t output_tab = output;

      // Get output name
      toml_datum_t name = toml_seek(output_tab, "name");
      if (name.type != TOML_STRING) {
        chroma_log("WARN", "Output mapping %d missing name", i);
        continue;
      }

      // Get image path
      toml_datum_t image = toml_seek(output_tab, "image");
      if (image.type != TOML_STRING) {
        chroma_log("WARN", "Output mapping %d missing image", i);
        continue;
      }

      char *expanded_path = chroma_expand_path(image.u.s);
      const char *path_to_use = expanded_path ? expanded_path : image.u.s;

      // Get optional settings with defaults from global config
      chroma_scale_mode_t out_scale = config->default_scale_mode;
      chroma_filter_quality_t out_filter = config->default_filter_quality;
      chroma_anchor_t out_anchor = config->default_anchor;
      float out_anchor_x = config->default_anchor_x;
      float out_anchor_y = config->default_anchor_y;

      toml_datum_t out_scale_val = toml_seek(output_tab, "scale");
      if (out_scale_val.type == TOML_STRING) {
        out_scale = parse_scale_mode(out_scale_val.u.s);
      }

      toml_datum_t out_filter_val = toml_seek(output_tab, "filter");
      if (out_filter_val.type == TOML_STRING) {
        out_filter = parse_filter_quality(out_filter_val.u.s);
      }

      toml_datum_t out_anchor_val = toml_seek(output_tab, "anchor");
      if (out_anchor_val.type == TOML_STRING) {
        out_anchor = parse_anchor(out_anchor_val.u.s);
      }

      toml_datum_t out_anchor_x_val = toml_seek(output_tab, "anchor_x");
      if (out_anchor_x_val.type == TOML_INT64) {
        out_anchor_x = (float)out_anchor_x_val.u.int64;
      } else if (out_anchor_x_val.type == TOML_FP64) {
        out_anchor_x = (float)out_anchor_x_val.u.fp64;
      }

      toml_datum_t out_anchor_y_val = toml_seek(output_tab, "anchor_y");
      if (out_anchor_y_val.type == TOML_INT64) {
        out_anchor_y = (float)out_anchor_y_val.u.int64;
      } else if (out_anchor_y_val.type == TOML_FP64) {
        out_anchor_y = (float)out_anchor_y_val.u.fp64;
      }

      // Add the mapping
      if (add_output_mapping(config, name.u.s, path_to_use, out_scale,
                             out_filter, out_anchor, out_anchor_x,
                             out_anchor_y) != CHROMA_OK) {
        chroma_log("ERROR", "Failed to add TOML output mapping: %s", name.u.s);
      }

      if (expanded_path) {
        free(expanded_path);
      }
    }
  }

  toml_free(result);

  chroma_log("INFO",
             "Loaded TOML configuration: %d output mappings, default image: %s",
             config->mapping_count, config->default_image);

  return CHROMA_OK;
}

// Initialize configuration with defaults
static void init_default_config(chroma_config_t *config) {
  if (!config)
    return;

  memset(config, 0, sizeof(chroma_config_t));

  config->daemon_mode = false;
  config->mapping_count = 0;

  // Set default scaling and filtering
  config->default_scale_mode = CHROMA_SCALE_FILL;
  config->default_filter_quality = CHROMA_FILTER_LINEAR;
  config->default_anchor = CHROMA_ANCHOR_CENTER;
  config->default_anchor_x = 50.0f; // center
  config->default_anchor_y = 50.0f; // center

  // Set default downsampling settings
  config->enable_downsampling = true; // enable by default, performance etc.
  config->max_output_width = 3840;    // 4K width
  config->max_output_height = 2160;   // 4K height
  config->min_scale_factor = 0.25f;   // don't scale below 25%

  // Feature defaults
  config->solid_color[0] = '\0';
  config->transition_enabled = false;
  config->transition_duration_ms = 300;
  config->animation_enabled = true;
  config->animation_max_fps = 30;
  config->ipc_enabled = false;
  config->ipc_socket_path[0] = '\0';

  // Leave default_image empty - user must configure it explicitly
  // This avoids errors when the hardcoded path doesn't exist
  config->default_image[0] = '\0';
}

// Load configuration from file (TOML format only)
int chroma_config_load(chroma_config_t *config, const char *config_file) {
  if (!config) {
    return CHROMA_ERROR_INIT;
  }

  // Initialize with defaults
  init_default_config(config);

  if (!config_file) {
    chroma_log("INFO", "No config file specified, using defaults");
    return CHROMA_OK;
  }

  // Check if file exists
  if (!chroma_path_exists(config_file)) {
    chroma_log("INFO", "Config file not found: %s (using defaults)",
               config_file);
    return CHROMA_OK;
  }

  // Load TOML configuration
  int result = chroma_config_load_toml(config, config_file);
  if (result != CHROMA_OK) {
    chroma_log("ERROR", "Failed to load TOML config from %s", config_file);
    return result;
  }

  // Log configuration memory usage
  size_t config_size =
      sizeof(chroma_config_t) +
      ((size_t)config->mapping_count * sizeof(chroma_config_mapping_t));
  chroma_log_resource_allocation("config_data", config_size,
                                 "configuration structure");
  chroma_log_memory_stats("post-config-load");

  return CHROMA_OK;
}

// Free configuration resources
void chroma_config_free(chroma_config_t *config) {
  if (!config) {
    return;
  }

  // Log configuration deallocation
  size_t config_size =
      sizeof(chroma_config_t) +
      ((size_t)config->mapping_count * sizeof(chroma_config_mapping_t));
  chroma_log_resource_deallocation("config_data", config_size,
                                   "configuration structure");

  chroma_log("TRACE", "Clearing %d output mappings from configuration",
             config->mapping_count);
  memset(config->mappings, 0, sizeof(config->mappings));
  config->mapping_count = 0;

  memset(config->default_image, 0, sizeof(config->default_image));

  chroma_log("DEBUG", "Freed configuration");
}

// Get image path for specific output
const char *chroma_config_get_image_for_output(chroma_config_t *config,
                                               const char *output_name,
                                               const char *output_description) {
  if (!config || !output_name) {
    return NULL;
  }

  // Look for specific output mapping (name or description match)
  for (int i = 0; i < config->mapping_count; i++) {
    if (match_output(config->mappings[i].output_name, output_name,
                     output_description)) {
      chroma_log("DEBUG", "Found specific mapping for output %s (desc: %s): %s",
                 output_name, output_description ? output_description : "none",
                 config->mappings[i].image_path);
      return config->mappings[i].image_path;
    }
  }

  // Return default image if no specific mapping found
  if (strlen(config->default_image) > 0) {
    chroma_log("DEBUG", "Using default image for output %s: %s", output_name,
               config->default_image);
    return config->default_image;
  }

  chroma_log("WARN", "No image configured for output: %s", output_name);
  return NULL;
}

// Get configuration mapping for output, including scale mode, filter
// quality, anchor, and custom anchor coordinates
int chroma_config_get_mapping_for_output(
    chroma_config_t *config, const char *output_name,
    const char *output_description, chroma_scale_mode_t *scale_mode,
    chroma_filter_quality_t *filter_quality, chroma_anchor_t *anchor,
    float *anchor_x, float *anchor_y) {
  if (!config || !output_name || !scale_mode || !filter_quality || !anchor ||
      !anchor_x || !anchor_y) {
    return CHROMA_ERROR_INIT;
  }

  // Look for specific output mapping (name or description match)
  for (int i = 0; i < config->mapping_count; i++) {
    if (match_output(config->mappings[i].output_name, output_name,
                     output_description)) {
      *scale_mode = config->mappings[i].scale_mode;
      *filter_quality = config->mappings[i].filter_quality;
      *anchor = config->mappings[i].anchor;
      *anchor_x = config->mappings[i].anchor_x;
      *anchor_y = config->mappings[i].anchor_y;
      chroma_log("DEBUG",
                 "Found specific mapping for output %s (desc: %s): scale=%s, "
                 "filter=%s, anchor=%s @ %.1f,%.1f",
                 output_name, output_description ? output_description : "none",
                 scale_mode_to_string(*scale_mode),
                 filter_quality_to_string(*filter_quality),
                 anchor_to_string(*anchor), (double)*anchor_x,
                 (double)*anchor_y);
      return CHROMA_OK;
    }
  }

  // Return defaults if no specific mapping found
  *scale_mode = config->default_scale_mode;
  *filter_quality = config->default_filter_quality;
  *anchor = config->default_anchor;
  *anchor_x = config->default_anchor_x;
  *anchor_y = config->default_anchor_y;
  chroma_log("DEBUG",
             "Using defaults for output %s: scale=%s, filter=%s, anchor=%s @ "
             "%.1f,%.1f",
             output_name, scale_mode_to_string(*scale_mode),
             filter_quality_to_string(*filter_quality),
             anchor_to_string(*anchor), (double)*anchor_x, (double)*anchor_y);
  return CHROMA_OK;
}

// Print current configuration for debugging
void chroma_config_print(const chroma_config_t *config) {
  if (!config) {
    return;
  }

  chroma_log("INFO", "=== Configuration ===");
  chroma_log("INFO", "Default image: %s", config->default_image);
  chroma_log("INFO", "Daemon mode: %s", config->daemon_mode ? "true" : "false");
  chroma_log("INFO", "Default scale mode: %s",
             scale_mode_to_string(config->default_scale_mode));
  chroma_log("INFO", "Default filter quality: %s",
             filter_quality_to_string(config->default_filter_quality));
  chroma_log("INFO", "Downsampling: %s",
             config->enable_downsampling ? "enabled" : "disabled");
  if (config->enable_downsampling) {
    chroma_log("INFO", "Max output size: %dx%d", config->max_output_width,
               config->max_output_height);
    chroma_log("INFO", "Min scale factor: %.2f",
               (double)config->min_scale_factor);
  }
  if (config->solid_color[0] != '\0') {
    chroma_log("INFO", "Solid color: %s", config->solid_color);
  }
  chroma_log("INFO", "Transitions: %s (%dms)",
             config->transition_enabled ? "enabled" : "disabled",
             config->transition_duration_ms);
  chroma_log("INFO", "Animations: %s (max %d fps)",
             config->animation_enabled ? "enabled" : "disabled",
             config->animation_max_fps);
  chroma_log("INFO", "IPC: %s (%s)",
             config->ipc_enabled ? "enabled" : "disabled",
             config->ipc_enabled ? config->ipc_socket_path : "n/a");
  chroma_log("INFO", "Output mappings: %d", config->mapping_count);

  for (int i = 0; i < config->mapping_count; i++) {
    chroma_log("INFO", "  %s -> %s (scale: %s, filter: %s)",
               config->mappings[i].output_name, config->mappings[i].image_path,
               scale_mode_to_string(config->mappings[i].scale_mode),
               filter_quality_to_string(config->mappings[i].filter_quality));
    chroma_log(
        "TRACE",
        "  Mapping %d: output='%s', image='%s', scale='%s', filter='%s', "
        "path_exists=%s",
        i, config->mappings[i].output_name, config->mappings[i].image_path,
        scale_mode_to_string(config->mappings[i].scale_mode),
        filter_quality_to_string(config->mappings[i].filter_quality),
        chroma_path_exists(config->mappings[i].image_path) ? "yes" : "no");
  }
  chroma_log("INFO", "====================");
}
