// Beaver API client - single-user direct mode.

import type {
  AuthzStatus,
  AuthUser,
  ActiveTask,
  AgentConfigPayload,
  ChatLogsResponse,
  BackendTask,
  ChatMessage,
  ChannelConnectionView,
  ChannelConfigDetail,
  ChannelConfigPayload,
  ChannelConnectorDescriptor,
  ConnectorSessionResponse,
  ConnectorSessionStartPayload,
  ChannelEventRecord,
  CronJob,
  FileAttachment,
  NotificationDetail,
  NotificationRun,
  ProviderConfigPayload,
  Session,
  SessionDetail,
  Skill,
  SkillDetailResponse,
  SkillDraft,
  SkillDraftEvalReport,
  SkillDraftSafetyReport,
  SkillFileContent,
  SkillHubInstallResponse,
  SkillHubSearchItem,
  SkillHubSearchResponse,
  SkillHubVersionResponse,
  SkillHubVersionsResponse,
  SkillLearningCandidate,
  SkillReviewRecord,
  SessionProcessProjection,
  SystemStatus,
  TokenResponse,
  OutlookConnectionPayload,
  OutlookConnectionTestResult,
  OutlookConnectResult,
  OutlookEventListResponse,
  OutlookMessageDetail,
  OutlookMessageListResponse,
  OutlookOverview,
  OutlookStatus,
  UiAgentDescriptor,
  UiSubagentDescriptor,
  UiMcpServerDescriptor,
  WsEvent,
} from '@/types';
import { getCurrentAppLocale, pickAppText, type AppLocale } from '@/lib/i18n/core';

const API_URL = process.env.NEXT_PUBLIC_API_URL?.trim();
const WS_URL = process.env.NEXT_PUBLIC_WS_URL?.trim();
const DEFAULT_API_URL = 'http://127.0.0.1:18080';
const ACCESS_TOKEN_KEY = 'beaver_access_token';
const REFRESH_TOKEN_KEY = 'beaver_refresh_token';
export const AUTH_CLEARED_EVENT = 'beaver-auth-cleared';
const REQUEST_TIMEOUT_MS = 8000;
const OUTLOOK_REQUEST_TIMEOUT_MS = 45000;
const SKILL_LEARNING_REQUEST_TIMEOUT_MS = 120000;

export type PromptLocale = 'zh-Hans' | 'zh-Hant' | 'en';

export function promptLocaleForAppLocale(locale: AppLocale): PromptLocale {
  if (locale === 'zh-Hant') {
    return 'zh-Hant';
  }
  return locale === 'en-US' ? 'en' : 'zh-Hans';
}

function isBrowser(): boolean {
  return typeof window !== 'undefined';
}

function normalizeBaseUrl(value?: string | null): string | null {
  const trimmed = value?.trim();
  if (!trimmed) return null;
  return trimmed.replace(/\/+$/, '');
}

export function buildAuthHandoffUrl(response: TokenResponse, nextPath: string): string | null {
  const targetBaseUrl = normalizeBaseUrl(
    response.backend_connection?.frontend_base_url ||
    response.backend_connection?.public_base_url ||
    response.backend_connection?.api_base_url ||
    response.local_backend?.public_base_url
  );
  if (!targetBaseUrl) return null;
  const handoffCode = response.handoff_code?.trim();
  if (!handoffCode) return null;

  const target = new URL('/handoff', targetBaseUrl);
  target.searchParams.set('code', handoffCode);
  if (nextPath) {
    target.searchParams.set('next', nextPath);
  }
  return target.toString();
}

function getApiBaseUrl(): string {
  if (API_URL) return API_URL;
  if (isBrowser()) return window.location.origin;
  return DEFAULT_API_URL;
}

function buildApiUrl(path: string): string {
  if (!API_URL && isBrowser()) {
    return path;
  }
  return `${getApiBaseUrl()}${path}`;
}

type FetchJsonOptions = RequestInit & {
  timeoutMs?: number;
};

export class ApiError extends Error {
  status: number;
  detail: string;

  constructor(message: string, options: { status: number; detail: string }) {
    super(message);
    this.name = 'ApiError';
    this.status = options.status;
    this.detail = options.detail;
  }
}

export function isApiError(error: unknown, status?: number): error is ApiError {
  return error instanceof ApiError && (status === undefined || error.status === status);
}

function parseErrorDetail(text: string): string {
  try {
    const parsed = JSON.parse(text);
    if (parsed && typeof parsed.detail === 'string') {
      return parsed.detail;
    }
  } catch {
    // keep raw text
  }
  return text;
}

function withTimeout(
  signal?: AbortSignal,
  timeoutMs: number = REQUEST_TIMEOUT_MS
): { signal: AbortSignal; cleanup: () => void } {
  const locale = getCurrentAppLocale();
  const controller = new AbortController();
  const timeoutId = globalThis.setTimeout(() => {
    controller.abort(new DOMException(pickAppText(locale, '请求超时', 'Request timed out'), 'AbortError'));
  }, timeoutMs);

  const forwardAbort = () => controller.abort(signal?.reason);
  signal?.addEventListener('abort', forwardAbort, { once: true });

  return {
    signal: controller.signal,
    cleanup: () => {
      globalThis.clearTimeout(timeoutId);
      signal?.removeEventListener('abort', forwardAbort);
    },
  };
}

// ---------------------------------------------------------------------------
// Token management
// ---------------------------------------------------------------------------

export function getAccessToken(): string | null {
  if (!isBrowser()) return null;
  return localStorage.getItem(ACCESS_TOKEN_KEY);
}

export function getRefreshToken(): string | null {
  if (!isBrowser()) return null;
  return localStorage.getItem(REFRESH_TOKEN_KEY);
}

export function setTokens(access: string, refresh: string): void {
  if (!isBrowser()) return;
  localStorage.setItem(ACCESS_TOKEN_KEY, access);
  localStorage.setItem(REFRESH_TOKEN_KEY, refresh);
}

