/**
 * @license
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * TanStack Query hooks wrapping the Fiesta REST client in `./fiestaApi`.
 *
 * Components call these (never fetch directly) to get caching, dedup, and
 * loading/error state. These power the operational pages (Inventory, Orders &
 * Deliveries, Operations, Reports, Stores/Logistics/Staffing); the Dashboard
 * continues to use the Hasura hooks in `./queries`.
 */

import { useQuery, useMutation, useQueryClient, useQueries } from '@tanstack/react-query';
import type { Row } from './fiestaApi';
import { loginRequest, matchTenantUser, buildAuthUser, type AuthUser } from './auth';
import {
  FIESTA_TENANT_ID,
  FIESTA_APPLOCATION_ID,
  FIESTA_PRIMARY_LOCATION_ID,
  getOrderSummary,
  getSalesSummary,
  getRevenueSummary,
  getTimeSeries,
  getLocationSummary,
  getOrderInsight,
  getOrders,
  getDeliverySummary,
  getDeliveries,
  getDeliveryInsight,
  getDeliveryReport,
  getFleetSummary,
  getOrderDetails,
  getCustomerOrders,
  getRiders,
  getRiderShifts,
  getRiderPeriodicLogs,
  getRiderLogs,
  getBatchEfficiency,
  updateDelivery,
  reassignDeliveries,
  getTenantLocations,
  getAllTenants,
  getTenantCustomers,
  getStockStatement,
  getProductsCount,
  getProductStocks,
  getProductLocations,
  createProductLocations,
  getMasterCatalog,
  getProductCategories,
  getProductSubcategories,
  getAllUsers,
  getUserById,
  createUser,
  updateUser,
  setUserPassword,
  assignRiderToOrders,
  changeDeliveryRider,
  notifyRider,
  NotifyRiderInput,
  CreateUserInput,
  createTenantUser,
  createTenantLocation,
  CreateTenantInput,
  CreateTenantLocationInput,
  updateTenantLocation,
  UpdateTenantLocationInput,
  deleteLocation,
  createProductLocation,
  deleteProductLocation,
  createStockRequest,
  getStockRequests,
  updateStockRequest,
  CreateProductLocationInput,
  DeleteProductLocationInput,
  StockRequestInput,
  UpdateStockRequestInput,
  getGlobalBrands,
  getGlobalCategories,
  getGlobalProducts,
  getGlobalProduct,
} from './fiestaApi';

export const fiestaKeys = {
  orderSummary: (tenantid: number, fromdate: string, todate: string, locationid?: number) =>
    ['fiesta', 'orderSummary', tenantid, fromdate, todate, locationid ?? 0] as const,
  salesSummary: (tenantid: number, locationid: number | undefined, fromdate: string, todate: string) => 
    ['fiesta', 'salesSummary', tenantid, locationid, fromdate, todate] as const,
  revenueSummary: (params: Record<string, unknown>) => ['fiesta', 'revenueSummary', params] as const,
  timeSeries: (params: Record<string, unknown>) => ['fiesta', 'timeSeries', params] as const,
  locationSummary: (tenantid: number) => ['fiesta', 'locationSummary', tenantid] as const,
  orderInsight: (tenantid: number) => ['fiesta', 'orderInsight', tenantid] as const,
  orders: (params: Record<string, unknown>) => ['fiesta', 'orders', params] as const,
  deliverySummary: (params: Record<string, unknown>) => ['fiesta', 'deliverySummary', params] as const,
  deliveries: (params: Record<string, unknown>) => ['fiesta', 'deliveries', params] as const,
  deliveryInsight: (tenantid: number) => ['fiesta', 'deliveryInsight', tenantid] as const,
  riders: (params: Record<string, unknown>) => ['fiesta', 'riders', params] as const,
  riderShifts: (applocationid: number) => ['fiesta', 'riderShifts', applocationid] as const,
  riderPeriodicLogs: (params: Record<string, unknown>) => ['fiesta', 'riderPeriodicLogs', params] as const,
  riderLogs: (params: Record<string, unknown>) => ['fiesta', 'riderLogs', params] as const,
  batchEfficiency: (params: Record<string, unknown>) => ['fiesta', 'batchEfficiency', params] as const,
  // v2: bumped when test-row filtering was added to getTenantLocations so any
  // warm cache holding the old unfiltered (duplicated/junk) rows is bypassed.
  tenantLocations: (tenantid: number) => ['fiesta', 'tenantLocations', 'v2', tenantid] as const,
  allTenants: (params: Record<string, unknown>) => ['fiesta', 'allTenants', params] as const,
  tenantCustomers: (params: Record<string, unknown>) => ['fiesta', 'tenantCustomers', params] as const,
  stockStatement: (params: Record<string, unknown>) => ['fiesta', 'stockStatement', params] as const,
  productsCount: (params: Record<string, unknown>) => ['fiesta', 'productsCount', params] as const,
  productStocks: (params: Record<string, unknown>) => ['fiesta', 'productStocks', params] as const,
  productLocations: (params: Record<string, unknown>) => ['fiesta', 'productLocations', params] as const,
  masterCatalog: (params: Record<string, unknown>) => ['fiesta', 'masterCatalog', params] as const,
  productCategories: () => ['fiesta', 'productCategories'] as const,
  productSubcategories: (params: Record<string, unknown>) => ['fiesta', 'productSubcategories', params] as const,
  orderDetails: (orderheaderid: number | string) => ['fiesta', 'orderDetails', orderheaderid] as const,
  customerOrders: (params: Record<string, unknown>) => ['fiesta', 'customerOrders', params] as const,
  deliveryReport: (params: Record<string, unknown>) => ['fiesta', 'deliveryReport', params] as const,
  fleetSummary: (params: Record<string, unknown>) => ['fiesta', 'fleetSummary', params] as const,
  users: (params: Record<string, unknown>) => ['fiesta', 'users', params] as const,
  user: (userid: number) => ['fiesta', 'user', userid] as const,
  stockRequests: (params: Record<string, unknown>) => ['fiesta', 'stockRequests', params] as const,
  globalBrands: () => ['fiesta', 'globalBrands'] as const,
  globalCategories: (brand: string) => ['fiesta', 'globalCategories', brand] as const,
  globalProducts: (params: Record<string, unknown>) => ['fiesta', 'globalProducts', params] as const,
};

