import type { PiggyChatContext } from '@pig/core';
import { ApiError, getSupabase } from './api';

/**
 * Re-exported from @pig/core rather than declared here. The old local copy was
 * a fourth definition of a shape that already existed in the relay's zod
 * schema, the Piggy server's own interface and the model prompt — and widening
 * it for the docked panel meant widening it in all of them or getting a 400
 * from whichever hop was missed.
 */
export type { PiggyChatContext };
export {
  PiggyContextProvider,
  usePiggyContext,
  usePiggyCurrentContext,
} from './piggy-context';

export interface PiggyChatTurn {
  role: 'user' | 'assistant';
  content: string;
}

export type PiggyChatEvent =
  | { type: 'meta'; model: string }
  | { type: 'reasoning_delta'; delta: string }
  | { type: 'content_delta'; delta: string }
  | { type: 'tool_call'; id: string; name: string; arguments: unknown }
  | { type: 'tool_result'; id: string; name: string; ok: boolean; result?: unknown; error?: string }
  | { type: 'done'; inputTokens: number | null; outputTokens: number | null }
  | { type: 'error'; message: string };

export interface PiggyStatus {
  enabled: boolean;
  canUse: boolean;
}

export async function* streamPiggyChat(
  request: {
    message: string;
    history?: PiggyChatTurn[];
    context?: PiggyChatContext;
  },
  signal?: AbortSignal,
): AsyncGenerator<PiggyChatEvent> {
  const supabase = getSupabase();
  const token = supabase ? (await supabase.auth.getSession()).data.session?.access_token : null;
  const response = await fetch('/api/piggy/chat', {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      ...(token ? { authorization: `Bearer ${token}` } : {}),
    },
    body: JSON.stringify(request),
    signal,
  });

  if (!response.ok) {
    let message = response.statusText;
    let code: string | undefined;
    try {
      const body = (await response.json()) as { error?: string; code?: string };
      message = body.error ?? message;
      code = body.code;
    } catch {
      // The authenticated proxy normally returns JSON, but an upstream proxy may not.
    }
    throw new ApiError(message, response.status, code);
  }
  if (!response.body) throw new Error('Piggy returned no response stream.');

  yield* readNdjson<PiggyChatEvent>(response.body, signal);
}

export async function* readNdjson<Value>(
  stream: ReadableStream<Uint8Array>,
  signal?: AbortSignal,
): AsyncGenerator<Value> {
  const reader = stream.getReader();
  const decoder = new TextDecoder();
  let buffer = '';
  try {
    while (true) {
      if (signal?.aborted) throw signal.reason;
      const { done, value } = await reader.read();
      buffer += decoder.decode(value, { stream: !done });
      const lines = buffer.split('\n');
      buffer = lines.pop() ?? '';
      for (const line of lines) {
        if (line.trim()) yield JSON.parse(line) as Value;
      }
      if (done) {
        if (buffer.trim()) yield JSON.parse(buffer) as Value;
        return;
      }
    }
  } finally {
    reader.releaseLock();
  }
}
