import type { SourceControlDiscoveryResult } from "@belweave/contracts";
import { Atom, type AtomRegistry } from "effect/unstable/reactivity";

/* --- Types ---------------------------------------------------------- */

export interface SourceControlDiscoveryState {
  readonly data: SourceControlDiscoveryResult | null;
  readonly error: string | null;
  readonly isPending: boolean;
}

export interface SourceControlDiscoveryTarget {
  readonly key: string | null;
}

export interface SourceControlDiscoveryClient {
  readonly discoverSourceControl: () => Promise<SourceControlDiscoveryResult>;
}

/* --- Constants ------------------------------------------------------ */

export const EMPTY_SOURCE_CONTROL_DISCOVERY_STATE = Object.freeze<SourceControlDiscoveryState>({
  data: null,
  error: null,
  isPending: false,
});

const INITIAL_SOURCE_CONTROL_DISCOVERY_STATE = Object.freeze<SourceControlDiscoveryState>({
  data: null,
  error: null,
  isPending: true,
});

/* --- Atoms ---------------------------------------------------------- */

const knownSourceControlDiscoveryKeys = new Set<string>();

export const sourceControlDiscoveryStateAtom = Atom.family((key: string) => {
  knownSourceControlDiscoveryKeys.add(key);
  return Atom.make(INITIAL_SOURCE_CONTROL_DISCOVERY_STATE).pipe(
    Atom.keepAlive,
    Atom.withLabel(`source-control-discovery:${key}`),
  );
});

export const EMPTY_SOURCE_CONTROL_DISCOVERY_ATOM = Atom.make(
  EMPTY_SOURCE_CONTROL_DISCOVERY_STATE,
).pipe(Atom.keepAlive, Atom.withLabel("source-control-discovery:null"));

/* --- Helpers -------------------------------------------------------- */

export function getSourceControlDiscoveryTargetKey(
  target: SourceControlDiscoveryTarget,
): string | null {
  const key = target.key?.trim();
  return key && key.length > 0 ? key : null;
}

/* --- Refresh manager ------------------------------------------------ */

export interface SourceControlDiscoveryManagerConfig {
  /**
   * Get the atom registry used to read/write source-control discovery snapshots.
   */
  readonly getRegistry: () => AtomRegistry.AtomRegistry;
  /**
   * Resolve the runtime client for a discovery target key.
   *
   * Web currently uses a single `"primary"` target, but keeping this keyed
   * lets mobile or future multi-environment clients provide separate discovery
   * clients without changing the state primitive.
   */
  readonly getClient: (key: string) => SourceControlDiscoveryClient | null;
}

export function createSourceControlDiscoveryManager(config: SourceControlDiscoveryManagerConfig) {
  const refreshInFlight = new Map<string, Promise<SourceControlDiscoveryResult | null>>();

  /* -- Atom helpers -------------------------------------------------- */

  function setState(targetKey: string, nextState: SourceControlDiscoveryState): void {
    config.getRegistry().set(sourceControlDiscoveryStateAtom(targetKey), nextState);
  }

  function markPending(targetKey: string): void {
    const current = config.getRegistry().get(sourceControlDiscoveryStateAtom(targetKey));
    const next: SourceControlDiscoveryState =
      current.data === null
        ? INITIAL_SOURCE_CONTROL_DISCOVERY_STATE
        : {
            data: current.data,
            error: null,
            isPending: true,
          };

    if (
      current.data === next.data &&
      current.error === next.error &&
      current.isPending === next.isPending
    ) {
      return;
    }

    setState(targetKey, next);
  }

  function setData(targetKey: string, data: SourceControlDiscoveryResult): void {
    setState(targetKey, {
      data,
      error: null,
      isPending: false,
    });
  }

  function setError(targetKey: string, error: unknown): void {
    const current = config.getRegistry().get(sourceControlDiscoveryStateAtom(targetKey));
    setState(targetKey, {
      data: current.data,
      error: error instanceof Error ? error.message : "Failed to discover source control tools.",
      isPending: false,
    });
  }

  /* -- Public API ---------------------------------------------------- */

  /**
   * Trigger a one-shot source-control discovery RPC for a target.
   *
   * Calls are deduplicated while a refresh for the same target key is in
   * flight. On failure, the previous successful snapshot is kept in `data`
   * and the error message is stored separately so UI can keep rendering stale
   * discovery results while showing the failure.
   *
   * @param target The logical runtime target to refresh.
   * @param client Optional pre-resolved client, useful in tests.
   */
  function refresh(
    target: SourceControlDiscoveryTarget,
    client?: SourceControlDiscoveryClient,
  ): Promise<SourceControlDiscoveryResult | null> {
    const targetKey = getSourceControlDiscoveryTargetKey(target);
    if (targetKey === null) {
      return Promise.resolve(null);
    }

    const existing = refreshInFlight.get(targetKey);
    if (existing) {
      return existing;
    }

    const resolvedClient = client ?? config.getClient(targetKey);
    if (!resolvedClient) {
      const error = new Error("Source control discovery client is unavailable.");
      setError(targetKey, error);
      return Promise.resolve(getSnapshot(target).data);
    }

    markPending(targetKey);
    const promise = resolvedClient.discoverSourceControl().then(
      (result) => {
        setData(targetKey, result);
        return result;
      },
      (error: unknown) => {
        setError(targetKey, error);
        return getSnapshot(target).data;
      },
    );
    const tracked = promise.finally(() => refreshInFlight.delete(targetKey));
    refreshInFlight.set(targetKey, tracked);
    return tracked;
  }

  /**
   * Read the current atom snapshot for `target`.
   *
   * Invalid targets return the inert empty state rather than creating a new
   * family atom entry.
   */
  function getSnapshot(target: SourceControlDiscoveryTarget): SourceControlDiscoveryState {
    const targetKey = getSourceControlDiscoveryTargetKey(target);
    if (targetKey === null) {
      return EMPTY_SOURCE_CONTROL_DISCOVERY_STATE;
    }

    return config.getRegistry().get(sourceControlDiscoveryStateAtom(targetKey));
  }

  /**
   * Clear in-flight refresh tracking and reset every known discovery atom.
   * Primarily used by tests and runtime teardown.
   */
  function reset(): void {
    refreshInFlight.clear();
    for (const key of knownSourceControlDiscoveryKeys) {
      setState(key, INITIAL_SOURCE_CONTROL_DISCOVERY_STATE);
    }
    knownSourceControlDiscoveryKeys.clear();
  }

  return {
    refresh,
    getSnapshot,
    reset,
  };
}