export function clearTokens(): void {
  if (!isBrowser()) return;
  localStorage.removeItem(ACCESS_TOKEN_KEY);
  localStorage.removeItem(REFRESH_TOKEN_KEY);
  window.dispatchEvent(new CustomEvent(AUTH_CLEARED_EVENT));
}

export function isLoggedIn(): boolean {
  return !!getAccessToken();
}

// ---------------------------------------------------------------------------
// HTTP helpers
// ---------------------------------------------------------------------------

function authHeaders(includeJsonContentType: boolean = true): Record<string, string> {
  const headers: Record<string, string> = {};
  if (includeJsonContentType) {
    headers['Content-Type'] = 'application/json';
  }
  const token = getAccessToken();
  if (token) {
    headers.Authorization = `Bearer ${token}`;
  }
  return headers;
}

async function fetchJSON<T>(path: string, options?: FetchJsonOptions): Promise<T> {
  const locale = getCurrentAppLocale();
  const mergedHeaders = {
    ...authHeaders(),
    ...(options?.headers as Record<string, string> | undefined),
  };
  const { signal, cleanup } = withTimeout(options?.signal ?? undefined, options?.timeoutMs);
  let res: Response;

  try {
    res = await fetch(buildApiUrl(path), {
      headers: mergedHeaders,
      ...options,
      signal,
    });
  } catch (error) {
    cleanup();
    if (error instanceof DOMException && error.name === 'AbortError') {
      throw new Error(pickAppText(locale, '请求超时', 'Request timed out'));
    }
    throw error;
  }

  cleanup();

  if (!res.ok) {
    const text = await res.text();
    if (res.status === 401) {
      clearTokens();
    }
    const detail = parseErrorDetail(text);
    throw new ApiError(`${pickAppText(locale, '接口错误', 'API error')} ${res.status}: ${detail}`, {
      status: res.status,
      detail,
    });
  }
  return res.json();
}

// ---------------------------------------------------------------------------
// Auth API
// ---------------------------------------------------------------------------

export async function register(username: string, email: string, password: string): Promise<TokenResponse> {
  return fetchJSON('/api/auth/register', {
    method: 'POST',
    body: JSON.stringify({ username, email, password }),
  });
}

export async function login(username: string, password: string): Promise<TokenResponse> {
  return fetchJSON('/api/auth/login', {
    method: 'POST',
    body: JSON.stringify({ username, password }),
  });
}

export async function consumeHandoffCode(code: string): Promise<TokenResponse> {
  return fetchJSON('/api/auth/handoff/consume', {
    method: 'POST',
    body: JSON.stringify({ code }),
  });
}

export async function logout(): Promise<void> {
  try {
    await fetchJSON('/api/auth/logout', { method: 'POST' });
  } catch {
    // ignore logout network failures
  } finally {
    clearTokens();
  }
}

export async function getMe(): Promise<AuthUser> {
  return fetchJSON('/api/auth/me');
}

// ---------------------------------------------------------------------------
// Chat (proxied via /api/)
// ---------------------------------------------------------------------------

export async function sendMessage(
  message: string,
  sessionId: string = 'web:default',
  attachments?: FileAttachment[],
  options?: {
    replyToScheduledRunId?: string;
    scheduledReplyIntent?: 'revise_once' | 'update_future' | 'continue_task';
    thinkingEnabled?: boolean;
    promptLocale?: PromptLocale;
  }
): Promise<{
  response?: string;
  status?: string;
  session_id: string;
  run_id?: string;
  task_id?: string | null;
  task_status?: string | null;
  evidence_status?: string | null;
}> {
  const body: Record<string, unknown> = {
    message,
    session_id: sessionId,
    prompt_locale: options?.promptLocale || promptLocaleForAppLocale(getCurrentAppLocale()),
  };
  if (attachments && attachments.length > 0) {
    body.attachments = attachments;
  }
  if (options?.replyToScheduledRunId) {
    body.reply_to_scheduled_run_id = options.replyToScheduledRunId;
    body.scheduled_reply_intent = options.scheduledReplyIntent || 'revise_once';
  }
  if (typeof options?.thinkingEnabled === 'boolean') {
    body.thinking_enabled = options.thinkingEnabled;
  }
  const result = await fetchJSON<{
    response?: string;
    status?: string;
    session_id: string;
    run_id?: string;
    output_text?: string;
    finish_reason?: string;
    task_id?: string | null;
    task_status?: string | null;
    evidence_status?: string | null;
  }>('/api/chat', {
    method: 'POST',
    body: JSON.stringify(body),
  });
  return {
    response: result.response ?? result.output_text,
    status: result.status ?? result.finish_reason,
    session_id: result.session_id,
    run_id: result.run_id,
    task_id: result.task_id,
    task_status: result.task_status,
    evidence_status: result.evidence_status,
  };
}

export async function submitChatFeedback(params: {
  sessionId: string;
  runId: string;
  feedbackType: 'accept' | 'revise' | 'abandon';
  comment?: string;
}): Promise<{
  session_id: string;
  run_id: string;
  task_id: string;
  task_status: string;
  acceptance_type: string;
  feedback_type: string;
}> {
  return fetchJSON('/api/chat/acceptance', {
    method: 'POST',
    body: JSON.stringify({
      session_id: params.sessionId,
      run_id: params.runId,
      acceptance_type: params.feedbackType,
      comment: params.comment,
    }),
  });
}

export function streamMessage(
  message: string,
  sessionId: string,
  onChunk: (content: string) => void,
  onDone: () => void,
  onError: (error: string) => void
): () => void {
  const controller = new AbortController();
  const locale = getCurrentAppLocale();

  (async () => {
    try {
      const res = await fetch(buildApiUrl('/api/chat/stream'), {
        method: 'POST',
        headers: authHeaders(),
        body: JSON.stringify({
          message,
          session_id: sessionId,
          prompt_locale: promptLocaleForAppLocale(getCurrentAppLocale()),
        }),
        signal: controller.signal,
      });

      if (!res.ok || !res.body) {
        onError(`${pickAppText(locale, 'HTTP 错误', 'HTTP error')} ${res.status}`);
        return;
      }

      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buffer = '';

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split('\n');
        buffer = lines.pop() || '';

        for (const line of lines) {
          if (!line.startsWith('data: ')) continue;
          const data = line.slice(6);
          try {
            const parsed = JSON.parse(data);
            if (parsed.type === 'content') {
              onChunk(parsed.content);
            } else if (parsed.type === 'done') {
              onDone();
            } else if (parsed.type === 'error') {
              onError(parsed.error);
            }
          } catch {
            // skip parse errors
          }
        }
      }
    } catch (err: any) {
      if (err.name !== 'AbortError') {
        onError(err.message || pickAppText(locale, '流式请求失败', 'Streaming request failed'));
      }
    }
  })();

  return () => controller.abort();
}

