# Generated Hooks

The plugin-kit auto-generates React hooks from your functions. Run `npm run build` to generate hooks in `ui/src/generated/`.

## Query Hooks (Auto-Fetch)

Query hooks automatically fetch data when the component mounts.

```typescript
// From this function definition:
.function('health', {
  description: 'Check plugin health status',
  input: {
    type: 'object',
    properties: {},
    additionalProperties: false
  },
  output: {
    type: 'object',
    properties: {
      status: { type: 'string' },
      timestamp: { type: 'string' }
    }
  },
  handler: async (input, ctx) => {
    return {
      status: 'ok',
      timestamp: new Date().toISOString()
    };
  }
})

// Generates this hook:
export function useHealth(
  input?: HealthInput,
  options?: { enabled?: boolean; refetchInterval?: number }
): UseQueryResult<HealthOutput>
```

### Basic Usage

```typescript
// ui/src/components/DashboardPage.tsx
import { useHealth } from '../generated/hooks';

export function DashboardPage() {
  // Auto-fetches on mount
  const { data, error, loading, refetch } = useHealth();

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <div data-majk-id="healthStatus">{data.status}</div>
      <button onClick={refetch} data-majk-id="refreshButton">
        Refresh
      </button>
    </div>
  );
}
```

### With Input Parameters

```typescript
// Function with parameters
.function('getAnalytics', {
  description: 'Get analytics for time period',
  input: {
    type: 'object',
    properties: {
      period: { type: 'string', enum: ['24h', '7d', '30d'] }
    },
    required: ['period']
  },
  output: { /* ... */ }
})

// Generated hook
export function useGetAnalytics(
  input: GetAnalyticsInput,
  options?: { enabled?: boolean; refetchInterval?: number }
): UseQueryResult<GetAnalyticsOutput>
```

Usage:

```typescript
import { useGetAnalytics } from '../generated/hooks';

export function AnalyticsPage() {
  // Pass input as first parameter
  const { data, loading, error } = useGetAnalytics({ period: '7d' });

  return (
    <div data-majk-id="analyticsContainer">
      {loading && <div data-majk-state="loading">Loading...</div>}
      {!loading && data && (
        <div data-majk-state="populated">
          <div data-majk-id="activeUsersCount">{data.metrics.activeUsers}</div>
          <div data-majk-id="totalSessions">{data.metrics.totalSessions}</div>
        </div>
      )}
    </div>
  );
}
```

### Auto-Refresh

```typescript
export function LiveDashboard() {
  // Refetch every 30 seconds
  const { data } = useHealth({}, {
    refetchInterval: 30000  // milliseconds
  });

  return <div data-majk-id="liveStatus">{data?.status}</div>;
}
```

### Conditional Fetching

```typescript
export function ConditionalData() {
  const [fetchEnabled, setFetchEnabled] = useState(false);

  // Only fetch when enabled is true
  const { data, loading } = useGetAnalytics(
    { period: '7d' },
    { enabled: fetchEnabled }
  );

  return (
    <div>
      <button
        onClick={() => setFetchEnabled(true)}
        data-majk-id="loadDataButton"
      >
        Load Data
      </button>
      {fetchEnabled && loading && <div>Loading...</div>}
      {data && <div>{JSON.stringify(data)}</div>}
    </div>
  );
}
```

## Mutation Hooks (Manual Trigger)

Mutation hooks don't auto-fetch. Call `.mutate(input)` manually.

```typescript
// From this function (mutation - modifies state):
.function('clearEvents', {
  description: 'Clear all logged events',
  input: {
    type: 'object',
    properties: {},
    additionalProperties: false
  },
  output: {
    type: 'object',
    properties: {
      success: { type: 'boolean' },
      clearedCount: { type: 'number' }
    }
  },
  handler: async (input, ctx) => {
    const events = await ctx.storage.get('events') || [];
    await ctx.storage.set('events', []);
    return { success: true, clearedCount: events.length };
  }
})

// Generates this hook:
export function useClearEvents(): UseMutationResult<ClearEventsInput, ClearEventsOutput>
```

### Basic Usage

