Editor/Projection/Appearance/AppearanceScss.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using Grains.RazorDesigner.Document;
using static Grains.RazorDesigner.Projection.Appearance.ScssEnums;

namespace Grains.RazorDesigner.Projection.Appearance;

public static class AppearanceScss
{
    public static IReadOnlyList<string> Emit(
        IAppearance a,
        bool isRoot,
        bool isContainer,
        int childCount,
        bool isLabel = false,
        bool isCheckbox = false,
        Length checkboxSize = default,
        IAppearance baseForDiff = null )    // NEW: when non-null, only emit props where a.X != baseForDiff.X
    {
        var lines = new List<string>();

        // --- Width / Height ---
        if ( a.Width.Unit  != LengthUnit.Auto
             && ( baseForDiff is null || !a.Width.Equals( baseForDiff.Width ) ) )
            lines.Add( $"width: {a.Width.ToCss()};" );
        if ( a.Height.Unit != LengthUnit.Auto
             && ( baseForDiff is null || !a.Height.Equals( baseForDiff.Height ) ) )
            lines.Add( $"height: {a.Height.ToCss()};" );

        if ( a.Position == PositionKind.Absolute
             && ( baseForDiff is null || a.Position != baseForDiff.Position ) )
            lines.Add( $"position: {Css( a.Position )};" );
        if ( a.Top.Unit    != LengthUnit.Auto
             && ( baseForDiff is null || !a.Top.Equals( baseForDiff.Top ) ) )
            lines.Add( $"top: {a.Top.ToCss()};" );
        if ( a.Left.Unit   != LengthUnit.Auto
             && ( baseForDiff is null || !a.Left.Equals( baseForDiff.Left ) ) )
            lines.Add( $"left: {a.Left.ToCss()};" );
        if ( a.Right.Unit  != LengthUnit.Auto
             && ( baseForDiff is null || !a.Right.Equals( baseForDiff.Right ) ) )
            lines.Add( $"right: {a.Right.ToCss()};" );
        if ( a.Bottom.Unit != LengthUnit.Auto
             && ( baseForDiff is null || !a.Bottom.Equals( baseForDiff.Bottom ) ) )
            lines.Add( $"bottom: {a.Bottom.ToCss()};" );

        // --- Flex-self (skip on root — no parent flex container) ---
        if ( !isRoot )
        {
            // Guards: engine defaults (grow=0, shrink=1), not creation hints.
            if ( a.FlexGrow   != 0f
                 && ( baseForDiff is null || a.FlexGrow != baseForDiff.FlexGrow ) )
                lines.Add( $"flex-grow: {a.FlexGrow.ToString( "0.##", CultureInfo.InvariantCulture )};" );
            if ( a.FlexShrink != 1f
                 && ( baseForDiff is null || a.FlexShrink != baseForDiff.FlexShrink ) )
                lines.Add( $"flex-shrink: {a.FlexShrink.ToString( "0.##", CultureInfo.InvariantCulture )};" );
            if ( a.FlexBasis.Unit != LengthUnit.Auto
                 && ( baseForDiff is null || !a.FlexBasis.Equals( baseForDiff.FlexBasis ) ) )
                lines.Add( $"flex-basis: {a.FlexBasis.ToCss()};" );
            // align-self: per-child cross-axis override; Auto = inherit parent's align-items, emits nothing (grd-7s3).
            if ( a.AlignSelf != AlignSelfKind.Auto
                 && ( baseForDiff is null || a.AlignSelf != baseForDiff.AlignSelf ) )
                lines.Add( $"align-self: {Css( a.AlignSelf )};" );
        }

        // --- Flex-container props (containers only) ---
        if ( isContainer )
        {
            // YogaWrapper engine defaults: direction=Row, justify=Start, align=Stretch.
            if ( a.Direction != FlexDirection.Row
                 && ( baseForDiff is null || a.Direction != baseForDiff.Direction ) )
                lines.Add( $"flex-direction: {Css( a.Direction )};" );
            if ( a.Justify   != JustifyContent.Start
                 && ( baseForDiff is null || a.Justify != baseForDiff.Justify ) )
                lines.Add( $"justify-content: {Css( a.Justify )};" );
            if ( a.Align     != AlignItems.Stretch
                 && ( baseForDiff is null || a.Align != baseForDiff.Align ) )
                lines.Add( $"align-items: {Css( a.Align )};" );
            // Gap only emits when there are >= 2 siblings to space (phantom gap guard).
            if ( a.Gap > 0f && childCount >= 2
                 && ( baseForDiff is null || a.Gap != baseForDiff.Gap ) )
                lines.Add( $"gap: {Px( a.Gap )};" );
            if ( a.Wrap      != FlexWrap.NoWrap
                 && ( baseForDiff is null || a.Wrap != baseForDiff.Wrap ) )
                lines.Add( $"flex-wrap: {Css( a.Wrap )};" );
        }

        if ( !a.Padding.IsAllAuto && !a.Padding.IsDefaultZero
             && ( baseForDiff is null || a.Padding != baseForDiff.Padding ) )
            lines.Add( $"padding: {a.Padding.ToCss()};" );


        if ( a.OverrideTypography )
        {
            if ( !string.IsNullOrEmpty( a.FontFamily )
                 && ( baseForDiff is null || !string.Equals( a.FontFamily, baseForDiff.FontFamily, StringComparison.Ordinal ) ) )
                lines.Add( $"font-family: {a.FontFamily};" );
            if ( a.FontSize.Unit != LengthUnit.Auto
                 && ( baseForDiff is null || !a.FontSize.Equals( baseForDiff.FontSize ) ) )
                lines.Add( $"font-size: {a.FontSize.ToCss()};" );
            if ( baseForDiff is null || a.FontWeight != baseForDiff.FontWeight )
                lines.Add( $"font-weight: {a.FontWeight.ToString( CultureInfo.InvariantCulture )};" );
            if ( baseForDiff is null || !ColorsEqual( a.Color, baseForDiff.Color ) )
                lines.Add( $"color: {a.Color.Hex};" );
            if ( isLabel
                 && ( baseForDiff is null || a.TextAlign != baseForDiff.TextAlign ) )
                lines.Add( $"text-align: {Css( a.TextAlign )};" );
            if ( a.FontStyleItalic
                 && ( baseForDiff is null || a.FontStyleItalic != baseForDiff.FontStyleItalic ) )
                lines.Add( "font-style: italic;" );
            if ( a.TextTransform != TextTransformKind.None
                 && ( baseForDiff is null || a.TextTransform != baseForDiff.TextTransform ) )
                lines.Add( $"text-transform: {Css( a.TextTransform )};" );
            if ( a.LetterSpacing.Unit != LengthUnit.Auto
                 && ( baseForDiff is null || !a.LetterSpacing.Equals( baseForDiff.LetterSpacing ) ) )
                lines.Add( $"letter-spacing: {a.LetterSpacing.ToCss()};" );
            if ( a.LineHeight.Unit    != LengthUnit.Auto
                 && ( baseForDiff is null || !a.LineHeight.Equals( baseForDiff.LineHeight ) ) )
                lines.Add( $"line-height: {a.LineHeight.ToCss()};" );
        }

        if ( a.OverrideBackground )
        {
            if ( baseForDiff is null || !ColorsEqual( a.BackgroundColor, baseForDiff.BackgroundColor ) )
                lines.Add( $"background-color: {a.BackgroundColor.Hex};" );
            var imageEmpty = string.IsNullOrWhiteSpace( a.BackgroundImage );
            if ( imageEmpty )
            {
                var baseAlsoEmptyOverride = baseForDiff is not null
                    && baseForDiff.OverrideBackground
                    && string.IsNullOrWhiteSpace( baseForDiff.BackgroundImage );
                if ( !baseAlsoEmptyOverride )
                    lines.Add( "background-image: none;" );
            }
            else if ( baseForDiff is null || !string.Equals( a.BackgroundImage.Trim(), baseForDiff.BackgroundImage?.Trim(), StringComparison.Ordinal ) )
                lines.Add( $"background-image: {a.BackgroundImage.Trim()};" );
            if ( !string.IsNullOrWhiteSpace( a.BackgroundSize )
                 && ( baseForDiff is null || !string.Equals( a.BackgroundSize.Trim(), baseForDiff.BackgroundSize.Trim(), StringComparison.Ordinal ) ) )
                lines.Add( $"background-size: {a.BackgroundSize.Trim()};" );
            if ( !string.IsNullOrWhiteSpace( a.BackgroundPosition )
                 && ( baseForDiff is null || !string.Equals( a.BackgroundPosition.Trim(), baseForDiff.BackgroundPosition.Trim(), StringComparison.Ordinal ) ) )
                lines.Add( $"background-position: {a.BackgroundPosition.Trim()};" );
            if ( !string.IsNullOrWhiteSpace( a.BackgroundRepeat )
                 && ( baseForDiff is null || !string.Equals( a.BackgroundRepeat.Trim(), baseForDiff.BackgroundRepeat.Trim(), StringComparison.Ordinal ) ) )
                lines.Add( $"background-repeat: {a.BackgroundRepeat.Trim()};" );
        }

        if ( a.OverrideBorder )
        {
            if ( a.BorderRadius.Unit != LengthUnit.Auto
                 && ( baseForDiff is null || !a.BorderRadius.Equals( baseForDiff.BorderRadius ) ) )
                lines.Add( $"border-radius: {a.BorderRadius.ToCss()};" );
            var hasVisibleBorder = a.BorderWidth.Unit != LengthUnit.Auto && a.BorderWidth.Value != 0f;
            if ( hasVisibleBorder
                 && ( baseForDiff is null || !a.BorderWidth.Equals( baseForDiff.BorderWidth ) || !ColorsEqual( a.BorderColor, baseForDiff.BorderColor ) ) )
            {
                if ( baseForDiff is null || !a.BorderWidth.Equals( baseForDiff.BorderWidth ) )
                    lines.Add( $"border-width: {a.BorderWidth.ToCss()};" );
                if ( baseForDiff is null || !ColorsEqual( a.BorderColor, baseForDiff.BorderColor ) )
                    lines.Add( $"border-color: {a.BorderColor.Hex};" );
            }
        }

        if ( a.OverrideEffects )
        {
            var inset = a.BoxShadowInset ? " inset" : "";
            if ( baseForDiff is null
                 || !a.BoxShadowX.Equals( baseForDiff.BoxShadowX )
                 || !a.BoxShadowY.Equals( baseForDiff.BoxShadowY )
                 || !a.BoxShadowBlur.Equals( baseForDiff.BoxShadowBlur )
                 || !ColorsEqual( a.BoxShadowColor, baseForDiff.BoxShadowColor )
                 || a.BoxShadowInset != baseForDiff.BoxShadowInset )
                lines.Add( $"box-shadow: {a.BoxShadowX.ToCss()} {a.BoxShadowY.ToCss()} {a.BoxShadowBlur.ToCss()} {a.BoxShadowColor.Hex}{inset};" );
            if ( baseForDiff is null || a.Opacity != baseForDiff.Opacity )
                lines.Add( $"opacity: {a.Opacity.ToString( "0.##", CultureInfo.InvariantCulture )};" );
        }

        if ( a.OverrideConstraints )
        {
            if ( !isRoot && !a.Margin.IsAllAuto && !a.Margin.IsDefaultZero
                 && ( baseForDiff is null || a.Margin != baseForDiff.Margin ) )
                lines.Add( $"margin: {a.Margin.ToCss()};" );
            if ( a.MinWidth.Unit  != LengthUnit.Auto
                 && ( baseForDiff is null || !a.MinWidth.Equals( baseForDiff.MinWidth ) ) )
                lines.Add( $"min-width: {a.MinWidth.ToCss()};" );
            if ( a.MaxWidth.Unit  != LengthUnit.Auto
                 && ( baseForDiff is null || !a.MaxWidth.Equals( baseForDiff.MaxWidth ) ) )
                lines.Add( $"max-width: {a.MaxWidth.ToCss()};" );
            if ( a.MinHeight.Unit != LengthUnit.Auto
                 && ( baseForDiff is null || !a.MinHeight.Equals( baseForDiff.MinHeight ) ) )
                lines.Add( $"min-height: {a.MinHeight.ToCss()};" );
            if ( a.MaxHeight.Unit != LengthUnit.Auto
                 && ( baseForDiff is null || !a.MaxHeight.Equals( baseForDiff.MaxHeight ) ) )
                lines.Add( $"max-height: {a.MaxHeight.ToCss()};" );
        }

        if ( isCheckbox && checkboxSize.Unit != LengthUnit.Auto )
        {
            var glyphFontSize = new Length( checkboxSize.Value * 0.75f, checkboxSize.Unit );
            lines.Add( "> .checkmark {" );
            lines.Add( $"    width: {checkboxSize.ToCss()};" );
            lines.Add( $"    height: {checkboxSize.ToCss()};" );
            lines.Add( $"    font-size: {glyphFontSize.ToCss()};" );
            lines.Add( "}" );
        }

        // --- Interaction (gated by OverrideInteraction) ---
        if ( a.OverrideInteraction )
        {
            if ( baseForDiff is null || a.Cursor != baseForDiff.Cursor )
                lines.Add( $"cursor: {Css( a.Cursor )};" );
            if ( baseForDiff is null || a.Overflow != baseForDiff.Overflow )
                lines.Add( $"overflow: {Css( a.Overflow )};" );
            // Interaction extras (Tier 3 — grd-7t2z). Gated on non-default for byte-stability.
            if ( a.ZIndex != 0
                 && ( baseForDiff is null || a.ZIndex != baseForDiff.ZIndex ) )
                lines.Add( $"z-index: {a.ZIndex.ToString( CultureInfo.InvariantCulture )};" );
            if ( !a.PointerEvents
                 && ( baseForDiff is null || a.PointerEvents != baseForDiff.PointerEvents ) )
                lines.Add( "pointer-events: none;" );
        }

        return lines;
    }

    private static bool ColorsEqual( Color x, Color y ) =>
        string.Equals( x.Hex, y.Hex, StringComparison.Ordinal );
}