# @bytelyst/survey-client

TypeScript client for the ByteLyst Survey platform. Provides survey discovery, question flow management, response submission, and offline caching.

## Installation

```bash
npm install @bytelyst/survey-client
# or
pnpm add @bytelyst/survey-client
```

## Quick Start

```typescript
import { createSurveyClient } from '@bytelyst/survey-client';

const client = createSurveyClient({
  baseURL: 'https://api.bytelyst.io/v1',
  productId: 'lysnrai',
  getAuthToken: async () => {
    return localStorage.getItem('token');
  }
});

// Check for active survey
const [survey, error] = await client.getActiveSurvey();
if (survey) {
  console.log('Survey available:', survey.title);
}
```

## API Reference

### `createSurveyClient(config)`

Creates a new survey client instance.

**Config:**
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `baseURL` | string | Yes | API base URL |
| `productId` | string | Yes | Product identifier |
| `getAuthToken` | () => Promise<string> | Yes | Function to retrieve JWT token |
| `enableOfflineCache` | boolean | No | Enable offline response caching (default: true) |

### Methods

#### `getActiveSurvey()`
Check if there's an active survey for the current user.

```typescript
const [survey, error] = await client.getActiveSurvey();
// Returns: ActiveSurvey | null
```

#### `startSurvey(surveyId: string)`
Start a survey session.

```typescript
const [state, error] = await client.startSurvey('srv_123');
// Returns: { surveyStateId: string, currentQuestionIndex: number }
```

#### `submitAnswer(surveyId: string, questionId: string, answer: SurveyAnswer)`
Submit an answer for a specific question.

```typescript
const [result, error] = await client.submitAnswer('srv_123', 'q1', {
  type: 'rating',
  value: { value: 8 }
});
// Returns: { currentQuestionIndex: number, nextQuestionId: string, isComplete: boolean }
```

**Answer Types:**
```typescript
// Single choice
{ type: 'single_choice', value: { value: 'option_1' } }

// Multiple choice
{ type: 'multiple_choice', value: { values: ['opt_1', 'opt_2'] } }

// Rating/NPS/Scale
{ type: 'rating', value: { value: 8 } }

// Text
{ type: 'text', value: { value: 'My feedback here' } }

// Ranking
{ type: 'ranking', value: { order: ['opt_1', 'opt_2', 'opt_3'] } }
```

#### `completeSurvey(surveyId: string)`
Mark survey as complete and claim any incentive.

```typescript
const [completion, error] = await client.completeSurvey('srv_123');
// Returns: { success: boolean, incentiveClaimed: boolean, incentiveType?: string, incentiveAmount?: number }
```

#### `dismissSurvey(surveyId: string)`
Dismiss survey without completing.

```typescript
await client.dismissSurvey('srv_123');
```

#### `startPolling(intervalMs: number, callback: (survey) => void)`
Start polling for active surveys.

```typescript
client.startPolling(60000, (survey) => {
  if (survey) {
    showSurveyModal(survey);
  }
});
```

#### `stopPolling()`
Stop survey polling.

```typescript
client.stopPolling();
```

#### `flushOfflineQueue()`
Manually flush any cached offline responses.

```typescript
await client.flushOfflineQueue();
```

## React Integration

### Hook Usage

```typescript
import { useSurvey } from './hooks/useSurvey';

function SurveyWidget() {
  const { 
    activeSurvey, 
    currentQuestion, 
    submitAnswer, 
    completeSurvey,
    progress 
  } = useSurvey({
    autoStart: true,
    pollingInterval: 60000
  });

  if (!activeSurvey) return null;

  return (
    <SurveyModal
      survey={activeSurvey}
      currentQuestion={currentQuestion}
      progress={progress}
      onSubmit={submitAnswer}
      onComplete={completeSurvey}
    />
  );
}
```

### Complete Survey Flow Example