// ── Orders ──────────────────────────────────────────────────────────────────
export function useFiestaOrderSummary(tenantid: number = FIESTA_TENANT_ID, fromdate: string, todate: string, locationid?: number) {
  return useQuery({
    queryKey: fiestaKeys.orderSummary(tenantid, fromdate, todate, locationid),
    queryFn: async () => {
      // The backend's /orders/getordersummary endpoint ignores locationid and returns tenant-wide
      // totals. To fix this data leak for store managers, we manually fetch the core statuses for 
      // the specific location when a locationid is provided.
      if (locationid) {
        const [created, pending, processing, delivered, cancelled] = await Promise.all([
          getOrders({ tenantid, locationid, status: 'created', fromdate, todate, pagesize: 500 }),
          getOrders({ tenantid, locationid, status: 'pending', fromdate, todate, pagesize: 500 }),
          getOrders({ tenantid, locationid, status: 'processing', fromdate, todate, pagesize: 500 }),
          getOrders({ tenantid, locationid, status: 'delivered', fromdate, todate, pagesize: 500 }),
          getOrders({ tenantid, locationid, status: 'cancelled', fromdate, todate, pagesize: 500 }),
        ]);
        
        return {
          total: created.length + pending.length + processing.length + delivered.length + cancelled.length,
          created: created.length,
          pending: pending.length,
          processing: processing.length,
          delivered: delivered.length,
          cancelled: cancelled.length,
          tenantid,
          tenantname: 'Local Store'
        };
      }
      
      return getOrderSummary(tenantid, fromdate, todate, locationid);
    },
    enabled: Boolean(tenantid && fromdate && todate),
  });
}

export function useFiestaRevenueSummary(opts: {
  tenantid: number;
  fromdate: string;
  todate: string;
  locationid?: number;
}) {
  return useQuery({
    queryKey: fiestaKeys.revenueSummary(opts as Record<string, unknown>),
    queryFn: () => getRevenueSummary(opts),
    enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
  });
}

export function useFiestaTimeSeries(opts: {
  tenantid: number;
  granularity: 'day' | 'month' | 'year';
  fromdate: string;
  todate: string;
  locationid?: number;
}) {
  return useQuery({
    queryKey: fiestaKeys.timeSeries(opts as Record<string, unknown>),
    queryFn: () => getTimeSeries(opts),
    enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
  });
}

export function useFiestaSalesSummary(opts: {
  tenantid: number;
  locationid?: number;
  fromdate: string;
  todate: string;
}) {
  return useQuery({
    queryKey: fiestaKeys.salesSummary(opts.tenantid, opts.locationid, opts.fromdate, opts.todate),
    queryFn: () => getSalesSummary(opts),
    enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
  });
}

