/**
 * @file
 * @brief Qualpal API for generating qualitative color palettes
 *
 * This file defines the Qualpal class, which provides a builder-style
 * interface for configuring and generating maximally distinct color palettes
 * using perceptual color difference. The API supports multiple input formats,
 * color vision deficiency simulation, and flexible configuration options.
 *
 * Usage example:
 * @code{.cpp}
 * // Basic usage with RGB input, 6 color palette
 * qualpal::Qualpal qp;
 * qp.setInputRGB(<rgb_colors>)
 *   .setCvd({{"protan", 0.5}, {"deuter", 0.2}})
 *   .setBackground("#ffffff")
 *   .setMetric(qualpal::metrics::MetricType::DIN99d)
 *   .setWhitePoint(qualpal::WhitePoint::D50)  // Optional: D50 for printing
 *   .setMemoryLimit(2.0);
 * auto palette = qp.generate(6);
 *
 * // Use colorspace input (HSL space)
 * qualpal::Qualpal qp4;
 * qp4.setInputColorspace({0, 360}, {0.5, 1.0}, {0.3, 0.7});
 * auto palette4 = qp4.generate(8);
 *
 * // Use multiple colorspace regions (warm and cool colors only)
 * qualpal::Qualpal qp5;
 * qp5.setInputColorspaceRegions({
 *   {{0, 60}, {0.5, 1.0}, {0.3, 0.7}},      // Warm: reds/oranges
 *   {{180, 240}, {0.5, 1.0}, {0.3, 0.7}}    // Cool: cyans/blues
 * }, qualpal::ColorspaceType::HSL);
 * auto palette5 = qp5.generate(8);
 *
 * // Using hex color input
 * qualpal::Qualpal qp2;
 * qp2.setInputHex({"#ff0000", "#00ff00", "#0000ff"});
 * auto palette2 = qp2.generate(3);
 *
 * // Use a built-in palette
 * auto palette3 =
 *   qualpal::Qualpal{}.setInputPalette("ColorBrewer:Set2").generate(5);
 *
 * // Extend an existing palette
 * std::vector<qualpal::colors::RGB> base{ "#ff0000", "#a9ef93", "#ffe302" };
 * auto pal_extended = qualpal::Qualpal{}.extend(base, 3); // base + 3 new
 * @endcode
 */

#pragma once

#include <map>
#include <optional>
#include <qualpal/colors.h>
#include <qualpal/metrics.h>
#include <vector>

/**
 * @namespace qualpal
 * @brief Qualitative color palette generation library
 */
namespace qualpal {

/**
 * @enum ColorspaceType
 * @brief Supported cylindrical color spaces for input colors
 *
 * This enum defines the available cylindrical color spaces that can be used
 * for generating palettes. Currently, it supports HSL (Hue, Saturation,
 * Lightness) and LCHab (Lightness, Chroma, Hue in CIE Lab).
 */
enum class ColorspaceType
{
  HSL,
  LCHab
};

/**
 * @struct ColorspaceRegion
 * @brief Defines a rectangular region in a cylindrical color space
 *
 * This struct represents a 3D rectangular subset of a cylindrical color space
 * (HSL or LCHab). Multiple regions can be combined to sample from arbitrary
 * unions of subsets within the same color space. Regions may overlap, which
 * will result in higher sampling density in overlapping areas.
 */
struct ColorspaceRegion
{
  std::array<double, 2> h_lim;      ///< Hue range in degrees [-360, 360]
  std::array<double, 2> s_or_c_lim; ///< Saturation/Chroma range
  std::array<double, 2> l_lim;      ///< Lightness range
};

/**
 * @class Qualpal
 * @brief Builder for qualitative color palette generation
 *
 * The Qualpal class allows step-by-step configuration of palette generation
 * options. Users can set the input source (RGB, hex, palette name, or HSL
 * colorspace), color vision deficiency simulation parameters, background color,
 * color difference metric, and memory limit. The palette is generated by
 * calling generate().
 */
class Qualpal
{
public:
  /**
   * @brief Default constructor. Initializes with no input source.
   */
  Qualpal() = default;

  /**
   * @brief Set input colors from a vector of RGB values.
   * @param colors Vector of RGB colors to use as input. Each
   * channel should be in [0, 1].
   * @return Reference to this object for chaining.
   */
  Qualpal& setInputRGB(const std::vector<colors::RGB>& colors);

  /**
   * @brief Set input colors from a vector of hex color strings.
   * @param colors Vector of hex color strings (e.g., "#ff0000").
   * @return Reference to this object for chaining.
   * @throws std::invalid_argument if any hex string is invalid.
   */
  Qualpal& setInputHex(const std::vector<std::string>& colors);

  /**
   * @brief Set input colors from a named palette.
   * @param palette_name Palette name in the format "Package:Palette".
   * @return Reference to this object for chaining.
   * @throws std::invalid_argument if the palette name is invalid.
   */
  Qualpal& setInputPalette(const std::string& palette_name);