// ---------------------------------------------------------------------------
// WebSocket Manager
// ---------------------------------------------------------------------------

export type WsStatus = 'disconnected' | 'connecting' | 'connected';

export type WsMessageHandler = (data: WsEvent | Record<string, unknown>) => void;

export type WsStatusListener = (status: WsStatus) => void;

function getWsUrl(): string {
  const baseUrl =
    WS_URL ||
    (API_URL ? API_URL : isBrowser() ? window.location.origin : DEFAULT_API_URL);
  const url = new URL(baseUrl);
  let protocol: 'ws:' | 'wss:';
  if (url.protocol === 'ws:' || url.protocol === 'wss:') {
    protocol = url.protocol;
  } else if (url.protocol === 'https:') {
    protocol = 'wss:';
  } else {
    protocol = 'ws:';
  }
  return `${protocol}//${url.host}`;
}

class WebSocketManager {
  private ws: WebSocket | null = null;
  private sessionId: string | null = null;
  private messageHandlers: WsMessageHandler[] = [];
  private statusListeners: WsStatusListener[] = [];
  private status: WsStatus = 'disconnected';
  private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
  private pingTimer: ReturnType<typeof setInterval> | null = null;
  private reconnectDelay = 1000;
  private maxReconnectDelay = 30000;
  private intentionalClose = false;

  connect(sessionId: string): void {
    if (this.sessionId === sessionId && this.ws?.readyState === globalThis.WebSocket?.OPEN) {
      return;
    }

    this.intentionalClose = false;
    this.sessionId = sessionId;
    this.reconnectDelay = 1000;
    this._connect();
  }

  disconnect(): void {
    this.intentionalClose = true;
    this._cleanup();
    this._setStatus('disconnected');
  }

  sendMessage(content: string): void {
    if (this.ws?.readyState === globalThis.WebSocket?.OPEN) {
      this.ws.send(JSON.stringify({ type: 'message', content }));
    }
  }

  sendRaw(payload: Record<string, unknown>): void {
    if (this.ws?.readyState === globalThis.WebSocket?.OPEN) {
      this.ws.send(JSON.stringify(payload));
    }
  }

  onMessage(handler: WsMessageHandler): () => void {
    this.messageHandlers.push(handler);
    return () => {
      this.messageHandlers = this.messageHandlers.filter((h) => h !== handler);
    };
  }

  onStatusChange(listener: WsStatusListener): () => void {
    this.statusListeners.push(listener);
    listener(this.status);
    return () => {
      this.statusListeners = this.statusListeners.filter((l) => l !== listener);
    };
  }

  getStatus(): WsStatus {
    return this.status;
  }

  private _connect(): void {
    this._cleanup();

    if (!this.sessionId) return;

    this._setStatus('connecting');

    const wsUrl = getWsUrl();
    const token = getAccessToken();
    const query = token ? `?token=${encodeURIComponent(token)}` : '';
    const ws = new globalThis.WebSocket(`${wsUrl}/ws/${this.sessionId}${query}`);

    ws.onopen = () => {
      this.reconnectDelay = 1000;
      this._setStatus('connected');
      this._startPing();
    };

    ws.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data);
        if (data.type === 'pong') return;
        for (const handler of this.messageHandlers) {
          handler(data);
        }
      } catch {
        // ignore parse errors
      }
    };

    ws.onclose = () => {
      this._stopPing();
      if (!this.intentionalClose) {
        this._setStatus('disconnected');
        this._scheduleReconnect();
      }
    };

    ws.onerror = () => {
      // onclose will fire after onerror
    };

    this.ws = ws;
  }

  private _cleanup(): void {
    this._stopPing();
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    if (this.ws) {
      this.ws.onopen = null;
      this.ws.onmessage = null;
      this.ws.onclose = null;
      this.ws.onerror = null;
      if (this.ws.readyState === globalThis.WebSocket?.OPEN ||
          this.ws.readyState === globalThis.WebSocket?.CONNECTING) {
        this.ws.close();
      }
      this.ws = null;
    }
  }

  private _scheduleReconnect(): void {
    if (this.reconnectTimer) return;
    this.reconnectTimer = setTimeout(() => {
      this.reconnectTimer = null;
      this._connect();
    }, this.reconnectDelay);
    this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);
  }

  private _startPing(): void {
    this._stopPing();
    this.pingTimer = setInterval(() => {
      if (this.ws?.readyState === globalThis.WebSocket?.OPEN) {
        this.ws.send(JSON.stringify({ type: 'ping' }));
      }
    }, 30000);
  }

  private _stopPing(): void {
    if (this.pingTimer) {
      clearInterval(this.pingTimer);
      this.pingTimer = null;
    }
  }

  private _setStatus(status: WsStatus): void {
    if (this.status === status) return;
    this.status = status;
    for (const listener of this.statusListeners) {
      listener(status);
    }
  }
}

export const wsManager = new WebSocketManager();

// ---------------------------------------------------------------------------
// Sessions (proxied)
// ---------------------------------------------------------------------------

export async function listSessions(): Promise<Session[]> {
  return fetchJSON('/api/sessions');
}

export async function createSession(key: string): Promise<SessionDetail> {
  return fetchJSON(`/api/sessions/${encodeURIComponent(key)}`, { method: 'POST' });
}

export async function getSession(key: string): Promise<SessionDetail> {
  return fetchJSON(`/api/sessions/${encodeURIComponent(key)}`);
}

