// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

use std::collections::HashMap;

use serde::{ Deserialize, Serialize };

use crate::types::Color;

use super::super::paint::{ ColorStop, GradientSpace, LinearGradient, Paint, RadialGradient };
use super::super::shadow::{ BlendMode, InsetShadow, Shadow, ShadowsRef };
use super::super::slots::{ Metadata, Slot };
use super::super::surface::Surface;
use super::super::text_style::
{
	FontRef, FontStyle, LineHeight, TextDecoration, TextStyle, TextTransform,
};
use super::super::WallpaperFit;

use super::color_serde;

// ─── Enum mirrors with defaults ──────────────────────────────────────────────

#[ derive( Debug, Clone, Copy, Serialize, Deserialize ) ]
#[ serde( rename_all = "kebab-case" ) ]
pub( super ) enum RawGradientSpace
{
	Srgb,
	LinearRgb,
	Oklab,
}

impl Default for RawGradientSpace { fn default() -> Self { RawGradientSpace::LinearRgb } }

impl From<RawGradientSpace> for GradientSpace
{
	fn from( r: RawGradientSpace ) -> Self
	{
		match r
		{
			RawGradientSpace::Srgb      => GradientSpace::Srgb,
			RawGradientSpace::LinearRgb => GradientSpace::LinearRgb,
			RawGradientSpace::Oklab     => GradientSpace::Oklab,
		}
	}
}

#[ derive( Debug, Clone, Copy, Serialize, Deserialize ) ]
#[ serde( rename_all = "kebab-case" ) ]
pub( super ) enum RawBlendMode
{
	Normal,
	PlusLighter,
	Overlay,
	Multiply,
	Screen,
}

impl Default for RawBlendMode { fn default() -> Self { RawBlendMode::Normal } }

impl From<RawBlendMode> for BlendMode
{
	fn from( r: RawBlendMode ) -> Self
	{
		match r
		{
			RawBlendMode::Normal      => BlendMode::Normal,
			RawBlendMode::PlusLighter => BlendMode::PlusLighter,
			RawBlendMode::Overlay     => BlendMode::Overlay,
			RawBlendMode::Multiply    => BlendMode::Multiply,
			RawBlendMode::Screen      => BlendMode::Screen,
		}
	}
}

#[ derive( Debug, Clone, Copy, Serialize, Deserialize ) ]
#[ serde( rename_all = "kebab-case" ) ]
pub( super ) enum RawFontStyle { Normal, Italic }

impl Default for RawFontStyle { fn default() -> Self { RawFontStyle::Normal } }

impl From<RawFontStyle> for FontStyle
{
	fn from( r: RawFontStyle ) -> Self
	{
		match r
		{
			RawFontStyle::Normal => FontStyle::Normal,
			RawFontStyle::Italic => FontStyle::Italic,
		}
	}
}

#[ derive( Debug, Clone, Copy, Serialize, Deserialize ) ]
#[ serde( rename_all = "kebab-case" ) ]
pub( super ) enum RawTextTransform { None, Uppercase, Lowercase, Capitalize }

impl Default for RawTextTransform { fn default() -> Self { RawTextTransform::None } }

impl From<RawTextTransform> for TextTransform
{
	fn from( r: RawTextTransform ) -> Self
	{
		match r
		{
			RawTextTransform::None       => TextTransform::None,
			RawTextTransform::Uppercase  => TextTransform::Uppercase,
			RawTextTransform::Lowercase  => TextTransform::Lowercase,
			RawTextTransform::Capitalize => TextTransform::Capitalize,
		}
	}
}

#[ derive( Debug, Clone, Copy, Serialize, Deserialize ) ]
#[ serde( rename_all = "kebab-case" ) ]
pub( super ) enum RawTextDecoration { None, Underline, Strikethrough }

impl Default for RawTextDecoration { fn default() -> Self { RawTextDecoration::None } }

impl From<RawTextDecoration> for TextDecoration
{
	fn from( r: RawTextDecoration ) -> Self
	{
		match r
		{
			RawTextDecoration::None          => TextDecoration::None,
			RawTextDecoration::Underline     => TextDecoration::Underline,
			RawTextDecoration::Strikethrough => TextDecoration::Strikethrough,
		}
	}
}

