# Implementation Plan: Level 1 Meta Compatibility

## Objective
Make WooNooW listen to ALL standard WordPress/WooCommerce hooks for custom meta fields automatically.

## Principles (From Documentation Review)

### From ADDON_BRIDGE_PATTERN.md:
1. ✅ WooNooW Core = Zero addon dependencies
2. ✅ We listen to WP/WooCommerce hooks (NOT WooNooW-specific)
3. ✅ Community does NOTHING extra
4. ❌ We do NOT support specific plugins
5. ❌ We do NOT integrate plugins into core

### From ADDON_DEVELOPMENT_GUIDE.md:
1. ✅ Hook system for functional extensions
2. ✅ Zero coupling with core
3. ✅ WordPress-style filters and actions

### From ADDON_REACT_INTEGRATION.md:
1. ✅ Expose React runtime on window
2. ✅ Support vanilla JS/jQuery addons
3. ✅ No build process required for simple addons

---

## Implementation Strategy

### Phase 1: Backend API Enhancement (2-3 days)

#### 1.1 OrdersController - Expose Meta Data

**File:** `includes/Api/OrdersController.php`

**Changes:**
```php
public static function show(WP_REST_Request $req) {
    $order = wc_get_order($id);
    
    // ... existing data ...
    
    // Expose meta data (Level 1 compatibility)
    $meta_data = self::get_order_meta_data($order);
    $data['meta'] = $meta_data;
    
    // Allow plugins to modify response
    $data = apply_filters('woonoow/order_api_data', $data, $order, $req);
    
    return new WP_REST_Response($data, 200);
}

/**
 * Get order meta data for API exposure
 * Filters out internal meta unless explicitly allowed
 */
private static function get_order_meta_data($order) {
    $meta_data = [];
    
    foreach ($order->get_meta_data() as $meta) {
        $key = $meta->key;
        $value = $meta->value;
        
        // Skip internal WooCommerce meta (starts with _wc_)
        if (strpos($key, '_wc_') === 0) {
            continue;
        }
        
        // Public meta (no underscore) - always expose
        if (strpos($key, '_') !== 0) {
            $meta_data[$key] = $value;
            continue;
        }
        
        // Private meta (starts with _) - check if allowed
        $allowed_private = apply_filters('woonoow/order_allowed_private_meta', [
            // Common shipping tracking fields
            '_tracking_number',
            '_tracking_provider',
            '_tracking_url',
            '_shipment_tracking_items',
            '_wc_shipment_tracking_items',
            
            // Allow plugins to add their meta
        ], $order);
        
        if (in_array($key, $allowed_private, true)) {
            $meta_data[$key] = $value;
        }
    }
    
    return $meta_data;
}
```

**Update Method:**
```php
public static function update(WP_REST_Request $req) {
    $order = wc_get_order($id);
    $data = $req->get_json_params();
    
    // ... existing update logic ...
    
    // Update custom meta fields (Level 1 compatibility)
    if (isset($data['meta']) && is_array($data['meta'])) {
        self::update_order_meta_data($order, $data['meta']);
    }
    
    $order->save();
    
    // Allow plugins to perform additional updates
    do_action('woonoow/order_updated', $order, $data, $req);
    
    return new WP_REST_Response(['success' => true], 200);
}

/**
 * Update order meta data from API
 */
private static function update_order_meta_data($order, $meta_updates) {
    // Get allowed updatable meta keys
    $allowed = apply_filters('woonoow/order_updatable_meta', [
        '_tracking_number',
        '_tracking_provider',
        '_tracking_url',
        // Allow plugins to add their meta
    ], $order);
    
    foreach ($meta_updates as $key => $value) {
        // Public meta (no underscore) - always allow
        if (strpos($key, '_') !== 0) {
            $order->update_meta_data($key, $value);
            continue;
        }
        
        // Private meta - check if allowed
        if (in_array($key, $allowed, true)) {
            $order->update_meta_data($key, $value);
        }
    }
}
```

#### 1.2 ProductsController - Expose Meta Data

**File:** `includes/Api/ProductsController.php`