export async function getSessionProcess(key: string): Promise<SessionProcessProjection> {
  return fetchJSON(`/api/sessions/${encodeURIComponent(key)}/process`);
}

export async function getChatLogs(limit = 50): Promise<ChatLogsResponse> {
  return fetchJSON(`/api/debug/chat-logs?limit=${encodeURIComponent(String(limit))}`, {
    timeoutMs: 30000,
  });
}

export async function archiveSession(key: string): Promise<void> {
  await fetchJSON(`/api/sessions/${encodeURIComponent(key)}/archive`, { method: 'POST' });
}

// ---------------------------------------------------------------------------
// Status (proxied)
// ---------------------------------------------------------------------------

export async function getStatus(): Promise<SystemStatus> {
  return fetchJSON('/api/status');
}

export async function updateAgentConfig(payload: AgentConfigPayload): Promise<{ ok: boolean }> {
  return fetchJSON('/api/agent-config', {
    method: 'POST',
    body: JSON.stringify(payload),
  });
}

export async function updateProviderConfig(
  providerId: string,
  payload: ProviderConfigPayload
): Promise<{ ok: boolean; provider: string; enabled: boolean }> {
  return fetchJSON(`/api/providers/${encodeURIComponent(providerId)}/config`, {
    method: 'POST',
    body: JSON.stringify(payload),
  });
}

export async function getChannelConfig(channelId: string): Promise<ChannelConfigDetail> {
  return fetchJSON(`/api/channels/${encodeURIComponent(channelId)}/config`);
}

export async function updateChannelConfig(
  channelId: string,
  payload: ChannelConfigPayload
): Promise<{ ok: boolean; channel_id: string; restart_required: boolean; channel: ChannelConfigDetail }> {
  return fetchJSON(`/api/channels/${encodeURIComponent(channelId)}/config`, {
    method: 'POST',
    body: JSON.stringify(payload),
  });
}

export async function listChannelEvents(channelId: string, limit: number = 100): Promise<ChannelEventRecord[]> {
  return fetchJSON(`/api/channels/${encodeURIComponent(channelId)}/events?limit=${limit}`);
}

export async function listChannelConnectors(): Promise<ChannelConnectorDescriptor[]> {
  return fetchJSON('/api/channel-connectors');
}

export async function listChannelConnections(): Promise<ChannelConnectionView[]> {
  return fetchJSON('/api/channel-connections');
}

export async function startChannelConnectorSession(
  payload: ConnectorSessionStartPayload
): Promise<ConnectorSessionResponse> {
  return fetchJSON('/api/channel-connector-sessions', {
    method: 'POST',
    body: JSON.stringify({
      kind: payload.kind,
      displayName: payload.displayName,
      ownerUserId: payload.ownerUserId,
      options: payload.options || {},
    }),
  });
}

export async function getChannelConnectorSession(sessionId: string): Promise<ConnectorSessionResponse> {
  return fetchJSON(`/api/channel-connector-sessions/${encodeURIComponent(sessionId)}`);
}

export async function restartRuntime(): Promise<{ ok: boolean; restarting: boolean }> {
  return fetchJSON('/api/runtime/restart', {
    method: 'POST',
    timeoutMs: 5000,
  });
}

// ---------------------------------------------------------------------------
// Cron (proxied)
// ---------------------------------------------------------------------------

export async function listCronJobs(includeDisabled: boolean = true): Promise<CronJob[]> {
  return fetchJSON(`/api/cron/jobs?include_disabled=${includeDisabled}`);
}

export async function addCronJob(params: {
  name: string;
  message: string;
  every_seconds?: number;
  cron_expr?: string;
  at_iso?: string;
  tz?: string;
  session_key?: string;
  mode?: 'notification' | 'task';
  requires_followup?: boolean;
}): Promise<CronJob> {
  return fetchJSON('/api/cron/jobs', {
    method: 'POST',
    body: JSON.stringify(params),
  });
}

export async function removeCronJob(jobId: string): Promise<void> {
  await fetchJSON(`/api/cron/jobs/${jobId}`, { method: 'DELETE' });
}

export async function toggleCronJob(jobId: string, enabled: boolean): Promise<CronJob> {
  return fetchJSON(`/api/cron/jobs/${jobId}/toggle`, {
    method: 'PUT',
    body: JSON.stringify({ enabled }),
  });
}

export async function runCronJob(jobId: string): Promise<void> {
  await fetchJSON(`/api/cron/jobs/${jobId}/run`, { method: 'POST' });
}

export async function listNotifications(): Promise<NotificationRun[]> {
  return fetchJSON('/api/notifications');
}

export async function getNotification(scheduledRunId: string): Promise<NotificationDetail> {
  return fetchJSON(`/api/notifications/${encodeURIComponent(scheduledRunId)}`);
}

export async function engageNotification(
  scheduledRunId: string,
  intent: 'revise_once' | 'update_future' | 'continue_task'
): Promise<{ ok: boolean; task_id: string; intent: string }> {
  return fetchJSON(`/api/notifications/${encodeURIComponent(scheduledRunId)}/engage`, {
    method: 'POST',
    body: JSON.stringify({ intent }),
  });
}

export async function listBackendTasks(): Promise<BackendTask[]> {
  return fetchJSON('/api/tasks');
}

export async function getBackendTask(taskId: string): Promise<BackendTask> {
  return fetchJSON(`/api/tasks/${encodeURIComponent(taskId)}`);
}

export async function deleteBackendTask(taskId: string): Promise<void> {
  await fetchJSON(`/api/tasks/${encodeURIComponent(taskId)}`, { method: 'DELETE' });
}

export async function getActiveTask(sessionId: string): Promise<ActiveTask | null> {
  return fetchJSON(`/api/sessions/${encodeURIComponent(sessionId)}/active-task`);
}

export async function ping(): Promise<{ message: string }> {
  return fetchJSON('/api/ping');
}

// ---------------------------------------------------------------------------
// Skills (proxied)
// ---------------------------------------------------------------------------

export async function listSkills(): Promise<Skill[]> {
  return fetchJSON('/api/skills');
}