  /**
   * @brief Set input colors by sampling a colorspace (HSL or LCHab).
   * @param h_lim Hue range in degrees [-360, 360].
   * @param s_or_c_lim Saturation or Chroma (depending on `ColorspaceType`)
   * range [0, 1] or >= 0.
   * @param l_lim Lightness range [0, 1] or [0, 100].
   * @param space Colorspace type
   * @return Reference to this object for chaining.
   * @throws std::invalid_argument for invalid ranges.
   */
  Qualpal& setInputColorspace(const std::array<double, 2>& h_lim,
                              const std::array<double, 2>& s_or_c_lim,
                              const std::array<double, 2>& l_lim,
                              ColorspaceType space = ColorspaceType::HSL);

  /**
   * @brief Set input colors by sampling multiple regions in a colorspace.
   * @param regions Vector of colorspace regions to sample from. Regions may
   * overlap, which will increase sampling density in overlapping areas.
   * @param space Colorspace type (HSL or LCHab)
   * @return Reference to this object for chaining.
   * @throws std::invalid_argument if regions is empty or any region has
   * invalid ranges.
   *
   * @code{.cpp}
   * // Sample from warm and cool colors only
   * qualpal::Qualpal qp;
   * qp.setInputColorspaceRegions({
   *   {{0, 60}, {0.5, 1.0}, {0.3, 0.7}},    // Reds/oranges
   *   {{180, 240}, {0.5, 1.0}, {0.3, 0.7}}  // Cyans/blues
   * }, qualpal::ColorspaceType::HSL);
   * auto palette = qp.generate(8);
   * @endcode
   */
  Qualpal& setInputColorspaceRegions(
    const std::vector<ColorspaceRegion>& regions,
    ColorspaceType space = ColorspaceType::HSL);

  /**
   * @brief Set color vision deficiency simulation parameters.
   * @param cvd_params Map of CVD type to severity, e.g., {{"protan", 0.5},
   * {"deutan", 0.2}}. Valid keys: "protan", "deutan", "tritan". Severity must
   * be in [0, 1].
   * @return Reference to this object for chaining.
   * @throws std::invalid_argument if an invalid key or out-of-range severity is
   * provided.
   */
  Qualpal& setCvd(const std::map<std::string, double>& cvd_params);

  /**
   * @brief Set the background color for palette generation.
   * @param bg_color RGB background color.
   * @return Reference to this object for chaining.
   */
  Qualpal& setBackground(const colors::RGB& bg_color);

  /**
   * @brief Set the color difference metric to use.
   * @param metric Metric type (e.g., DIN99d).
   * @return Reference to this object for chaining.
   */
  Qualpal& setMetric(metrics::MetricType metric);

  /**
   * @brief Set the maximum memory limit (in GB) for color difference matrix.
   * @param gb Memory limit in gigabytes.
   * @return Reference to this object for chaining.
   * @throws std::invalid_argument if gb <= 0.
   */
  Qualpal& setMemoryLimit(double gb);

  /**
   * @brief Set the number of points in the colorspace grid for HSL and LCHab
   * input.
   * @param n_points Number of points to sample in the colorspace grid.
   * @return Reference to this object for chaining.
   * @throws std::invalid_argument if n_points <= 0.
   */
  Qualpal& setColorspaceSize(std::size_t n_points);

  /**
   * @brief Set the reference white point for color conversions.
   * @param wp White point enum value (D65, D50, D55, A, or E).
   * @return Reference to this object for chaining.
   */
  Qualpal& setWhitePoint(WhitePoint wp);

  /**
   * @brief Set the reference white point using custom XYZ values.
   * @param white_point Custom XYZ tristimulus values for the white point.
   * @return Reference to this object for chaining.
   */
  Qualpal& setWhitePoint(const std::array<double, 3>& white_point);

  /**
   * @brief Generate a qualitative color palette with the configured options.
   * @param n Number of colors to generate.
   * @return Vector of n selected RGB colors, each channel in [0, 1].
   * @throws std::runtime_error if no input source is configured.
   * @throws std::invalid_argument for invalid configuration.
   */
  std::vector<colors::RGB> generate(std::size_t n);

  /**
   * @brief Extend an existing palette by adding n new colors.
   * @param palette Existing palette (RGB colors) to keep fixed.
   * @param n Total size of the new palette to generate, including
   * existing colors. In other words, the new palette will have
   * `n` colors.
   * @return Vector of palette + n new RGB colors.
   */
  std::vector<colors::RGB> extend(const std::vector<colors::RGB>& palette,
                                  std::size_t n);

private:
  std::vector<colors::RGB> selectColors(
    std::size_t n,
    const std::vector<colors::RGB>& fixed_palette = {});

  std::vector<colors::RGB> rgb_colors_in;

  std::vector<std::string> hex_colors;

  std::string palette;

  std::vector<ColorspaceRegion> colorspace_regions;
  std::size_t n_points = 1000;

  /**
   * @brief Internal mode for tracking input source.
   */
  enum class Mode
  {
    NONE,
    RGB,
    HEX,
    PALETTE,
    COLORSPACE
  } mode = Mode::NONE;

  std::map<std::string, double> cvd;
  std::optional<colors::RGB> bg;
  metrics::MetricType metric = metrics::MetricType::CIEDE2000;
  double max_memory = 1;
  ColorspaceType colorspace_input = ColorspaceType::HSL;
  std::array<double, 3> white_point = { 0.95047, 1, 1.08883 }; // D65
};

} // namespace qualpal
