import * as React from "react";
import { isValidOrgSlug } from "@executor-js/api";
import {
  DEFAULT_EXECUTOR_SERVER_ORIGIN,
  EXECUTOR_ORG_SELECTOR_HEADER,
  getExecutorServerAuthorizationHeader as getAuthorizationHeaderForConnection,
  normalizeExecutorServerConnection,
  originFromApiBaseUrl,
  type ExecutorServerConnection,
  type ExecutorServerConnectionInput,
} from "@executor-js/sdk/shared";

export {
  DEFAULT_EXECUTOR_SERVER_ORIGIN,
  EXECUTOR_ORG_SELECTOR_HEADER,
  apiBaseUrlForServerOrigin,
  normalizeExecutorServerConnection,
  normalizeExecutorServerOrigin,
  originFromApiBaseUrl,
  type ExecutorServerAuth,
  type ExecutorServerConnection,
  type ExecutorServerConnectionInput,
  type ExecutorServerConnectionKind,
} from "@executor-js/sdk/shared";

interface ExecutorWindowBridge {
  readonly serverConnection?: ExecutorServerConnectionInput;
  readonly getServerConnection?: () => Promise<ExecutorServerConnectionInput | null>;
  /**
   * The desktop bearer token, fetched on demand for the "Connect an agent"
   * install command (an external agent needs it in plaintext). The renderer's
   * own requests don't use it — the desktop main process injects the header at
   * the session layer.
   */
  readonly getServerAuthToken?: () => Promise<string | null>;
  readonly getServerProfiles?: () => Promise<string | null>;
  readonly setServerProfiles?: (value: string) => Promise<void>;
}

declare global {
  interface Window {
    readonly executor?: ExecutorWindowBridge;
  }
}

export const resolveBrowserExecutorServerConnection = (input: {
  readonly locationOrigin?: string;
  readonly bridge?: ExecutorWindowBridge;
}): ExecutorServerConnection => {
  const configured = input.bridge?.serverConnection;
  if (configured) {
    return normalizeExecutorServerConnection(configured);
  }

  return normalizeExecutorServerConnection({
    kind: "http",
    origin: input.locationOrigin ?? DEFAULT_EXECUTOR_SERVER_ORIGIN,
  });
};

const resolveInitialExecutorServerConnection = (): ExecutorServerConnection => {
  const browserWindow = globalThis.window;
  if (!browserWindow) {
    return normalizeExecutorServerConnection();
  }

  return resolveBrowserExecutorServerConnection({
    locationOrigin: browserWindow.location?.origin,
    bridge: browserWindow.executor,
  });
};

let activeConnection = resolveInitialExecutorServerConnection();

// ---------------------------------------------------------------------------
// Active org selector — the org the console URL is scoped to.
// ---------------------------------------------------------------------------
//
// Org-scoped hosts (cloud) send this on every API request so the server scopes
// to the URL's org, not the session's stored one — which is what lets two
// browser tabs sit in two different orgs at once (each tab's window.location is
// its own).
//
// Read straight from `window.location` at REQUEST time, not from a
// React-synced mirror: the API clients' `transformClient` runs only in the
// browser, so this never touches SSR/hydration, and it's always exactly the
// current URL with no effect-timing to get wrong. The org is the first path
// segment when it's a valid slug; reserved console roots (`/policies`,
// `/login`, …) are NOT valid slugs, so a bare path sends no header and the
// server falls back to the session org. Hosts without slugs (self-host,
// cloudflare) never produce one either.
export const EXECUTOR_ORG_HEADER = EXECUTOR_ORG_SELECTOR_HEADER;

// The router-authoritative org scope, set by the host (ExecutorProvider's
// scopeKey) during render. Preferred over the window.location fallback
// because the router's pathname can be a tick AHEAD of history during a
// client-side navigation — a fetch fired in that window would otherwise
// derive the OLD URL's scope (e.g. none, on the bare → slugged
// canonicalization) while the atom registry is already keyed to the new one.
// One value per tab (module state), so tabs stay independent.
let activeOrgSlugOverride: string | null = null;

export const setActiveOrgSlugOverride = (slug: string | null): void => {
  activeOrgSlugOverride = slug;
};

export const getActiveOrgSlug = (): string | null => {
  if (activeOrgSlugOverride) return activeOrgSlugOverride;
  const pathname = globalThis.window?.location?.pathname;
  if (!pathname) return null;
  const first = pathname.split("/")[1];
  return first && isValidOrgSlug(first) ? first : null;
};