export function useFiestaLocationSummary(tenantid: number = FIESTA_TENANT_ID, fromdate?: string, todate?: string) {
  return useQuery({
    queryKey: [...fiestaKeys.locationSummary(tenantid), fromdate, todate],
    queryFn: () => getLocationSummary(tenantid, fromdate, todate),
    enabled: Boolean(tenantid),
  });
}

export function useFiestaOrderInsight(tenantid: number = FIESTA_TENANT_ID) {
  return useQuery({
    queryKey: fiestaKeys.orderInsight(tenantid),
    queryFn: () => getOrderInsight(tenantid),
    enabled: Boolean(tenantid),
  });
}

export function useFiestaOrders(opts: {
  tenantid: number;
  status: string;
  fromdate: string;
  todate: string;
  locationid?: number;
  applocationid?: number;
  keyword?: string;
  pageno?: number;
  pagesize?: number;
}) {
  return useQuery({
    queryKey: fiestaKeys.orders(opts),
    queryFn: () => getOrders(opts),
    enabled: Boolean(opts.tenantid && opts.status && opts.fromdate && opts.todate),
  });
}

/**
 * Fetches orders across all statuses for a given date range by firing one
 * request per status in parallel and merging the results. This is needed
 * because the /orders/getorders API requires an explicit status param and
 * returns an empty array when status is blank or 'all'.
 */
export function useFiestaAllOrders(opts: {
  tenantid: number;
  fromdate: string;
  todate: string;
  locationid?: number;
  applocationid?: number;
  keyword?: string;
}) {
  return useQuery({
    queryKey: ['fiesta', 'allOrders', opts],
    queryFn: async () => {
      // Include all known statuses from ORDER_STATUS_MAP to ensure we don't miss orders
      const statuses = [
        'created', 'pending', 'processing', 'delivered', 'cancelled',
        'accepted', 'assigned', 'ready', 'picked', 'active', 'arrived'
      ];
      // Fetch sequentially to avoid rate-limiting or proxy dropping parallel requests
      const results: Row[][] = [];
      for (const status of statuses) {
        try {
          const res = await getOrders({
            tenantid: opts.tenantid,
            status,
            fromdate: opts.fromdate,
            todate: opts.todate,
            locationid: opts.locationid,
            applocationid: opts.applocationid,
            keyword: opts.keyword,
            pagesize: 500,
          });
          results.push(res);
        } catch (e) {
          results.push([]);
        }
      }
      // Merge and deduplicate by orderid/orderheaderid
      const merged: Row[] = [];
      const seen = new Set<string>();
      for (const list of results) {
        for (const row of list) {
          const id = String(row.orderid || row.orderheaderid || Math.random());
          if (!seen.has(id)) {
            seen.add(id);
            merged.push(row);
          }
        }
      }
      return merged;
    },
    enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
  });
}

// ── Deliveries ────────────────────────────────────────────────────────────────
export function useFiestaDeliverySummary(opts: {
  tenantid: number;
  applocationid?: number;
  locationid?: number;
  fromdate: string;
  todate: string;
}) {
  return useQuery({
    queryKey: fiestaKeys.deliverySummary(opts),
    queryFn: () => getDeliverySummary(opts),
    enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
  });
}

export function useFiestaDeliveries(opts: {
  tenantid: number;
  fromdate: string;
  todate: string;
  status?: string;
  locationid?: number;
  applocationid?: number;
  keyword?: string;
  pageno?: number;
  pagesize?: number;
}) {
  return useQuery({
    queryKey: fiestaKeys.deliveries(opts),
    queryFn: () => getDeliveries(opts),
    enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
  });
}

export function useFiestaDeliveryInsight(tenantid: number = FIESTA_TENANT_ID) {
  return useQuery({
    queryKey: fiestaKeys.deliveryInsight(tenantid),
    queryFn: () => getDeliveryInsight(tenantid),
    enabled: Boolean(tenantid),
  });
}

/**
 * Bulk-assign one rider to many orders (the Orders board's multi-select assign).
 * Fires one updatedelivery per row in parallel, tolerates partial failure, and
 * refreshes the orders + deliveries lists on completion.
 */