```typescript
import { useClearEvents } from '../generated/hooks';

export function EventsPage() {
  const { mutate: clearEvents, data, loading, error } = useClearEvents();

  const handleClear = async () => {
    try {
      const result = await clearEvents({});  // Call mutate with input
      console.log(`Cleared ${result.clearedCount} events`);
    } catch (err) {
      console.error('Failed to clear events:', err);
    }
  };

  return (
    <button
      onClick={handleClear}
      disabled={loading}
      data-majk-id="clearEventsButton"
      data-majk-state={loading ? 'clearing' : 'idle'}
    >
      {loading ? 'Clearing...' : 'Clear Events'}
    </button>
  );
}
```

### With Input Parameters

```typescript
// Function with input
.function('updateSettings', {
  description: 'Update plugin settings',
  input: {
    type: 'object',
    properties: {
      theme: { type: 'string', enum: ['light', 'dark'] },
      notifications: { type: 'boolean' }
    },
    required: ['theme']
  },
  output: { /* ... */ }
})
```

Usage:

```typescript
import { useUpdateSettings } from '../generated/hooks';

export function SettingsPage() {
  const { mutate: updateSettings, loading, error } = useUpdateSettings();

  const handleSave = async () => {
    try {
      await updateSettings({
        theme: 'dark',
        notifications: true
      });
      alert('Settings saved!');
    } catch (err) {
      alert(`Error: ${err.message}`);
    }
  };

  return (
    <button
      onClick={handleSave}
      disabled={loading}
      data-majk-id="saveSettingsButton"
    >
      Save
    </button>
  );
}
```

### Tracking Mutation State

```typescript
export function DeleteButton({ itemId }: { itemId: string }) {
  const { mutate: deleteItem, loading, data, error } = useDeleteItem();

  return (
    <div>
      <button
        onClick={() => deleteItem({ id: itemId })}
        disabled={loading}
        data-majk-id="deleteButton"
        data-majk-state={loading ? 'deleting' : error ? 'error' : data ? 'success' : 'idle'}
      >
        {loading ? 'Deleting...' : 'Delete'}
      </button>

      {error && (
        <div data-majk-id="errorMessage" data-majk-state="error">
          Error: {error.message}
        </div>
      )}

      {data?.success && (
        <div data-majk-id="successMessage" data-majk-state="success">
          Deleted successfully!
        </div>
      )}
    </div>
  );
}
```

## uiLog Hook Pattern

**Best practice:** Use `useUiLog` hook for logging throughout your UI.

```typescript
// Define uiLog function in plugin
.function('uiLog', {
  description: 'Log messages from UI for debugging',
  input: {
    type: 'object',
    properties: {
      level: { type: 'string', enum: ['debug', 'info', 'warn', 'error'] },
      component: { type: 'string' },
      message: { type: 'string' },
      data: { type: 'object' }
    },
    required: ['level', 'component', 'message']
  },
  output: {
    type: 'object',
    properties: { success: { type: 'boolean' } }
  },
  handler: async (input, ctx) => {
    ctx.logger[input.level](`[UI:${input.component}] ${input.message}`, input.data);
    return { success: true };
  }
})
```

Use in every component:

```typescript
import { useUiLog } from '../generated/hooks';
import { useEffect } from 'react';

export function DashboardPage() {
  const { mutate: uiLog } = useUiLog();

  // Log lifecycle events
  useEffect(() => {
    uiLog({
      level: 'info',
      component: 'DashboardPage',
      message: 'Component mounted'
    });

    return () => {
      uiLog({
        level: 'info',
        component: 'DashboardPage',
        message: 'Component unmounting'
      });
    };
  }, [uiLog]);

  // Log user actions
  const handleButtonClick = () => {
    uiLog({
      level: 'info',
      component: 'DashboardPage',
      message: 'Button clicked',
      data: { button: 'refresh', timestamp: Date.now() }
    });
    // ... handle click
  };

  // Log errors
  const handleOperation = async () => {
    try {
      await riskyOperation();
    } catch (error) {
      uiLog({
        level: 'error',
        component: 'DashboardPage',
        message: 'Operation failed',
        data: {
          error: error.message,
          stack: error.stack
        }
      });
    }
  };

  return <div>...</div>;
}
```

## Hook Return Types

### UseQueryResult

