import {
  createContext,
  createElement,
  useContext,
  useEffect,
  useState,
  type ReactNode
} from 'react';

import type { PicloudClient } from '../client.js';
import type { SubscribeOptions } from '../types.js';

const PicloudContext = createContext<PicloudClient | null>(null);

export interface PicloudProviderProps {
  client: PicloudClient;
  children?: ReactNode;
}

/** Provides a `PicloudClient` to `useTopic` / `useEndpoint`. */
export function PicloudProvider(props: PicloudProviderProps) {
  return createElement(PicloudContext.Provider, { value: props.client }, props.children);
}

/** The client from the nearest `PicloudProvider`. Throws if absent. */
export function usePicloud(): PicloudClient {
  const client = useContext(PicloudContext);
  if (!client) {
    throw new Error('usePicloud: wrap your tree in <PicloudProvider client={...}>');
  }
  return client;
}

/**
 * Subscribe to a realtime topic; returns the accumulated messages in
 * arrival order. Re-subscribes when `topic` changes; unsubscribes on
 * unmount.
 */
export function useTopic<T = unknown>(topic: string, opts?: SubscribeOptions<T>): T[] {
  const client = usePicloud();
  const [messages, setMessages] = useState<T[]>([]);
  useEffect(() => {
    setMessages([]);
    const unsubscribe = client.subscribe<T>(
      topic,
      (event) => setMessages((prev) => [...prev, event.message]),
      opts
    );
    return () => unsubscribe();
    // `opts` is intentionally excluded: a new object literal each render
    // would otherwise resubscribe every render.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [client, topic]);
  return messages;
}

export interface QueryState<T> {
  data: T | null;
  loading: boolean;
  error: unknown;
}

export interface MutationState<T> {
  data: T | null;
  loading: boolean;
  error: unknown;
}

/**
 * F-T-001: flat GET hook. Replaces `useEndpoint(path).get()` which
 * violated the Rules of Hooks (calling `useState`/`useEffect` from
 * inside a returned function). Auto-fires once per `path` change and
 * re-runs on mount.
 */
export function useEndpointGet<Res = unknown>(path: string): QueryState<Res> {
  const client = usePicloud();
  const [state, setState] = useState<QueryState<Res>>({
    data: null,
    loading: true,
    error: null
  });
  useEffect(() => {
    let active = true;
    setState({ data: null, loading: true, error: null });
    client
      .endpoint<unknown, Res>(path)
      .get()
      .then((data) => active && setState({ data, loading: false, error: null }))
      .catch((error) => active && setState({ data: null, loading: false, error }));
    return () => {
      active = false;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [client, path]);
  return state;
}

/**
 * F-T-001 + F-T-002: event-driven POST hook. Returns
 * `{ mutate, data, loading, error }` — `mutate(body)` fires the POST.
 * Does NOT auto-fire on mount (the previous `useEndpoint(path).post()`
 * shape would create a user / send an email on every render-refresh).
 */
export function useEndpointPost<Req = unknown, Res = unknown>(
  path: string
): MutationState<Res> & { mutate: (body?: Req) => Promise<void> } {
  const client = usePicloud();
  const [state, setState] = useState<MutationState<Res>>({
    data: null,
    loading: false,
    error: null
  });
  const mutate = async (body?: Req) => {
    setState({ data: null, loading: true, error: null });
    try {
      const data = await client.endpoint<Req, Res>(path).post(body);
      setState({ data, loading: false, error: null });
    } catch (error) {
      setState({ data: null, loading: false, error });
    }
  };
  return { ...state, mutate };
}