**Changes:** (Same pattern as OrdersController)
```php
public static function get_product(WP_REST_Request $request) {
    $product = wc_get_product($id);
    
    // ... existing data ...
    
    // Expose meta data (Level 1 compatibility)
    $meta_data = self::get_product_meta_data($product);
    $data['meta'] = $meta_data;
    
    // Allow plugins to modify response
    $data = apply_filters('woonoow/product_api_data', $data, $product, $request);
    
    return new WP_REST_Response($data, 200);
}

private static function get_product_meta_data($product) {
    // Same logic as orders
}

public static function update_product(WP_REST_Request $request) {
    // ... existing logic ...
    
    if (isset($data['meta']) && is_array($data['meta'])) {
        self::update_product_meta_data($product, $data['meta']);
    }
    
    do_action('woonoow/product_updated', $product, $data, $request);
}
```

---

### Phase 2: Frontend Components (3-4 days)

#### 2.1 MetaFields Component

**File:** `admin-spa/src/components/MetaFields.tsx`

**Purpose:** Generic component to display/edit meta fields

```tsx
interface MetaField {
  key: string;
  label: string;
  type: 'text' | 'textarea' | 'number' | 'select' | 'date' | 'checkbox';
  options?: Array<{value: string; label: string}>;
  section?: string;
  description?: string;
  placeholder?: string;
}

interface MetaFieldsProps {
  meta: Record<string, any>;
  fields: MetaField[];
  onChange: (key: string, value: any) => void;
  readOnly?: boolean;
}

export function MetaFields({ meta, fields, onChange, readOnly }: MetaFieldsProps) {
  if (fields.length === 0) return null;
  
  // Group fields by section
  const sections = fields.reduce((acc, field) => {
    const section = field.section || 'Additional Fields';
    if (!acc[section]) acc[section] = [];
    acc[section].push(field);
    return acc;
  }, {} as Record<string, MetaField[]>);
  
  return (
    <div className="space-y-6">
      {Object.entries(sections).map(([section, sectionFields]) => (
        <Card key={section}>
          <CardHeader>
            <CardTitle>{section}</CardTitle>
          </CardHeader>
          <CardContent className="space-y-4">
            {sectionFields.map(field => (
              <div key={field.key}>
                <Label htmlFor={field.key}>
                  {field.label}
                  {field.description && (
                    <span className="text-xs text-muted-foreground ml-2">
                      {field.description}
                    </span>
                  )}
                </Label>
                
                {field.type === 'text' && (
                  <Input
                    id={field.key}
                    value={meta[field.key] || ''}
                    onChange={(e) => onChange(field.key, e.target.value)}
                    disabled={readOnly}
                    placeholder={field.placeholder}
                  />
                )}
                
                {field.type === 'textarea' && (
                  <Textarea
                    id={field.key}
                    value={meta[field.key] || ''}
                    onChange={(e) => onChange(field.key, e.target.value)}
                    disabled={readOnly}
                    placeholder={field.placeholder}
                    rows={4}
                  />
                )}
                
                {field.type === 'number' && (
                  <Input
                    id={field.key}
                    type="number"
                    value={meta[field.key] || ''}
                    onChange={(e) => onChange(field.key, e.target.value)}
                    disabled={readOnly}
                    placeholder={field.placeholder}
                  />
                )}
                
                {field.type === 'select' && field.options && (
                  <Select
                    value={meta[field.key] || ''}
                    onValueChange={(value) => onChange(field.key, value)}
                    disabled={readOnly}
                  >
                    <SelectTrigger id={field.key}>
                      <SelectValue placeholder={field.placeholder || 'Select...'} />
                    </SelectTrigger>
                    <SelectContent>
                      {field.options.map(opt => (
                        <SelectItem key={opt.value} value={opt.value}>
                          {opt.label}
                        </SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                )}
                
                {field.type === 'checkbox' && (
                  <div className="flex items-center space-x-2">
                    <Checkbox
                      id={field.key}
                      checked={!!meta[field.key]}
                      onCheckedChange={(checked) => onChange(field.key, checked)}
                      disabled={readOnly}
                    />
                    <label htmlFor={field.key} className="text-sm cursor-pointer">
                      {field.placeholder || 'Enable'}
                    </label>
                  </div>
                )}
              </div>
            ))}
          </CardContent>
        </Card>
      ))}
    </div>
  );
}
```

#### 2.2 useMetaFields Hook

**File:** `admin-spa/src/hooks/useMetaFields.ts`

**Purpose:** Hook to get registered meta fields from global registry

