import type {
  AgentChatResponse,
  AgentChatResultResponse,
  AccessToolId,
  AccessUserRole,
  AccessUserStatus,
  AdminAccessMatrixResponse,
  AdminAccessMutationResponse,
  AdminAccessSummaryResponse,
  AdminAccessUsersResponse,
  ApiResult,
  ConversationRecord,
  HwpodNodeOpsResponse,
  HwpodSpecsResponse,
  LiveBuildsPayload,
  LiveProbePayload,
  ProviderProfile,
  ProviderProfileValidation,
  ProviderProfilesResponse,
  WebPerformanceSummaryResponse,
  SkillsResponse,
  SkillFileResponse,
  SkillTreeResponse,
  WorkspaceRecord
} from "../../types/domain";
import { recordApiTiming } from "../performance/rum";

export interface ActivityRef {
  lastActivityAt: number;
  lastActivityIso?: string;
  waitingFor?: string | null;
  lastEventLabel?: string | null;
}

export type ActivityRefSource = (() => ActivityRef | null) | null;

export interface ApiRequestOptions extends RequestInit {
  timeoutMs?: number;
  timeoutName?: string;
  /**
   * Inactivity-timeout reference. When supplied, the abort is re-evaluated
   * against `Date.now() - lastActivityAt`, so a long-running fetch stays
   * alive as long as upstream keeps emitting trace events. Mirrors the
   * pre-React fetchJson activityRef contract so the Code Agent / trace /
   * probe endpoints share the same model.
   */
  activityRef?: ActivityRef | ActivityRefSource;
}

const ACTIVITY_RESCHEDULER_MS = 1000;
const DEFAULT_TIMEOUT_MS = 4500;

export async function fetchJson<T>(path: string, options: ApiRequestOptions = {}): Promise<ApiResult<T>> {
  const controller = new AbortController();
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
  const activitySource = resolveActivitySource(options.activityRef);
  let timer: number | null = null;
  const inactivityRef: { current: { idleMs: number; lastActivityAt: number; lastActivityIso?: string; waitingFor?: string | null; lastEventLabel?: string | null } | null } = { current: null };
  const startedAt = Date.now();
  const method = typeof options.method === "string" ? options.method : "GET";

  function readActivity(): { lastActivityAt: number; lastActivityIso?: string; waitingFor?: string | null; lastEventLabel?: string | null } {
    const fallback = { lastActivityAt: startedAt, lastActivityIso: new Date(startedAt).toISOString() };
    if (!activitySource) return fallback;
    const ref = activitySource();
    if (!ref || !Number.isFinite(ref.lastActivityAt) || ref.lastActivityAt <= 0) return fallback;
    return ref;
  }

  function schedule(): void {
    if (timer !== null) {
      window.clearTimeout(timer);
      timer = null;
    }
    if (!activitySource) {
      timer = window.setTimeout(() => controller.abort(), timeoutMs);
      return;
    }
    const activity = readActivity();
    const idleMs = Math.max(0, Date.now() - activity.lastActivityAt);
    const remaining = timeoutMs - idleMs;
    if (remaining <= 0) {
      inactivityRef.current = { idleMs, lastActivityAt: activity.lastActivityAt, lastActivityIso: activity.lastActivityIso, waitingFor: activity.waitingFor, lastEventLabel: activity.lastEventLabel };
      controller.abort();
      return;
    }
    timer = window.setTimeout(schedule, Math.max(250, Math.min(remaining, ACTIVITY_RESCHEDULER_MS)));
  }
  schedule();

  try {
    const response = await fetch(path, {
      ...options,
      credentials: options.credentials ?? "same-origin",
      headers: {
        accept: "application/json",
        ...(options.body ? { "content-type": "application/json" } : {}),
        ...(options.headers ?? {})
      },
      signal: controller.signal
    });
    const payload = await response.json().catch(() => null) as T | null;
    recordApiTiming({ path, method, status: response.status, durationMs: Date.now() - startedAt, outcome: response.ok ? "ok" : "http_error" });
    return {
      ok: response.ok,
      status: response.status,
      data: payload,
      error: response.ok ? null : errorMessage(payload, `HTTP ${response.status}`)
    };
  } catch (error) {
    recordApiTiming({ path, method, status: 0, durationMs: Date.now() - startedAt, outcome: error instanceof DOMException && error.name === "AbortError" ? "timeout" : "network_error" });
    if (error instanceof DOMException && error.name === "AbortError") {
      if (inactivityRef.current) {
        const detail = inactivityRef.current;
        const idleSeconds = Math.round(detail.idleMs / 1000);
        return {
          ok: false,
          status: 0,
          data: null,
          error: `${options.timeoutName ?? path} 超过 ${timeoutMs}ms 无新活动（idle ${idleSeconds}s；waitingFor=${detail.waitingFor ?? "unknown"}；lastEventLabel=${detail.lastEventLabel ?? "none"}）`
        };
      }
      return {
        ok: false,
        status: 0,
        data: null,
        error: `${options.timeoutName ?? path} 超时`
      };
    }
    return {
      ok: false,
      status: 0,
      data: null,
      error: error instanceof Error ? error.message : String(error)
    };
  } finally {
    if (timer !== null) window.clearTimeout(timer);
  }
}