// ─── Metadata ────────────────────────────────────────────────────────────────

#[ derive( Debug, Clone, Default, Serialize, Deserialize ) ]
pub( super ) struct RawMetadata
{
	#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
	pub( super ) semantic: Option<String>,
	#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
	pub( super ) fluent:   Option<String>,
	#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
	pub( super ) usage:    Option<String>,
	#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
	pub( super ) note:     Option<String>,
}

impl From<RawMetadata> for Metadata
{
	fn from( r: RawMetadata ) -> Self
	{
		Metadata { semantic: r.semantic, fluent: r.fluent, usage: r.usage, note: r.note }
	}
}

// ─── Gradient stops and gradients ────────────────────────────────────────────

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawColorStop
{
	#[ serde( alias = "position" ) ]
	pub( super ) pos:   f32,
	#[ serde( with = "color_serde" ) ]
	pub( super ) color: Color,
}

impl From<RawColorStop> for ColorStop
{
	fn from( r: RawColorStop ) -> Self { ColorStop { position: r.pos, color: r.color } }
}

// ─── Paint (for `surface.fill`) ──────────────────────────────────────────────

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( tag = "type", rename_all = "kebab-case", deny_unknown_fields ) ]
pub( super ) enum RawPaint
{
	Solid
	{
		#[ serde( with = "color_serde" ) ]
		color: Color,
	},
	Linear
	{
		angle_deg: f32,
		#[ serde( default ) ]
		space: RawGradientSpace,
		stops: Vec<RawColorStop>,
	},
	Radial
	{
		center: [f32; 2],
		radius: f32,
		#[ serde( default ) ]
		space: RawGradientSpace,
		stops: Vec<RawColorStop>,
	},
}

impl From<RawPaint> for Paint
{
	fn from( r: RawPaint ) -> Self
	{
		match r
		{
			RawPaint::Solid { color } => Paint::Solid( color ),
			RawPaint::Linear { angle_deg, space, stops } => Paint::Linear( LinearGradient
			{
				angle_deg,
				space:  space.into(),
				stops:  collect_capped_stops( stops ),
			}),
			RawPaint::Radial { center, radius, space, stops } => Paint::Radial( RadialGradient
			{
				center,
				radius,
				space:  space.into(),
				stops:  collect_capped_stops( stops ),
			}),
		}
	}
}

/// Soft cap on the number of color stops a gradient may carry. Realistic
/// designs use 2 – 6 stops, the shipped theme tops out at 4, and
/// 64 leaves comfortable headroom for hand-tuned tonemap palettes. The
/// cap exists to bound CPU + memory under a hostile theme document — a
/// 200 000-stop linear gradient would otherwise build a 6 MB
/// `Vec<ColorStop>` per slot at parse time. Exceeding the cap truncates
/// the tail and logs once via stderr.
pub( super ) const MAX_GRADIENT_STOPS: usize = 64;

pub( super ) fn collect_capped_stops( raw: Vec<RawColorStop> ) -> Vec<ColorStop>
{
	if raw.len() > MAX_GRADIENT_STOPS
	{
		eprintln!(
			"[ltk theme] gradient declares {} stops, truncating to {}",
			raw.len(), MAX_GRADIENT_STOPS,
		);
		raw.into_iter().take( MAX_GRADIENT_STOPS ).map( Into::into ).collect()
	}
	else
	{
		raw.into_iter().map( Into::into ).collect()
	}
}

// ─── Shadows ─────────────────────────────────────────────────────────────────

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawShadow
{
	pub( super ) offset: [f32; 2],
	pub( super ) blur:   f32,
	#[ serde( default ) ]
	pub( super ) spread: f32,
	#[ serde( with = "color_serde" ) ]
	pub( super ) color:  Color,
	#[ serde( default ) ]
	pub( super ) blend:  RawBlendMode,
}

impl From<RawShadow> for Shadow
{
	fn from( r: RawShadow ) -> Self
	{
		Shadow { offset: r.offset, blur: r.blur, spread: r.spread, color: r.color, blend: r.blend.into() }
	}
}