export function useFiestaAssignRider() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (input: { userid: number; orders: Row[] }) => assignRiderToOrders(input.userid, input.orders),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['fiesta', 'orders'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'orderSummary'] });
      // Refresh the Deliveries board AND its KPI summary so a freshly-assigned
      // order shows up on the deliveries page immediately (table + count cards).
      qc.invalidateQueries({ queryKey: ['fiesta', 'deliveries'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'deliverySummary'] });
    },
  });
}

/**
 * Move a delivery to a different rider. Refreshes the deliveries board and its
 * KPI cards, plus the orders list, since the order's rider is shown there too.
 */
export function useFiestaChangeRider() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (input: { deliveryid: number; orderheaderid: number; userid: number }) =>
      changeDeliveryRider(input),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['fiesta', 'deliveries'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'deliverySummary'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'orders'] });
    },
  });
}

/**
 * Push a message to a rider's device.
 *
 * Deliberately NOT chained into the assign mutation's onSuccess: the delivery
 * is already committed by then, so a failed push must not read as a failed
 * assignment. Callers fire this after the write and report the two outcomes
 * separately.
 */
export function useFiestaNotifyRider() {
  return useMutation({
    mutationFn: (input: NotifyRiderInput) => notifyRider(input),
  });
}

// ── Partners / Riders ─────────────────────────────────────────────────────────
/**
 * Riders on duty right now — see getRiders for why this is presence, not a
 * roster. Enabled on ANY scope: gating on tenantid alone kept the query off for
 * callers that legitimately scope by app-location, which is the only scope that
 * actually returns riders.
 */
export function useFiestaRiders(opts: { applocationid?: number; tenantid?: number; partnerid?: number }) {
  return useQuery({
    queryKey: fiestaKeys.riders(opts),
    queryFn: () => getRiders(opts),
    enabled: Boolean(opts.applocationid || opts.partnerid || opts.tenantid),
    // Presence goes stale quickly — a rider logging off mid-shift should drop
    // out of the assign list rather than linger for the whole session.
    staleTime: 60_000,
    refetchInterval: 120_000,
  });
}

export function useFiestaRiderShifts(applocationid: number = FIESTA_APPLOCATION_ID) {
  return useQuery({
    queryKey: fiestaKeys.riderShifts(applocationid),
    queryFn: () => getRiderShifts(applocationid),
    enabled: Boolean(applocationid),
  });
}

// ── Dispatch / Telemetry ─────────────────────────────────────────────────────
export function useFiestaRiderPeriodicLogs(opts: {
  userid?: number;
  riderid?: number;
  fromdate: string;
  todate: string;
  tenantid?: number;
  applocationid?: number;
}) {
  return useQuery({
    queryKey: fiestaKeys.riderPeriodicLogs(opts),
    queryFn: () => getRiderPeriodicLogs(opts),
    // A rider is required. Without this guard the query fired on every page load
    // with no rider selected — and `riders/getriderperiodiclogs` 404s in both the
    // riders/ and partners/ namespaces (no such backend route exists), so every
    // load spent a request on a guaranteed failure.
    enabled: Boolean((opts.userid || opts.riderid) && opts.fromdate && opts.todate),
  });
}

export function useFiestaRiderLogs(opts: {
  userid?: number;
  riderid?: number;
  fromdate: string;
  todate: string;
  tenantid?: number;
  applocationid?: number;
  pageno?: number;
  pagesize?: number;
}) {
  return useQuery({
    queryKey: fiestaKeys.riderLogs(opts),
    queryFn: () => getRiderLogs(opts),
    enabled: Boolean(opts.fromdate && opts.todate),
  });
}

export function useFiestaBatchEfficiency(opts: {
  partnerid?: number;
  tenantid: number;
  fromdate: string;
  todate: string;
}) {
  return useQuery({
    queryKey: fiestaKeys.batchEfficiency(opts),
    queryFn: () => getBatchEfficiency(opts),
    enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
  });
}

export function useFiestaUpdateDelivery() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (input: { deliveryid: number; updates: Row }) =>
      updateDelivery(input.deliveryid, input.updates),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['fiesta', 'deliveries'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'deliverySummary'] });
    },
  });
}

/**
 * Move several deliveries to one rider. Each needs its orderheaderid as well as
 * its deliveryid — updatedelivery keys on both — so this takes delivery rows
 * rather than the bare id list it used to.
 */
export function useFiestaReassignDeliveries() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (input: { userid: number; deliveries: { deliveryid: number; orderheaderid: number }[] }) =>
      reassignDeliveries(input),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['fiesta', 'deliveries'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'deliverySummary'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'orders'] });
    },
  });
}