function resolveActivitySource(ref: ApiRequestOptions["activityRef"]): ActivityRefSource {
  if (typeof ref === "function") return ref;
  if (ref && typeof ref === "object") return () => ref;
  return null;
}

export async function fetchText(path: string, options: ApiRequestOptions = {}): Promise<ApiResult<string>> {
  const controller = new AbortController();
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
  const activitySource = resolveActivitySource(options.activityRef);
  let timer: number | null = null;
  const inactivityRef: { current: { idleMs: number; lastActivityAt: number } | null } = { current: null };
  const startedAt = Date.now();
  const method = typeof options.method === "string" ? options.method : "GET";
  function readActivity(): number {
    if (!activitySource) return startedAt;
    const ref = activitySource();
    if (!ref || !Number.isFinite(ref.lastActivityAt) || ref.lastActivityAt <= 0) return startedAt;
    return ref.lastActivityAt;
  }
  function schedule(): void {
    if (timer !== null) window.clearTimeout(timer);
    if (!activitySource) {
      timer = window.setTimeout(() => controller.abort(), timeoutMs);
      return;
    }
    const idleMs = Math.max(0, Date.now() - readActivity());
    const remaining = timeoutMs - idleMs;
    if (remaining <= 0) {
      inactivityRef.current = { idleMs, lastActivityAt: readActivity() };
      controller.abort();
      return;
    }
    timer = window.setTimeout(schedule, Math.max(250, Math.min(remaining, ACTIVITY_RESCHEDULER_MS)));
  }
  schedule();
  try {
    const response = await fetch(path, { ...options, credentials: options.credentials ?? "same-origin", signal: controller.signal });
    const text = await response.text();
    recordApiTiming({ path, method, status: response.status, durationMs: Date.now() - startedAt, outcome: response.ok ? "ok" : "http_error" });
    return { ok: response.ok, status: response.status, data: text, error: response.ok ? null : `HTTP ${response.status}` };
  } catch (error) {
    recordApiTiming({ path, method, status: 0, durationMs: Date.now() - startedAt, outcome: error instanceof DOMException && error.name === "AbortError" ? "timeout" : "network_error" });
    if (error instanceof DOMException && error.name === "AbortError") {
      if (inactivityRef.current) {
        const detail = inactivityRef.current;
        return { ok: false, status: 0, data: null, error: `${options.timeoutName ?? path} 超过 ${timeoutMs}ms 无新活动（idle ${Math.round(detail.idleMs / 1000)}s）` };
      }
      return { ok: false, status: 0, data: null, error: `${options.timeoutName ?? path} 超时` };
    }
    return { ok: false, status: 0, data: null, error: error instanceof Error ? error.message : String(error) };
  } finally {
    if (timer !== null) window.clearTimeout(timer);
  }
}