```tsx
interface MetaFieldsRegistry {
  orders: MetaField[];
  products: MetaField[];
}

// Global registry exposed by PHP
declare global {
  interface Window {
    WooNooWMetaFields?: MetaFieldsRegistry;
  }
}

export function useMetaFields(type: 'orders' | 'products'): MetaField[] {
  const [fields, setFields] = useState<MetaField[]>([]);
  
  useEffect(() => {
    // Get fields from global registry (set by PHP)
    const registry = window.WooNooWMetaFields || { orders: [], products: [] };
    setFields(registry[type] || []);
    
    // Listen for dynamic field registration
    const handleFieldsUpdated = (e: CustomEvent) => {
      if (e.detail.type === type) {
        setFields(e.detail.fields);
      }
    };
    
    window.addEventListener('woonoow:meta_fields_updated', handleFieldsUpdated as EventListener);
    
    return () => {
      window.removeEventListener('woonoow:meta_fields_updated', handleFieldsUpdated as EventListener);
    };
  }, [type]);
  
  return fields;
}
```

#### 2.3 Integration in Order Edit

**File:** `admin-spa/src/routes/Orders/Edit.tsx`

```tsx
import { MetaFields } from '@/components/MetaFields';
import { useMetaFields } from '@/hooks/useMetaFields';

export default function OrderEdit() {
  const { id } = useParams();
  const metaFields = useMetaFields('orders');
  
  const [formData, setFormData] = useState({
    // ... existing fields ...
    meta: {},
  });
  
  useEffect(() => {
    if (orderQ.data) {
      setFormData(prev => ({
        ...prev,
        meta: orderQ.data.meta || {},
      }));
    }
  }, [orderQ.data]);
  
  const handleMetaChange = (key: string, value: any) => {
    setFormData(prev => ({
      ...prev,
      meta: {
        ...prev.meta,
        [key]: value,
      },
    }));
  };
  
  return (
    <div className="space-y-6">
      {/* Existing order form fields */}
      <OrderForm data={formData} onChange={setFormData} />
      
      {/* Custom meta fields (Level 1 compatibility) */}
      {metaFields.length > 0 && (
        <MetaFields
          meta={formData.meta}
          fields={metaFields}
          onChange={handleMetaChange}
        />
      )}
    </div>
  );
}
```

---

### Phase 3: PHP Registry System (2-3 days)

#### 3.1 MetaFieldsRegistry Class

**File:** `includes/Compat/MetaFieldsRegistry.php`

**Purpose:** Allow plugins to register meta fields for display in SPA

```php
<?php
namespace WooNooW\Compat;

class MetaFieldsRegistry {
    
    private static $order_fields = [];
    private static $product_fields = [];
    
    public static function init() {
        add_action('admin_enqueue_scripts', [__CLASS__, 'localize_fields']);
        
        // Allow plugins to register fields
        do_action('woonoow/register_meta_fields');
    }
    
    /**
     * Register order meta field
     * 
     * @param string $key Meta key (e.g., '_tracking_number')
     * @param array $args Field configuration
     */
    public static function register_order_field($key, $args = []) {
        $defaults = [
            'key' => $key,
            'label' => self::format_label($key),
            'type' => 'text',
            'section' => 'Additional Fields',
            'description' => '',
            'placeholder' => '',
        ];
        
        self::$order_fields[$key] = array_merge($defaults, $args);
        
        // Auto-add to allowed meta lists
        add_filter('woonoow/order_allowed_private_meta', function($allowed) use ($key) {
            if (!in_array($key, $allowed, true)) {
                $allowed[] = $key;
            }
            return $allowed;
        });
        
        add_filter('woonoow/order_updatable_meta', function($allowed) use ($key) {
            if (!in_array($key, $allowed, true)) {
                $allowed[] = $key;
            }
            return $allowed;
        });
    }
    
    /**
     * Register product meta field
     */
    public static function register_product_field($key, $args = []) {
        $defaults = [
            'key' => $key,
            'label' => self::format_label($key),
            'type' => 'text',
            'section' => 'Additional Fields',
            'description' => '',
            'placeholder' => '',
        ];
        
        self::$product_fields[$key] = array_merge($defaults, $args);
        
        // Auto-add to allowed meta lists
        add_filter('woonoow/product_allowed_private_meta', function($allowed) use ($key) {
            if (!in_array($key, $allowed, true)) {
                $allowed[] = $key;
            }
            return $allowed;
        });
        
        add_filter('woonoow/product_updatable_meta', function($allowed) use ($key) {
            if (!in_array($key, $allowed, true)) {
                $allowed[] = $key;
            }
            return $allowed;
        });
    }
    
    /**
     * Format meta key to human-readable label
     */
    private static function format_label($key) {
        // Remove leading underscore
        $label = ltrim($key, '_');
        
        // Replace underscores with spaces
        $label = str_replace('_', ' ', $label);
        
        // Capitalize words
        $label = ucwords($label);
        
        return $label;
    }
    
    /**
     * Localize fields to JavaScript
     */
    public static function localize_fields() {
        if (!is_admin()) return;
        
        // Allow plugins to modify fields before localizing
        $order_fields = apply_filters('woonoow/meta_fields_orders', array_values(self::$order_fields));
        $product_fields = apply_filters('woonoow/meta_fields_products', array_values(self::$product_fields));
        
        wp_localize_script('woonoow-admin', 'WooNooWMetaFields', [
            'orders' => $order_fields,
            'products' => $product_fields,
        ]);
    }
}
```

