type SetupWindowLike = {
  isDestroyed: () => boolean;
};

type OpenHandlerDecision = { action: 'deny' };

type FocusableWindowLike = {
  focus: () => void;
};

type AnilistSetupWebContentsLike = {
  setWindowOpenHandler: (...args: any[]) => unknown;
  on: (...args: any[]) => unknown;
  getURL: () => string;
};

type AnilistSetupWindowLike = FocusableWindowLike & {
  webContents: AnilistSetupWebContentsLike;
  on: (...args: any[]) => unknown;
  isDestroyed: () => boolean;
};

export function createHandleManualAnilistSetupSubmissionHandler(deps: {
  consumeCallbackUrl: (rawUrl: string) => boolean;
  redirectUri: string;
  logWarn: (message: string) => void;
}) {
  return (rawUrl: string): boolean => {
    if (!rawUrl.startsWith('subminer://anilist-setup')) {
      return false;
    }
    try {
      const parsed = new URL(rawUrl);
      const accessToken = parsed.searchParams.get('access_token')?.trim() ?? '';
      if (accessToken.length > 0) {
        return deps.consumeCallbackUrl(
          `${deps.redirectUri}#access_token=${encodeURIComponent(accessToken)}`,
        );
      }
      deps.logWarn('AniList setup submission missing access token');
      return true;
    } catch {
      deps.logWarn('AniList setup submission had invalid callback input');
      return true;
    }
  };
}

export function createMaybeFocusExistingAnilistSetupWindowHandler(deps: {
  getSetupWindow: () => FocusableWindowLike | null;
}) {
  return (): boolean => {
    const window = deps.getSetupWindow();
    if (!window) {
      return false;
    }
    window.focus();
    return true;
  };
}

export function createAnilistSetupWindowOpenHandler(deps: {
  isAllowedExternalUrl: (url: string) => boolean;
  openExternal: (url: string) => void;
  logWarn: (message: string, details?: unknown) => void;
}) {
  return ({ url }: { url: string }): OpenHandlerDecision => {
    if (!deps.isAllowedExternalUrl(url)) {
      deps.logWarn('Blocked unsafe AniList setup external URL', { url });
      return { action: 'deny' };
    }
    deps.openExternal(url);
    return { action: 'deny' };
  };
}

export function createAnilistSetupWillNavigateHandler(deps: {
  handleManualSubmission: (url: string) => boolean;
  consumeCallbackUrl: (url: string) => boolean;
  redirectUri: string;
  isAllowedNavigationUrl: (url: string) => boolean;
  logWarn: (message: string, details?: unknown) => void;
}) {
  return (params: { url: string; preventDefault: () => void }): void => {
    const { url, preventDefault } = params;
    if (deps.handleManualSubmission(url)) {
      preventDefault();
      return;
    }
    if (deps.consumeCallbackUrl(url)) {
      preventDefault();
      return;
    }
    if (url.startsWith(deps.redirectUri)) {
      preventDefault();
      return;
    }
    if (url.startsWith(`${deps.redirectUri}#`)) {
      preventDefault();
      return;
    }
    if (deps.isAllowedNavigationUrl(url)) {
      return;
    }
    preventDefault();
    deps.logWarn('Blocked unsafe AniList setup navigation URL', { url });
  };
}

export function createAnilistSetupWillRedirectHandler(deps: {
  consumeCallbackUrl: (url: string) => boolean;
}) {
  return (params: { url: string; preventDefault: () => void }): void => {
    if (deps.consumeCallbackUrl(params.url)) {
      params.preventDefault();
    }
  };
}

export function createAnilistSetupDidNavigateHandler(deps: {
  consumeCallbackUrl: (url: string) => boolean;
}) {
  return (url: string): void => {
    deps.consumeCallbackUrl(url);
  };
}

export function createAnilistSetupDidFailLoadHandler(deps: {
  onLoadFailure: (details: {
    errorCode: number;
    errorDescription: string;
    validatedURL: string;
  }) => void;
}) {
  return (details: { errorCode: number; errorDescription: string; validatedURL: string }): void => {
    deps.onLoadFailure(details);
  };
}

export function createAnilistSetupDidFinishLoadHandler(deps: {
  getLoadedUrl: () => string;
  onBlankPageLoaded: () => void;
}) {
  return (): void => {
    const loadedUrl = deps.getLoadedUrl();
    if (!loadedUrl || loadedUrl === 'about:blank') {
      deps.onBlankPageLoaded();
    }
  };
}

export function createHandleAnilistSetupWindowClosedHandler(deps: {
  clearSetupWindow: () => void;
  setSetupPageOpened: (opened: boolean) => void;
}) {
  return (): void => {
    deps.clearSetupWindow();
    deps.setSetupPageOpened(false);
  };
}

export function createHandleAnilistSetupWindowOpenedHandler(deps: {
  setSetupWindow: () => void;
  setSetupPageOpened: (opened: boolean) => void;
}) {
  return (): void => {
    deps.setSetupWindow();
    deps.setSetupPageOpened(true);
  };
}

export function createAnilistSetupFallbackHandler(deps: {
  authorizeUrl: string;
  developerSettingsUrl: string;
  setupWindow: SetupWindowLike;
  openSetupInBrowser: () => void;
  loadManualTokenEntry: () => void;
  logError: (message: string, details: unknown) => void;
  logWarn: (message: string) => void;
}) {
  return {
    onLoadFailure: (details: {
      errorCode: number;
      errorDescription: string;
      validatedURL: string;
    }) => {
      deps.logError('AniList setup window failed to load', details);
      deps.openSetupInBrowser();
      if (!deps.setupWindow.isDestroyed()) {
        deps.loadManualTokenEntry();
      }
    },
    onBlankPageLoaded: () => {
      deps.logWarn('AniList setup loaded a blank page; using fallback');
      deps.openSetupInBrowser();
      if (!deps.setupWindow.isDestroyed()) {
        deps.loadManualTokenEntry();
      }
    },
  };
}