```typescript
function SurveyFlow() {
  const client = useSurveyClient();
  const [survey, setSurvey] = useState<ActiveSurvey | null>(null);
  const [currentIndex, setCurrentIndex] = useState(0);
  const [answers, setAnswers] = useState<Record<string, SurveyAnswer>>({});

  useEffect(() => {
    loadSurvey();
  }, []);

  async function loadSurvey() {
    const [activeSurvey] = await client.getActiveSurvey();
    if (activeSurvey) {
      await client.startSurvey(activeSurvey.id);
      setSurvey(activeSurvey);
    }
  }

  async function handleAnswer(questionId: string, answer: SurveyAnswer) {
    const [result] = await client.submitAnswer(survey!.id, questionId, answer);
    
    setAnswers(prev => ({ ...prev, [questionId]: answer }));
    setCurrentIndex(result.currentQuestionIndex);

    if (result.isComplete) {
      const [completion] = await client.completeSurvey(survey!.id);
      if (completion.incentiveClaimed) {
        showIncentiveToast(completion.incentiveAmount, completion.incentiveType);
      }
    }
  }

  if (!survey) return null;

  const question = survey.questions[currentIndex];
  const isLast = currentIndex === survey.questions.length - 1;

  return (
    <QuestionView
      question={question}
      onSubmit={(answer) => handleAnswer(question.id, answer)}
      onSkip={question.required ? undefined : () => handleSkip(question.id)}
    />
  );
}
```

## Offline Support

The client automatically caches responses when offline:

```typescript
const client = createSurveyClient({
  baseURL: 'https://api.bytelyst.io/v1',
  productId: 'lysnrai',
  getAuthToken: () => getToken(),
  enableOfflineCache: true  // Enabled by default
});

// Responses are queued when offline
// Flush queue manually or on reconnect
window.addEventListener('online', () => {
  client.flushOfflineQueue();
});
```

## Types

```typescript
interface ActiveSurvey {
  id: string;
  title: string;
  description?: string;
  questions: Question[];
  currentQuestionIndex: number;
  incentive?: {
    type: 'pro_days' | 'credits';
    amount: number;
  };
}

interface Question {
  id: string;
  type: 'single_choice' | 'multiple_choice' | 'rating' | 'scale' | 
        'nps' | 'text_short' | 'text_long' | 'ranking' | 'dropdown';
  text: string;
  description?: string;
  required: boolean;
  options?: QuestionOption[];
  minValue?: number;
  maxValue?: number;
  maxLength?: number;
  showIf?: ShowIfCondition;
}

interface QuestionOption {
  id: string;
  text: string;
  emoji?: string;
}

interface SurveyAnswer {
  type: string;
  value: Record<string, unknown>;
}

interface SurveyConfig {
  baseURL: string;
  productId: string;
  getAuthToken: () => Promise<string>;
  enableOfflineCache?: boolean;
}
```

## Question Type Reference

| Type | Answer Format | UI Component |
|------|--------------|--------------|
| `single_choice` | `{ value: string }` | Radio group |
| `multiple_choice` | `{ values: string[] }` | Checkboxes |
| `rating` | `{ value: number }` | Star rating (1-5) |
| `scale` | `{ value: number }` | Numeric slider |
| `nps` | `{ value: number }` | 0-10 buttons |
| `text_short` | `{ value: string }` | Single line input |
| `text_long` | `{ value: string }` | Textarea |
| `ranking` | `{ order: string[] }` | Drag-to-sort |
| `dropdown` | `{ value: string }` | Select dropdown |

## Conditional Logic

Questions can be shown/hidden based on previous answers:

```typescript
// Question only shows if q1 answer is NOT 9 or 10
{
  id: 'q2',
  type: 'text_long',
  text: 'What could we improve?',
  showIf: {
    questionId: 'q1',
    operator: 'not_equals',
    value: ['9', '10']
  }
}
```

**Operators:** `equals`, `not_equals`, `greater_than`, `less_than`, `contains`

## Error Handling

```typescript
const [data, error] = await client.submitAnswer('srv_123', 'q1', answer);

if (error) {
  switch (error.code) {
    case 'SURVEY_NOT_FOUND':
      console.error('Survey expired or unavailable');
      break;
    case 'ALREADY_COMPLETED':
      console.error('User already completed this survey');
      break;
    case 'VALIDATION_ERROR':
      console.error('Invalid answer format');
      break;
    default:
      console.error('Survey error:', error.message);
  }
}
```

## Browser Support

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

## License

MIT © ByteLyst