export async function getSkillDetail(skillName: string): Promise<SkillDetailResponse> {
  return fetchJSON(`/api/skills/${encodeURIComponent(skillName)}/detail`);
}

export async function getSkillVersion(skillName: string, version: string): Promise<SkillDetailResponse> {
  return fetchJSON(`/api/skills/${encodeURIComponent(skillName)}/versions/${encodeURIComponent(version)}`);
}

export async function getSkillFile(skillName: string, version: string, filePath: string): Promise<SkillFileContent> {
  const search = new URLSearchParams({ path: filePath });
  return fetchJSON(
    `/api/skills/${encodeURIComponent(skillName)}/versions/${encodeURIComponent(version)}/file?${search.toString()}`
  );
}

export async function listSkillCandidates(status?: string): Promise<SkillLearningCandidate[]> {
  const query = status ? `?status=${encodeURIComponent(status)}` : '';
  return fetchJSON(`/api/skills/candidates${query}`);
}

export async function synthesizeSkillDraft(candidateId: string): Promise<SkillDraft> {
  return fetchJSON(`/api/skills/candidates/${encodeURIComponent(candidateId)}/draft`, {
    method: 'POST',
    body: JSON.stringify({}),
    timeoutMs: SKILL_LEARNING_REQUEST_TIMEOUT_MS,
  });
}

export async function regenerateSkillDraft(candidateId: string): Promise<SkillDraft> {
  return fetchJSON(`/api/skills/candidates/${encodeURIComponent(candidateId)}/regenerate`, {
    method: 'POST',
    body: JSON.stringify({}),
    timeoutMs: SKILL_LEARNING_REQUEST_TIMEOUT_MS,
  });
}

export async function runSkillLearningOnce(): Promise<{
  processed: number;
  succeeded: number;
  failed: number;
  skipped: number;
  failures: Array<Record<string, string>>;
}> {
  return fetchJSON('/api/skills/learning/run-once', {
    method: 'POST',
    body: JSON.stringify({}),
    timeoutMs: SKILL_LEARNING_REQUEST_TIMEOUT_MS,
  });
}

export async function listSkillDrafts(): Promise<SkillDraft[]> {
  return fetchJSON('/api/skills/drafts');
}

export async function getSkillDraft(skillName: string, draftId: string): Promise<SkillDraft> {
  return fetchJSON(`/api/skills/${encodeURIComponent(skillName)}/drafts/${encodeURIComponent(draftId)}`);
}

export async function getSkillDraftSafety(skillName: string, draftId: string): Promise<SkillDraftSafetyReport> {
  return fetchJSON(`/api/skills/${encodeURIComponent(skillName)}/drafts/${encodeURIComponent(draftId)}/safety`);
}

export async function recheckSkillDraftSafety(skillName: string, draftId: string): Promise<SkillDraftSafetyReport> {
  return fetchJSON(`/api/skills/${encodeURIComponent(skillName)}/drafts/${encodeURIComponent(draftId)}/safety`, {
    method: 'POST',
    body: JSON.stringify({}),
  });
}

export async function getSkillDraftEval(skillName: string, draftId: string): Promise<SkillDraftEvalReport> {
  return fetchJSON(`/api/skills/${encodeURIComponent(skillName)}/drafts/${encodeURIComponent(draftId)}/eval`);
}

export async function submitSkillDraft(
  skillName: string,
  draftId: string,
  notes: string = ''
): Promise<SkillReviewRecord> {
  return fetchJSON(`/api/skills/${encodeURIComponent(skillName)}/drafts/${encodeURIComponent(draftId)}/submit`, {
    method: 'POST',
    body: JSON.stringify({ notes }),
  });
}

export async function approveSkillDraft(
  skillName: string,
  draftId: string,
  notes: string = ''
): Promise<SkillReviewRecord> {
  return fetchJSON(`/api/skills/${encodeURIComponent(skillName)}/drafts/${encodeURIComponent(draftId)}/approve`, {
    method: 'POST',
    body: JSON.stringify({ notes }),
  });
}

export async function rejectSkillDraft(
  skillName: string,
  draftId: string,
  notes: string = ''
): Promise<SkillReviewRecord> {
  return fetchJSON(`/api/skills/${encodeURIComponent(skillName)}/drafts/${encodeURIComponent(draftId)}/reject`, {
    method: 'POST',
    body: JSON.stringify({ notes }),
  });
}

export async function publishSkillDraft(
  skillName: string,
  draftId: string,
  notes: string = '',
  confirmHighRisk: boolean = false
): Promise<Record<string, unknown>> {
  return fetchJSON(`/api/skills/${encodeURIComponent(skillName)}/drafts/${encodeURIComponent(draftId)}/publish`, {
    method: 'POST',
    body: JSON.stringify({ notes, confirm_high_risk: confirmHighRisk }),
  });
}

export async function disablePublishedSkill(skillName: string, reason: string = ''): Promise<Record<string, unknown>> {
  return fetchJSON(`/api/skills/${encodeURIComponent(skillName)}/disable`, {
    method: 'POST',
    body: JSON.stringify({ reason }),
  });
}

export async function rollbackPublishedSkill(
  skillName: string,
  targetVersion: string,
  reason: string = ''
): Promise<Record<string, unknown>> {
  return fetchJSON(`/api/skills/${encodeURIComponent(skillName)}/rollback`, {
    method: 'POST',
    body: JSON.stringify({ target_version: targetVersion, reason }),
  });
}

export async function listAgents(): Promise<UiAgentDescriptor[]> {
  return fetchJSON('/api/agents');
}

export async function addAgent(payload: {
  id?: string;
  name?: string;
  description?: string;
  protocol?: string;
  base_url?: string;
  endpoint?: string;
  card_url?: string;
  auth_env?: string;
  auth_mode?: string;
  auth_audience?: string;
  auth_scopes?: string[];
  enabled?: boolean;
  tags?: string[];
  aliases?: string[];
  metadata?: Record<string, unknown>;
}): Promise<UiAgentDescriptor> {
  return fetchJSON('/api/agents', {
    method: 'POST',
    body: JSON.stringify(payload),
  });
}