export function createOpenAnilistSetupWindowHandler<TWindow extends AnilistSetupWindowLike>(deps: {
  maybeFocusExistingSetupWindow: () => boolean;
  createSetupWindow: () => TWindow;
  buildAuthorizeUrl: () => string;
  consumeCallbackUrl: (rawUrl: string) => boolean;
  openSetupInBrowser: (authorizeUrl: string) => void;
  loadManualTokenEntry: (setupWindow: TWindow, authorizeUrl: string) => void;
  redirectUri: string;
  developerSettingsUrl: string;
  isAllowedExternalUrl: (url: string) => boolean;
  isAllowedNavigationUrl: (url: string) => boolean;
  logWarn: (message: string, details?: unknown) => void;
  logError: (message: string, details: unknown) => void;
  clearSetupWindow: () => void;
  setSetupPageOpened: (opened: boolean) => void;
  setSetupWindow: (window: TWindow) => void;
  openExternal: (url: string) => void;
}) {
  return (): void => {
    if (deps.maybeFocusExistingSetupWindow()) {
      return;
    }

    const setupWindow = deps.createSetupWindow();
    const authorizeUrl = deps.buildAuthorizeUrl();
    const consumeCallbackUrl = (rawUrl: string): boolean => deps.consumeCallbackUrl(rawUrl);
    const openSetupInBrowser = () => deps.openSetupInBrowser(authorizeUrl);
    const loadManualTokenEntry = () => deps.loadManualTokenEntry(setupWindow, authorizeUrl);
    const handleManualSubmission = createHandleManualAnilistSetupSubmissionHandler({
      consumeCallbackUrl: (rawUrl) => consumeCallbackUrl(rawUrl),
      redirectUri: deps.redirectUri,
      logWarn: (message) => deps.logWarn(message),
    });
    const fallback = createAnilistSetupFallbackHandler({
      authorizeUrl,
      developerSettingsUrl: deps.developerSettingsUrl,
      setupWindow,
      openSetupInBrowser,
      loadManualTokenEntry,
      logError: (message, details) => deps.logError(message, details),
      logWarn: (message) => deps.logWarn(message),
    });
    const handleWindowOpen = createAnilistSetupWindowOpenHandler({
      isAllowedExternalUrl: (url) => deps.isAllowedExternalUrl(url),
      openExternal: (url) => deps.openExternal(url),
      logWarn: (message, details) => deps.logWarn(message, details),
    });
    const handleWillNavigate = createAnilistSetupWillNavigateHandler({
      handleManualSubmission: (url) => handleManualSubmission(url),
      consumeCallbackUrl: (url) => consumeCallbackUrl(url),
      redirectUri: deps.redirectUri,
      isAllowedNavigationUrl: (url) => deps.isAllowedNavigationUrl(url),
      logWarn: (message, details) => deps.logWarn(message, details),
    });
    const handleWillRedirect = createAnilistSetupWillRedirectHandler({
      consumeCallbackUrl: (url) => consumeCallbackUrl(url),
    });
    const handleDidNavigate = createAnilistSetupDidNavigateHandler({
      consumeCallbackUrl: (url) => consumeCallbackUrl(url),
    });
    const handleDidFailLoad = createAnilistSetupDidFailLoadHandler({
      onLoadFailure: (details) => fallback.onLoadFailure(details),
    });
    const handleDidFinishLoad = createAnilistSetupDidFinishLoadHandler({
      getLoadedUrl: () => setupWindow.webContents.getURL(),
      onBlankPageLoaded: () => fallback.onBlankPageLoaded(),
    });
    const handleWindowClosed = createHandleAnilistSetupWindowClosedHandler({
      clearSetupWindow: () => deps.clearSetupWindow(),
      setSetupPageOpened: (opened) => deps.setSetupPageOpened(opened),
    });
    const handleWindowOpened = createHandleAnilistSetupWindowOpenedHandler({
      setSetupWindow: () => deps.setSetupWindow(setupWindow),
      setSetupPageOpened: (opened) => deps.setSetupPageOpened(opened),
    });

    setupWindow.webContents.setWindowOpenHandler(({ url }: { url: string }) =>
      handleWindowOpen({ url }),
    );
    setupWindow.webContents.on('will-navigate', (event: unknown, url: string) => {
      handleWillNavigate({
        url,
        preventDefault: () => {
          if (event && typeof event === 'object' && 'preventDefault' in event) {
            const typedEvent = event as { preventDefault?: () => void };
            typedEvent.preventDefault?.();
          }
        },
      });
    });
    setupWindow.webContents.on('will-redirect', (event: unknown, url: string) => {
      handleWillRedirect({
        url,
        preventDefault: () => {
          if (event && typeof event === 'object' && 'preventDefault' in event) {
            const typedEvent = event as { preventDefault?: () => void };
            typedEvent.preventDefault?.();
          }
        },
      });
    });
    setupWindow.webContents.on('did-navigate', (_event: unknown, url: string) => {
      handleDidNavigate(url);
    });
    setupWindow.webContents.on(
      'did-fail-load',
      (_event: unknown, errorCode: number, errorDescription: string, validatedURL: string) => {
        handleDidFailLoad({
          errorCode,
          errorDescription,
          validatedURL,
        });
      },
    );
    setupWindow.webContents.on('did-finish-load', () => {
      handleDidFinishLoad();
    });
    loadManualTokenEntry();
    setupWindow.on('closed', () => {
      handleWindowClosed();
    });
    handleWindowOpened();
  };
}
