/*
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * ---------------------------------------------------------------------
 *
 * Portions of this file were modified and ported to Vala by Victor Vintorez in 2026.
 * These modifications are licensed under the European Union Public License (EUPL),
 * Version 1.2 or later. You may obtain a copy of the License at:
 * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
 */

using GLib;

namespace MaterialColorUtilities {
	/** {@link ColorSpec} implementation for the 2021 spec. */
	public class ColorSpec2021 : ColorSpec, Object {

		////////////////////////////////////////////////////////////////
		// Main Palettes                                              //
		////////////////////////////////////////////////////////////////

		public virtual DynamicColor primary_palette_key_color {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("primary_palette_key_color")
				        .set_palette((s) => s.primary_palette)
				        .set_tone((s) => s.primary_palette.key_color.tone)
				        .build();
			}
		}

		public virtual DynamicColor secondary_palette_key_color {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("secondary_palette_key_color")
				        .set_palette((s) => s.secondary_palette)
				        .set_tone((s) => s.secondary_palette.key_color.tone)
				        .build();
			}
		}

		public virtual DynamicColor tertiary_palette_key_color {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("tertiary_palette_key_color")
				        .set_palette((s) => s.tertiary_palette)
				        .set_tone((s) => s.tertiary_palette.key_color.tone)
				        .build();
			}
		}

		public virtual DynamicColor neutral_palette_key_color {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("neutral_palette_key_color")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.neutral_palette.key_color.tone)
				        .build();
			}
		}

		public virtual DynamicColor neutral_variant_palette_key_color {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("neutral_variant_palette_key_color")
				        .set_palette((s) => s.neutral_variant_palette)
				        .set_tone((s) => s.neutral_variant_palette.key_color.tone)
				        .build();
			}
		}

		public virtual DynamicColor? error_palette_key_color {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("error_palette_key_color")
				        .set_palette((s) => s.error_palette)
				        .set_tone((s) => s.error_palette.key_color.tone)
				        .build();
			}
		}

		////////////////////////////////////////////////////////////////
		// Surfaces [S]                                               //
		////////////////////////////////////////////////////////////////

		public virtual DynamicColor background {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("background")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? 6.0 : 98.0)
				        .set_is_background(true)
				        .build();
			}
		}

		public virtual DynamicColor on_background {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_background")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? 90.0 : 10.0)
				        .set_background((s) => background)
				        .set_contrast_curve((s) => new ContrastCurve(3.0, 3.0, 4.5, 7.0))
				        .build();
			}
		}

		public virtual DynamicColor surface {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("surface")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? 6.0 : 98.0)
				        .set_is_background(true)
				        .build();
			}
		}

		public virtual DynamicColor surface_dim {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("surface_dim")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? 6.0 : new ContrastCurve(87.0, 87.0, 80.0, 75.0).get_value(s.contrast_level))
				        .set_is_background(true)
				        .build();
			}
		}

		public virtual DynamicColor surface_bright {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("surface_bright")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? new ContrastCurve(24.0, 24.0, 29.0, 34.0).get_value(s.contrast_level) : 98.0)
				        .set_is_background(true)
				        .build();
			}
		}

		public virtual DynamicColor surface_container_lowest {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("surface_container_lowest")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? new ContrastCurve(4.0, 4.0, 2.0, 0.0).get_value(s.contrast_level) : 100.0)
				        .set_is_background(true)
				        .build();
			}
		}

		public virtual DynamicColor surface_container_low {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("surface_container_low")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? new ContrastCurve(10.0, 10.0, 11.0, 12.0).get_value(s.contrast_level) : new ContrastCurve(96.0, 96.0, 96.0, 95.0).get_value(s.contrast_level))
				        .set_is_background(true)
				        .build();
			}
		}

		public virtual DynamicColor surface_container {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("surface_container")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? new ContrastCurve(12.0, 12.0, 16.0, 20.0).get_value(s.contrast_level) : new ContrastCurve(94.0, 94.0, 92.0, 90.0).get_value(s.contrast_level))
				        .set_is_background(true)
				        .build();
			}
		}

		public virtual DynamicColor surface_container_high {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("surface_container_high")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? new ContrastCurve(17.0, 17.0, 21.0, 25.0).get_value(s.contrast_level) : new ContrastCurve(92.0, 92.0, 88.0, 85.0).get_value(s.contrast_level))
				        .set_is_background(true)
				        .build();
			}
		}

		public virtual DynamicColor surface_container_highest {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("surface_container_highest")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? new ContrastCurve(22.0, 22.0, 26.0, 30.0).get_value(s.contrast_level) : new ContrastCurve(90.0, 90.0, 84.0, 80.0).get_value(s.contrast_level))
				        .set_is_background(true)
				        .build();
			}
		}

		public virtual DynamicColor on_surface {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_surface")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? 90.0 : 10.0)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(4.5, 7.0, 11.0, 21.0))
				        .build();
			}
		}

		public virtual DynamicColor surface_variant {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("surface_variant")
				        .set_palette((s) => s.neutral_variant_palette)
				        .set_tone((s) => s.is_dark ? 30.0 : 90.0)
				        .set_is_background(true)
				        .build();
			}
		}

		public virtual DynamicColor on_surface_variant {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_surface_variant")
				        .set_palette((s) => s.neutral_variant_palette)
				        .set_tone((s) => s.is_dark ? 80.0 : 30.0)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(3.0, 4.5, 7.0, 11.0))
				        .build();
			}
		}

		public virtual DynamicColor inverse_surface {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("inverse_surface")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? 90.0 : 20.0)
				        .set_is_background(true)
				        .build();
			}
		}

		public virtual DynamicColor inverse_on_surface {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("inverse_on_surface")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? 20.0 : 95.0)
				        .set_background((s) => inverse_surface)
				        .set_contrast_curve((s) => new ContrastCurve(4.5, 7.0, 11.0, 21.0))
				        .build();
			}
		}

		public virtual DynamicColor outline {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("outline")
				        .set_palette((s) => s.neutral_variant_palette)
				        .set_tone((s) => s.is_dark ? 60.0 : 50.0)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(1.5, 3.0, 4.5, 7.0))
				        .build();
			}
		}

		public virtual DynamicColor outline_variant {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("outline_variant")
				        .set_palette((s) => s.neutral_variant_palette)
				        .set_tone((s) => s.is_dark ? 30.0 : 80.0)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(1.0, 1.0, 3.0, 4.5))
				        .build();
			}
		}

		public virtual DynamicColor shadow {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("shadow")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => 0.0)
				        .build();
			}
		}

		public virtual DynamicColor scrim {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("scrim")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => 0.0)
				        .build();
			}
		}

		public virtual DynamicColor surface_tint {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("surface_tint")
				        .set_palette((s) => s.primary_palette)
				        .set_tone((s) => s.is_dark ? 80.0 : 40.0)
				        .set_is_background(true)
				        .build();
			}
		}

		////////////////////////////////////////////////////////////////
		// Primaries [P]                                              //
		////////////////////////////////////////////////////////////////

		public virtual DynamicColor primary {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("primary")
				        .set_palette((s) => s.primary_palette)
				        .set_tone((s) => {
					if (is_monochrome(s)) {
						return s.is_dark ? 100.0 : 0.0;
					}
					return s.is_dark ? 80.0 : 40.0;
				})
				        .set_is_background(true)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(3.0, 4.5, 7.0, 7.0))
				        .set_tone_delta_pair((s) => new ToneDeltaPair(primary_container, primary, 10.0, TonePolarity.NEARER, false))
				        .build();
			}
		}

		public virtual DynamicColor? primary_dim {
			owned get {
				return null;
			}
		}

		public virtual DynamicColor on_primary {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_primary")
				        .set_palette((s) => s.primary_palette)
				        .set_tone((s) => {
					if (is_monochrome(s)) {
						return s.is_dark ? 10.0 : 90.0;
					}
					return s.is_dark ? 20.0 : 100.0;
				})
				        .set_background((s) => primary)
				        .set_contrast_curve((s) => new ContrastCurve(4.5, 7.0, 11.0, 21.0))
				        .build();
			}
		}

		public virtual DynamicColor primary_container {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("primary_container")
				        .set_palette((s) => s.primary_palette)
				        .set_tone((s) => {
					if (is_fidelity(s)) {
						return s.source_color_hct.tone;
					}
					if (is_monochrome(s)) {
						return s.is_dark ? 85.0 : 25.0;
					}
					return s.is_dark ? 30.0 : 90.0;
				})
				        .set_is_background(true)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(1.0, 1.0, 3.0, 4.5))
				        .set_tone_delta_pair((s) => new ToneDeltaPair(primary_container, primary, 10.0, TonePolarity.NEARER, false))
				        .build();
			}
		}

		public virtual DynamicColor on_primary_container {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_primary_container")
				        .set_palette((s) => s.primary_palette)
				        .set_tone((s) => {
					if (is_fidelity(s)) {
						return DynamicColor.foreground_tone(primary_container.tone(s), 4.5);
					}
					if (is_monochrome(s)) {
						return s.is_dark ? 0.0 : 100.0;
					}
					return s.is_dark ? 90.0 : 30.0;
				})
				        .set_background((s) => primary_container)
				        .set_contrast_curve((s) => new ContrastCurve(3.0, 4.5, 7.0, 11.0))
				        .build();
			}
		}

		public virtual DynamicColor inverse_primary {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("inverse_primary")
				        .set_palette((s) => s.primary_palette)
				        .set_tone((s) => s.is_dark ? 40.0 : 80.0)
				        .set_background((s) => inverse_surface)
				        .set_contrast_curve((s) => new ContrastCurve(3.0, 4.5, 7.0, 7.0))
				        .build();
			}
		}

		////////////////////////////////////////////////////////////////
		// Secondaries [Q]                                            //
		////////////////////////////////////////////////////////////////

		public virtual DynamicColor secondary {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("secondary")
				        .set_palette((s) => s.secondary_palette)
				        .set_tone((s) => s.is_dark ? 80.0 : 40.0)
				        .set_is_background(true)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(3.0, 4.5, 7.0, 7.0))
				        .set_tone_delta_pair((s) => new ToneDeltaPair(secondary_container, secondary, 10.0, TonePolarity.NEARER, false))
				        .build();
			}
		}

		public virtual DynamicColor? secondary_dim {
			owned get {
				return null;
			}
		}

		public virtual DynamicColor on_secondary {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_secondary")
				        .set_palette((s) => s.secondary_palette)
				        .set_tone((s) => {
					if (is_monochrome(s)) {
						return s.is_dark ? 10.0 : 100.0;
					}
					return s.is_dark ? 20.0 : 100.0;
				})
				        .set_background((s) => secondary)
				        .set_contrast_curve((s) => new ContrastCurve(4.5, 7.0, 11.0, 21.0))
				        .build();
			}
		}

		public virtual DynamicColor secondary_container {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("secondary_container")
				        .set_palette((s) => s.secondary_palette)
				        .set_tone((s) => {
					double initial_tone = s.is_dark ? 30.0 : 90.0;
					if (is_monochrome(s)) {
						return s.is_dark ? 30.0 : 85.0;
					}
					if (!is_fidelity(s)) {
						return initial_tone;
					}
					return find_desired_chroma_by_tone(s.secondary_palette.hue, s.secondary_palette.chroma, initial_tone, !s.is_dark);
				})
				        .set_is_background(true)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(1.0, 1.0, 3.0, 4.5))
				        .set_tone_delta_pair((s) => new ToneDeltaPair(secondary_container, secondary, 10.0, TonePolarity.NEARER, false))
				        .build();
			}
		}

		public virtual DynamicColor on_secondary_container {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_secondary_container")
				        .set_palette((s) => s.secondary_palette)
				        .set_tone((s) => {
					if (is_monochrome(s)) {
						return s.is_dark ? 90.0 : 10.0;
					}
					if (!is_fidelity(s)) {
						return s.is_dark ? 90.0 : 30.0;
					}
					return DynamicColor.foreground_tone(secondary_container.tone(s), 4.5);
				})
				        .set_background((s) => secondary_container)
				        .set_contrast_curve((s) => new ContrastCurve(3.0, 4.5, 7.0, 11.0))
				        .build();
			}
		}

		////////////////////////////////////////////////////////////////
		// Tertiaries [T]                                             //
		////////////////////////////////////////////////////////////////

		public virtual DynamicColor tertiary {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("tertiary")
				        .set_palette((s) => s.tertiary_palette)
				        .set_tone((s) => {
					if (is_monochrome(s)) {
						return s.is_dark ? 90.0 : 25.0;
					}
					return s.is_dark ? 80.0 : 40.0;
				})
				        .set_is_background(true)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(3.0, 4.5, 7.0, 7.0))
				        .set_tone_delta_pair((s) => new ToneDeltaPair(tertiary_container, tertiary, 10.0, TonePolarity.NEARER, false))
				        .build();
			}
		}

		public virtual DynamicColor? tertiary_dim {
			owned get {
				return null;
			}
		}

		public virtual DynamicColor on_tertiary {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_tertiary")
				        .set_palette((s) => s.tertiary_palette)
				        .set_tone((s) => {
					if (is_monochrome(s)) {
						return s.is_dark ? 10.0 : 90.0;
					}
					return s.is_dark ? 20.0 : 100.0;
				})
				        .set_background((s) => tertiary)
				        .set_contrast_curve((s) => new ContrastCurve(4.5, 7.0, 11.0, 21.0))
				        .build();
			}
		}

		public virtual DynamicColor tertiary_container {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("tertiary_container")
				        .set_palette((s) => s.tertiary_palette)
				        .set_tone((s) => {
					if (is_monochrome(s)) {
						return s.is_dark ? 60.0 : 49.0;
					}
					if (!is_fidelity(s)) {
						return s.is_dark ? 30.0 : 90.0;
					}
					Hct proposed_hct = s.tertiary_palette.get_hct(s.source_color_hct.tone);
					return DislikeAnalyzer.fix_if_disliked(proposed_hct).tone;
				})
				        .set_is_background(true)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(1.0, 1.0, 3.0, 4.5))
				        .set_tone_delta_pair((s) => new ToneDeltaPair(tertiary_container, tertiary, 10.0, TonePolarity.NEARER, false))
				        .build();
			}
		}

		public virtual DynamicColor on_tertiary_container {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_tertiary_container")
				        .set_palette((s) => s.tertiary_palette)
				        .set_tone((s) => {
					if (is_monochrome(s)) {
						return s.is_dark ? 0.0 : 100.0;
					}
					if (!is_fidelity(s)) {
						return s.is_dark ? 90.0 : 30.0;
					}
					return DynamicColor.foreground_tone(tertiary_container.tone(s), 4.5);
				})
				        .set_background((s) => tertiary_container)
				        .set_contrast_curve((s) => new ContrastCurve(3.0, 4.5, 7.0, 11.0))
				        .build();
			}
		}

		////////////////////////////////////////////////////////////////
		// Errors [E]                                                 //
		////////////////////////////////////////////////////////////////

		public virtual DynamicColor error {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("error")
				        .set_palette((s) => s.error_palette)
				        .set_tone((s) => s.is_dark ? 80.0 : 40.0)
				        .set_is_background(true)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(3.0, 4.5, 7.0, 7.0))
				        .set_tone_delta_pair((s) => new ToneDeltaPair(error_container, error, 10.0, TonePolarity.NEARER, false))
				        .build();
			}
		}

		public virtual DynamicColor? error_dim {
			owned get {
				return null;
			}
		}

		public virtual DynamicColor on_error {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_error")
				        .set_palette((s) => s.error_palette)
				        .set_tone((s) => s.is_dark ? 20.0 : 100.0)
				        .set_background((s) => error)
				        .set_contrast_curve((s) => new ContrastCurve(4.5, 7.0, 11.0, 21.0))
				        .build();
			}
		}

		public virtual DynamicColor error_container {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("error_container")
				        .set_palette((s) => s.error_palette)
				        .set_tone((s) => s.is_dark ? 30.0 : 90.0)
				        .set_is_background(true)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(1.0, 1.0, 3.0, 4.5))
				        .set_tone_delta_pair((s) => new ToneDeltaPair(error_container, error, 10.0, TonePolarity.NEARER, false))
				        .build();
			}
		}

		public virtual DynamicColor on_error_container {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_error_container")
				        .set_palette((s) => s.error_palette)
				        .set_tone((s) => {
					if (is_monochrome(s)) {
						return s.is_dark ? 90.0 : 10.0;
					}
					return s.is_dark ? 90.0 : 30.0;
				})
				        .set_background((s) => error_container)
				        .set_contrast_curve((s) => new ContrastCurve(3.0, 4.5, 7.0, 11.0))
				        .build();
			}
		}

		////////////////////////////////////////////////////////////////
		// Primary Fixed Colors [PF]                                  //
		////////////////////////////////////////////////////////////////

		public virtual DynamicColor primary_fixed {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("primary_fixed")
				        .set_palette((s) => s.primary_palette)
				        .set_tone((s) => is_monochrome(s) ? 40.0 : 90.0)
				        .set_is_background(true)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(1.0, 1.0, 3.0, 4.5))
				        .set_tone_delta_pair((s) => new ToneDeltaPair(primary_fixed, primary_fixed_dim, 10.0, TonePolarity.LIGHTER, true))
				        .build();
			}
		}

		public virtual DynamicColor primary_fixed_dim {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("primary_fixed_dim")
				        .set_palette((s) => s.primary_palette)
				        .set_tone((s) => is_monochrome(s) ? 30.0 : 80.0)
				        .set_is_background(true)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(1.0, 1.0, 3.0, 4.5))
				        .set_tone_delta_pair((s) => new ToneDeltaPair(primary_fixed, primary_fixed_dim, 10.0, TonePolarity.LIGHTER, true))
				        .build();
			}
		}

		public virtual DynamicColor on_primary_fixed {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_primary_fixed")
				        .set_palette((s) => s.primary_palette)
				        .set_tone((s) => is_monochrome(s) ? 100.0 : 10.0)
				        .set_background((s) => primary_fixed_dim)
				        .set_second_background((s) => primary_fixed)
				        .set_contrast_curve((s) => new ContrastCurve(4.5, 7.0, 11.0, 21.0))
				        .build();
			}
		}

		public virtual DynamicColor on_primary_fixed_variant {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_primary_fixed_variant")
				        .set_palette((s) => s.primary_palette)
				        .set_tone((s) => is_monochrome(s) ? 90.0 : 30.0)
				        .set_background((s) => primary_fixed_dim)
				        .set_second_background((s) => primary_fixed)
				        .set_contrast_curve((s) => new ContrastCurve(3.0, 4.5, 7.0, 11.0))
				        .build();
			}
		}

		////////////////////////////////////////////////////////////////
		// Secondary Fixed Colors [QF]                                //
		////////////////////////////////////////////////////////////////

		public virtual DynamicColor secondary_fixed {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("secondary_fixed")
				        .set_palette((s) => s.secondary_palette)
				        .set_tone((s) => is_monochrome(s) ? 80.0 : 90.0)
				        .set_is_background(true)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(1.0, 1.0, 3.0, 4.5))
				        .set_tone_delta_pair((s) => new ToneDeltaPair(secondary_fixed, secondary_fixed_dim, 10.0, TonePolarity.LIGHTER, true))
				        .build();
			}
		}

		public virtual DynamicColor secondary_fixed_dim {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("secondary_fixed_dim")
				        .set_palette((s) => s.secondary_palette)
				        .set_tone((s) => is_monochrome(s) ? 70.0 : 80.0)
				        .set_is_background(true)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(1.0, 1.0, 3.0, 4.5))
				        .set_tone_delta_pair((s) => new ToneDeltaPair(secondary_fixed, secondary_fixed_dim, 10.0, TonePolarity.LIGHTER, true))
				        .build();
			}
		}

		public virtual DynamicColor on_secondary_fixed {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_secondary_fixed")
				        .set_palette((s) => s.secondary_palette)
				        .set_tone((s) => 10.0)
				        .set_background((s) => secondary_fixed_dim)
				        .set_second_background((s) => secondary_fixed)
				        .set_contrast_curve((s) => new ContrastCurve(4.5, 7.0, 11.0, 21.0))
				        .build();
			}
		}

		public virtual DynamicColor on_secondary_fixed_variant {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_secondary_fixed_variant")
				        .set_palette((s) => s.secondary_palette)
				        .set_tone((s) => is_monochrome(s) ? 25.0 : 30.0)
				        .set_background((s) => secondary_fixed_dim)
				        .set_second_background((s) => secondary_fixed)
				        .set_contrast_curve((s) => new ContrastCurve(3.0, 4.5, 7.0, 11.0))
				        .build();
			}
		}

		////////////////////////////////////////////////////////////////
		// Tertiary Fixed Colors [TF]                                 //
		////////////////////////////////////////////////////////////////

		public virtual DynamicColor tertiary_fixed {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("tertiary_fixed")
				        .set_palette((s) => s.tertiary_palette)
				        .set_tone((s) => is_monochrome(s) ? 40.0 : 90.0)
				        .set_is_background(true)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(1.0, 1.0, 3.0, 4.5))
				        .set_tone_delta_pair((s) => new ToneDeltaPair(tertiary_fixed, tertiary_fixed_dim, 10.0, TonePolarity.LIGHTER, true))
				        .build();
			}
		}

		public virtual DynamicColor tertiary_fixed_dim {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("tertiary_fixed_dim")
				        .set_palette((s) => s.tertiary_palette)
				        .set_tone((s) => is_monochrome(s) ? 30.0 : 80.0)
				        .set_is_background(true)
				        .set_background((s) => s.is_dark ? surface_bright : surface_dim)
				        .set_contrast_curve((s) => new ContrastCurve(1.0, 1.0, 3.0, 4.5))
				        .set_tone_delta_pair((s) => new ToneDeltaPair(tertiary_fixed, tertiary_fixed_dim, 10.0, TonePolarity.LIGHTER, true))
				        .build();
			}
		}

		public virtual DynamicColor on_tertiary_fixed {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_tertiary_fixed")
				        .set_palette((s) => s.tertiary_palette)
				        .set_tone((s) => is_monochrome(s) ? 100.0 : 10.0)
				        .set_background((s) => tertiary_fixed_dim)
				        .set_second_background((s) => tertiary_fixed)
				        .set_contrast_curve((s) => new ContrastCurve(4.5, 7.0, 11.0, 21.0))
				        .build();
			}
		}

		public virtual DynamicColor on_tertiary_fixed_variant {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("on_tertiary_fixed_variant")
				        .set_palette((s) => s.tertiary_palette)
				        .set_tone((s) => is_monochrome(s) ? 90.0 : 30.0)
				        .set_background((s) => tertiary_fixed_dim)
				        .set_second_background((s) => tertiary_fixed)
				        .set_contrast_curve((s) => new ContrastCurve(3.0, 4.5, 7.0, 11.0))
				        .build();
			}
		}

		//////////////////////////////////////////////////////////////////
		// Android-only Colors                                          //
		//////////////////////////////////////////////////////////////////

		/**
		 * These colors were present in Android framework before Android U, and used by MDC controls. They
		 * should be avoided, if possible. It's unclear if they're used on multiple backgrounds, and if
		 * they are, they can't be adjusted for contrast.* For now, they will be set with no background,
		 * and those won't adjust for contrast, avoiding issues.
		 *
		 * <p>* For example, if the same color is on a white background _and_ black background, there's no
		 * way to increase contrast with either without losing contrast with the other.
		 */

		// colorControlActivated documented as colorAccent in M3 & GM3.
		// colorAccent documented as colorSecondary in M3 and colorPrimary in GM3.
		// Android used Material's Container as Primary/Secondary/Tertiary at launch.
		// Therefore, this is a duplicated version of Primary Container.
		public virtual DynamicColor control_activated {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("control_activated")
				        .set_palette((s) => s.primary_palette)
				        .set_tone((s) => s.is_dark ? 30.0 : 90.0)
				        .set_is_background(true)
				        .build();
			}
		}

		// colorControlNormal documented as textColorSecondary in M3 & GM3.
		// In Material, textColorSecondary points to onSurfaceVariant in the non-disabled state,
		// which is Neutral Variant T30/80 in light/dark.
		public virtual DynamicColor control_normal {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("control_normal")
				        .set_palette((s) => s.neutral_variant_palette)
				        .set_tone((s) => s.is_dark ? 80.0 : 30.0)
				        .build();
			}
		}

		// colorControlHighlight documented, in both M3 & GM3:
		// Light mode: #1f000000 dark mode: #33ffffff.
		// These are black and white with some alpha.
		// 1F hex = 31 decimal; 31 / 255 = 12% alpha.
		// 33 hex = 51 decimal; 51 / 255 = 20% alpha.
		// DynamicColors do not support alpha currently, and _may_ not need it for this use case,
		// depending on how MDC resolved alpha for the other cases.
		// Returning black in dark mode, white in light mode.
		public virtual DynamicColor control_highlight {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("control_highlight")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? 100.0 : 0.0)
				        .set_opacity((s) => s.is_dark ? 0.20 : 0.12)
				        .build();
			}
		}

		// textColorPrimaryInverse documented, in both M3 & GM3, documented as N10/N90.
		public virtual DynamicColor text_primary_inverse {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("text_primary_inverse")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? 10.0 : 90.0)
				        .build();
			}
		}

		// textColorSecondaryInverse and textColorTertiaryInverse both documented, in both M3 & GM3, as
		// NV30/NV80
		public virtual DynamicColor text_secondary_and_tertiary_inverse {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("text_secondary_and_tertiary_inverse")
				        .set_palette((s) => s.neutral_variant_palette)
				        .set_tone((s) => s.is_dark ? 30.0 : 80.0)
				        .build();
			}
		}

		// textColorPrimaryInverseDisableOnly documented, in both M3 & GM3, as N10/N90
		public virtual DynamicColor text_primary_inverse_disable_only {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("text_primary_inverse_disable_only")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? 10.0 : 90.0)
				        .build();
			}
		}

		// textColorSecondaryInverse and textColorTertiaryInverse in disabled state both documented,
		// in both M3 & GM3, as N10/N90
		public virtual DynamicColor text_secondary_and_tertiary_inverse_disabled {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("text_secondary_and_tertiary_inverse_disabled")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? 10.0 : 90.0)
				        .build();
			}
		}

		// textColorHintInverse documented, in both M3 & GM3, as N10/N90
		public virtual DynamicColor text_hint_inverse {
			owned get {
				return new DynamicColorBuilder()
				        .set_name("text_hint_inverse")
				        .set_palette((s) => s.neutral_palette)
				        .set_tone((s) => s.is_dark ? 10.0 : 90.0)
				        .build();
			}
		}

		////////////////////////////////////////////////////////////////
		// Other                                                      //
		////////////////////////////////////////////////////////////////

		public virtual DynamicColor highest_surface(DynamicScheme s) {
			return s.is_dark ? surface_bright : surface_dim;
		}

		private static bool is_fidelity(DynamicScheme scheme) {
			return scheme.variant == Variant.FIDELITY || scheme.variant == Variant.CONTENT;
		}

		private static bool is_monochrome(DynamicScheme scheme) {
			return scheme.variant == Variant.MONOCHROME;
		}

		private static double find_desired_chroma_by_tone(double hue, double chroma, double tone, bool by_decreasing_tone) {
			double answer = tone;

			Hct closest_to_chroma = Hct.from(hue, chroma, tone);
			if (closest_to_chroma.chroma < chroma) {
				double chroma_peak = closest_to_chroma.chroma;
				while (closest_to_chroma.chroma < chroma) {
					answer += by_decreasing_tone ? -1.0 : 1.0;
					Hct potential_solution = Hct.from(hue, chroma, answer);
					if (chroma_peak > potential_solution.chroma) {
						break;
					}
					if (Math.fabs(potential_solution.chroma - chroma) < 0.4) {
						break;
					}

					double potential_delta = Math.fabs(potential_solution.chroma - chroma);
					double current_delta = Math.fabs(closest_to_chroma.chroma - chroma);
					if (potential_delta < current_delta) {
						closest_to_chroma = potential_solution;
					}
					chroma_peak = Math.fmax(chroma_peak, potential_solution.chroma);
				}
			}

			return answer;
		}

		/////////////////////////////////////////////////////////////////
		// Color value calculations                                    //
		/////////////////////////////////////////////////////////////////

		public virtual Hct get_hct(DynamicScheme scheme, DynamicColor color) {
			// This is crucial for aesthetics: we aren't simply the taking the standard color
			// and changing its tone for contrast. Rather, we find the tone for contrast, then
			// use the specified chroma from the palette to construct a new color.
			//
			// For example, this enables colors with standard tone of T90, which has limited chroma, to
			// "recover" intended chroma as contrast increases.
			double tone = get_tone(scheme, color);
			return color.palette(scheme).get_hct(tone);
		}

		public virtual double get_tone(DynamicScheme scheme, DynamicColor color) {
			bool decreasing_contrast = scheme.contrast_level < 0;
			ToneDeltaPair tone_delta_pair = color.tone_delta_pair == null ? null : color.tone_delta_pair(scheme);

			// Case 1: dual foreground, pair of colors with delta constraint.
			if (tone_delta_pair != null) {
				DynamicColor role_a = tone_delta_pair.get_role_a();
				DynamicColor role_b = tone_delta_pair.get_role_b();
				double delta = tone_delta_pair.get_delta();
				TonePolarity polarity = tone_delta_pair.get_polarity();
				bool stay_together = tone_delta_pair.get_stay_together();

				bool a_is_nearer = (polarity == TonePolarity.NEARER || (polarity == TonePolarity.LIGHTER && !scheme.is_dark) || (polarity == TonePolarity.DARKER && !scheme.is_dark));
				DynamicColor nearer = a_is_nearer ? role_a : role_b;
				DynamicColor farther = a_is_nearer ? role_b : role_a;
				bool am_nearer = color.name == nearer.name;
				double expansion_dir = scheme.is_dark ? 1 : -1;
				double n_tone = nearer.tone(scheme);
				double f_tone = farther.tone(scheme);

				if (color.background != null && nearer.contrast_curve != null && farther.contrast_curve != null) {
					DynamicColor bg = color.background(scheme);
					ContrastCurve n_contrast_curve = nearer.contrast_curve(scheme);
					ContrastCurve f_contrast_curve = farther.contrast_curve(scheme);
					if (bg != null && n_contrast_curve != null && f_contrast_curve != null) {
						double n_contrast = n_contrast_curve.get_value(scheme.contrast_level);
						double f_contrast = f_contrast_curve.get_value(scheme.contrast_level);
						double bg_tone = bg.get_tone(scheme);

						// If a color is good enough, it is not adjusted.
						// Initial and adjusted tones for `nearer`
						if (Contrast.ratio_of_tones(bg_tone, n_tone) < n_contrast) {
							n_tone = DynamicColor.foreground_tone(bg_tone, n_contrast);
						}
						// Initial and adjusted tones for `farther`
						if (Contrast.ratio_of_tones(bg_tone, f_tone) < f_contrast) {
							f_tone = DynamicColor.foreground_tone(bg_tone, f_contrast);
						}

						if (decreasing_contrast) {
							// If decreasing contrast, adjust color to the "bare minimum"
							// that satisfies contrast.
							n_tone = DynamicColor.foreground_tone(bg_tone, n_contrast);
							f_tone = DynamicColor.foreground_tone(bg_tone, f_contrast);
						}
					}
				}

				// If constraint is not satisfied, try another round.
				if ((f_tone - n_tone) * expansion_dir < delta) {
					// 2nd round: expand farther to match delta.
					f_tone = MathUtils.clamp_double(0, 100, n_tone + delta * expansion_dir);
					// If constraint is not satisfied, try another round.
					if ((f_tone - n_tone) * expansion_dir < delta) {
						// 3rd round: contract nearer to match delta.
						n_tone = MathUtils.clamp_double(0, 100, f_tone - delta * expansion_dir);
					}
				}

				// Avoids the 50-59 awkward zone.
				if (50 <= n_tone && n_tone < 60) {
					// If `nearer` is in the awkward zone, move it away, together with
					// `farther`.
					if (expansion_dir > 0) {
						n_tone = 60;
						f_tone = Math.fmax(f_tone, n_tone + delta * expansion_dir);
					} else {
						n_tone = 49;
						f_tone = Math.fmin(f_tone, n_tone + delta * expansion_dir);
					}
				} else if (50 <= f_tone && f_tone < 60) {
					if (stay_together) {
						// Fixes both, to avoid two colors on opposite sides of the "awkward
						// zone".
						if (expansion_dir > 0) {
							n_tone = 60;
							f_tone = Math.fmax(f_tone, n_tone + delta * expansion_dir);
						} else {
							n_tone = 49;
							f_tone = Math.fmin(f_tone, n_tone + delta * expansion_dir);
						}
					} else {
						// Not required to stay together; fixes just one.
						if (expansion_dir > 0) {
							f_tone = 60;
						} else {
							f_tone = 49;
						}
					}
				}

				// Returns `n_tone` if this color is `nearer`, otherwise `f_tone`.
				return am_nearer ? n_tone : f_tone;
			} else {
				// Case 2: No contrast pair; just solve for itself.
				double answer = color.tone(scheme);

				if (color.background == null || color.background(scheme) == null || color.contrast_curve == null || color.contrast_curve(scheme) == null) {
					return answer;
				}

				double bg_tone = color.background(scheme).get_tone(scheme);
				double desired_ratio = color.contrast_curve(scheme).get_value(scheme.contrast_level);

				if (Contrast.ratio_of_tones(bg_tone, answer) >= desired_ratio) {
					// Don't "improve" what's good enough.
				} else {
					// Rough improvement.
					answer = DynamicColor.foreground_tone(bg_tone, desired_ratio);
				}

				if (decreasing_contrast) {
					answer = DynamicColor.foreground_tone(bg_tone, desired_ratio);
				}

				if (color.is_background && 50 <= answer && answer < 60) {
					// Must adjust
					if (Contrast.ratio_of_tones(49, bg_tone) >= desired_ratio) {
						answer = 49;
					} else {
						answer = 60;
					}
				}

				if (color.second_background == null || color.second_background(scheme) == null) {
					return answer;
				}

				// Case 3: Adjust for dual backgrounds.
				double bg_tone_1 = color.background(scheme).get_tone(scheme);
				double bg_tone_2 = color.second_background(scheme).get_tone(scheme);

				double upper = Math.fmax(bg_tone_1, bg_tone_2);
				double lower = Math.fmin(bg_tone_1, bg_tone_2);

				if (Contrast.ratio_of_tones(upper, answer) >= desired_ratio && Contrast.ratio_of_tones(lower, answer) >= desired_ratio) {
					return answer;
				}

				// The darkest light tone that satisfies the desired ratio,
				// or -1 if such ratio cannot be reached.
				double light_option = Contrast.lighter(upper, desired_ratio);

				// The lightest dark tone that satisfies the desired ratio,
				// or -1 if such ratio cannot be reached.
				double dark_option = Contrast.darker(lower, desired_ratio);

				// Tones suitable for the foreground.
				double[] availables = {};
				if (light_option != -1) {
					availables += light_option;
				}
				if (dark_option != -1) {
					availables += dark_option;
				}

				bool prefers_light = DynamicColor.tone_prefers_light_foreground(bg_tone_1) || DynamicColor.tone_prefers_light_foreground(bg_tone_2);
				if (prefers_light) {
					return (light_option == -1) ? 100 : light_option;
				}
				if (availables.length == 1) {
					return availables[0];
				}
				return (dark_option == -1) ? 0 : dark_option;
			}
		}

		//////////////////////////////////////////////////////////////////
		// Scheme Palettes                                              //
		//////////////////////////////////////////////////////////////////

		public virtual TonalPalette get_primary_palette(Variant variant, Hct source_color_hct, bool is_dark, Platform platform, double contrast_level) {
			switch (variant) {
			case CONTENT :
			case FIDELITY :
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, source_color_hct.chroma);
			case FRUIT_SALAD :
				return TonalPalette.from_hue_and_chroma(MathUtils.sanitize_degrees_double(source_color_hct.hue - 50.0), 48.0);
			case MONOCHROME :
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 0.0);
			case NEUTRAL :
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 12.0);
			case RAINBOW:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 48.0);
			case TONAL_SPOT:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 36.0);
			case EXPRESSIVE:
				return TonalPalette.from_hue_and_chroma(MathUtils.sanitize_degrees_double(source_color_hct.hue + 240), 40);
			case VIBRANT:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 200.0);
			default:
				GLib.error("IllegalArgumentException: %s variant is not supported in current spec.".printf(variant.to_string()));
			}
		}

		public virtual TonalPalette get_secondary_palette(Variant variant, Hct source_color_hct, bool is_dark, Platform platform, double contrast_level) {
			switch (variant) {
			case CONTENT:
			case FIDELITY:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, Math.fmax(source_color_hct.chroma - 32.0, source_color_hct.chroma * 0.5));
			case FRUIT_SALAD:
				return TonalPalette.from_hue_and_chroma(MathUtils.sanitize_degrees_double(source_color_hct.hue - 50.0), 36.0);
			case MONOCHROME:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 0.0);
			case NEUTRAL:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 8.0);
			case RAINBOW:
			case TONAL_SPOT:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 16.0);
			case EXPRESSIVE:
				return TonalPalette.from_hue_and_chroma(DynamicScheme.get_rotated_hue(source_color_hct, new double[] { 0, 21, 51, 121, 151, 191, 271, 321, 360 }, new double[] { 45, 95, 45, 20, 45, 90, 45, 45, 45 }), 24.0);
			case VIBRANT:
				return TonalPalette.from_hue_and_chroma(DynamicScheme.get_rotated_hue(source_color_hct, new double[] { 0, 41, 61, 101, 131, 181, 251, 301, 360 }, new double[] { 18, 15, 10, 12, 15, 18, 15, 12, 12 }), 24.0);
			default:
				GLib.error("IllegalArgumentException: %s variant is not supported in current spec.".printf(variant.to_string()));
			}
		}

		public virtual TonalPalette get_tertiary_palette(Variant variant, Hct source_color_hct, bool is_dark, Platform platform, double contrast_level) {
			switch (variant) {
			case CONTENT:
				return TonalPalette.from_hct(DislikeAnalyzer.fix_if_disliked(new TemperatureCache(source_color_hct).get_analogous_colors(3, 6).get(2)));
			case FIDELITY:
				return TonalPalette.from_hct(DislikeAnalyzer.fix_if_disliked(new TemperatureCache(source_color_hct).get_complement()));
			case FRUIT_SALAD:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 36.0);
			case MONOCHROME:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 0.0);
			case NEUTRAL:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 16.0);
			case RAINBOW:
			case TONAL_SPOT:
				return TonalPalette.from_hue_and_chroma(MathUtils.sanitize_degrees_double(source_color_hct.hue + 60.0), 24.0);
			case EXPRESSIVE:
				return TonalPalette.from_hue_and_chroma(DynamicScheme.get_rotated_hue(source_color_hct, new double[] { 0, 21, 51, 121, 151, 191, 271, 321, 360 }, new double[] { 120, 120, 20, 45, 20, 15, 20, 120, 120 }), 32.0);
			case VIBRANT:
				return TonalPalette.from_hue_and_chroma(DynamicScheme.get_rotated_hue(source_color_hct, new double[] { 0, 41, 61, 101, 131, 181, 251, 301, 360 }, new double[] { 35, 30, 20, 25, 30, 35, 30, 25, 25 }), 32.0);
			default:
				GLib.error("IllegalArgumentException: %s variant is not supported in current spec.".printf(variant.to_string()));
			}
		}

		public virtual TonalPalette get_neutral_palette(Variant variant, Hct source_color_hct, bool is_dark, Platform platform, double contrast_level) {
			switch (variant) {
			case CONTENT:
			case FIDELITY:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, source_color_hct.chroma / 8.0);
			case FRUIT_SALAD:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 10.0);
			case MONOCHROME:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 0.0);
			case NEUTRAL:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 2.0);
			case RAINBOW:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 0.0);
			case TONAL_SPOT:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 6.0);
			case EXPRESSIVE:
				return TonalPalette.from_hue_and_chroma(MathUtils.sanitize_degrees_double(source_color_hct.hue + 15.0), 8);
			case VIBRANT:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 10);
			default:
				GLib.error("IllegalArgumentException: %s variant is not supported in current spec.".printf(variant.to_string()));
			}
		}

		public virtual TonalPalette get_neutral_variant_palette(Variant variant, Hct source_color_hct, bool is_dark, Platform platform, double contrast_level) {
			switch (variant) {
			case CONTENT:
			case FIDELITY:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, (source_color_hct.chroma / 8.0) + 4.0);
			case FRUIT_SALAD:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 16.0);
			case MONOCHROME:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 0.0);
			case NEUTRAL:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 2.0);
			case RAINBOW:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 0.0);
			case TONAL_SPOT:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 8.0);
			case EXPRESSIVE:
				return TonalPalette.from_hue_and_chroma(MathUtils.sanitize_degrees_double(source_color_hct.hue + 15), 12);
			case VIBRANT:
				return TonalPalette.from_hue_and_chroma(source_color_hct.hue, 12);
			default:
				GLib.error("IllegalArgumentException: %s variant is not supported in current spec.".printf(variant.to_string()));
			}
		}

		public virtual TonalPalette ? get_error_palette(Variant variant, Hct source_color_hct, bool is_dark, Platform platform, double contrast_level) {
			switch (variant) {
			case CONTENT :
			case FIDELITY :
			case FRUIT_SALAD:
			case MONOCHROME:
			case NEUTRAL:
			case RAINBOW:
			case TONAL_SPOT:
			case EXPRESSIVE:
			case VIBRANT:
				return null;
			default:
				GLib.error("IllegalArgumentException: %s variant is not supported in current spec.".printf(variant.to_string()));
			}
		}
	}
}
