/**
 * Utility functions for serializing and applying saved searches on the Triage page.
 *
 * These functions handle the conversion between URL parameters and saved search
 * query strings, preserving filters, groupBy selections, and time range.
 *
 * GroupBy keys are stored as entries in the same `var-filters` URL parameter using the
 * `key|groupBy` 2-part format defined by Scenes' AdHocFiltersVariable URL sync handler.
 */

import { type AdHocVariableFilter, type RawTimeRange, dateMath, makeTimeRange } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { AdHocFiltersVariable, GROUP_BY_OPERATOR, type SceneObject, sceneGraph } from '@grafana/scenes';

import { toFilter, toUrl } from '../../../../variables/adhoc/urlParser';
import { type SavedSearch } from '../../components/saved-searches/savedSearchesSchema';
import { TRIAGE_STATE_URL_PARAMS, URL_PARAMS, VARIABLES } from '../constants';

/**
 * Merges predefined and user saved searches, with the default search first.
 *
 * @param predefinedSearches - Predefined searches (with isDefault applied)
 * @param userSavedSearches - User-created saved searches (with isDefault applied)
 * @param defaultId - ID of the search set as default, or null
 * @returns Merged list with default item first, then predefined, then user
 */
export function mergeTriageSavedSearches(
  predefinedSearches: SavedSearch[],
  userSavedSearches: SavedSearch[],
  defaultId: string | null
): SavedSearch[] {
  const merged = [...predefinedSearches, ...userSavedSearches];
  const defaultIndex = merged.findIndex((s) => s.id === defaultId);
  if (defaultIndex <= 0) {
    return merged;
  }
  const defaultItem = merged[defaultIndex];
  return [defaultItem, ...merged.slice(0, defaultIndex), ...merged.slice(defaultIndex + 1)];
}

/** Filter shape compatible with AdHocVariableFilter (key, operator, value). Used for building query strings. */
export interface TriageFilterInput {
  key: string;
  operator: string;
  value: string;
}

/**
 * Builds a triage saved search query string from filters, groupBy, and time range.
 * Shared by serializeTriageState (Scene state) and triagePredefinedSearches (static definitions).
 * Uses URL_PARAMS and toUrl so format stays in sync across all triage saved searches.
 *
 * GroupBy keys are appended to var-filters as `key|groupBy` (2-part, no value), matching
 * the Scenes AdHocFiltersVariable URL sync format.
 */
export function buildTriageQueryStringFromParts(input: {
  filters?: TriageFilterInput[];
  groupBy: string[];
  timeRange: RawTimeRange;
}): string {
  const params = new URLSearchParams();

  if (input.filters?.length) {
    toUrl(input.filters).forEach((filterStr) => {
      params.append(URL_PARAMS.filters, filterStr);
    });
  }

  // Encode groupBy keys as `key|groupBy` filter entries (Scenes AdHocFiltersVariable URL format).
  input.groupBy.filter(Boolean).forEach((key) => {
    params.append(URL_PARAMS.filters, `${key}|${GROUP_BY_OPERATOR}`);
  });

  const fromValue =
    typeof input.timeRange.from === 'string' ? input.timeRange.from : input.timeRange.from.toISOString();
  const toValue = typeof input.timeRange.to === 'string' ? input.timeRange.to : input.timeRange.to.toISOString();
  params.set(URL_PARAMS.timeFrom, fromValue);
  params.set(URL_PARAMS.timeTo, toValue);

  return params.toString();
}

/**
 * Serializes the current triage page state from the URL to a query string.
 *
 * This function reads the current URL parameters that define the triage view state
 * and returns them as a serialized query string suitable for storage.
 *
 * @returns Serialized query string containing filters (including groupBy entries) and time range
 *
 * @example
 * // URL: /alerting/alerts?var-filters=alertname|=|test&var-filters=grafana_folder|groupBy&from=now-1h&to=now
 * serializeCurrentSearchState()
 * // Returns: "var-filters=alertname%7C%3D%7Ctest&var-filters=grafana_folder%7CgroupBy&from=now-1h&to=now"
 */
export function serializeCurrentSearchState(): string {
  const currentParams = locationService.getSearch();
  const params = new URLSearchParams();

  // Copy relevant params - use getAll() for multi-value support (var-filters)
  TRIAGE_STATE_URL_PARAMS.forEach((key) => {
    const values = currentParams.getAll(key);
    values.forEach((value) => params.append(key, value));
  });

  return params.toString();
}

/**
 * Serializes triage state (filters, groupBy, time range) into a query string.
 * This is used by the component to serialize the current Scene state for saved searches.
 *
 * @param filters - Array of AdHocVariableFilter objects (both regular filters and groupBy entries)
 * @param timeRange - Raw time range with from/to as strings, Date, or DateTime objects
 * @returns Serialized query string
 */
