<?php
/**
 * Core converter: Figma tree → logical rows/columns with depth-budget flattening.
 */

if ( ! defined( 'ABSPATH' ) ) exit;

class FTE_Converter {

    private FTE_Row_Detector  $detector;
    private FTE_Widget_Mapper $mapper;
    private string $flatten_strategy;

    public function __construct( string $flatten_strategy = 'serialize' ) {
        $this->detector         = new FTE_Row_Detector();
        $this->mapper           = new FTE_Widget_Mapper();
        $this->flatten_strategy = $flatten_strategy;
    }

    /**
     * Convert a Figma frame into a flat list of logical rows (= Elementor sections).
     *
     * @param array $root_frame Top-level Figma frame node.
     * @return array[]
     */
    public function convert( array $root_frame ): array {
        $rows = $this->detector->detect_rows( $root_frame );

        return array_map(
            fn( $row ) => $this->convert_row( $row, $root_frame, 2 ),
            $rows
        );
    }

    // -----------------------------------------------------------------------
    // Recursive descent with depth budget
    //
    // Budget 2 = can emit Section (top-level)
    // Budget 1 = can emit Inner Section
    // Budget 0 = must flatten to widgets only
    // -----------------------------------------------------------------------

    private function convert_row( array $row, array $parent, int $depth_budget ): array {
        $columns = $this->detector->detect_columns( $row, $parent );

        $logical_cols = [];
        foreach ( $columns as $col ) {
            $child_nodes = $this->convert_node_children( $col['node'], $depth_budget - 1 );
            $logical_cols[] = [
                'type'     => 'column',
                'widthPct' => $col['widthPct'],
                'children' => $child_nodes,
                'settings' => $this->extract_column_settings( $col['node'] ),
            ];
        }

        return [
            'type'     => 'row',
            'children' => $logical_cols,
            'gap'      => $parent['itemSpacing'] ?? 0,
            'settings' => $this->extract_row_settings( $parent ),
        ];
    }

    /**
     * Convert a node's children into logical nodes.
     * Decides whether to create sub-rows, flatten, or produce widgets.
     */
    private function convert_node_children( array $node, int $depth_budget ): array {
        if ( $this->detector->is_leaf_widget( $node ) ) {
            return [ $this->mapper->map( $node ) ];
        }
        if ( in_array( $node['type'] ?? '', [ 'TEXT', 'VECTOR' ], true ) ) {
            return [ $this->mapper->map( $node ) ];
        }

        if ( $this->mapper->is_vector_group( $node ) ) {
            return [ $this->mapper->map( $node ) ];
        }

        $sub_rows = $this->detector->detect_rows( $node );

        // Depth exhausted → flatten
        if ( $depth_budget <= 0 ) {
            return $this->flatten( $node, $sub_rows );
        }

        $result = [];
        foreach ( $sub_rows as $sub_row ) {
            if (
                count( $sub_row['children'] ) === 1
                && $this->detector->is_leaf_widget( $sub_row['children'][0] )
            ) {
                $result[] = $this->mapper->map( $sub_row['children'][0] );
            } elseif (
                count( $sub_row['children'] ) === 1
                && $this->is_simple_container( $sub_row['children'][0] )
            ) {
                // Collapse transparent wrappers
                $inner = $this->convert_node_children( $sub_row['children'][0], $depth_budget );
                array_push( $result, ...$inner );
            } else {
                $result[] = $this->convert_row( $sub_row, $node, $depth_budget );
            }
        }
        return $result;
    }

    // -----------------------------------------------------------------------
    // Flattening strategies
    // -----------------------------------------------------------------------

    private function flatten( array $node, array $rows ): array {
        if ( $this->flatten_strategy === 'rasterize' ) {
            $box = $node['absoluteBoundingBox'] ?? [ 'width' => 300, 'height' => 200 ];
            return [ [
                'type'        => 'rasterized',
                'figmaNodeId' => $node['id'],
                'width'       => $box['width'],
                'height'      => $box['height'],
            ] ];
        }

        // Default: serialize (depth-first widget collection)
        return $this->serialize_to_widgets( $rows );
    }

