const std = @import("std");
const bun = @import("bun");
const Allocator = std.mem.Allocator;

pub const css = @import("../css_parser.zig");
const Property = css.Property;
const PropertyIdTag = css.PropertyIdTag;
const PropertyCategory = css.logical.PropertyCategory;



const LengthPercentageOrAuto = css.css_values.length.LengthPercentageOrAuto;

/// A value for the [inset](https://drafts.csswg.org/css-logical/#propdef-inset) shorthand property.
pub const Inset = struct {
    top: LengthPercentageOrAuto,
    right: LengthPercentageOrAuto,
    bottom: LengthPercentageOrAuto,
    left: LengthPercentageOrAuto,

    // TODO: bring this back
    // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.inset);
    const css_impl = css.DefineRectShorthand(@This(), LengthPercentageOrAuto);
    pub const toCss = css_impl.toCss;
    pub const parse = css_impl.parse;

    pub const PropertyFieldMap = .{
        .top = css.PropertyIdTag.top,
        .right = css.PropertyIdTag.right,
        .bottom = css.PropertyIdTag.bottom,
        .left = css.PropertyIdTag.left,
    };

    pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
        return css.implementDeepClone(@This(), this, allocator);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

/// A value for the [inset-block](https://drafts.csswg.org/css-logical/#propdef-inset-block) shorthand property.
pub const InsetBlock = struct {
    /// The block start value.
    block_start: LengthPercentageOrAuto,
    /// The block end value.
    block_end: LengthPercentageOrAuto,

    // TODO: bring this back
    // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"inset-block");
    const css_impl = css.DefineSizeShorthand(@This(), LengthPercentageOrAuto);
    pub const toCss = css_impl.toCss;
    pub const parse = css_impl.parse;

    pub const PropertyFieldMap = .{
        .block_start = css.PropertyIdTag.@"inset-block-start",
        .block_end = css.PropertyIdTag.@"inset-block-end",
    };

    pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
        return css.implementDeepClone(@This(), this, allocator);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

/// A value for the [inset-inline](https://drafts.csswg.org/css-logical/#propdef-inset-inline) shorthand property.
pub const InsetInline = struct {
    /// The inline start value.
    inline_start: LengthPercentageOrAuto,
    /// The inline end value.
    inline_end: LengthPercentageOrAuto,

    pub const PropertyFieldMap = .{
        .inline_start = css.PropertyIdTag.@"inset-inline-start",
        .inline_end = css.PropertyIdTag.@"inset-inline-end",
    };

    // TODO: bring this back
    // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"inset-inline");
    const css_impl = css.DefineSizeShorthand(@This(), LengthPercentageOrAuto);
    pub const toCss = css_impl.toCss;
    pub const parse = css_impl.parse;

    pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
        return css.implementDeepClone(@This(), this, allocator);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

/// A value for the [margin-block](https://drafts.csswg.org/css-logical/#propdef-margin-block) shorthand property.
pub const MarginBlock = struct {
    /// The block start value.
    block_start: LengthPercentageOrAuto,
    /// The block end value.
    block_end: LengthPercentageOrAuto,

    // TODO: bring this back
    // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"margin-block");
    const css_impl = css.DefineSizeShorthand(@This(), LengthPercentageOrAuto);
    pub const toCss = css_impl.toCss;
    pub const parse = css_impl.parse;

    pub const PropertyFieldMap = .{
        .block_start = css.PropertyIdTag.@"margin-block-start",
        .block_end = css.PropertyIdTag.@"margin-block-end",
    };

    pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
        return css.implementDeepClone(@This(), this, allocator);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

/// A value for the [margin-inline](https://drafts.csswg.org/css-logical/#propdef-margin-inline) shorthand property.
pub const MarginInline = struct {
    /// The inline start value.
    inline_start: LengthPercentageOrAuto,
    /// The inline end value.
    inline_end: LengthPercentageOrAuto,

    // TODO: bring this back
    // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"margin-inline");
    const css_impl = css.DefineSizeShorthand(@This(), LengthPercentageOrAuto);
    pub const toCss = css_impl.toCss;
    pub const parse = css_impl.parse;

    pub const PropertyFieldMap = .{
        .inline_start = css.PropertyIdTag.@"margin-inline-start",
        .inline_end = css.PropertyIdTag.@"margin-inline-end",
    };

    pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
        return css.implementDeepClone(@This(), this, allocator);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

/// A value for the [margin](https://drafts.csswg.org/css-box-4/#propdef-margin) shorthand property.
pub const Margin = struct {
    top: LengthPercentageOrAuto,
    right: LengthPercentageOrAuto,
    bottom: LengthPercentageOrAuto,
    left: LengthPercentageOrAuto,

    // TODO: bring this back
    // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.margin);
    const css_impl = css.DefineRectShorthand(@This(), LengthPercentageOrAuto);
    pub const toCss = css_impl.toCss;
    pub const parse = css_impl.parse;

    pub const PropertyFieldMap = .{
        .top = css.PropertyIdTag.@"margin-top",
        .right = css.PropertyIdTag.@"margin-right",
        .bottom = css.PropertyIdTag.@"margin-bottom",
        .left = css.PropertyIdTag.@"margin-left",
    };

    pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
        return css.implementDeepClone(@This(), this, allocator);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

/// A value for the [padding-block](https://drafts.csswg.org/css-logical/#propdef-padding-block) shorthand property.
pub const PaddingBlock = struct {
    /// The block start value.
    block_start: LengthPercentageOrAuto,
    /// The block end value.
    block_end: LengthPercentageOrAuto,

    // TODO: bring this back
    // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"padding-block");
    const css_impl = css.DefineSizeShorthand(@This(), LengthPercentageOrAuto);
    pub const toCss = css_impl.toCss;
    pub const parse = css_impl.parse;

    pub const PropertyFieldMap = .{
        .block_start = css.PropertyIdTag.@"padding-block-start",
        .block_end = css.PropertyIdTag.@"padding-block-end",
    };

    pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
        return css.implementDeepClone(@This(), this, allocator);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

/// A value for the [padding-inline](https://drafts.csswg.org/css-logical/#propdef-padding-inline) shorthand property.
pub const PaddingInline = struct {
    /// The inline start value.
    inline_start: LengthPercentageOrAuto,
    /// The inline end value.
    inline_end: LengthPercentageOrAuto,

    // TODO: bring this back
    // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"padding-inline");
    const css_impl = css.DefineSizeShorthand(@This(), LengthPercentageOrAuto);
    pub const toCss = css_impl.toCss;
    pub const parse = css_impl.parse;

    pub const PropertyFieldMap = .{
        .inline_start = css.PropertyIdTag.@"padding-inline-start",
        .inline_end = css.PropertyIdTag.@"padding-inline-end",
    };

    pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
        return css.implementDeepClone(@This(), this, allocator);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

/// A value for the [padding](https://drafts.csswg.org/css-box-4/#propdef-padding) shorthand property.
pub const Padding = struct {
    top: LengthPercentageOrAuto,
    right: LengthPercentageOrAuto,
    bottom: LengthPercentageOrAuto,
    left: LengthPercentageOrAuto,

    // TODO: bring this back
    // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.padding);
    const css_impl = css.DefineRectShorthand(@This(), LengthPercentageOrAuto);
    pub const toCss = css_impl.toCss;
    pub const parse = css_impl.parse;

    pub const PropertyFieldMap = .{
        .top = css.PropertyIdTag.@"padding-top",
        .right = css.PropertyIdTag.@"padding-right",
        .bottom = css.PropertyIdTag.@"padding-bottom",
        .left = css.PropertyIdTag.@"padding-left",
    };

    pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
        return css.implementDeepClone(@This(), this, allocator);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

/// A value for the [scroll-margin-block](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-margin-block) shorthand property.
pub const ScrollMarginBlock = struct {
    /// The block start value.
    block_start: LengthPercentageOrAuto,
    /// The block end value.
    block_end: LengthPercentageOrAuto,

    // TODO: bring this back
    // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"scroll-margin-block");
    const css_impl = css.DefineSizeShorthand(@This(), LengthPercentageOrAuto);
    pub const toCss = css_impl.toCss;
    pub const parse = css_impl.parse;

    pub const PropertyFieldMap = .{
        .block_start = css.PropertyIdTag.@"scroll-margin-block-start",
        .block_end = css.PropertyIdTag.@"scroll-margin-block-end",
    };

    pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
        return css.implementDeepClone(@This(), this, allocator);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

/// A value for the [scroll-margin-inline](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-margin-inline) shorthand property.
pub const ScrollMarginInline = struct {
    /// The inline start value.
    inline_start: LengthPercentageOrAuto,
    /// The inline end value.
    inline_end: LengthPercentageOrAuto,

    // TODO: bring this back
    // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"scroll-margin-inline");
    const css_impl = css.DefineSizeShorthand(@This(), LengthPercentageOrAuto);
    pub const toCss = css_impl.toCss;
    pub const parse = css_impl.parse;

    pub const PropertyFieldMap = .{
        .inline_start = css.PropertyIdTag.@"scroll-margin-inline-start",
        .inline_end = css.PropertyIdTag.@"scroll-margin-inline-end",
    };

    pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
        return css.implementDeepClone(@This(), this, allocator);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

/// A value for the [scroll-margin](https://drafts.csswg.org/css-scroll-snap/#scroll-margin) shorthand property.
pub const ScrollMargin = struct {
    top: LengthPercentageOrAuto,
    right: LengthPercentageOrAuto,
    bottom: LengthPercentageOrAuto,
    left: LengthPercentageOrAuto,

    // TODO: bring this back
    // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"scroll-margin");
    const css_impl = css.DefineRectShorthand(@This(), LengthPercentageOrAuto);
    pub const toCss = css_impl.toCss;
    pub const parse = css_impl.parse;

    pub const PropertyFieldMap = .{
        .top = css.PropertyIdTag.@"scroll-margin-top",
        .right = css.PropertyIdTag.@"scroll-margin-right",
        .bottom = css.PropertyIdTag.@"scroll-margin-bottom",
        .left = css.PropertyIdTag.@"scroll-margin-left",
    };

    pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
        return css.implementDeepClone(@This(), this, allocator);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

/// A value for the [scroll-padding-block](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-padding-block) shorthand property.
pub const ScrollPaddingBlock = struct {
    /// The block start value.
    block_start: LengthPercentageOrAuto,
    /// The block end value.
    block_end: LengthPercentageOrAuto,

    // TODO: bring this back
    // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"scroll-padding-block");
    const css_impl = css.DefineSizeShorthand(@This(), LengthPercentageOrAuto);
    pub const toCss = css_impl.toCss;
    pub const parse = css_impl.parse;

    pub const PropertyFieldMap = .{
        .block_start = css.PropertyIdTag.@"scroll-padding-block-start",
        .block_end = css.PropertyIdTag.@"scroll-padding-block-end",
    };

    pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
        return css.implementDeepClone(@This(), this, allocator);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

/// A value for the [scroll-padding-inline](https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-padding-inline) shorthand property.
pub const ScrollPaddingInline = struct {
    /// The inline start value.
    inline_start: LengthPercentageOrAuto,
    /// The inline end value.
    inline_end: LengthPercentageOrAuto,

    // TODO: bring this back
    // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"scroll-padding-inline");
    const css_impl = css.DefineSizeShorthand(@This(), LengthPercentageOrAuto);
    pub const toCss = css_impl.toCss;
    pub const parse = css_impl.parse;

    pub const PropertyFieldMap = .{
        .inline_start = css.PropertyIdTag.@"scroll-padding-inline-start",
        .inline_end = css.PropertyIdTag.@"scroll-padding-inline-end",
    };

    pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
        return css.implementDeepClone(@This(), this, allocator);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

/// A value for the [scroll-padding](https://drafts.csswg.org/css-scroll-snap/#scroll-padding) shorthand property.
pub const ScrollPadding = struct {
    top: LengthPercentageOrAuto,
    right: LengthPercentageOrAuto,
    bottom: LengthPercentageOrAuto,
    left: LengthPercentageOrAuto,

    // TODO: bring this back
    // (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"scroll-padding");
    const css_impl = css.DefineRectShorthand(@This(), LengthPercentageOrAuto);
    pub const toCss = css_impl.toCss;
    pub const parse = css_impl.parse;

    pub const PropertyFieldMap = .{
        .top = css.PropertyIdTag.@"scroll-padding-top",
        .right = css.PropertyIdTag.@"scroll-padding-right",
        .bottom = css.PropertyIdTag.@"scroll-padding-bottom",
        .left = css.PropertyIdTag.@"scroll-padding-left",
    };

    pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
        return css.implementDeepClone(@This(), this, allocator);
    }

    pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
        return css.implementEql(@This(), lhs, rhs);
    }
};

pub const MarginHandler = NewSizeHandler(
    PropertyIdTag.@"margin-top",
    PropertyIdTag.@"margin-bottom",
    PropertyIdTag.@"margin-left",
    PropertyIdTag.@"margin-right",
    PropertyIdTag.@"margin-block-start",
    PropertyIdTag.@"margin-block-end",
    PropertyIdTag.@"margin-inline-start",
    PropertyIdTag.@"margin-inline-end",
    PropertyIdTag.margin,
    PropertyIdTag.@"margin-block",
    PropertyIdTag.@"margin-inline",
    PropertyCategory.physical,
    .{
        .feature = css.Feature.logical_margin,
        .shorthand_feature = css.Feature.logical_margin_shorthand,
    },
);

pub const PaddingHandler = NewSizeHandler(
    PropertyIdTag.@"padding-top",
    PropertyIdTag.@"padding-bottom",
    PropertyIdTag.@"padding-left",
    PropertyIdTag.@"padding-right",
    PropertyIdTag.@"padding-block-start",
    PropertyIdTag.@"padding-block-end",
    PropertyIdTag.@"padding-inline-start",
    PropertyIdTag.@"padding-inline-end",
    PropertyIdTag.padding,
    PropertyIdTag.@"padding-block",
    PropertyIdTag.@"padding-inline",
    PropertyCategory.physical,
    .{
        .feature = css.Feature.logical_padding,
        .shorthand_feature = css.Feature.logical_padding_shorthand,
    },
);

pub const ScrollMarginHandler = NewSizeHandler(
    PropertyIdTag.@"scroll-margin-top",
    PropertyIdTag.@"scroll-margin-bottom",
    PropertyIdTag.@"scroll-margin-left",
    PropertyIdTag.@"scroll-margin-right",
    PropertyIdTag.@"scroll-margin-block-start",
    PropertyIdTag.@"scroll-margin-block-end",
    PropertyIdTag.@"scroll-margin-inline-start",
    PropertyIdTag.@"scroll-margin-inline-end",
    PropertyIdTag.@"scroll-margin",
    PropertyIdTag.@"scroll-margin-block",
    PropertyIdTag.@"scroll-margin-inline",
    PropertyCategory.physical,
    null,
);

pub const InsetHandler = NewSizeHandler(
    PropertyIdTag.top,
    PropertyIdTag.bottom,
    PropertyIdTag.left,
    PropertyIdTag.right,
    PropertyIdTag.@"inset-block-start",
    PropertyIdTag.@"inset-block-end",
    PropertyIdTag.@"inset-inline-start",
    PropertyIdTag.@"inset-inline-end",
    PropertyIdTag.inset,
    PropertyIdTag.@"inset-block",
    PropertyIdTag.@"inset-inline",
    PropertyCategory.physical,
    .{
        .feature = css.Feature.logical_inset,
        .shorthand_feature = css.Feature.logical_inset,
    },
);

pub fn NewSizeHandler(
    comptime top_prop: css.PropertyIdTag,
    comptime bottom_prop: css.PropertyIdTag,
    comptime left_prop: css.PropertyIdTag,
    comptime right_prop: css.PropertyIdTag,
    comptime block_start_prop: css.PropertyIdTag,
    comptime block_end_prop: css.PropertyIdTag,
    comptime inline_start_prop: css.PropertyIdTag,
    comptime inline_end_prop: css.PropertyIdTag,
    comptime shorthand_prop: css.PropertyIdTag,
    comptime block_shorthand: css.PropertyIdTag,
    comptime inline_shorthand: css.PropertyIdTag,
    comptime shorthand_category: css.logical.PropertyCategory,
    comptime shorthand_extra: ?struct { feature: css.compat.Feature, shorthand_feature: css.compat.Feature },
) type {
    return struct {
        top: ?LengthPercentageOrAuto = null,
        bottom: ?LengthPercentageOrAuto = null,
        left: ?LengthPercentageOrAuto = null,
        right: ?LengthPercentageOrAuto = null,
        block_start: ?Property = null,
        block_end: ?Property = null,
        inline_start: ?Property = null,
        inline_end: ?Property = null,
        has_any: bool = false,
        category: css.logical.PropertyCategory = css.logical.PropertyCategory.default(),

        pub fn handleProperty(
            this: *@This(),
            property: *const Property,
            dest: *css.DeclarationList,
            context: *css.PropertyHandlerContext,
        ) bool {
            switch (@as(PropertyIdTag, property.*)) {
                top_prop => this.propertyHelper("top", top_prop.valueType(), &@field(property, @tagName(top_prop)), PropertyCategory.physical, dest, context),
                bottom_prop => this.propertyHelper("bottom", bottom_prop.valueType(), &@field(property, @tagName(bottom_prop)), PropertyCategory.physical, dest, context),
                left_prop => this.propertyHelper("left", left_prop.valueType(), &@field(property, @tagName(left_prop)), PropertyCategory.physical, dest, context),
                right_prop => this.propertyHelper("right", right_prop.valueType(), &@field(property, @tagName(right_prop)), PropertyCategory.physical, dest, context),
                block_start_prop => {
                    this.flushHelper("block_start", block_start_prop.valueType(), &@field(property, @tagName(block_start_prop)), PropertyCategory.logical, dest, context);
                    this.logicalPropertyHelper("block_start", property.deepClone(context.allocator), dest, context);
                },
                block_end_prop => {
                    this.flushHelper("block_end", block_end_prop.valueType(), &@field(property, @tagName(block_end_prop)), PropertyCategory.logical, dest, context);
                    this.logicalPropertyHelper("block_end", property.deepClone(context.allocator), dest, context);
                },
                inline_start_prop => {
                    this.flushHelper("inline_start", inline_start_prop.valueType(), &@field(property, @tagName(inline_start_prop)), PropertyCategory.logical, dest, context);
                    this.logicalPropertyHelper("inline_start", property.deepClone(context.allocator), dest, context);
                },
                inline_end_prop => {
                    this.flushHelper("inline_end", inline_end_prop.valueType(), &@field(property, @tagName(inline_end_prop)), PropertyCategory.logical, dest, context);
                    this.logicalPropertyHelper("inline_end", property.deepClone(context.allocator), dest, context);
                },
                block_shorthand => {
                    const val = &@field(property, @tagName(block_shorthand));
                    this.flushHelper("block_start", block_start_prop.valueType(), &val.block_start, .logical, dest, context);
                    this.flushHelper("block_end", block_end_prop.valueType(), &val.block_end, .logical, dest, context);
                    this.logicalPropertyHelper("block_start", @unionInit(Property, @tagName(block_start_prop), val.block_start.deepClone(context.allocator)), dest, context);
                    this.logicalPropertyHelper("block_end", @unionInit(Property, @tagName(block_end_prop), val.block_end.deepClone(context.allocator)), dest, context);
                },
                inline_shorthand => {
                    const val = &@field(property, @tagName(inline_shorthand));
                    this.flushHelper("inline_start", inline_start_prop.valueType(), &val.inline_start, .logical, dest, context);
                    this.flushHelper("inline_end", inline_end_prop.valueType(), &val.inline_end, .logical, dest, context);
                    this.logicalPropertyHelper("inline_start", @unionInit(Property, @tagName(inline_start_prop), val.inline_start.deepClone(context.allocator)), dest, context);
                    this.logicalPropertyHelper("inline_end", @unionInit(Property, @tagName(inline_end_prop), val.inline_end.deepClone(context.allocator)), dest, context);
                },
                shorthand_prop => {
                    const val = &@field(property, @tagName(shorthand_prop));
                    this.flushHelper("top", top_prop.valueType(), &val.top, shorthand_category, dest, context);
                    this.flushHelper("right", right_prop.valueType(), &val.right, shorthand_category, dest, context);
                    this.flushHelper("bottom", bottom_prop.valueType(), &val.bottom, shorthand_category, dest, context);
                    this.flushHelper("left", left_prop.valueType(), &val.left, shorthand_category, dest, context);
                    this.top = val.top.deepClone(context.allocator);
                    this.right = val.right.deepClone(context.allocator);
                    this.bottom = val.bottom.deepClone(context.allocator);
                    this.left = val.left.deepClone(context.allocator);
                    this.block_start = null;
                    this.block_end = null;
                    this.inline_start = null;
                    this.inline_end = null;
                    this.has_any = true;
                },
                css.PropertyIdTag.unparsed => {
                    switch (property.unparsed.property_id) {
                        top_prop, bottom_prop, left_prop, right_prop, block_start_prop, block_end_prop, inline_start_prop, inline_end_prop, block_shorthand, inline_shorthand, shorthand_prop => {
                            // Even if we weren't able to parse the value (e.g. due to var() references),
                            // we can still add vendor prefixes to the property itself.
                            switch (property.unparsed.property_id) {
                                block_start_prop => this.logicalPropertyHelper("block_start", property.deepClone(context.allocator), dest, context),
                                block_end_prop => this.logicalPropertyHelper("block_end", property.deepClone(context.allocator), dest, context),
                                inline_start_prop => this.logicalPropertyHelper("inline_start", property.deepClone(context.allocator), dest, context),
                                inline_end_prop => this.logicalPropertyHelper("inline_end", property.deepClone(context.allocator), dest, context),
                                else => {
                                    this.flush(dest, context);
                                    dest.append(context.allocator, property.deepClone(context.allocator)) catch unreachable;
                                },
                            }
                        },
                        else => return false,
                    }
                },
                else => return false,
            }

            return true;
        }

        pub fn finalize(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext) void {
            this.flush(dest, context);
        }

        fn flushHelper(
            this: *@This(),
            comptime field: []const u8,
            comptime T: type,
            val: *const T,
            comptime category: PropertyCategory,
            dest: *css.DeclarationList,
            context: *css.PropertyHandlerContext,
        ) void {
            // If the category changes betweet logical and physical,
            // or if the value contains syntax that isn't supported across all targets,
            // preserve the previous value as a fallback.
            if (category != this.category or (@field(this, field) != null and context.targets.browsers != null and !val.isCompatible(context.targets.browsers.?))) {
                this.flush(dest, context);
            }
        }

        fn propertyHelper(
            this: *@This(),
            comptime field: []const u8,
            comptime T: type,
            val: *const T,
            comptime category: PropertyCategory,
            dest: *css.DeclarationList,
            context: *css.PropertyHandlerContext,
        ) void {
            this.flushHelper(field, T, val, category, dest, context);
            @field(this, field) = val.deepClone(context.allocator);
            this.category = category;
            this.has_any = true;
        }

        fn logicalPropertyHelper(
            this: *@This(),
            comptime field: []const u8,
            val: css.Property,
            dest: *css.DeclarationList,
            context: *css.PropertyHandlerContext,
        ) void {
            // Assume unparsed properties might contain unsupported syntax that we must preserve as a fallback.
            if (this.category != PropertyCategory.logical or (@field(this, field) != null and val == .unparsed)) {
                this.flush(dest, context);
            }

            if (@field(this, field)) |*p| p.deinit(context.allocator);
            @field(this, field) = val;
            this.category = PropertyCategory.logical;
            this.has_any = true;
        }

        fn flush(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext) void {
            if (!this.has_any) return;

            this.has_any = false;

            const top = bun.take(&this.top);
            const bottom = bun.take(&this.bottom);
            const left = bun.take(&this.left);
            const right = bun.take(&this.right);
            const logical_supported = if (comptime shorthand_extra != null) !context.shouldCompileLogical(shorthand_extra.?.feature) else true;

            if ((shorthand_category != .logical or logical_supported) and top != null and bottom != null and left != null and right != null) {
                dest.append(
                    context.allocator,
                    @unionInit(
                        Property,
                        @tagName(shorthand_prop),
                        .{
                            .top = top.?,
                            .bottom = bottom.?,
                            .left = left.?,
                            .right = right.?,
                        },
                    ),
                ) catch bun.outOfMemory();
            } else {
                if (top) |t| {
                    dest.append(
                        context.allocator,
                        @unionInit(Property, @tagName(top_prop), t),
                    ) catch bun.outOfMemory();
                }

                if (bottom) |b| {
                    dest.append(
                        context.allocator,
                        @unionInit(Property, @tagName(bottom_prop), b),
                    ) catch bun.outOfMemory();
                }

                if (left) |b| {
                    dest.append(
                        context.allocator,
                        @unionInit(Property, @tagName(left_prop), b),
                    ) catch bun.outOfMemory();
                }

                if (right) |b| {
                    dest.append(
                        context.allocator,
                        @unionInit(Property, @tagName(right_prop), b),
                    ) catch bun.outOfMemory();
                }
            }

            var block_start = bun.take(&this.block_start);
            var block_end = bun.take(&this.block_end);
            var inline_start = bun.take(&this.inline_start);
            var inline_end = bun.take(&this.inline_end);

            if (logical_supported) {
                this.logicalSideHelper(&block_start, &block_end, "block_start", "block_end", block_shorthand, block_start_prop, block_end_prop, logical_supported, dest, context);
            } else {
                this.prop(&block_start, block_start_prop, top_prop, dest, context);
                this.prop(&block_end, block_end_prop, bottom_prop, dest, context);
            }

            if (logical_supported) {
                this.logicalSideHelper(&inline_start, &inline_end, "inline_start", "inline_end", inline_shorthand, inline_start_prop, inline_end_prop, logical_supported, dest, context);
            } else if (inline_start != null or inline_end != null) {
                if (inline_start != null and inline_start.? == @field(Property, @tagName(inline_start_prop)) and inline_end != null and inline_end.? == @field(Property, @tagName(inline_end_prop)) and
                    @field(inline_start.?, @tagName(inline_start_prop)).eql(&@field(inline_end.?, @tagName(inline_end_prop))))
                {
                    this.prop(&inline_start, inline_start_prop, left_prop, dest, context);
                    this.prop(&inline_end, inline_end_prop, right_prop, dest, context);
                } else {
                    this.logicalPropHelper(&inline_start, inline_start_prop, left_prop, right_prop, dest, context);
                    this.logicalPropHelper(&inline_end, inline_end_prop, right_prop, left_prop, dest, context);
                }
            }
        }

        inline fn logicalPropHelper(
            this: *@This(),
            val: *?Property,
            comptime logical: css.PropertyIdTag,
            comptime ltr: css.PropertyIdTag,
            comptime rtl: css.PropertyIdTag,
            dest: *css.DeclarationList,
            context: *css.PropertyHandlerContext,
        ) void {
            _ = this; // autofix
            _ = dest; // autofix
            if (val.*) |*_v| {
                if (@as(css.PropertyIdTag, _v.*) == logical) {
                    const v = &@field(_v, @tagName(logical));
                    context.addLogicalRule(
                        context.allocator,
                        @unionInit(Property, @tagName(ltr), v.deepClone(context.allocator)),
                        @unionInit(Property, @tagName(rtl), v.deepClone(context.allocator)),
                    );
                } else if (_v.* == .unparsed) {
                    const v = &_v.unparsed;
                    context.addLogicalRule(
                        context.allocator,
                        Property{
                            .unparsed = v.withPropertyId(context.allocator, ltr),
                        },
                        Property{
                            .unparsed = v.withPropertyId(context.allocator, rtl),
                        },
                    );
                }
            }
        }

        inline fn logicalSideHelper(
            this: *@This(),
            start: *?Property,
            end: *?Property,
            comptime start_name: []const u8,
            comptime end_name: []const u8,
            comptime shorthand_property: css.PropertyIdTag,
            comptime start_prop: css.PropertyIdTag,
            comptime end_prop: css.PropertyIdTag,
            logical_supported: bool,
            dest: *css.DeclarationList,
            context: *css.PropertyHandlerContext,
        ) void {
            _ = this; // autofix
            const shorthand_supported = logical_supported and if (comptime shorthand_extra != null) !context.shouldCompileLogical(shorthand_extra.?.shorthand_feature) else true;

            if (start.* != null and @as(PropertyIdTag, start.*.?) == start_prop and
                end.* != null and @as(PropertyIdTag, end.*.?) == end_prop and
                shorthand_supported)
            {
                const ValueType = shorthand_property.valueType();
                var value: ValueType = undefined;
                @field(value, start_name) = @field(start.*.?, @tagName(start_prop)).deepClone(context.allocator);
                @field(value, end_name) = @field(end.*.?, @tagName(end_prop)).deepClone(context.allocator);
                if (std.meta.fields(ValueType).len != 2) {
                    @compileError(@typeName(ValueType) ++ " has more than two fields. This could cause undefined memory.");
                }

                dest.append(context.allocator, @unionInit(
                    Property,
                    @tagName(shorthand_property),
                    value,
                )) catch bun.outOfMemory();
            } else {
                if (start.* != null) {
                    dest.append(context.allocator, start.*.?) catch bun.outOfMemory();
                }
                if (end.* != null) {
                    dest.append(context.allocator, end.*.?) catch bun.outOfMemory();
                }
            }
        }

        inline fn prop(
            this: *@This(),
            val: *?Property,
            comptime logical: css.PropertyIdTag,
            comptime physical: css.PropertyIdTag,
            dest: *css.DeclarationList,
            context: *css.PropertyHandlerContext,
        ) void {
            _ = this; // autofix
            if (val.*) |*v| {
                if (@as(css.PropertyIdTag, v.*) == logical) {
                    dest.append(
                        context.allocator,
                        @unionInit(
                            Property,
                            @tagName(physical),
                            @field(v, @tagName(logical)),
                        ),
                    ) catch bun.outOfMemory();
                } else if (v.* == .unparsed) {
                    dest.append(
                        context.allocator,
                        Property{
                            .unparsed = v.unparsed.withPropertyId(context.allocator, physical),
                        },
                    ) catch bun.outOfMemory();
                }
            }
        }
    };
}