// ── Tenants / Customers ─────────────────────────────────────────────────────────
export function useFiestaTenantLocations(tenantid: number = FIESTA_TENANT_ID, userid?: number) {
  return useQuery({
    queryKey: [...fiestaKeys.tenantLocations(tenantid), userid],
    queryFn: () => getTenantLocations(tenantid, userid),
    enabled: Boolean(tenantid),
  });
}

export function useFiestaAllTenants(opts: {
  applocationid?: number;
  status?: string;
  pageno?: number;
  pagesize?: number;
} = {}) {
  return useQuery({
    queryKey: fiestaKeys.allTenants(opts),
    queryFn: () => getAllTenants(opts),
  });
}

export function useFiestaTenantCustomers(opts: {
  tenantid: number;
  locationid?: number;
  keyword?: string;
  pageno?: number;
  pagesize?: number;
}) {
  return useQuery({
    queryKey: fiestaKeys.tenantCustomers(opts),
    queryFn: () => getTenantCustomers({ ...opts, locationid: opts.locationid || 0 }),
    enabled: Boolean(opts.tenantid),
  });
}

// ── Products / Stock ─────────────────────────────────────────────────────────
export function useFiestaStockStatement(opts: {
  tenantid: number;
  locationid: number;
  subcategoryid?: number;
  keyword?: string;
  pageno?: number;
  pagesize?: number;
}) {
  return useQuery({
    queryKey: fiestaKeys.stockStatement(opts),
    queryFn: () => getStockStatement(opts),
    enabled: Boolean(opts.tenantid && opts.locationid),
  });
}

export function useFiestaProductsCount(opts: { tenantid: number; categoryid: number; subcategoryid?: number }) {
  return useQuery({
    queryKey: fiestaKeys.productsCount(opts),
    queryFn: () => getProductsCount(opts),
    enabled: Boolean(opts.tenantid && opts.categoryid),
  });
}

export interface StoreStock {
  locationid: number;
  locationname: string;
  isLoading: boolean;
  isError: boolean;
  rows: Row[];
}

/**
 * Live stock statement for every outlet under the tenant — powers the admin's
 * "all stores' stock" view. One query per location (deduped/cached by React
 * Query); the returned array stays aligned with the `locations` input.
 */
export function useFiestaStoresStock(
  tenantid: number,
  locations: Array<{ locationid: number; locationname: string }>,
): StoreStock[] {
  const results = useQueries({
    queries: locations.map((loc) => ({
      queryKey: fiestaKeys.stockStatement({ scope: 'stores', tenantid, locationid: loc.locationid }),
      queryFn: () => getStockStatement({ tenantid, locationid: loc.locationid, pagesize: 200 }),
      enabled: Boolean(tenantid && loc.locationid),
    })),
  });
  return locations.map((loc, i) => ({
    locationid: loc.locationid,
    locationname: loc.locationname,
    isLoading: results[i]?.isLoading ?? true,
    isError: results[i]?.isError ?? false,
    rows: (results[i]?.data as Row[]) ?? [],
  }));
}

export function useFiestaStoresOrderSummary(
  tenantid: number,
  locations: Array<{ locationid: number; locationname: string }>,
  fromdate: string,
  todate: string
) {
  const results = useQueries({
    queries: locations.map((loc) => ({
      queryKey: fiestaKeys.orderSummary(tenantid, fromdate, todate, loc.locationid),
      queryFn: () => getOrderSummary(tenantid, fromdate, todate, loc.locationid),
      enabled: Boolean(tenantid && loc.locationid && fromdate && todate),
    })),
  });

  const agg = { total: 0, created: 0, pending: 0, processing: 0, delivered: 0, cancelled: 0 };
  let isLoading = false;
  results.forEach(res => {
    if (res.isLoading) isLoading = true;
    if (res.data) {
      agg.total += res.data.total;
      agg.created += res.data.created;
      agg.pending += res.data.pending;
      agg.processing += res.data.processing;
      agg.delivered += res.data.delivered;
      agg.cancelled += res.data.cancelled;
    }
  });

  return { isLoading, data: agg };
}

