# LinkSelector Component

## Overview

The **LinkSelector** component provides an interactive interface for selecting and linking translated content across languages. It features debounced search with autocomplete, keyboard navigation, multi-selection support, and comprehensive REST API integration.

## Features

- **Debounced Search**: 300ms debounce for optimal performance
- **Autocomplete Dropdown**: Real-time search results with filtering
- **Keyboard Navigation**: Arrow keys, Enter, and Escape support
- **Multi-Selection**: Select multiple linked translations
- **Post Type Filtering**: Filter by post, page, or custom post types
- **Language Filtering**: Search within specific target languages
- **Loading States**: Visual feedback during API calls
- **Error Handling**: Graceful error display with retry capability
- **Empty States**: User-friendly messages when no results found
- **Accessibility**: Full ARIA support, keyboard navigation, screen reader friendly
- **Dark Mode**: Automatic dark mode support
- **Responsive**: Mobile-optimized interface

## Installation

### Import the Component

```javascript
import { LinkSelector } from './components/LinkSelector.js';
```

### Import Styles

The component styles are automatically included when importing `main.scss`:

```scss
@import 'styles/main.scss';
```

## Usage

### Basic Example

```javascript
// Create a new LinkSelector instance
const linkSelector = new LinkSelector({
    container: '#link-selector-container',
    sourcePostId: 123,
    sourceLanguage: 'en',
    targetLanguage: 'es',
    onChange: (selectedLinks) => {
        console.log('Selected links changed:', selectedLinks);
        // Save to database or update state
    }
});
```

### Advanced Example with Options

```javascript
const linkSelector = new LinkSelector({
    // Required: Container element (selector or HTMLElement)
    container: document.getElementById('my-link-selector'),

    // Required: Source post ID
    sourcePostId: 456,

    // Optional: Source language (default: 'en')
    sourceLanguage: 'en',

    // Required: Target language for search
    targetLanguage: 'fr',

    // Optional: Allowed post types (default: ['post', 'page'])
    postTypes: ['post', 'page', 'product'],

    // Optional: Pre-selected links
    selectedLinks: [
        { id: 789, title: 'Article traduit', post_type: 'post' },
        { id: 790, title: 'Page traduite', post_type: 'page' }
    ],

    // Optional: Change callback
    onChange: (selectedLinks) => {
        // Handle selection changes
        console.log('Current selection:', selectedLinks);

        // Save to WordPress via AJAX
        wp.ajax.post('save_translations', {
            source_id: 456,
            translations: selectedLinks
        });
    },

    // Optional: Custom API endpoint (default: '/wp-json/multilingual-press-zone/v1/posts')
    apiEndpoint: '/wp-json/custom/v1/posts'
});
```

### HTML Container

```html
<div id="link-selector-container"></div>
```

The component will render its complete interface inside this container.

## API Reference

### Constructor Options

| Option | Type | Required | Default | Description |
|--------|------|----------|---------|-------------|
| `container` | `string\|HTMLElement` | Yes | - | Container element or CSS selector |
| `sourcePostId` | `number` | Yes | - | ID of the source post being linked |
| `sourceLanguage` | `string` | No | `'en'` | Language code of source post |
| `targetLanguage` | `string` | Yes | - | Language code for search results |
| `postTypes` | `Array<string>` | No | `['post', 'page']` | Allowed post types for search |
| `selectedLinks` | `Array<Object>` | No | `[]` | Pre-selected linked posts |
| `onChange` | `Function` | No | `() => {}` | Callback when selection changes |
| `apiEndpoint` | `string` | No | `/wp-json/multilingual-press-zone/v1/posts` | REST API endpoint for search |

### Selected Link Object

```javascript
{
    id: 123,              // Post ID (number)
    title: 'Post Title',  // Post title (string)
    post_type: 'post'     // Post type (string)
}
```

### Methods

#### `getSelectedLinks()`

Get currently selected links.

```javascript
const links = linkSelector.getSelectedLinks();
console.log(links); // [{ id: 123, title: 'Post', post_type: 'post' }]
```

**Returns:** `Array<Object>` - Array of selected link objects

#### `setSelectedLinks(links)`

Programmatically set selected links.

```javascript
linkSelector.setSelectedLinks([
    { id: 456, title: 'New Post', post_type: 'post' }
]);
```

**Parameters:**
- `links` (`Array<Object>`): Array of link objects to set

#### `destroy()`

Destroy the component and clean up resources.

```javascript
linkSelector.destroy();
```

## REST API Integration

The component expects the following REST API endpoint structure:

### Endpoint

```
GET /wp-json/multilingual-press-zone/v1/posts
```