```typescript
interface UseQueryResult<T> {
  data: T | undefined;          // Function output (undefined until loaded)
  error: Error | undefined;     // Error if fetch failed
  loading: boolean;             // True while fetching
  refetch: () => void;          // Manually trigger re-fetch
}
```

### UseMutationResult

```typescript
interface UseMutationResult<TInput, TOutput> {
  mutate: (input: TInput) => Promise<TOutput>;  // Call to execute function
  data: TOutput | undefined;                     // Last successful result
  error: Error | undefined;                      // Last error
  loading: boolean;                              // True while executing
}
```

## Common Patterns

### Loading States

```typescript
export function DataDisplay() {
  const { data, loading, error } = useGetData();

  return (
    <div data-majk-id="dataContainer" data-majk-state={
      loading ? 'loading' :
      error ? 'error' :
      data ? 'populated' : 'empty'
    }>
      {loading && <Spinner data-majk-id="spinner" />}
      {error && <ErrorMessage data-majk-id="error" error={error} />}
      {data && <DataView data={data} />}
    </div>
  );
}
```

### Dependent Queries

```typescript
export function UserProfile({ userId }: { userId: string }) {
  // First query
  const { data: user, loading: userLoading } = useGetUser({ id: userId });

  // Second query depends on first
  const { data: projects, loading: projectsLoading } = useGetUserProjects(
    { userId },
    { enabled: !!user }  // Only fetch when user exists
  );

  if (userLoading) return <div>Loading user...</div>;
  if (!user) return <div>User not found</div>;

  return (
    <div>
      <div data-majk-id="userName">{user.name}</div>
      {projectsLoading ? (
        <div data-majk-state="loading">Loading projects...</div>
      ) : (
        <div data-majk-state="populated">
          {projects?.map(p => <div key={p.id}>{p.name}</div>)}
        </div>
      )}
    </div>
  );
}
```

### Optimistic Updates

```typescript
export function TodoList() {
  const { data: todos, refetch } = useGetTodos();
  const { mutate: completeTodo } = useCompleteTodo();

  const handleComplete = async (todoId: string) => {
    // Optimistically update UI
    const optimisticTodos = todos?.map(t =>
      t.id === todoId ? { ...t, completed: true } : t
    );

    // Update backend
    try {
      await completeTodo({ id: todoId });
      refetch();  // Refetch to get source of truth
    } catch (error) {
      // Revert on error (or refetch)
      refetch();
    }
  };

  return <div>...</div>;
}
```

## Testing with Generated Hooks

Hooks are tested through UI tests:

```typescript
// tests/plugin/ui/unit/dashboard.test.js
import { test, bot, mock } from '@majkapp/plugin-test';

test('dashboard uses hooks correctly', async () => {
  const context = mock()
    .withMockHandler('health', async () => ({
      status: 'ok',
      timestamp: new Date().toISOString()
    }))
    .build();

  const b = await bot(context);
  await b.goto('/plugin-screens/my-plugin/dashboard');

  // Wait for useHealth() to fetch and render
  await b.waitForSelector('[data-majk-id="healthStatus"][data-majk-state="populated"]');

  const text = await b.getText('[data-majk-id="healthStatus"]');
  assert.ok(text.includes('ok'));

  await b.close();
});
```

## Hook Generation

Hooks are generated by running your build:

```bash
npm run build
```

Generated files (DO NOT EDIT manually):
- `ui/src/generated/hooks.ts` - React hooks
- `ui/src/generated/client.ts` - RPC client
- `ui/src/generated/types.ts` - TypeScript types
- `ui/src/generated/index.ts` - Barrel export
- `ui/src/generated/generation.log` - Build log with warnings

### Hook Performance Optimization

Generated hooks use **schema-driven dependency extraction** for optimal performance. Primitive fields (string, number, boolean) are tracked individually via `Object.is()` instead of expensive serialization. Check `generation.log` for dependency analysis and warnings about schema improvements.

## Next Steps

Run `npx @majkapp/plugin-kit --context` - ctx API for accessing MAJK
Run `npx @majkapp/plugin-kit --testing` - Test your hooks
Run `npx @majkapp/plugin-kit --config` - Build configuration