export function useFiestaStoresRevenueSummary(
  tenantid: number,
  locations: Array<{ locationid: number; locationname: string }>,
  fromdate: string,
  todate: string
) {
  const results = useQueries({
    queries: locations.map((loc) => ({
      queryKey: fiestaKeys.revenueSummary({ scope: 'stores', tenantid, fromdate, todate, locationid: loc.locationid }),
      queryFn: () => getRevenueSummary({ tenantid, fromdate, todate, locationid: loc.locationid }),
      enabled: Boolean(tenantid && loc.locationid && fromdate && todate),
    })),
  });

  const agg = { grossrevenue: 0, netrevenue: 0, profit: 0, ordercount: 0, avgordervalue: 0 };
  let isLoading = false;
  results.forEach(res => {
    if (res.isLoading) isLoading = true;
    if (res.data) {
      agg.grossrevenue += res.data.overallrevenue || 0;
      agg.netrevenue += res.data.overallrevenue || 0;
      agg.profit += (res.data.overallrevenue || 0) * 0.15; // Mock since API doesn't provide
      agg.ordercount += 0;
    }
  });
  if (agg.ordercount > 0) {
    agg.avgordervalue = Math.round(agg.grossrevenue / agg.ordercount);
  }

  return { isLoading, data: agg };
}

export function useFiestaStoresTimeSeries(
  tenantid: number,
  locations: Array<{ locationid: number; locationname: string }>,
  granularity: 'day' | 'month' | 'year',
  fromdate: string,
  todate: string
) {
  const results = useQueries({
    queries: locations.map((loc) => ({
      queryKey: fiestaKeys.timeSeries({ scope: 'stores', tenantid, granularity, fromdate, todate, locationid: loc.locationid }),
      queryFn: () => getTimeSeries({ tenantid, granularity, fromdate, todate, locationid: loc.locationid }),
      enabled: Boolean(tenantid && loc.locationid && fromdate && todate),
    })),
  });

  const map = new Map<string, any>();
  let isLoading = false;
  results.forEach(res => {
    if (res.isLoading) isLoading = true;
    if (res.data) {
      res.data.forEach((d: any) => {
        const label = d.label || d.date || d.createdat || 'Unknown';
        if (!map.has(label)) {
          map.set(label, { label, orders: 0, revenue: 0, cancelled: 0, skus: 0 });
        }
        const existing = map.get(label);
        existing.orders += Number(d.orders || d.totalorders || d.total || 0);
        existing.revenue += Number(d.revenue || d.grossrevenue || d.overallrevenue || 0);
        existing.cancelled += Number(d.cancelled || 0);
        existing.skus += Number(d.activeskus || d.skus || 0);
      });
    }
  });

  const sortedData = Array.from(map.values()).sort((a, b) => {
    if (a.label === 'Unknown') return 1;
    if (b.label === 'Unknown') return -1;
    return new Date(a.label).getTime() - new Date(b.label).getTime();
  });

  return { isLoading, data: sortedData };
}

// ── Order details / customer history ───────────────────────────────────────────
export function useFiestaOrderDetails(orderheaderid: number | string | null | undefined) {
  return useQuery({
    queryKey: fiestaKeys.orderDetails(orderheaderid ?? ''),
    queryFn: () => getOrderDetails(orderheaderid as number | string),
    enabled: Boolean(orderheaderid),
  });
}

export function useFiestaCustomerOrders(opts: {
  customerid: number | string | null | undefined;
  status?: string;
  pageno?: number;
  pagesize?: number;
}) {
  return useQuery({
    queryKey: fiestaKeys.customerOrders(opts as Record<string, unknown>),
    queryFn: () =>
      getCustomerOrders({
        customerid: opts.customerid as number | string,
        status: opts.status,
        pageno: opts.pageno,
        pagesize: opts.pagesize,
      }),
    enabled: Boolean(opts.customerid),
  });
}

// ── Deliveries report / fleet ───────────────────────────────────────────────────
export function useFiestaDeliveryReport(opts: {
  tenantid: number;
  applocationid?: number;
  partnerid?: number;
  userid?: number;
  fromdate: string;
  todate: string;
}) {
  return useQuery({
    queryKey: fiestaKeys.deliveryReport(opts),
    queryFn: () => getDeliveryReport(opts),
    enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
  });
}

export function useFiestaFleetSummary(opts: {
  tenantid: number;
  applocationid?: number;
  partnerid?: number;
  fromdate: string;
  todate: string;
}) {
  return useQuery({
    queryKey: fiestaKeys.fleetSummary(opts),
    queryFn: () => getFleetSummary(opts),
    enabled: Boolean(opts.tenantid && opts.fromdate && opts.todate),
  });
}