export const getExecutorOrganizationHeaders = (): Readonly<Record<string, string>> => {
  const orgSlug = getActiveOrgSlug();
  return orgSlug ? { [EXECUTOR_ORG_HEADER]: orgSlug } : {};
};

export const getExecutorServerConnection = (): ExecutorServerConnection => activeConnection;

export const setExecutorServerConnection = (input: ExecutorServerConnectionInput): void => {
  activeConnection = normalizeExecutorServerConnection(input);
};

export const setExecutorServerApiBaseUrl = (apiBaseUrl: string): void => {
  activeConnection = normalizeExecutorServerConnection({
    ...activeConnection,
    apiBaseUrl,
    origin: originFromApiBaseUrl(apiBaseUrl),
  });
};

export const getExecutorApiBaseUrl = (): string => activeConnection.apiBaseUrl;

export const getExecutorServerAuthPassword = (): string | null =>
  activeConnection.auth?.kind === "basic" ? activeConnection.auth.password : null;

export const getExecutorServerAuthorizationHeader = (
  connection: ExecutorServerConnection = activeConnection,
): string | null => getAuthorizationHeaderForConnection(connection);

interface ExecutorServerConnectionContextValue {
  readonly connection: ExecutorServerConnection;
  readonly setConnection: (input: ExecutorServerConnectionInput) => void;
}

const ExecutorServerConnectionContext =
  React.createContext<ExecutorServerConnectionContextValue | null>(null);

const hasDesktopServerConnectionBridge = (): boolean =>
  typeof globalThis.window?.executor?.getServerConnection === "function";

export function ExecutorServerConnectionProvider(
  props: React.PropsWithChildren<{
    readonly connection?: ExecutorServerConnectionInput;
  }>,
) {
  const initialConnection = React.useMemo(
    () =>
      props.connection
        ? normalizeExecutorServerConnection(props.connection)
        : getExecutorServerConnection(),
    [props.connection],
  );
  const [connection, setConnection] = React.useState(initialConnection);
  const setActiveConnection = React.useCallback((input: ExecutorServerConnectionInput): void => {
    const next = normalizeExecutorServerConnection(input);
    if (hasDesktopServerConnectionBridge() && next.kind !== "desktop-sidecar") return;
    activeConnection = next;
    setConnection(next);
  }, []);

  React.useEffect(() => {
    const next = props.connection
      ? normalizeExecutorServerConnection(props.connection)
      : getExecutorServerConnection();
    activeConnection = next;
    setConnection(next);
  }, [props.connection]);

  React.useEffect(() => {
    const bridge = globalThis.window?.executor;
    if (props.connection || !bridge) return;
    if (typeof bridge?.getServerConnection !== "function") return;

    let cancelled = false;
    void bridge.getServerConnection().then(
      (input) => {
        if (cancelled || !input) return;
        const next = normalizeExecutorServerConnection(input);
        setConnection(() => {
          // Electron loads the UI from a local URL before the async bridge
          // answers. Once it does, the bridge is the authoritative app server.
          activeConnection = next;
          return next;
        });
      },
      () => undefined,
    );

    return () => {
      cancelled = true;
    };
  }, [props.connection]);

  activeConnection = connection;
  const value = React.useMemo(
    () => ({
      connection,
      setConnection: setActiveConnection,
    }),
    [connection, setActiveConnection],
  );

  return (
    <ExecutorServerConnectionContext.Provider value={value}>
      {props.children}
    </ExecutorServerConnectionContext.Provider>
  );
}

export function useExecutorServerConnection(): ExecutorServerConnection {
  return (
    React.useContext(ExecutorServerConnectionContext)?.connection ?? getExecutorServerConnection()
  );
}

export function useSetExecutorServerConnection(): (input: ExecutorServerConnectionInput) => void {
  return (
    React.useContext(ExecutorServerConnectionContext)?.setConnection ?? setExecutorServerConnection
  );
}

export function useExecutorServerConnectionControls(): ExecutorServerConnectionContextValue {
  const value = React.useContext(ExecutorServerConnectionContext);
  return (
    value ?? {
      connection: getExecutorServerConnection(),
      setConnection: setExecutorServerConnection,
    }
  );
}