export async function deleteAgent(agentId: string): Promise<void> {
  await fetchJSON(`/api/agents/${encodeURIComponent(agentId)}`, { method: 'DELETE' });
}

export async function refreshAgents(): Promise<{ agents: UiAgentDescriptor[] }> {
  return fetchJSON('/api/agents/refresh', { method: 'POST' });
}

export async function listSubagents(): Promise<UiSubagentDescriptor[]> {
  return fetchJSON('/api/subagents');
}

export async function createSubagent(payload: {
  id: string;
  name?: string;
  description?: string;
  system_prompt?: string;
  model?: string;
  enabled?: boolean;
  delegation_mode?: string;
  allow_mcp?: boolean;
  tags?: string[];
  aliases?: string[];
  mcp_servers?: Record<string, Record<string, unknown>>;
  metadata?: Record<string, unknown>;
}): Promise<UiSubagentDescriptor> {
  return fetchJSON('/api/subagents', {
    method: 'POST',
    body: JSON.stringify(payload),
  });
}

export async function updateSubagent(
  subagentId: string,
  payload: {
    id: string;
    name?: string;
    description?: string;
    system_prompt?: string;
    model?: string;
    enabled?: boolean;
    delegation_mode?: string;
    allow_mcp?: boolean;
    tags?: string[];
    aliases?: string[];
    mcp_servers?: Record<string, Record<string, unknown>>;
    metadata?: Record<string, unknown>;
  }
): Promise<UiSubagentDescriptor> {
  return fetchJSON(`/api/subagents/${encodeURIComponent(subagentId)}`, {
    method: 'PUT',
    body: JSON.stringify(payload),
  });
}

export async function deleteSubagent(subagentId: string): Promise<void> {
  await fetchJSON(`/api/subagents/${encodeURIComponent(subagentId)}`, {
    method: 'DELETE',
  });
}

export async function listMcpServers(): Promise<UiMcpServerDescriptor[]> {
  return fetchJSON('/api/mcp/servers');
}

export async function addMcpServer(payload: {
  id: string;
  command?: string;
  args?: string[];
  env?: Record<string, string>;
  url?: string;
  headers?: Record<string, string>;
  auth_mode?: string;
  auth_audience?: string;
  auth_scopes?: string[];
  tool_timeout?: number;
}): Promise<UiMcpServerDescriptor> {
  return fetchJSON('/api/mcp/servers', {
    method: 'POST',
    body: JSON.stringify(payload),
  });
}

export async function updateMcpServer(
  serverId: string,
  payload: {
    id: string;
    command?: string;
    args?: string[];
    env?: Record<string, string>;
    url?: string;
    headers?: Record<string, string>;
    auth_mode?: string;
    auth_audience?: string;
    auth_scopes?: string[];
    tool_timeout?: number;
  }
): Promise<UiMcpServerDescriptor> {
  return fetchJSON(`/api/mcp/servers/${encodeURIComponent(serverId)}`, {
    method: 'PUT',
    body: JSON.stringify(payload),
  });
}

export async function deleteMcpServer(serverId: string): Promise<void> {
  await fetchJSON(`/api/mcp/servers/${encodeURIComponent(serverId)}`, {
    method: 'DELETE',
  });
}

export async function getAuthzStatus(): Promise<AuthzStatus> {
  return fetchJSON('/api/authz/status');
}

export async function testMcpServer(serverId: string): Promise<Record<string, unknown>> {
  return fetchJSON(`/api/mcp/servers/${encodeURIComponent(serverId)}/test`, {
    method: 'POST',
  });
}

export async function listMcpTools(): Promise<Array<{ server_id: string; tools: Array<Record<string, unknown>> }>> {
  return fetchJSON('/api/mcp/tools');
}

export async function getOutlookStatus(): Promise<OutlookStatus> {
  return fetchJSON('/api/integrations/outlook/status', {
    timeoutMs: OUTLOOK_REQUEST_TIMEOUT_MS,
  });
}

export async function testOutlookConnection(
  payload: OutlookConnectionPayload
): Promise<OutlookConnectionTestResult> {
  return fetchJSON('/api/integrations/outlook/test-connection', {
    method: 'POST',
    body: JSON.stringify(payload),
    timeoutMs: OUTLOOK_REQUEST_TIMEOUT_MS,
  });
}

export async function connectOutlook(
  payload: OutlookConnectionPayload
): Promise<OutlookConnectResult> {
  return fetchJSON('/api/integrations/outlook/connect', {
    method: 'POST',
    body: JSON.stringify(payload),
    timeoutMs: OUTLOOK_REQUEST_TIMEOUT_MS,
  });
}

export async function disconnectOutlook(): Promise<{ ok: boolean }> {
  return fetchJSON('/api/integrations/outlook/disconnect', {
    method: 'POST',
    timeoutMs: OUTLOOK_REQUEST_TIMEOUT_MS,
  });
}

export async function getOutlookOverview(): Promise<OutlookOverview> {
  return fetchJSON('/api/integrations/outlook/overview', {
    timeoutMs: OUTLOOK_REQUEST_TIMEOUT_MS,
  });
}

export async function getOutlookMessages(
  folder: string,
  options?: {
    top?: number;
    skip?: number;
    unreadOnly?: boolean;
  }
): Promise<OutlookMessageListResponse> {
  const params = new URLSearchParams({
    folder,
    top: String(options?.top ?? 20),
    skip: String(options?.skip ?? 0),
  });
  if (options?.unreadOnly) {
    params.set('unread_only', 'true');
  }
  return fetchJSON(`/api/integrations/outlook/messages?${params.toString()}`, {
    timeoutMs: OUTLOOK_REQUEST_TIMEOUT_MS,
  });
}

export async function getOutlookEvents(options: {
  startTime: string;
  endTime: string;
  top?: number;
  skip?: number;
}): Promise<OutlookEventListResponse> {
  const params = new URLSearchParams({
    start_time: options.startTime,
    end_time: options.endTime,
    top: String(options.top ?? 20),
    skip: String(options.skip ?? 0),
  });
  return fetchJSON(`/api/integrations/outlook/events?${params.toString()}`, {
    timeoutMs: OUTLOOK_REQUEST_TIMEOUT_MS,
  });
}

