import type { FMTable } from "../../orm/table";
import { getTableName } from "../../orm/table";
import { buildRecordPath, type RecordLocator } from "../builders/mutation-helpers";
import { resolveTableId } from "../builders/table-utils";

/**
 * Configuration for navigation from RecordBuilder or EntitySet
 */
export interface NavigationConfig {
  recordLocator?: RecordLocator;
  relation: string;
  relationEntityId?: string;
  sourceTableName: string;
  sourceTableEntityId?: string;
  baseRelation?: string; // For chained navigations from navigated EntitySets
  baseRelationEntityId?: string;
  basePath?: string; // Full base path for chained entity set navigations
  basePathEntityId?: string;
}

/**
 * Builds OData query URLs for different navigation modes.
 * Handles:
 * - Record navigation: /database/sourceTable('recordId')/relation
 * - Entity set navigation: /database/sourceTable/relation
 * - Count endpoint: /database/tableId/$count
 * - Standard queries: /database/tableId
 */
export class QueryUrlBuilder {
  private readonly databaseName: string;
  // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration
  private readonly occurrence: FMTable<any, any>;
  private readonly useEntityIds: boolean;

  // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration
  constructor(databaseName: string, occurrence: FMTable<any, any>, useEntityIds: boolean) {
    this.databaseName = databaseName;
    this.occurrence = occurrence;
    this.useEntityIds = useEntityIds;
  }

  /**
   * Builds the full URL for a query request.
   *
   * @param queryString - The OData query string (e.g., "?$filter=...&$select=...")
   * @param options - Options including whether this is a count query, useEntityIds override, and navigation config
   */
  build(
    queryString: string,
    options: {
      isCount?: boolean;
      useEntityIds?: boolean;
      navigation?: NavigationConfig;
    },
  ): string {
    return `/${this.databaseName}${this.buildPath(queryString, options)}`;
  }

  /**
   * Builds a query string path (without database prefix) for getQueryString().
   * Used when the full URL is not needed.
   */
  buildPath(
    queryString: string,
    options?: { isCount?: boolean; useEntityIds?: boolean; navigation?: NavigationConfig },
  ): string {
    const effectiveUseEntityIds = options?.useEntityIds ?? this.useEntityIds;
    const navigation = options?.navigation;
    const tableId = resolveTableId(this.occurrence, getTableName(this.occurrence), effectiveUseEntityIds);
    const suffix = options?.isCount ? "/$count" : "";

    if (navigation?.recordLocator !== undefined && navigation?.relation) {
      const sourceTable = effectiveUseEntityIds
        ? (navigation.sourceTableEntityId ?? navigation.sourceTableName)
        : navigation.sourceTableName;
      const baseRelation = effectiveUseEntityIds
        ? (navigation.baseRelationEntityId ?? navigation.baseRelation)
        : navigation.baseRelation;
      const relation = effectiveUseEntityIds
        ? (navigation.relationEntityId ?? navigation.relation)
        : navigation.relation;
      const { recordLocator } = navigation;
      if (recordLocator === undefined) {
        throw new Error("recordLocator is required for record navigation");
      }
      const base = baseRelation
        ? buildRecordPath(`${sourceTable}/${baseRelation}`, recordLocator)
        : buildRecordPath(sourceTable, recordLocator);
      return queryString ? `/${base}/${relation}${suffix}${queryString}` : `/${base}/${relation}${suffix}`;
    }
    if (navigation?.relation) {
      const sourceTable = effectiveUseEntityIds
        ? (navigation.sourceTableEntityId ?? navigation.sourceTableName)
        : navigation.sourceTableName;
      const basePath = effectiveUseEntityIds
        ? (navigation.basePathEntityId ?? navigation.basePath)
        : navigation.basePath;
      const relation = effectiveUseEntityIds
        ? (navigation.relationEntityId ?? navigation.relation)
        : navigation.relation;
      const base = basePath || sourceTable;
      return queryString ? `/${base}/${relation}${suffix}${queryString}` : `/${base}/${relation}${suffix}`;
    }
    return queryString ? `/${tableId}${suffix}${queryString}` : `/${tableId}${suffix}`;
  }

  /**
   * Build URL for record operations (single record by ID).
   * Used by RecordBuilder to build URLs like /database/table('id').
   *
   * @param recordLocator - The record locator
   * @param queryString - The OData query string (e.g., "?$select=...")
   * @param options - Options including operation type and useEntityIds override
   */
  buildRecordUrl(
    recordLocator: RecordLocator,
    queryString: string,
    options?: {
      operation?: "getSingleField";
      operationParam?: string;
      useEntityIds?: boolean;
      isNavigateFromEntitySet?: boolean;
      navigateSourceTableName?: string;
      navigateRelation?: string;
    },
  ): string {
    const effectiveUseEntityIds = options?.useEntityIds ?? this.useEntityIds;
    const tableId = resolveTableId(this.occurrence, getTableName(this.occurrence), effectiveUseEntityIds);

    // Build the base URL depending on whether this came from a navigated EntitySet
    let url: string;
    if (options?.isNavigateFromEntitySet && options.navigateSourceTableName && options.navigateRelation) {
      // From navigated EntitySet: /sourceTable/relation('recordId')
      url = `/${this.databaseName}/${buildRecordPath(
        `${options.navigateSourceTableName}/${options.navigateRelation}`,
        recordLocator,
      )}`;
    } else {
      // Normal record: /tableName('recordId') - use FMTID if configured
      url = `/${this.databaseName}/${buildRecordPath(tableId, recordLocator)}`;
    }

    if (options?.operation === "getSingleField" && options.operationParam) {
      url += `/${options.operationParam}`;
    }

    return url + queryString;
  }
}