impl From<RawShadow> for InsetShadow
{
	fn from( r: RawShadow ) -> Self
	{
		InsetShadow { offset: r.offset, blur: r.blur, spread: r.spread, color: r.color, blend: r.blend.into() }
	}
}

/// Reference to a shadow stack inside a surface: either a string id
/// pointing at a `shadows` slot, or a literal list.
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( untagged ) ]
pub( super ) enum RawShadowsRef
{
	Named( String ),
	Inline( Vec<RawShadow> ),
}

impl From<RawShadowsRef> for ShadowsRef
{
	fn from( r: RawShadowsRef ) -> Self
	{
		match r
		{
			RawShadowsRef::Named( id )     => ShadowsRef::Named( id ),
			RawShadowsRef::Inline( items ) => ShadowsRef::Inline( items.into_iter().map( Into::into ).collect() ),
		}
	}
}

// ─── Surface ─────────────────────────────────────────────────────────────────

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawSurfaceBody
{
	pub( super ) fill: Box<RawPaint>,
	#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
	pub( super ) shadows: Option<RawShadowsRef>,
	#[ serde( default ) ]
	pub( super ) inset_shadows: Vec<RawShadow>,
}

impl From<RawSurfaceBody> for Surface
{
	fn from( r: RawSurfaceBody ) -> Self
	{
		Surface
		{
			fill:          Paint::from( *r.fill ),
			shadows:       r.shadows.map( Into::into ),
			inset_shadows: r.inset_shadows.into_iter().map( Into::into ).collect(),
		}
	}
}

// ─── Text style ──────────────────────────────────────────────────────────────

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( untagged ) ]
pub( super ) enum RawLineHeight
{
	Px { px: f32 },
	Multiplier { mul: f32 },
}

impl From<RawLineHeight> for LineHeight
{
	fn from( r: RawLineHeight ) -> Self
	{
		match r
		{
			RawLineHeight::Px         { px }  => LineHeight::Px( px ),
			RawLineHeight::Multiplier { mul } => LineHeight::Multiplier( mul ),
		}
	}
}

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawFontRef
{
	pub( super ) r#ref: String,
}