export async function getOutlookMessageDetail(
  messageId: string,
  changekey?: string | null
): Promise<OutlookMessageDetail> {
  const params = new URLSearchParams({ message_id: messageId });
  if (changekey) {
    params.set('changekey', changekey);
  }
  return fetchJSON(`/api/integrations/outlook/message-detail?${params.toString()}`, {
    timeoutMs: OUTLOOK_REQUEST_TIMEOUT_MS,
  });
}

export async function downloadSkill(name: string): Promise<void> {
  const url = buildApiUrl(`/api/skills/${encodeURIComponent(name)}/download`);
  const res = await fetch(url, { headers: authHeaders(false) });
  if (!res.ok) {
    const text = await res.text();
    throw new Error(`下载失败：${text}`);
  }

  const blob = await res.blob();
  const a = document.createElement('a');
  a.href = URL.createObjectURL(blob);
  a.download = `${name}.zip`;
  document.body.appendChild(a);
  a.click();
  a.remove();
  URL.revokeObjectURL(a.href);
}

export async function deleteSkill(name: string): Promise<void> {
  await fetchJSON(`/api/skills/${encodeURIComponent(name)}`, { method: 'DELETE' });
}

export async function uploadSkill(file: File): Promise<Skill> {
  const formData = new FormData();
  formData.append('file', file);

  const token = getAccessToken();
  const headers: Record<string, string> = {};
  if (token) headers.Authorization = `Bearer ${token}`;

  const url = buildApiUrl('/api/skills/upload');
  const res = await fetch(url, {
    method: 'POST',
    headers,
    body: formData,
  });

  if (!res.ok) {
    const text = await res.text();
    throw new Error(`接口错误 ${res.status}: ${parseErrorDetail(text)}`);
  }
  return res.json();
}

// ---------------------------------------------------------------------------
// SkillHub marketplace
// ---------------------------------------------------------------------------

export async function searchSkillHubSkills(params: {
  q?: string;
  sort?: 'relevance' | 'downloads' | 'newest';
  page?: number;
  size?: number;
  namespace?: string;
} = {}): Promise<SkillHubSearchResponse> {
  const search = new URLSearchParams();
  if (params.q) search.set('q', params.q);
  if (params.sort) search.set('sort', params.sort);
  if (typeof params.page === 'number') search.set('page', String(params.page));
  if (typeof params.size === 'number') search.set('size', String(params.size));
  if (params.namespace) search.set('namespace', params.namespace);
  const suffix = search.toString();
  return fetchJSON(`/api/marketplaces/skills/search${suffix ? `?${suffix}` : ''}`);
}

export async function getSkillHubDetail(namespace: string, slug: string): Promise<SkillHubSearchItem> {
  return fetchJSON(
    `/api/marketplaces/skills/${encodeURIComponent(namespace.replace(/^@/, ''))}/${encodeURIComponent(slug)}`
  );
}

export async function getSkillHubVersion(
  namespace: string,
  slug: string,
  version: string
): Promise<SkillHubVersionResponse> {
  return fetchJSON(
    `/api/marketplaces/skills/${encodeURIComponent(namespace.replace(/^@/, ''))}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}`
  );
}

export async function getSkillHubVersions(namespace: string, slug: string): Promise<SkillHubVersionsResponse> {
  return fetchJSON(
    `/api/marketplaces/skills/${encodeURIComponent(namespace.replace(/^@/, ''))}/${encodeURIComponent(slug)}/versions`
  );
}

export async function getSkillHubFile(
  namespace: string,
  slug: string,
  version: string,
  filePath: string
): Promise<SkillFileContent> {
  const search = new URLSearchParams({ path: filePath });
  return fetchJSON(
    `/api/marketplaces/skills/${encodeURIComponent(namespace.replace(/^@/, ''))}/${encodeURIComponent(slug)}/versions/${encodeURIComponent(version)}/file?${search.toString()}`
  );
}

export async function installSkillHubSkill(
  namespace: string,
  slug: string,
  version?: string
): Promise<SkillHubInstallResponse> {
  return fetchJSON(
    `/api/marketplaces/skills/${encodeURIComponent(namespace.replace(/^@/, ''))}/${encodeURIComponent(slug)}/install`,
    {
      method: 'POST',
      body: JSON.stringify({ version }),
      timeoutMs: 45000,
    }
  );
}

// ---------------------------------------------------------------------------
// Files (proxied)
// ---------------------------------------------------------------------------

const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB

export async function uploadFile(
  file: File,
  sessionId: string = 'web:default',
  onProgress?: (percent: number) => void
): Promise<FileAttachment> {
  const locale = getCurrentAppLocale();
  if (file.size > MAX_FILE_SIZE) {
    throw new Error(pickAppText(locale, '文件过大（最大 50MB）', 'File is too large (max 50MB)'));
  }

  const formData = new FormData();
  formData.append('file', file);
  formData.append('session_id', sessionId);

  const result = await new Promise<FileAttachment>((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('POST', buildApiUrl('/api/files/upload'));
    const token = getAccessToken();
    if (token) {
      xhr.setRequestHeader('Authorization', `Bearer ${token}`);
    }

    xhr.upload.onprogress = (e) => {
      if (e.lengthComputable && onProgress) {
        onProgress(Math.round((e.loaded / e.total) * 100));
      }
    };

    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        const data = JSON.parse(xhr.responseText);
        resolve(data);
      } else {
        reject(new Error(`${pickAppText(locale, '上传失败', 'Upload failed')}: ${xhr.status}`));
      }
    };

    xhr.onerror = () => reject(new Error(pickAppText(locale, '上传失败', 'Upload failed')));
    xhr.send(formData);
  });

  return result;
}

export async function listFiles(sessionId?: string): Promise<FileAttachment[]> {
  const params = sessionId ? `?session_id=${encodeURIComponent(sessionId)}` : '';
  return fetchJSON(`/api/files${params}`);
}