### Query Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `search` | `string` | Yes | Search term for post titles |
| `language` | `string` | Yes | Target language code |
| `post_type` | `string` | Yes | Comma-separated post types |
| `exclude` | `number` | No | Post ID to exclude from results |
| `per_page` | `number` | No | Number of results (default: 10) |

### Example Request

```
GET /wp-json/multilingual-press-zone/v1/posts?search=article&language=es&post_type=post,page&exclude=123&per_page=10
```

### Response Format

```json
[
    {
        "id": 456,
        "title": "Artículo de ejemplo",
        "post_type": "post",
        "post_date": "2024-01-15"
    },
    {
        "id": 457,
        "title": "Página de ejemplo",
        "post_type": "page",
        "post_date": "2024-01-16"
    }
]
```

### Example WordPress REST API Implementation

```php
<?php
// Register REST API endpoint
add_action('rest_api_init', function () {
    register_rest_route('multilingual-press-zone/v1', '/posts', [
        'methods' => 'GET',
        'callback' => 'mpz_search_posts',
        'permission_callback' => function () {
            return current_user_can('edit_posts');
        },
        'args' => [
            'search' => [
                'required' => true,
                'type' => 'string',
                'sanitize_callback' => 'sanitize_text_field'
            ],
            'language' => [
                'required' => true,
                'type' => 'string',
                'sanitize_callback' => 'sanitize_text_field'
            ],
            'post_type' => [
                'required' => true,
                'type' => 'string',
                'sanitize_callback' => 'sanitize_text_field'
            ],
            'exclude' => [
                'type' => 'integer',
                'sanitize_callback' => 'absint'
            ],
            'per_page' => [
                'type' => 'integer',
                'default' => 10,
                'sanitize_callback' => 'absint'
            ]
        ]
    ]);
});

function mpz_search_posts($request) {
    $search = $request->get_param('search');
    $language = $request->get_param('language');
    $post_type = $request->get_param('post_type');
    $exclude = $request->get_param('exclude');
    $per_page = $request->get_param('per_page');

    // Convert comma-separated post types to array
    $post_types = explode(',', $post_type);

    // Query posts
    $query = new WP_Query([
        's' => $search,
        'post_type' => $post_types,
        'post__not_in' => $exclude ? [$exclude] : [],
        'posts_per_page' => $per_page,
        'post_status' => 'any',
        'meta_query' => [
            [
                'key' => 'mpz_language',
                'value' => $language,
                'compare' => '='
            ]
        ]
    ]);

    $results = [];
    foreach ($query->posts as $post) {
        $results[] = [
            'id' => $post->ID,
            'title' => $post->post_title,
            'post_type' => $post->post_type,
            'post_date' => get_the_date('Y-m-d', $post->ID)
        ];
    }

    return $results;
}
```

## Styling

### CSS Classes

The component uses the following CSS class structure:

```
.mpz-link-selector
├── .mpz-link-selector__wrapper
│   ├── .mpz-link-selector__search
│   │   ├── .mpz-link-selector__input-wrapper
│   │   │   ├── .mpz-link-selector__input
│   │   │   ├── .mpz-link-selector__spinner
│   │   │   └── .mpz-link-selector__clear
│   │
│   ├── .mpz-link-selector__results
│   │   ├── .mpz-link-selector__loading
│   │   ├── .mpz-link-selector__error
│   │   ├── .mpz-link-selector__no-results
│   │   └── .mpz-link-selector__results-list
│   │       └── .mpz-link-selector__result-item
│   │           └── .mpz-link-selector__result-content
│   │               ├── .mpz-link-selector__result-title
│   │               └── .mpz-link-selector__result-meta
│   │                   ├── .mpz-link-selector__result-type
│   │                   └── .mpz-link-selector__result-date
│   │
│   └── .mpz-link-selector__selected
│       ├── .mpz-link-selector__empty
│       └── .mpz-link-selector__selected-list
│           └── .mpz-link-selector__selected-item
│               ├── .mpz-link-selector__selected-title
│               ├── .mpz-link-selector__selected-type
│               └── .mpz-link-selector__unlink
```

### Custom Styling

To customize the component appearance, override the SCSS variables:

```scss
// Import before the component
$primary-color: #your-color;
$border-radius-md: 8px;

@import 'components/link-selector';
```

Or override specific classes:

```scss
.mpz-link-selector {
    &__input {
        border-radius: 8px;
        font-size: 16px;
    }

    &__result-item {
        padding: 16px;

        &:hover {
            background-color: #f5f5f5;
        }
    }
}
```

## Keyboard Navigation