impl From<RawFontRef> for FontRef
{
	fn from( r: RawFontRef ) -> Self { FontRef::Named( r.r#ref ) }
}

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawTextStyleBody
{
	pub( super ) family: RawFontRef,
	pub( super ) weight: u16,
	#[ serde( default ) ]
	pub( super ) style: RawFontStyle,
	pub( super ) size: f32,
	pub( super ) line_height: RawLineHeight,
	#[ serde( default ) ]
	pub( super ) letter_spacing: f32,
	#[ serde( default ) ]
	pub( super ) transform: RawTextTransform,
	#[ serde( default ) ]
	pub( super ) decoration: RawTextDecoration,
	#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
	pub( super ) color: Option<String>,
}

impl From<RawTextStyleBody> for TextStyle
{
	fn from( r: RawTextStyleBody ) -> Self
	{
		TextStyle
		{
			family:         r.family.into(),
			weight:         r.weight,
			style:          r.style.into(),
			size:           r.size,
			line_height:    r.line_height.into(),
			letter_spacing: r.letter_spacing,
			transform:      r.transform.into(),
			decoration:     r.decoration.into(),
			color:          r.color,
		}
	}
}

// ─── Slot ────────────────────────────────────────────────────────────────────

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( tag = "type", rename_all = "kebab-case" ) ]
pub( super ) enum RawSlot
{
	Color
	{
		#[ serde( with = "color_serde" ) ]
		value: Color,
		#[ serde( default ) ]
		meta: RawMetadata,
	},
	Linear
	{
		angle_deg: f32,
		#[ serde( default ) ]
		space: RawGradientSpace,
		stops: Vec<RawColorStop>,
		#[ serde( default ) ]
		meta: RawMetadata,
	},
	Radial
	{
		center: [f32; 2],
		radius: f32,
		#[ serde( default ) ]
		space: RawGradientSpace,
		stops: Vec<RawColorStop>,
		#[ serde( default ) ]
		meta: RawMetadata,
	},
	Shadows
	{
		shadows: Vec<RawShadow>,
		#[ serde( default ) ]
		meta: RawMetadata,
	},
	Surface
	{
		#[ serde( flatten ) ]
		body: RawSurfaceBody,
		#[ serde( default ) ]
		meta: RawMetadata,
	},
	Typography
	{
		#[ serde( flatten ) ]
		body: RawTextStyleBody,
		#[ serde( default ) ]
		meta: RawMetadata,
	},
}

impl From<RawSlot> for Slot
{
	fn from( r: RawSlot ) -> Self
	{
		match r
		{
			RawSlot::Color { value, meta } => Slot::Color { value, meta: meta.into() },
			RawSlot::Linear { angle_deg, space, stops, meta } => Slot::Paint
			{
				value: Paint::Linear( LinearGradient
				{
					angle_deg,
					space:  space.into(),
					stops:  collect_capped_stops( stops ),
				}),
				meta: meta.into(),
			},
			RawSlot::Radial { center, radius, space, stops, meta } => Slot::Paint
			{
				value: Paint::Radial( RadialGradient
				{
					center,
					radius,
					space:  space.into(),
					stops:  collect_capped_stops( stops ),
				}),
				meta: meta.into(),
			},
			RawSlot::Shadows { shadows, meta } => Slot::Shadows
			{
				value: shadows.into_iter().map( Into::into ).collect(),
				meta:  meta.into(),
			},
			RawSlot::Surface { body, meta } => Slot::Surface
			{
				value: body.into(),
				meta:  meta.into(),
			},
			RawSlot::Typography { body, meta } => Slot::TextStyle
			{
				value: body.into(),
				meta:  meta.into(),
			},
		}
	}
}

// ─── Fonts block ─────────────────────────────────────────────────────────────

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawFontSource
{
	pub( super ) weight: u16,
	#[ serde( default ) ]
	pub( super ) style:  RawFontStyle,
	pub( super ) path:   String,
}

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawFontFamily
{
	pub( super ) name: String,
	#[ serde( default ) ]
	pub( super ) fallbacks: Vec<String>,
	#[ serde( default ) ]
	pub( super ) sources: Vec<RawFontSource>,
}

// ─── Wallpaper / window_controls ─────────────────────────────────────────────

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawWallpaper
{
	pub( super ) path: String,
	#[ serde( default ) ]
	pub( super ) fit:  WallpaperFit,
}

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawLauncher
{
	pub( super ) background:    String,
	pub( super ) border_radius: f32,
}

#[ derive( Debug, Clone, Serialize, Deserialize, Default ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawWindowControls
{
	#[ serde( default ) ] pub( super ) bar_bg:         Option<String>,
	#[ serde( default ) ] pub( super ) icon:           Option<String>,
	#[ serde( default ) ] pub( super ) hover_bg:       Option<String>,
	#[ serde( default ) ] pub( super ) pressed_bg:     Option<String>,
	#[ serde( default ) ] pub( super ) close_hover_bg: Option<String>,
	#[ serde( default ) ] pub( super ) close_icon:     Option<String>,
	#[ serde( default ) ] pub( super ) focus_ring:     Option<String>,
}

// ─── Mode / Modes / Document ─────────────────────────────────────────────────

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawMode
{
	#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
	pub( super ) wallpaper: Option<RawWallpaper>,
	#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
	pub( super ) lockscreen: Option<RawWallpaper>,
	#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
	pub( super ) launcher: Option<RawLauncher>,
	#[ serde( default, skip_serializing_if = "Option::is_none" ) ]
	pub( super ) window_controls: Option<RawWindowControls>,
	#[ serde( default ) ]
	pub( super ) slots: HashMap<String, RawSlot>,
}

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawModes
{
	pub( super ) light: RawMode,
	pub( super ) dark:  RawMode,
}

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawThemeMeta
{
	pub( super ) id:   String,
	pub( super ) name: String,
}

#[ derive( Debug, Clone, Serialize, Deserialize ) ]
#[ serde( deny_unknown_fields ) ]
pub( super ) struct RawThemeDocument
{
	pub( super ) theme: RawThemeMeta,
	#[ serde( default ) ]
	pub( super ) fonts: HashMap<String, RawFontFamily>,
	pub( super ) modes: RawModes,
}
