import type { AppServerClient } from "../app-server/connection/client";
import type { AppServerClientAccess } from "../app-server/connection/client-access";
import type { ObservedResultListener } from "../app-server/query/observed-result";
import { listHookCatalog, setHookItemEnabled, trustHookItem } from "../app-server/services/catalog";
import { deleteThread, restoreArchivedThread as restoreArchivedThreadOnAppServer } from "../app-server/services/threads";
import type { HookItem, ModelMetadata } from "../domain/catalog/metadata";
import type { ThreadCatalogArchivedReader, ThreadCatalogEventSink } from "../features/threads/catalog/thread-catalog";
import { createKeyedOperationQueue } from "../shared/runtime/keyed-operation-queue";
import type { SettingsDynamicDataAccess, SettingsHookCatalog } from "./dynamic-data";

interface SettingsAppServerQueries {
  modelsSnapshot(): readonly ModelMetadata[] | null;
  observeModelsResult(listener: ObservedResultListener<readonly ModelMetadata[]>, options?: { emitCurrent?: boolean }): () => void;
  fetchModels(): Promise<readonly ModelMetadata[]>;
  refreshModels(): Promise<readonly ModelMetadata[]>;
}

type SettingsArchivedThreadCatalog = ThreadCatalogArchivedReader & ThreadCatalogEventSink;

export interface SettingsAppServerDynamicDataOptions {
  vaultPath: string;
  clientAccess: AppServerClientAccess;
  appServerQueries: SettingsAppServerQueries;
  threadCatalog: SettingsArchivedThreadCatalog;
}

export function createSettingsAppServerDynamicData(options: SettingsAppServerDynamicDataOptions): SettingsDynamicDataAccess {
  const archivedThreadMutations = createKeyedOperationQueue<string>();
  const withSettingsConnection = <T>(operation: (client: AppServerClient) => Promise<T>): Promise<T> =>
    options.clientAccess.withClient(operation, {
      serverRequests: { kind: "reject", message: "Codex Panel settings does not handle server requests." },
    });
  const runArchivedThreadMutation = <T>(threadId: string, operation: () => Promise<T>): Promise<T> => {
    return archivedThreadMutations.run(threadId, operation);
  };
  const loadHooks = (client: AppServerClient): Promise<SettingsHookCatalog> => loadSettingsHookCatalog(client, options.vaultPath);
  const mutateHook = (hook: HookItem, mutation: (client: AppServerClient, hook: HookItem) => Promise<void>): Promise<SettingsHookCatalog> =>
    withSettingsConnection(async (client) => {
      await mutation(client, hook);
      return loadHooks(client);
    });

  return {
    modelsSnapshot: () => options.appServerQueries.modelsSnapshot(),
    observeModelsResult: (listener, observeOptions) => options.appServerQueries.observeModelsResult(listener, observeOptions),
    fetchModels: () => options.appServerQueries.fetchModels(),
    refreshModels: () => options.appServerQueries.refreshModels(),
    archivedThreadsSnapshot: () => options.threadCatalog.archivedSnapshot(),
    observeArchivedThreadsResult: (listener, observeOptions) => options.threadCatalog.observeArchived(listener, observeOptions),
    refreshArchivedThreads: () => options.threadCatalog.refreshArchived(),
    refreshHooks: () => withSettingsConnection(loadHooks),
    trustHook: (hook) => mutateHook(hook, trustHookItem),
    setHookEnabled: (hook, enabled) => mutateHook(hook, (client, item) => setHookItemEnabled(client, item, enabled)),
    restoreArchivedThread: (threadId) =>
      runArchivedThreadMutation(threadId, async () => {
        const thread = await withSettingsConnection((client) => restoreArchivedThreadOnAppServer(client, threadId));
        options.threadCatalog.apply({ type: "thread-restored", thread });
        return thread;
      }),
    deleteArchivedThread: (threadId) =>
      runArchivedThreadMutation(threadId, async () => {
        await withSettingsConnection((client) => deleteThread(client, threadId));
        options.threadCatalog.apply({ type: "thread-deleted", threadId });
      }),
  };
}

async function loadSettingsHookCatalog(client: AppServerClient, vaultPath: string): Promise<SettingsHookCatalog> {
  const catalog = await listHookCatalog(client, vaultPath);
  const hookCount = catalog.hooks.length;
  return {
    ...catalog,
    status: `Loaded ${String(hookCount)} hook${hookCount === 1 ? "" : "s"}.`,
  };
}