function errorMessage(payload: unknown, fallback: string): string {
  if (payload && typeof payload === "object") {
    const record = payload as Record<string, unknown>;
    const nested = record.error && typeof record.error === "object" ? record.error as Record<string, unknown> : null;
    const candidate = nested?.message ?? nested?.code ?? record.message ?? record.reason ?? record.error;
    if (typeof candidate === "string" && candidate.trim()) return candidate;
  }
  return fallback;
}

export const api = {
  session: (): Promise<ApiResult<{ authenticated?: boolean; user?: unknown; actor?: unknown; expiresAt?: string }>> => fetchJson("/auth/session", { timeoutName: "auth session" }),
  authBootstrap: (): Promise<ApiResult<{ authenticated?: boolean; user?: unknown; actor?: unknown; expiresAt?: string }>> => fetchJson("/auth/bootstrap", { timeoutName: "auth bootstrap" }),
  workspaceBootstrap: (projectId: string): Promise<ApiResult<{ authenticated?: boolean; user?: unknown; actor?: unknown; expiresAt?: string; projectId?: string; workspace?: WorkspaceRecord | null; workspaceStatus?: number | null; workspaceError?: unknown }>> => fetchJson(`/auth/workspace-bootstrap?projectId=${encodeURIComponent(projectId)}`, { timeoutName: "workspace bootstrap", timeoutMs: 8000 }),
  login: (username: string, password: string): Promise<ApiResult<{ authenticated?: boolean; user?: unknown; actor?: unknown; expiresAt?: string }>> => fetchJson("/auth/login", {
    method: "POST",
    body: JSON.stringify({ username, password }),
    timeoutName: "auth login"
  }),
  logout: (): Promise<ApiResult<{ ok?: boolean }>> => fetchJson("/auth/logout", { method: "POST", body: JSON.stringify({}) }),
  healthLive: (): Promise<ApiResult<LiveProbePayload>> => fetchJson("/health/live", { timeoutMs: 12000, timeoutName: "health/live" }),
  liveBuilds: (): Promise<ApiResult<LiveBuildsPayload>> => fetchJson("/v1/live-builds", { timeoutMs: 12000, timeoutName: "live builds" }),
  health: (): Promise<ApiResult<LiveProbePayload>> => fetchJson("/health", { timeoutMs: 12000, timeoutName: "health" }),
  restIndex: (): Promise<ApiResult<LiveProbePayload>> => fetchJson("/v1", { timeoutMs: 12000, timeoutName: "REST index" }),
  adapter: (): Promise<ApiResult<LiveProbePayload>> => fetchJson("/v1/rpc/system.health", { method: "POST", body: JSON.stringify({}), timeoutMs: 12000, timeoutName: "adapter" }),
  hwpodSpecs: (): Promise<ApiResult<HwpodSpecsResponse>> => fetchJson("/v1/hwpod/specs?probe=1", {
    timeoutMs: 12000,
    timeoutName: "HWPOD specs"
  }),
  hwpodNodeOpsHealth: (): Promise<ApiResult<HwpodNodeOpsResponse>> => fetchJson("/v1/hwpod-node-ops", {
    timeoutMs: 12000,
    timeoutName: "HWPOD node-ops"
  }),
  adminAccessSummary: (): Promise<ApiResult<AdminAccessSummaryResponse>> => fetchJson("/v1/admin/access/summary", { timeoutMs: 12000, timeoutName: "admin access summary" }),
  adminAccessUsers: (): Promise<ApiResult<AdminAccessUsersResponse>> => fetchJson("/v1/admin/access/users", { timeoutMs: 12000, timeoutName: "admin access users" }),
  adminAccessUser: (userId: string): Promise<ApiResult<AdminAccessMatrixResponse>> => fetchJson(`/v1/admin/access/users/${encodeURIComponent(userId)}`, { timeoutMs: 12000, timeoutName: "admin access user" }),
  updateAdminAccessUser: (userId: string, payload: { role?: AccessUserRole; status?: AccessUserStatus }): Promise<ApiResult<AdminAccessMutationResponse>> => fetchJson(`/v1/admin/access/users/${encodeURIComponent(userId)}`, { method: "PATCH", body: JSON.stringify(payload), timeoutMs: 12000, timeoutName: "admin access user update" }),
  setAdminAccessTool: (userId: string, toolId: AccessToolId, allowed: boolean): Promise<ApiResult<AdminAccessMutationResponse>> => fetchJson(`/v1/admin/access/users/${encodeURIComponent(userId)}/tools/${encodeURIComponent(toolId)}/can-use`, { method: allowed ? "PUT" : "DELETE", body: JSON.stringify({}), timeoutMs: 12000, timeoutName: "admin access tool" }),
  providerProfiles: (): Promise<ApiResult<ProviderProfilesResponse>> => fetchJson("/v1/admin/provider-profiles", { timeoutMs: 12000, timeoutName: "provider profiles" }),
  providerProfileCatalog: (): Promise<ApiResult<ProviderProfilesResponse>> => fetchJson("/v1/provider-profiles", { timeoutMs: 12000, timeoutName: "provider profile catalog" }),
  setProviderProfileKey: (profile: ProviderProfile, apiKey: string): Promise<ApiResult<ProviderProfilesResponse>> => fetchJson(`/v1/admin/provider-profiles/${encodeURIComponent(profile)}/credential`, { method: "PUT", body: JSON.stringify({ apiKey }), timeoutMs: 30000, timeoutName: "provider profile key update" }),
  providerProfileConfig: (profile: ProviderProfile): Promise<ApiResult<ProviderProfilesResponse>> => fetchJson(`/v1/admin/provider-profiles/${encodeURIComponent(profile)}/config`, { timeoutMs: 12000, timeoutName: "provider profile config" }),
  setProviderProfileConfig: (profile: ProviderProfile, configToml: string): Promise<ApiResult<ProviderProfilesResponse>> => fetchJson(`/v1/admin/provider-profiles/${encodeURIComponent(profile)}/config`, { method: "PUT", body: JSON.stringify({ configToml }), timeoutMs: 30000, timeoutName: "provider profile config update" }),
  validateProviderProfile: (profile: ProviderProfile): Promise<ApiResult<ProviderProfilesResponse>> => fetchJson(`/v1/admin/provider-profiles/${encodeURIComponent(profile)}/validate`, { method: "POST", body: JSON.stringify({}), timeoutMs: 30000, timeoutName: "provider profile validation submit" }),
  providerProfileValidation: (profile: ProviderProfile, validationId: string): Promise<ApiResult<ProviderProfilesResponse & ProviderProfileValidation>> => fetchJson(`/v1/admin/provider-profiles/${encodeURIComponent(profile)}/validations/${encodeURIComponent(validationId)}`, { timeoutMs: 12000, timeoutName: "provider profile validation" }),
  workspace: (projectId: string): Promise<ApiResult<{ workspace?: WorkspaceRecord }>> => fetchJson(`/v1/workbench/workspace?projectId=${encodeURIComponent(projectId)}`, { timeoutMs: 8000, timeoutName: "workspace" }),
  updateWorkspace: (workspaceId: string, payload: Record<string, unknown>): Promise<ApiResult<{ workspace?: WorkspaceRecord }>> => fetchJson(`/v1/workbench/workspace/${encodeURIComponent(workspaceId)}`, { method: "PATCH", body: JSON.stringify(payload), timeoutMs: 8000, timeoutName: "workspace update" }),
  selectConversation: (workspaceId: string, payload: Record<string, unknown>): Promise<ApiResult<{ workspace?: WorkspaceRecord }>> => fetchJson(`/v1/workbench/workspace/${encodeURIComponent(workspaceId)}/select-conversation`, { method: "POST", body: JSON.stringify(payload), timeoutMs: 8000, timeoutName: "select conversation" }),
  conversations: (projectId: string): Promise<ApiResult<{ conversations?: ConversationRecord[] }>> => fetchJson(`/v1/agent/conversations?projectId=${encodeURIComponent(projectId)}`, { timeoutMs: 8000, timeoutName: "conversations" }),
  deleteConversation: (conversationId: string, payload: Record<string, unknown>): Promise<ApiResult<{ workspace?: WorkspaceRecord }>> => fetchJson(`/v1/agent/conversations/${encodeURIComponent(conversationId)}`, { method: "DELETE", body: JSON.stringify(payload), timeoutMs: 8000, timeoutName: "delete conversation" }),
  saveConversation: (conversationId: string, payload: Record<string, unknown>): Promise<ApiResult<{ ok?: boolean }>> => fetchJson(`/v1/agent/conversations/${encodeURIComponent(conversationId)}`, { method: "PUT", body: JSON.stringify(payload), timeoutMs: 8000, timeoutName: "save conversation" }),
  sendAgentMessage: (payload: Record<string, unknown>, timeoutMs: number, activityRef?: ApiRequestOptions["activityRef"]): Promise<ApiResult<AgentChatResponse>> => fetchJson("/v1/agent/chat", { method: "POST", body: JSON.stringify(payload), timeoutMs, timeoutName: "Code Agent", activityRef }),
  steerAgentMessage: (payload: Record<string, unknown>, timeoutMs: number, activityRef?: ApiRequestOptions["activityRef"]): Promise<ApiResult<AgentChatResponse>> => fetchJson("/v1/agent/chat/steer", { method: "POST", body: JSON.stringify(payload), timeoutMs, timeoutName: "Code Agent steer", activityRef }),
  getAgentChatResult: (resultUrl: string, timeoutMs = 8000, activityRef?: ApiRequestOptions["activityRef"]): Promise<ApiResult<AgentChatResultResponse>> => fetchJson(resultUrl, { timeoutMs, timeoutName: "Code Agent result", activityRef }),
  cancelAgentMessage: (payload: Record<string, unknown>): Promise<ApiResult<{ ok?: boolean; canceled?: boolean; error?: { code?: string; message?: string; userMessage?: string } }>> => fetchJson("/v1/agent/chat/cancel", { method: "POST", body: JSON.stringify(payload), timeoutMs: 30000, timeoutName: "Code Agent cancel" }),
  gateDiagnostics: (): Promise<ApiResult<GateDiagnosticsResponse>> => fetchJson("/v1/diagnostics/gate", { timeoutMs: 12000, timeoutName: "gate diagnostics" }),
  webPerformanceSummary: (): Promise<ApiResult<WebPerformanceSummaryResponse>> => fetchJson("/v1/web-performance/summary", { timeoutMs: 12000, timeoutName: "web performance summary" }),
  skills: (): Promise<ApiResult<SkillsResponse>> => fetchJson("/v1/skills", { timeoutMs: 12000, timeoutName: "skills" }),
  uploadSkills: (files: Array<{ relativePath: string; sizeBytes: number; contentBase64: string }>): Promise<ApiResult<{ skill?: { id?: string } }>> => fetchJson("/v1/skills/uploads", { method: "POST", body: JSON.stringify({ files }), timeoutMs: 60000, timeoutName: "skill upload" }),
  skillTree: (skillId: string): Promise<ApiResult<SkillTreeResponse>> => fetchJson(`/v1/skills/${encodeURIComponent(skillId)}/tree`, { timeoutMs: 12000, timeoutName: "skill tree" }),
  skillFile: (skillId: string, relativePath: string): Promise<ApiResult<SkillFileResponse>> => fetchJson(`/v1/skills/${encodeURIComponent(skillId)}/files?path=${encodeURIComponent(relativePath)}`, { timeoutMs: 12000, timeoutName: "skill file" })
};

export interface GateDiagnosticsResponse {
  serviceId?: string;
  route?: string;
  contractVersion?: string;
  status?: string;
  sourceKind?: string;
  liveBackend?: boolean;
  observedAt?: string;
  rowCount?: number;
  rowsCount?: number;
  columns?: string[];
  rows?: GateDiagnosticsRow[];
  error?: string;
}

export interface GateDiagnosticsRow {
  category?: string;
  check?: string;
  status?: string;
  statusKey?: string;
  owner?: string;
  detail?: string;
  evidence?: string[];
  updatedAt?: string;
  next?: string;
  sourceKind?: string;
}