    private function serialize_to_widgets( array $rows ): array {
        $widgets = [];
        foreach ( $rows as $row ) {
            foreach ( $row['children'] as $child ) {
                if ( $this->detector->is_leaf_widget( $child ) ) {
                    $widgets[] = $this->mapper->map( $child );
                } else {
                    array_push( $widgets, ...$this->collect_leaf_widgets( $child ) );
                }
            }
        }
        return $widgets;
    }

    private function collect_leaf_widgets( array $node ): array {
        if ( $this->detector->is_leaf_widget( $node ) ) {
            return [ $this->mapper->map( $node ) ];
        }
        if ( $this->mapper->is_vector_group( $node ) ) {
            return [ $this->mapper->map( $node ) ];
        }
        $results = [];
        foreach ( $node['children'] ?? [] as $child ) {
            if ( ( $child['visible'] ?? true ) === false ) continue;
            array_push( $results, ...$this->collect_leaf_widgets( $child ) );
        }
        return $results;

    }
    // -----------------------------------------------------------------------
    // Settings extraction
    // -----------------------------------------------------------------------

    private function extract_row_settings( array $node ): array {
        $s = [];

        foreach ( $node['fills'] ?? [] as $fill ) {
            if ( $fill['type'] === 'SOLID' && isset( $fill['color'] ) ) {
                $s['backgroundColor'] = FTE_Widget_Mapper::figma_color_to_hex( $fill['color'] );
                break;
            }
        }

        foreach ( [ 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight' ] as $key ) {
            if ( ! empty( $node[ $key ] ) ) $s[ $key ] = $node[ $key ];
        }

        if ( ! empty( $node['counterAxisAlignItems'] ) ) {
            $map = [ 'MIN' => 'top', 'CENTER' => 'center', 'MAX' => 'bottom' ];
            $s['contentPosition'] = $map[ $node['counterAxisAlignItems'] ] ?? 'top';
        }

        $gap = $node['itemSpacing'] ?? 0;
        if ( $gap === 0 )       $s['gap'] = 'no';
        elseif ( $gap <= 5 )    $s['gap'] = 'narrow';
        elseif ( $gap <= 15 )   $s['gap'] = 'default';
        elseif ( $gap <= 25 )   $s['gap'] = 'extended';
        elseif ( $gap <= 35 )   $s['gap'] = 'wide';
        else                    $s['gap'] = 'wider';

        return $s;
    }

    private function extract_column_settings( array $node ): array {
        $s = [];

        foreach ( $node['fills'] ?? [] as $fill ) {
            if ( $fill['type'] === 'SOLID' && isset( $fill['color'] ) ) {
                $s['backgroundColor'] = FTE_Widget_Mapper::figma_color_to_hex( $fill['color'] );
                break;
            }
        }

        foreach ( [ 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight' ] as $key ) {
            if ( ! empty( $node[ $key ] ) ) $s[ $key ] = $node[ $key ];
        }

        if ( ! empty( $node['primaryAxisAlignItems'] ) && ( $node['layoutMode'] ?? '' ) === 'VERTICAL' ) {
            $map = [ 'MIN' => 'top', 'CENTER' => 'middle', 'MAX' => 'bottom' ];
            $s['verticalAlign'] = $map[ $node['primaryAxisAlignItems'] ] ?? 'top';
        }

        return $s;
    }

    private function is_simple_container( array $node ): bool {
        if ( ! in_array( $node['type'] ?? '', [ 'FRAME', 'GROUP' ], true ) ) return false;
        $has_bg = false;
        foreach ( $node['fills'] ?? [] as $f ) {
            if ( $f['type'] === 'SOLID' && isset( $f['color'] ) && ( $f['color']['a'] ?? 1 ) > 0 ) {
                $has_bg = true; break;
            }
        }
        return ! $has_bg && ( $node['strokeWeight'] ?? 0 ) == 0;
    }
}

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *


Notice: ob_end_flush(): Failed to send buffer of zlib output compression (1) in /home/greenspacecoil/public_html/wp-includes/functions.php on line 5493

Notice: ob_end_flush(): Failed to send buffer of zlib output compression (1) in /home/greenspacecoil/public_html/wp-includes/functions.php on line 5493