export function serializeTriageState(filters: AdHocVariableFilter[], timeRange: RawTimeRange): string {
  const params = new URLSearchParams();

  filters.forEach((f) => {
    if (f.operator === GROUP_BY_OPERATOR) {
      // GroupBy entry: serialize as key|groupBy (2-part, no value)
      params.append(URL_PARAMS.filters, `${f.key}|${GROUP_BY_OPERATOR}`);
    } else {
      // Regular filter: use standard toUrl serialization
      toUrl([f]).forEach((filterStr) => {
        params.append(URL_PARAMS.filters, filterStr);
      });
    }
  });

  const fromValue = typeof timeRange.from === 'string' ? timeRange.from : timeRange.from.toISOString();
  const toValue = typeof timeRange.to === 'string' ? timeRange.to : timeRange.to.toISOString();
  params.set(URL_PARAMS.timeFrom, fromValue);
  params.set(URL_PARAMS.timeTo, toValue);

  return params.toString();
}

/**
 * Applies a saved search query to Scene variables.
 * Updates filters (including groupBy entries) and time range by directly calling variable methods.
 *
 * This uses direct state updates instead of URL navigation because Scenes' URL sync
 * has a limitation: updateFromUrl() only receives CHANGED values. When a parameter
 * is absent from the saved search (e.g., to clear groupBy), it won't trigger an update.
 *
 * Backward compatibility: also reads legacy `var-groupBy` params and converts them to
 * groupBy filter entries, so searches saved before the unified variable migration still work.
 *
 * @param scene - The scene object containing the variables
 * @param query - The saved search query string
 */
export function applyTriageSavedSearchState(scene: SceneObject, query: string): void {
  const params = new URLSearchParams(query);

  // Update filters (includes groupBy entries encoded as key|groupBy)
  const filtersVar = sceneGraph.lookupVariable(VARIABLES.filters, scene);
  if (filtersVar instanceof AdHocFiltersVariable) {
    filtersVar.updateFilters(extractFilterObjects(query));
  }

  // Update time range
  const sceneTimeRange = sceneGraph.getTimeRange(scene);
  const from = params.get(URL_PARAMS.timeFrom) ?? 'now-4h';
  const to = params.get(URL_PARAMS.timeTo) ?? 'now';
  const fromDateTime = dateMath.parse(from, false);
  const toDateTime = dateMath.parse(to, true);

  if (fromDateTime && toDateTime) {
    sceneTimeRange.onTimeRangeChange(makeTimeRange(fromDateTime, toDateTime));
  }
}

/**
 * Parses a saved search query string into AdHocVariableFilter objects.
 * Handles both regular 3-part filter entries (`key|operator|value`) and 2-part groupBy
 * entries (`key|groupBy`) from the unified var-filters param.
 *
 * Also migrates legacy `var-groupBy` entries (saved before the unified variable migration)
 * by converting them to groupBy filter objects.
 *
 * @param query - The saved search query string
 * @returns Array of filter objects compatible with AdHocFiltersVariable.updateFilters()
 */
export function extractFilterObjects(query: string): AdHocVariableFilter[] {
  const params = new URLSearchParams(query);

  const filterEntries: AdHocVariableFilter[] = params
    .getAll(URL_PARAMS.filters)
    .map(parseFilterEntry)
    .filter((f): f is AdHocVariableFilter => f !== null);

  // Backward compat: migrate legacy var-groupBy entries (written before the unified variable).
  const legacyGroupByKeys = params.getAll('var-groupBy').filter(Boolean);
  const legacyGroupByFilters: AdHocVariableFilter[] = legacyGroupByKeys.map((key) => ({
    key,
    operator: GROUP_BY_OPERATOR,
    value: '',
  }));

  return [...filterEntries, ...legacyGroupByFilters];
}

function parseFilterEntry(raw: string): AdHocVariableFilter | null {
  if (!raw || raw.split('|').length < 2) {
    return null;
  }

  // Normalize 2-part entries (e.g. key|groupBy) to 3-part (key|groupBy|)
  // so toFilter can parse all formats uniformly.
  const normalized = raw.split('|').length === 2 ? raw + '|' : raw;
  return toFilter(normalized);
}

/**
 * Generates a URL for applying a saved search.
 *
 * @param query - The saved search query string
 * @param basePath - The base path for the URL (defaults to /alerting/alerts)
 * @returns Full URL path with query parameters
 */
export function generateTriageUrl(query: string, basePath = '/alerting/alerts'): string {
  if (!query) {
    return basePath;
  }
  return `${basePath}?${query}`;
}

/**
 * Applies a saved search by navigating to the URL with saved parameters.
 *
 * This function:
 * 1. Parses the saved query string
 * 2. Builds a new URL with only the saved triage parameters
 * 3. Uses locationService to navigate, triggering proper React Router and Scene sync
 *
 * Using locationService ensures the Scene framework's URL sync properly updates
 * the Scene variables (filters, groupBy, time range) based on the new URL.
 *
 * @param query - The saved search query string to apply
 *
 * @example
 * // Apply a saved search
 * applySavedSearch("var-filters=alertname%7C%3D%7Ctest&var-filters=grafana_folder%7CgroupBy&from=now-1h&to=now");
 * // URL is updated and Scene variables are synced
 */
export function applySavedSearch(query: string): void {
  // Use generateTriageUrl to build the full path with query params
  const url = generateTriageUrl(query);

  // Use locationService for proper React Router integration
  // This triggers the Scene's URL sync mechanism
  locationService.push(url);
}