#### 3.2 Initialize Registry

**File:** `includes/Core/Plugin.php`

```php
// Add to init() method
\WooNooW\Compat\MetaFieldsRegistry::init();
```

---

## Testing Plan

### Test Case 1: WooCommerce Shipment Tracking
```php
// Plugin stores tracking number
update_post_meta($order_id, '_tracking_number', '1234567890');

// Expected: Field visible in WooNooW order edit
// Expected: Can edit and save tracking number
```

### Test Case 2: Advanced Custom Fields (ACF)
```php
// ACF stores custom field
update_post_meta($product_id, 'custom_field', 'value');

// Expected: Field visible in WooNooW product edit
// Expected: Can edit and save custom field
```

### Test Case 3: Custom Metabox Plugin
```php
// Plugin registers field
add_action('woonoow/register_meta_fields', function() {
    \WooNooW\Compat\MetaFieldsRegistry::register_order_field('_custom_field', [
        'label' => 'Custom Field',
        'type' => 'text',
        'section' => 'My Plugin',
    ]);
});

// Expected: Field appears in "My Plugin" section
// Expected: Can edit and save
```

---

## Implementation Checklist

### Backend (PHP)
- [ ] Add `get_order_meta_data()` to OrdersController
- [ ] Add `update_order_meta_data()` to OrdersController
- [ ] Add `get_product_meta_data()` to ProductsController
- [ ] Add `update_product_meta_data()` to ProductsController
- [ ] Add filters: `woonoow/order_allowed_private_meta`
- [ ] Add filters: `woonoow/order_updatable_meta`
- [ ] Add filters: `woonoow/product_allowed_private_meta`
- [ ] Add filters: `woonoow/product_updatable_meta`
- [ ] Add filters: `woonoow/order_api_data`
- [ ] Add filters: `woonoow/product_api_data`
- [ ] Add actions: `woonoow/order_updated`
- [ ] Add actions: `woonoow/product_updated`
- [ ] Create `MetaFieldsRegistry.php`
- [ ] Add action: `woonoow/register_meta_fields`
- [ ] Initialize registry in Plugin.php

### Frontend (React/TypeScript)
- [ ] Create `MetaFields.tsx` component
- [ ] Create `useMetaFields.ts` hook
- [ ] Update `Orders/Edit.tsx` to include meta fields
- [ ] Update `Orders/View.tsx` to display meta fields (read-only)
- [ ] Update `Products/Edit.tsx` to include meta fields
- [ ] Add meta fields to Product detail page

### Testing
- [ ] Test with WooCommerce Shipment Tracking
- [ ] Test with ACF (Advanced Custom Fields)
- [ ] Test with custom metabox plugin
- [ ] Test meta data save/update
- [ ] Test meta data display in detail view
- [ ] Test field registration via `woonoow/register_meta_fields`

---

## Timeline

- **Phase 1 (Backend):** 2-3 days
- **Phase 2 (Frontend):** 3-4 days
- **Phase 3 (Registry):** 2-3 days
- **Testing:** 1-2 days

**Total:** 8-12 days (1.5-2 weeks)

---

## Success Criteria

✅ Plugins using standard WP/WooCommerce meta storage work automatically
✅ No special integration needed from plugin developers
✅ Meta fields visible and editable in WooNooW admin
✅ Data saved correctly to WooCommerce database
✅ Compatible with popular plugins (Shipment Tracking, ACF, etc.)
✅ Follows 3-level compatibility strategy
✅ Zero coupling with specific plugins
✅ Community does NOTHING extra for Level 1 compatibility