The component supports full keyboard navigation:

| Key | Action |
|-----|--------|
| **Arrow Down** | Move selection down in results |
| **Arrow Up** | Move selection up in results |
| **Enter** | Select highlighted result |
| **Escape** | Close results dropdown |
| **Tab** | Navigate between elements |

## Accessibility

The component is built with accessibility in mind:

- **ARIA Attributes**: Proper `aria-label`, `aria-expanded`, `aria-selected` attributes
- **Keyboard Navigation**: Full keyboard support
- **Screen Reader Support**: Meaningful labels and status announcements
- **Focus Management**: Visible focus indicators
- **High Contrast Mode**: Enhanced borders in high contrast mode
- **Reduced Motion**: Respects `prefers-reduced-motion` setting

## Events

### onChange Callback

Called whenever the selection changes (item selected or unlinked).

```javascript
onChange: (selectedLinks) => {
    // selectedLinks is an array of selected link objects
    console.log('Selection changed:', selectedLinks);
}
```

## Error Handling

The component handles various error scenarios:

### Network Errors

```javascript
// Displayed in results dropdown
"⚠️ Failed to fetch results"
```

### API Errors

```javascript
// HTTP status errors
"⚠️ HTTP 404: Not Found"
"⚠️ HTTP 500: Internal Server Error"
```

### Empty Results

```javascript
// User-friendly empty state
"🔍 No results found"
"Try a different search term"
```

## Performance

The component is optimized for performance:

- **Debounced Search**: 300ms debounce prevents excessive API calls
- **Minimal Re-renders**: Only updates affected DOM elements
- **Event Delegation**: Uses event delegation for list items
- **Cleanup**: Proper cleanup in `destroy()` method
- **Lazy Loading**: Results are loaded on demand

## Browser Support

- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+

## Example Integration

### WordPress Post Editor

```javascript
// In post editor meta box
(function($) {
    'use strict';

    $(document).ready(function() {
        // Get post data
        const postId = $('#post_ID').val();
        const postLanguage = $('#mpz_post_language').val();
        const targetLanguage = $('#mpz_target_language').val();

        // Get existing translations
        const existingTranslations = JSON.parse(
            $('#mpz_translations').val() || '[]'
        );

        // Initialize LinkSelector
        const linkSelector = new LinkSelector({
            container: '#mpz-link-selector',
            sourcePostId: parseInt(postId),
            sourceLanguage: postLanguage,
            targetLanguage: targetLanguage,
            postTypes: ['post', 'page'],
            selectedLinks: existingTranslations,
            onChange: function(selectedLinks) {
                // Update hidden field for form submission
                $('#mpz_translations').val(
                    JSON.stringify(selectedLinks)
                );

                // Show save indicator
                $('#mpz-save-indicator').show();
            }
        });
    });
})(jQuery);
```

### React Integration

```jsx
import { useEffect, useRef } from 'react';
import { LinkSelector } from './components/LinkSelector';

function TranslationLinker({ postId, sourceLanguage, targetLanguage, onUpdate }) {
    const containerRef = useRef(null);
    const linkSelectorRef = useRef(null);

    useEffect(() => {
        if (containerRef.current) {
            linkSelectorRef.current = new LinkSelector({
                container: containerRef.current,
                sourcePostId: postId,
                sourceLanguage: sourceLanguage,
                targetLanguage: targetLanguage,
                onChange: (selectedLinks) => {
                    onUpdate(selectedLinks);
                }
            });
        }

        return () => {
            if (linkSelectorRef.current) {
                linkSelectorRef.current.destroy();
            }
        };
    }, [postId, sourceLanguage, targetLanguage]);

    return <div ref={containerRef} />;
}
```

## Troubleshooting

### Search Not Working

1. Check if REST API endpoint exists and is accessible
2. Verify `wpApiSettings.nonce` is available in global scope
3. Check browser console for network errors
4. Verify user has `edit_posts` capability

### Results Not Displaying

1. Check API response format matches expected structure
2. Verify language metadata is set on posts
3. Check post status (published, draft, etc.)
4. Verify post types are correctly registered

### Styling Issues

1. Ensure SCSS is compiled correctly
2. Check for CSS conflicts with other plugins
3. Verify `main.scss` imports are in correct order
4. Check dark mode media queries

## License

This component is part of Multilingual Press Zone plugin.

Copyright (c) 2024 Press.Zone
Commercial License - https://press.zone/license

## Support

For issues and feature requests, please contact:
- Email: support@press.zone
- Documentation: https://press.zone/docs
- GitHub: https://github.com/press-zone/multilingual-press-zone
