import { readable, type Readable } from 'svelte/store';

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

/**
 * A Svelte store of realtime messages for a topic. `$messages` is an
 * array that grows as events arrive. The SSE connection opens on the
 * first subscriber and closes when the last unsubscribes (standard
 * `readable` lifecycle).
 *
 * The client is passed explicitly (Svelte stores aren't components, so
 * there's no React-style context to read). See HANDBACK §7.
 */
export function topicStore<T = unknown>(
  client: PicloudClient,
  topic: string,
  opts?: SubscribeOptions<T>
): Readable<T[]> {
  return readable<T[]>([], (set) => {
    let items: T[] = [];
    const unsubscribe = client.subscribe<T>(
      topic,
      (event) => {
        items = [...items, event.message];
        set(items);
      },
      opts
    );
    return () => unsubscribe();
  });
}

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

export interface EndpointStore<Req, Res> {
  get: () => Readable<QueryState<Res>>;
  post: (body?: Req) => Readable<QueryState<Res>>;
}

/**
 * A Svelte store wrapper over a typed endpoint. `$query` is
 * `{ data, loading, error }`. The request fires when the store gains its
 * first subscriber.
 */
export function endpointStore<Res = unknown, Req = unknown>(
  client: PicloudClient,
  path: string
): EndpointStore<Req, Res> {
  const run = (exec: () => Promise<Res>): Readable<QueryState<Res>> =>
    readable<QueryState<Res>>({ data: null, loading: true, error: null }, (set) => {
      let active = true;
      exec()
        .then((data) => {
          if (active) set({ data, loading: false, error: null });
        })
        .catch((error) => {
          if (active) set({ data: null, loading: false, error });
        });
      return () => {
        active = false;
      };
    });
  return {
    get: () => run(() => client.endpoint<Req, Res>(path).get()),
    post: (body?: Req) => run(() => client.endpoint<Req, Res>(path).post(body))
  };
}