// ── Products: live stocks / catalog / categories ────────────────────────────────
export function useFiestaProductStocks(opts: { tenantid: number; locationid: number }) {
  return useQuery({
    queryKey: fiestaKeys.productStocks(opts),
    queryFn: () => getProductStocks(opts),
    enabled: Boolean(opts.tenantid && opts.locationid),
  });
}

export function useFiestaProductLocations(opts: {
  tenantid: number;
  locationid: number;
  subcategoryid?: number;
  pageno?: number;
  pagesize?: number;
}) {
  return useQuery({
    queryKey: fiestaKeys.productLocations(opts),
    queryFn: () => getProductLocations(opts),
    enabled: Boolean(opts.tenantid && opts.locationid),
  });
}

export function useFiestaMasterCatalog(opts: {
  tenantid: number;
  locationid?: number;
  subcategoryid?: number;
  keyword?: string;
  pageno?: number;
  pagesize?: number;
}) {
  return useQuery({
    queryKey: fiestaKeys.masterCatalog(opts),
    queryFn: () => getMasterCatalog(opts),
    enabled: Boolean(opts.tenantid),
  });
}

export function useFiestaProductCategories() {
  return useQuery({
    queryKey: fiestaKeys.productCategories(),
    queryFn: () => getProductCategories(),
  });
}

export function useFiestaProductSubcategories(opts: { categoryid: number; tenantid?: number }) {
  return useQuery({
    queryKey: fiestaKeys.productSubcategories(opts),
    queryFn: () => getProductSubcategories(opts),
    enabled: Boolean(opts.categoryid),
  });
}

export function useFiestaGetStockRequests(opts: {
  tenantid: number;
  locationid?: number;
  status?: string;
  date?: string;
  pageno?: number;
  pagesize?: number;
}) {
  return useQuery({
    queryKey: fiestaKeys.stockRequests(opts),
    queryFn: () => getStockRequests(opts),
    // Omitting locationid is a deliberate tenant-wide query (the admin console
    // needs every outlet's requests). But passing an EMPTY one is not: the
    // backend treats locationid=0 as "no filter" and returns every outlet, so a
    // store user whose outlet hasn't resolved yet would see other stores'
    // requests. Wait for a real id in that case.
    enabled: Boolean(opts.tenantid) && (opts.locationid === undefined || Boolean(opts.locationid)),
  });
}

export function useFiestaCreateStockRequest() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (input: StockRequestInput) => createStockRequest(input),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['fiesta', 'stockRequests'] });
    },
  });
}

export function useFiestaUpdateStockRequest() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (input: UpdateStockRequestInput) => updateStockRequest(input),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['fiesta', 'stockRequests'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'stockStatement'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'productStocks'] });
    },
  });
}

// ── Users ─────────────────────────────────────────────────────────────────────
export function useFiestaUsers(opts: {
  tenantid: number;
  roleid?: number;
  keyword?: string;
  pageno?: number;
  pagesize?: number;
}) {
  return useQuery({
    queryKey: fiestaKeys.users(opts),
    queryFn: () => getAllUsers(opts),
    enabled: Boolean(opts.tenantid),
  });
}

export function useFiestaUser(userid: number) {
  return useQuery({
    queryKey: fiestaKeys.user(userid),
    queryFn: () => getUserById(userid),
    enabled: Boolean(userid),
  });
}

/** Create a user, then refresh every users list on success. */
export function useFiestaCreateUser() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (input: CreateUserInput) => createUser(input),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['fiesta', 'users'] }),
  });
}

/** Update a user, then refresh every users list on success. */
export function useFiestaUpdateUser() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (input: Parameters<typeof updateUser>[0]) => updateUser(input),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['fiesta', 'users'] }),
  });
}

/** Create a new tenant and admin user, then refresh tenants list on success. */
export function useFiestaCreateTenant() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (input: CreateTenantInput) => createTenantUser(input),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['fiesta', 'allTenants'] }),
  });
}

/** Create a new tenant location, then refresh tenant locations list on success. */
export function useFiestaCreateLocation() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (input: CreateTenantLocationInput) => createTenantLocation(input),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['fiesta', 'tenantLocations'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'locationSummary'] });
    },
  });
}

/** Update a tenant location, then refresh tenant locations list on success. */
export function useFiestaUpdateLocation() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (input: UpdateTenantLocationInput) => updateTenantLocation(input),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['fiesta', 'tenantLocations'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'locationSummary'] });
    },
  });
}