export async function deleteFile(fileId: string): Promise<void> {
  await fetchJSON(`/api/files/${encodeURIComponent(fileId)}`, { method: 'DELETE' });
}

export function getFileUrl(fileId: string): string {
  return buildApiUrl(`/api/files/${encodeURIComponent(fileId)}`);
}

// ---------------------------------------------------------------------------
// Workspace Browser
// ---------------------------------------------------------------------------

export interface WorkspaceItem {
  name: string;
  path: string;
  type: 'file' | 'directory';
  size: number | null;
  content_type?: string;
  modified: string;
}

export interface BrowseResult {
  path: string;
  items: WorkspaceItem[];
}

export interface WorkspaceFileContent {
  name: string;
  path: string;
  size: number;
  content_type: string;
  modified: string;
  is_binary: boolean;
  is_truncated: boolean;
  content: string | null;
}

export async function browseWorkspace(path: string = ''): Promise<BrowseResult> {
  const params = path ? `?path=${encodeURIComponent(path)}` : '';
  return fetchJSON(`/api/workspace/browse${params}`);
}

export async function getWorkspaceFile(path: string): Promise<WorkspaceFileContent> {
  return fetchJSON(`/api/workspace/file?path=${encodeURIComponent(path)}`);
}

export function getWorkspaceDownloadUrl(path: string): string {
  return buildApiUrl(`/api/workspace/download?path=${encodeURIComponent(path)}`);
}

export async function uploadToWorkspace(
  file: File,
  dirPath: string = '',
  onProgress?: (percent: number) => void
): Promise<WorkspaceItem> {
  const locale = getCurrentAppLocale();
  if (file.size > MAX_FILE_SIZE) {
    throw new Error(pickAppText(locale, '文件过大（最大 50MB）', 'File is too large (max 50MB)'));
  }

  const formData = new FormData();
  formData.append('file', file);
  formData.append('path', dirPath);

  return new Promise<WorkspaceItem>((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('POST', buildApiUrl('/api/workspace/upload'));
    const token = getAccessToken();
    if (token) {
      xhr.setRequestHeader('Authorization', `Bearer ${token}`);
    }

    xhr.upload.onprogress = (e) => {
      if (e.lengthComputable && onProgress) {
        onProgress(Math.round((e.loaded / e.total) * 100));
      }
    };

    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(JSON.parse(xhr.responseText));
      } else {
        reject(new Error(`${pickAppText(locale, '上传失败', 'Upload failed')}: ${xhr.status}`));
      }
    };

    xhr.onerror = () => reject(new Error(pickAppText(locale, '上传失败', 'Upload failed')));
    xhr.send(formData);
  });
}

export async function deleteWorkspacePath(path: string): Promise<void> {
  await fetchJSON(`/api/workspace/delete?path=${encodeURIComponent(path)}`, {
    method: 'DELETE',
  });
}

export async function createWorkspaceDir(path: string): Promise<WorkspaceItem> {
  return fetchJSON(`/api/workspace/mkdir?path=${encodeURIComponent(path)}`, {
    method: 'POST',
  });
}

// ---------------------------------------------------------------------------
// User File System
// ---------------------------------------------------------------------------

export interface UserFileItem {
  name: string;
  path: string;
  type: 'file' | 'directory';
  size: number | null;
  content_type?: string | null;
  modified?: string | null;
}

export interface UserFileBrowseResult {
  path: string;
  items: UserFileItem[];
}

export interface UserFileContent {
  name: string;
  path: string;
  size: number;
  content_type: string;
  modified: string | null;
  is_binary: boolean;
  is_truncated: boolean;
  content: string | null;
}

export interface UserFilesStatus {
  configured: boolean;
  storage_mode: string;
  roots: string[];
  workspace_visible: boolean;
}

export async function getUserFilesStatus(): Promise<UserFilesStatus> {
  return fetchJSON('/api/user-files/status');
}

export async function browseUserFiles(path: string = ''): Promise<UserFileBrowseResult> {
  const params = path ? `?path=${encodeURIComponent(path)}` : '';
  return fetchJSON(`/api/user-files/browse${params}`);
}

export async function getUserFile(path: string): Promise<UserFileContent> {
  return fetchJSON(`/api/user-files/preview?path=${encodeURIComponent(path)}`);
}

export function getUserFileDownloadUrl(path: string): string {
  return buildApiUrl(`/api/user-files/download?path=${encodeURIComponent(path)}`);
}

export async function uploadUserFile(
  file: File,
  dirPath: string = 'uploads',
  onProgress?: (percent: number) => void
): Promise<UserFileItem> {
  const locale = getCurrentAppLocale();
  const formData = new FormData();
  formData.append('file', file);
  formData.append('path', dirPath);

  return new Promise<UserFileItem>((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('POST', buildApiUrl('/api/user-files/upload'));
    const token = getAccessToken();
    if (token) {
      xhr.setRequestHeader('Authorization', `Bearer ${token}`);
    }

    xhr.upload.onprogress = (e) => {
      if (e.lengthComputable && onProgress) {
        onProgress(Math.round((e.loaded / e.total) * 100));
      }
    };

    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(JSON.parse(xhr.responseText));
      } else {
        let detail = '';
        try {
          const data = JSON.parse(xhr.responseText);
          detail = typeof data?.detail === 'string' ? data.detail : '';
        } catch {
          detail = '';
        }
        reject(new Error(detail || `${pickAppText(locale, '上传失败', 'Upload failed')}: ${xhr.status}`));
      }
    };

    xhr.onerror = () => reject(new Error(pickAppText(locale, '上传失败', 'Upload failed')));
    xhr.send(formData);
  });
}

export async function deleteUserFile(path: string): Promise<void> {
  await fetchJSON(`/api/user-files/delete?path=${encodeURIComponent(path)}`, {
    method: 'DELETE',
  });
}

export async function createUserFileDir(path: string): Promise<UserFileItem> {
  return fetchJSON(`/api/user-files/mkdir?path=${encodeURIComponent(path)}`, {
    method: 'POST',
  });
}