// ── Auth ──────────────────────────────────────────────────────────────────────
/**
 * Verify login credentials against the Fiesta web-login endpoint. A mutation
 * (not a query) since it's a POST with side effects; the form drives it via
 * `mutate`/`mutateAsync` and reads `isPending`/`error` for loading + error UI.
 *
 * Both network calls go through React Query: the login POST is the mutation,
 * and the role-resolution fallback (when the login response omits the role) is
 * fetched via the query client — so it shares the Users-panel cache.
 */
export function useLogin() {
  const qc = useQueryClient();
  return useMutation<AuthUser, Error, { email: string; password: string }>({
    mutationFn: async ({ email, password }) => {
      const result = await loginRequest(email, password);
      let row = result.row;
      const resolvedTenantId = (row?.tenantid as number) || FIESTA_TENANT_ID;

      // The login response didn't carry a role OR it lacks location mapping — resolve it 
      // from the tenant user list through the query cache (deduped with useFiestaUsers).
      if (!result.hasRole || (row && !row.locationid && !row.applocationid)) {
        const params = { tenantid: resolvedTenantId, keyword: result.email, pagesize: 50 };
        const users = await qc.fetchQuery({
          queryKey: fiestaKeys.users(params),
          queryFn: () => getAllUsers(params),
        });
        const match = matchTenantUser(users, result.email);
        if (match) row = { ...(row ?? {}), ...match };
      }

      return buildAuthUser(row, result.email);
    },
  });
}

/**
 * Set (or reset) a user's login password — used by the login flow's
 * create-password step (see PasswordSetupRequiredError in ./auth) for a
 * first-time tenant admin or store login.
 */
export function useSetPassword() {
  return useMutation({
    mutationFn: (input: { userid: number; password: string }) =>
      setUserPassword(input.userid, input.password),
  });
}

export function useFiestaDeleteLocation() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (input: { tenantid: number; locationid: number }) => deleteLocation(input),
    onSuccess: (_, variables) => {
      qc.invalidateQueries({ queryKey: fiestaKeys.tenantLocations(variables.tenantid) });
      qc.invalidateQueries({ queryKey: fiestaKeys.locationSummary(variables.tenantid) });
    },
  });
}

export { FIESTA_TENANT_ID, FIESTA_APPLOCATION_ID, FIESTA_PRIMARY_LOCATION_ID };

// ── Global Catalogue ────────────────────────────────────────────────────────
export function useFiestaGlobalBrands() {
  return useQuery({
    queryKey: fiestaKeys.globalBrands(),
    queryFn: () => getGlobalBrands(),
  });
}

export function useFiestaGlobalCategories(brand: string) {
  return useQuery({
    queryKey: fiestaKeys.globalCategories(brand),
    queryFn: () => getGlobalCategories({ brand }),
    enabled: Boolean(brand),
  });
}

export function useFiestaGlobalProducts(opts: {
  brand: string;
  category?: string;
  keyword?: string;
  pageno?: number;
  pagesize?: number;
}) {
  return useQuery({
    queryKey: fiestaKeys.globalProducts(opts),
    queryFn: () => getGlobalProducts(opts),
    enabled: Boolean(opts.brand),
  });
}

// ── Store Catalogue (Product Locations) ─────────────────────────────────────
export function useFiestaCreateProductLocation() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (input: CreateProductLocationInput) => createProductLocation(input),
    onSuccess: (_, variables) => {
      qc.invalidateQueries({ queryKey: ['catalogue', 'imported'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'productLocations'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'productStocks'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'stockStatement'] });
    },
    onError: (error: any) => {
      alert(`Error adding product: ${error.message}`);
    },
  });
}

/**
 * Publish/reprice one product across many outlets in a single request. Used by
 * the admin catalogue, where a price the admin sets is meant to apply to every
 * store under the tenant — not just whichever outlet happened to load first.
 */
export function useFiestaCreateProductLocations() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (inputs: Array<{ tenantid: number; locationid: number; productid: number; price?: number; status?: string }>) =>
      createProductLocations(inputs),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['catalogue', 'imported'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'productLocations'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'productStocks'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'stockStatement'] });
    },
  });
}

export function useFiestaDeleteProductLocation() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (input: DeleteProductLocationInput) => deleteProductLocation(input),
    onSuccess: (_, variables) => {
      qc.invalidateQueries({ queryKey: ['catalogue', 'imported'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'productLocations'] });
      qc.invalidateQueries({ queryKey: ['fiesta', 'productStocks'] });
    },
  });
}
