import { useEffect, useState } from "react";
import { supabase } from "@/lib/supabase";
import { getCurrentTableroCdcAccess } from "@/lib/accessControl";
import {
  buildSmartFilterOptionsFromProjects,
  hasSmartFilter,
  projectMatchesSmartFilters,
} from "@/lib/filterNormalization";
import type { ColorId, Status } from "@/data/lists";

export interface Project {
  id: string;
  nombre: string;
  color: ColorId;
  cliente: string;
  bu: string;
  cm: string; // autocompletado desde BU
  marca: string;
  solicitante: string;
  comentarios: string;
  briefLink: string;
  propuestaLinks: string[];
  afLinks: string[];
  status: Status | "";
  monto: number | null; // solo Marrero
  mes: string;
  anio: number;
  createdAt: number;
  extraData?: Record<string, unknown>;
}

export type ProjectActivityType = "created" | "updated" | "note";

export interface ProjectActivity {
  id: string;
  projectId: string;
  type: ProjectActivityType;
  title: string;
  description: string;
  actorName: string;
  actorEmail: string;
  createdAt: number;
  extraData?: Record<string, unknown>;
}

export interface ProjectTimeEntryInput {
  country: string;
  taskName: string;
  durationMinutes: number;
  workDate: string;
  notes?: string;
}

export interface ProjectTimeEntry extends ProjectTimeEntryInput {
  id: string;
  projectId: string;
  actorName: string;
  actorEmail: string;
  createdAt: number;
  updatedAt: number;
}

export type ProjectPricingSource = "tariff" | "manual";

export interface ProjectPricingItemInput {
  id?: string;
  source: ProjectPricingSource;
  category: string;
  serviceName: string;
  workType?: string;
  complexityLevel?: string;
  referenceLabel?: string;
  referenceMin?: number | null;
  referenceMax?: number | null;
  amount: number;
  description?: string;
  tariffSectionId?: string;
  tariffCatalogId?: string;
}

export interface ProjectPricingItem extends ProjectPricingItemInput {
  id: string;
  projectId: string;
  createdAt: number;
}

export type SheetSyncStatus = "synced" | "skipped" | "failed" | "timeout";

export type SaveProjectResult = {
  projectId: string;
  sheetSyncStatus: SheetSyncStatus;
  sheetSyncMessage: string;
};

export type ProjectTab = "todos" | "abiertos" | "cerrados";

export type ProjectFilterOptions = {
  countries: string[];
  brands: string[];
  clients: string[];
  cms: string[];
};

export type ProjectPricingSummary = {
  totalAmount: number;
  projectCount: number;
  loadedAt: number | null;
};

export type ProjectQueryParams = {
  tab: ProjectTab;
  search: string;
  page: number;
  pageSize: number;
  country?: string;
  brand?: string;
  client?: string;
  cm?: string;
};

type ProjectLoadResult = {
  projects: Project[];
  totalProjects: number;
  filterOptions: ProjectFilterOptions;
};

type DbProjectLink = {
  id?: string;
  link_type: string | null;
  url: string | null;
  label?: string | null;
};

type DbProject = {
  id: string;
  title: string | null;
  client: string | null;
  brand: string | null;
  country: string | null;
  requested_by: string | null;
  country_manager: string | null;
  description: string | null;
  status: string | null;
  internal_amount?: number | string | null;
  brief_link: string | null;
  created_at: string | null;
  extra_data: Record<string, unknown> | null;
  project_links?: DbProjectLink[] | null;
};

type DbProjectActivity = {
  id: string;
  project_id: string;
  activity_type: string | null;
  title: string | null;
  description: string | null;
  actor_name: string | null;
  actor_email: string | null;
  created_at: string | null;
};

type DbProjectTimeEntry = {
  id: string;
  project_id: string;
  country: string | null;
  task_name: string | null;
  duration_minutes: number | string | null;
  work_date: string | null;
  notes: string | null;
  created_by_name: string | null;
  created_by_email: string | null;
  created_at: string | null;
  updated_at: string | null;
};

type DbProjectPricingItem = {
  id: string;
  project_id: string;
  source: string | null;
  category: string | null;
  service_name: string | null;
  work_type: string | null;
  complexity_level: string | null;
  reference_label: string | null;
  reference_min: number | string | null;
  reference_max: number | string | null;
  amount: number | string | null;
  description: string | null;
  tariff_section_id?: string | null;
  tariff_catalog_id?: string | null;
  created_at: string | null;
};

type RpcProjectsResponse = {
  projects?: DbProject[] | null;
  total_count?: number | string | null;
  filter_options?: Partial<ProjectFilterOptions> | null;
};

type RpcPricingSummaryResponse = {
  total_amount?: number | string | null;
  project_count?: number | string | null;
};

type DirectProjectQuery = {
  or: (filters: string) => DirectProjectQuery;
  not: (column: string, operator: string, value: unknown) => DirectProjectQuery;
  neq: (column: string, value: unknown) => DirectProjectQuery;
  range: (
    from: number,
    to: number,
  ) => Promise<{ data: unknown[] | null; error: { message: string } | null; count: number | null }>;
};

type DirectProjectSummaryRow = {
  id?: string | null;
  internal_amount?: number | string | null;
};

const DEFAULT_COLOR: ColorId = "blue";
const OPEN_STATUS = "Activo";
const DEFAULT_PROJECT_PARAMS: ProjectQueryParams = {
  tab: "todos",
  search: "",
  page: 1,
  pageSize: 24,
  country: "",
  brand: "",
  client: "",
  cm: "",
};

let listeners: Array<() => void> = [];
let cache: Project[] = [];
let totalProjects = 0;
let filterOptions: ProjectFilterOptions = { countries: [], brands: [], clients: [], cms: [] };
let loading = false;
let initialized = false;
let errorMessage: string | null = null;
let projectsRealtimeChannel: ReturnType<typeof supabase.channel> | null = null;
let realtimeRefreshTimeout: ReturnType<typeof setTimeout> | null = null;
let activeHookInstances = 0;
let currentParams: ProjectQueryParams = { ...DEFAULT_PROJECT_PARAMS };
let writeInProgress = 0;
let loadRequestSeq = 0;

function notify() {
  listeners.forEach((listener) => listener());
}

function asString(value: unknown, fallback = "") {
  return typeof value === "string" ? value : fallback;
}

function asNumber(value: unknown, fallback: number) {
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
}

function asYear(value: unknown, fallback: number) {
  if (typeof value === "number" && Number.isFinite(value)) return value;

  if (typeof value === "string") {
    const parsed = Number(value.trim());
    if (Number.isFinite(parsed)) return parsed;
  }

  return fallback;
}

function getDisplayMonth(extra: Record<string, unknown>, fallbackDate: Date) {
  return (
    asString(extra.legacy_month, "") ||
    asString(extra.mes, "") ||
    fallbackDate.toLocaleString("es-DO", { month: "long" })
  );
}

function getDisplayYear(extra: Record<string, unknown>, fallbackDate: Date) {
  return asYear(extra.legacy_year, asNumber(extra.anio, fallbackDate.getFullYear()));
}

function toUiStatus(status: string | null | undefined): Status | "" {
  // En base de datos usamos "Activo" para proyectos abiertos.
  // En la UI histórica, proyecto abierto = status vacío.
  if (!status || status === OPEN_STATUS) return "";
  return status as Status;
}

function toDbStatus(status: Status | "" | undefined | null): string {
  return status && status.trim() ? status : OPEN_STATUS;
}

function normalizeProjectParams(params?: Partial<ProjectQueryParams>): ProjectQueryParams {
  return {
    tab: params?.tab ?? currentParams.tab ?? DEFAULT_PROJECT_PARAMS.tab,
    search: String(params?.search ?? currentParams.search ?? DEFAULT_PROJECT_PARAMS.search).trim(),
    page: Math.max(
      1,
      Math.floor(Number(params?.page ?? currentParams.page ?? DEFAULT_PROJECT_PARAMS.page)),
    ),
    pageSize: Math.max(
      1,
      Math.min(
        100,
        Math.floor(
          Number(params?.pageSize ?? currentParams.pageSize ?? DEFAULT_PROJECT_PARAMS.pageSize),
        ),
      ),
    ),
    country: String(
      params?.country ?? currentParams.country ?? DEFAULT_PROJECT_PARAMS.country,
    ).trim(),
    brand: String(params?.brand ?? currentParams.brand ?? DEFAULT_PROJECT_PARAMS.brand).trim(),
    client: String(params?.client ?? currentParams.client ?? DEFAULT_PROJECT_PARAMS.client).trim(),
    cm: String(params?.cm ?? currentParams.cm ?? DEFAULT_PROJECT_PARAMS.cm).trim(),
  };
}

function paramsEqual(a: ProjectQueryParams, b: ProjectQueryParams) {
  return (
    a.tab === b.tab &&
    a.search === b.search &&
    a.page === b.page &&
    a.pageSize === b.pageSize &&
    (a.country || "") === (b.country || "") &&
    (a.brand || "") === (b.brand || "") &&
    (a.client || "") === (b.client || "") &&
    (a.cm || "") === (b.cm || "")
  );
}

function mapDbProject(row: DbProject): Project {
  const extra = row.extra_data || {};
  const links = row.project_links || [];
  const propuestaLinks = links
    .filter((link) => link.link_type === "proposal")
    .map((link) => link.url || "")
    .filter(Boolean);
  const afLinks = links
    .filter((link) => link.link_type === "final_art")
    .map((link) => link.url || "")
    .filter(Boolean);

  const createdAt = row.created_at ? new Date(row.created_at).getTime() : Date.now();
  const date = Number.isFinite(createdAt) ? new Date(createdAt) : new Date();

  return {
    id: row.id,
    nombre: row.title || "",
    color: asString(extra.color, DEFAULT_COLOR) as ColorId,
    cliente: row.client || "",
    bu: row.country || "",
    cm: row.country_manager || "",
    marca: row.brand || "",
    solicitante: row.requested_by || "",
    comentarios: row.description || "",
    briefLink: row.brief_link || "",
    propuestaLinks,
    afLinks,
    status: toUiStatus(row.status),
    monto: row.internal_amount == null ? null : Number(row.internal_amount),
    mes: getDisplayMonth(extra, date),
    anio: getDisplayYear(extra, date),
    createdAt: Number.isFinite(createdAt) ? createdAt : Date.now(),
    extraData: extra,
  };
}

function mapDbProjectActivity(row: DbProjectActivity): ProjectActivity {
  const createdAt = row.created_at ? new Date(row.created_at).getTime() : Date.now();

  return {
    id: row.id,
    projectId: row.project_id,
    type: (row.activity_type || "note") as ProjectActivityType,
    title: row.title || "Actividad registrada",
    description: row.description || "",
    actorName: row.actor_name || row.actor_email || "Usuario CDC",
    actorEmail: row.actor_email || "",
    createdAt: Number.isFinite(createdAt) ? createdAt : Date.now(),
  };
}

function mapDbProjectTimeEntry(row: DbProjectTimeEntry): ProjectTimeEntry {
  const createdAt = row.created_at ? new Date(row.created_at).getTime() : Date.now();
  const updatedAt = row.updated_at ? new Date(row.updated_at).getTime() : createdAt;
  const durationMinutes = Number(row.duration_minutes ?? 0);

  return {
    id: row.id,
    projectId: row.project_id,
    country: row.country || "",
    taskName: row.task_name || "",
    durationMinutes: Number.isFinite(durationMinutes) ? Math.max(0, durationMinutes) : 0,
    workDate: row.work_date || new Date().toISOString().slice(0, 10),
    notes: row.notes || "",
    actorName: row.created_by_name || row.created_by_email || "Usuario CDC",
    actorEmail: row.created_by_email || "",
    createdAt: Number.isFinite(createdAt) ? createdAt : Date.now(),
    updatedAt: Number.isFinite(updatedAt) ? updatedAt : Date.now(),
  };
}

function mapDbProjectPricingItem(row: DbProjectPricingItem): ProjectPricingItem {
  const createdAt = row.created_at ? new Date(row.created_at).getTime() : Date.now();
  const amount = Number(row.amount ?? 0);
  const referenceMin = row.reference_min == null ? null : Number(row.reference_min);
  const referenceMax = row.reference_max == null ? null : Number(row.reference_max);

  return {
    id: row.id,
    projectId: row.project_id,
    source: row.source === "manual" ? "manual" : "tariff",
    category: row.category || "",
    serviceName: row.service_name || "",
    workType: row.work_type || "",
    complexityLevel: row.complexity_level || "",
    referenceLabel: row.reference_label || "",
    referenceMin: Number.isFinite(referenceMin) ? referenceMin : null,
    referenceMax: Number.isFinite(referenceMax) ? referenceMax : null,
    amount: Number.isFinite(amount) ? amount : 0,
    description: row.description || "",
    tariffSectionId: row.tariff_section_id || "",
    tariffCatalogId: row.tariff_catalog_id || "",
    createdAt: Number.isFinite(createdAt) ? createdAt : Date.now(),
  };
}

function getUserDisplayName(user: Awaited<ReturnType<typeof getCurrentUser>>) {
  const metadata = user.user_metadata || {};

  return (
    metadata.full_name ||
    metadata.name ||
    metadata.display_name ||
    user.email?.split("@")[0] ||
    "Usuario CDC"
  );
}

function publicProjectStatus(value: Status | "" | null | undefined) {
  return value && value.trim() ? value : OPEN_STATUS;
}

function listCountLabel(count: number, singular: string, plural: string) {
  return `${count} ${count === 1 ? singular : plural}`;
}

function normalizeComparable(value: unknown) {
  if (Array.isArray(value))
    return value
      .map((item) => String(item || "").trim())
      .filter(Boolean)
      .join(" | ");
  return String(value ?? "").trim();
}

function appendChange(
  changes: string[],
  label: string,
  beforeValue: unknown,
  afterValue: unknown,
  options?: { privateValue?: boolean },
) {
  const before = normalizeComparable(beforeValue);
  const after = normalizeComparable(afterValue);

  if (before === after) return;

  if (options?.privateValue) {
    changes.push(`${label} actualizado.`);
    return;
  }

  changes.push(`${label}: ${before || "—"} → ${after || "—"}`);
}

function buildProjectChangeSummary(before: Project, after: Project, includePrivateFields: boolean) {
  const changes: string[] = [];

  appendChange(changes, "Nombre", before.nombre, after.nombre);
  appendChange(changes, "Cliente", before.cliente, after.cliente);
  appendChange(changes, "Marca", before.marca, after.marca);
  appendChange(changes, "País/BU", before.bu, after.bu);
  appendChange(changes, "CM", before.cm, after.cm);
  appendChange(changes, "Solicitado por", before.solicitante, after.solicitante);
  appendChange(
    changes,
    "Estatus",
    publicProjectStatus(before.status),
    publicProjectStatus(after.status),
  );

  if (before.color !== after.color) {
    changes.push("Etiqueta de color actualizada.");
  }

  if ((before.comentarios || "").trim() !== (after.comentarios || "").trim()) {
    changes.push("Comentarios actualizados.");
  }

  if ((before.briefLink || "").trim() !== (after.briefLink || "").trim()) {
    changes.push("Link del brief actualizado.");
  }

  if (before.propuestaLinks.join("|") !== after.propuestaLinks.join("|")) {
    changes.push(
      `Links de propuestas: ${listCountLabel(before.propuestaLinks.length, "link", "links")} → ${listCountLabel(after.propuestaLinks.length, "link", "links")}.`,
    );
  }

  if (before.afLinks.join("|") !== after.afLinks.join("|")) {
    changes.push(
      `Links de artes finales: ${listCountLabel(before.afLinks.length, "link", "links")} → ${listCountLabel(after.afLinks.length, "link", "links")}.`,
    );
  }

  if (includePrivateFields && Number(before.monto ?? 0) !== Number(after.monto ?? 0)) {
    appendChange(changes, "Monto interno", before.monto, after.monto, { privateValue: true });
  }

  return changes;
}

async function getCurrentUser() {
  const { data, error } = await supabase.auth.getUser();

  if (error || !data.user) {
    throw new Error("No hay una sesión activa de Supabase.");
  }

  return data.user;
}

async function getCurrentUserId(): Promise<string> {
  const user = await getCurrentUser();
  return user.id;
}

async function currentUserIsGerardo(): Promise<boolean> {
  const access = await getCurrentTableroCdcAccess();
  return access.role === "director_creativo";
}

async function currentUserCanManageInternalPricing(): Promise<boolean> {
  const access = await getCurrentTableroCdcAccess();
  return access.canManageInternalPricing;
}

async function currentUserCanDeleteProjects(): Promise<boolean> {
  const access = await getCurrentTableroCdcAccess();
  return access.canDeleteProjects;
}

function toProjectRow(project: Omit<Project, "id" | "createdAt"> | Partial<Project>) {
  const existingExtra =
    project.extraData && typeof project.extraData === "object" && !Array.isArray(project.extraData)
      ? project.extraData
      : {};

  return {
    title: project.nombre,
    client: project.cliente,
    brand: project.marca,
    country: project.bu,
    requested_by: project.solicitante,
    country_manager: project.cm,
    description: project.comentarios,
    status: toDbStatus(project.status),
    internal_amount: project.monto ?? null,
    brief_link: project.briefLink,
    extra_data: {
      ...existingExtra,
      color: project.color ?? asString(existingExtra.color, DEFAULT_COLOR),
      mes: project.mes,
      anio: project.anio,
    },
  };
}

function toLinkRows(projectId: string, project: Pick<Project, "propuestaLinks" | "afLinks">) {
  const proposalLinks = project.propuestaLinks
    .map((url) => url.trim())
    .filter(Boolean)
    .map((url, index) => ({
      project_id: projectId,
      link_type: "proposal",
      url,
      label: `Propuesta ${index + 1}`,
    }));

  const finalArtLinks = project.afLinks
    .map((url) => url.trim())
    .filter(Boolean)
    .map((url, index) => ({
      project_id: projectId,
      link_type: "final_art",
      url,
      label: `AF ${index + 1}`,
    }));

  return [...proposalLinks, ...finalArtLinks];
}

function setCache(next: Project[]) {
  cache = next;
  notify();
}

function cleanFilterOptionArray(values: unknown): string[] {
  if (!Array.isArray(values)) return [];

  return Array.from(
    new Set(values.map((value) => String(value ?? "").trim()).filter(Boolean)),
  ).sort((a, b) => a.localeCompare(b, "es", { sensitivity: "base" }));
}

function buildFilterOptionsFromProjects(projects: Project[]): ProjectFilterOptions {
  return {
    countries: cleanFilterOptionArray(projects.map((project) => project.bu)),
    brands: cleanFilterOptionArray(projects.map((project) => project.marca)),
    clients: cleanFilterOptionArray(projects.map((project) => project.cliente)),
    cms: cleanFilterOptionArray(projects.map((project) => project.cm)),
  };
}

function setError(message: string | null) {
  errorMessage = message;
  notify();
}

function replaceProjectInCache(project: Project) {
  const exists = cache.some((current) => current.id === project.id);

  if (!exists) return;

  setCache(cache.map((current) => (current.id === project.id ? project : current)));
}

function removeProjectFromCache(projectId: string) {
  if (!cache.some((project) => project.id === projectId)) return;

  setCache(cache.filter((project) => project.id !== projectId));
  totalProjects = Math.max(0, totalProjects - 1);
  notify();
}

function getSyncWebhookUrl() {
  return import.meta.env.VITE_WEBHOOK_URL?.trim() || "";
}

async function syncProjectToSheet(
  projectId: string,
  action: "create" | "update" | "delete",
): Promise<Omit<SaveProjectResult, "projectId">> {
  const webhookUrl = getSyncWebhookUrl();

  if (!webhookUrl) {
    console.info("No se configuró VITE_WEBHOOK_URL; se omite sincronización con Google Sheet.");
    return {
      sheetSyncStatus: "skipped",
      sheetSyncMessage:
        action === "delete"
          ? "No hay webhook configurado para eliminar el proyecto del Google Sheet."
          : "Proyecto guardado. No hay webhook configurado para sincronizar el Sheet.",
    };
  }

  const controller = new AbortController();
  const timeout = window.setTimeout(() => controller.abort(), 15000);

  try {
    const response = await fetch(webhookUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        project_id: projectId,
        action,
        source: "tablero-cdc",
        sent_at: new Date().toISOString(),
      }),
      signal: controller.signal,
    });

    if (!response.ok) {
      const errorText = await response.text().catch(() => "");
      const message = errorText || `${response.status} ${response.statusText}`.trim();

      console.warn("El proyecto se guardó en Supabase, pero n8n no respondió correctamente.", {
        status: response.status,
        statusText: response.statusText,
        errorText,
      });

      return {
        sheetSyncStatus: "failed",
        sheetSyncMessage:
          action === "delete"
            ? `No se pudo eliminar el proyecto del Google Sheet: ${message}`
            : `Proyecto guardado, pero el Sheet no se pudo sincronizar: ${message}`,
      };
    }

    return {
      sheetSyncStatus: "synced",
      sheetSyncMessage:
        action === "delete"
          ? "Proyecto eliminado del Google Sheet."
          : "Proyecto guardado y sincronizado con el Google Sheet.",
    };
  } catch (error) {
    const isTimeout = error instanceof DOMException && error.name === "AbortError";

    console.warn(
      "El proyecto se guardó en Supabase, pero no se pudo sincronizar con Google Sheet.",
      error,
    );

    return {
      sheetSyncStatus: isTimeout ? "timeout" : "failed",
      sheetSyncMessage: isTimeout
        ? action === "delete"
          ? "n8n tardó demasiado en responder. No se eliminó el proyecto para evitar inconsistencias."
          : "Proyecto guardado, pero n8n tardó demasiado en responder. Revisa el workflow si el Sheet no se actualizó."
        : action === "delete"
          ? "No se pudo eliminar el proyecto del Google Sheet. No se eliminó el proyecto para evitar inconsistencias."
          : "Proyecto guardado, pero no se pudo sincronizar con Google Sheet.",
    };
  } finally {
    window.clearTimeout(timeout);
  }
}

function scheduleRealtimeRefresh() {
  if (realtimeRefreshTimeout) {
    clearTimeout(realtimeRefreshTimeout);
  }

  realtimeRefreshTimeout = setTimeout(() => {
    realtimeRefreshTimeout = null;

    if (!loading && writeInProgress === 0) {
      void loadProjects(currentParams);
    }
  }, 350);
}

function startProjectsRealtime() {
  if (projectsRealtimeChannel) return;

  projectsRealtimeChannel = supabase
    .channel("tablero-cdc-projects")
    .on("postgres_changes", { event: "*", schema: "public", table: "tablero_cdc_projects" }, () =>
      scheduleRealtimeRefresh(),
    )
    .on(
      "postgres_changes",
      { event: "*", schema: "public", table: "tablero_cdc_project_links" },
      () => scheduleRealtimeRefresh(),
    )
    .subscribe((status) => {
      if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
        console.warn("Supabase Realtime no pudo suscribirse a proyectos:", status);
      }
    });
}

function stopProjectsRealtime() {
  if (realtimeRefreshTimeout) {
    clearTimeout(realtimeRefreshTimeout);
    realtimeRefreshTimeout = null;
  }

  if (!projectsRealtimeChannel) return;

  void supabase.removeChannel(projectsRealtimeChannel);
  projectsRealtimeChannel = null;
}

async function replaceProjectLinks(
  projectId: string,
  project: Pick<Project, "propuestaLinks" | "afLinks">,
) {
  const linkRows = toLinkRows(projectId, project);

  try {
    const { error: rpcError } = await supabase.rpc("tablero_cdc_replace_project_links", {
      p_project_id: projectId,
      p_links: linkRows.map(({ link_type, url, label }) => ({ link_type, url, label })),
    });

    if (rpcError) throw rpcError;
    return;
  } catch (error) {
    if (!isMissingReplaceLinksRpc(error)) throw error;

    console.warn(
      "RPC tablero_cdc_replace_project_links no existe todavía. Usando reemplazo de links por delete+insert.",
      error,
    );
  }

  const { error: deleteError } = await supabase
    .from("tablero_cdc_project_links")
    .delete()
    .eq("project_id", projectId);

  if (deleteError) throw deleteError;

  if (linkRows.length === 0) return;

  const { error: insertError } = await supabase.from("tablero_cdc_project_links").insert(linkRows);

  if (insertError) throw insertError;
}

function isMissingTariffTraceColumns(error: { message?: string } | null | undefined) {
  const message = String(error?.message || "");
  return (
    message.includes("tariff_section_id") ||
    message.includes("tariff_catalog_id") ||
    message.includes("schema cache")
  );
}

async function replaceProjectPricingItems(projectId: string, items?: ProjectPricingItemInput[]) {
  if (!Array.isArray(items)) return;

  const { error: deleteError } = await supabase
    .from("tablero_cdc_project_pricing_items")
    .delete()
    .eq("project_id", projectId);

  if (deleteError) throw deleteError;

  const cleanItems = items
    .map((item, index) => ({ item, index }))
    .filter(({ item }) => Number.isFinite(Number(item.amount)) && Number(item.amount) > 0)
    .map(({ item, index }) => ({
      project_id: projectId,
      source: item.source,
      category: item.category || "",
      service_name: item.serviceName || "Costo manual",
      work_type: item.workType || "",
      complexity_level: item.complexityLevel || "",
      reference_label: item.referenceLabel || "",
      reference_min: item.referenceMin ?? null,
      reference_max: item.referenceMax ?? null,
      amount: Number(item.amount),
      description: item.description || "",
      tariff_section_id: item.tariffSectionId || null,
      tariff_catalog_id: item.tariffCatalogId || null,
      sort_order: index,
    }));

  if (cleanItems.length === 0) return;

  const insertResult = await supabase
    .from("tablero_cdc_project_pricing_items")
    .insert(cleanItems);

  if (!insertResult.error) return;
  if (!isMissingTariffTraceColumns(insertResult.error)) throw insertResult.error;

  const legacyItems = cleanItems.map(({ tariff_section_id: _section, tariff_catalog_id: _catalog, ...item }) => item);
  const legacyInsert = await supabase
    .from("tablero_cdc_project_pricing_items")
    .insert(legacyItems);

  if (legacyInsert.error) throw legacyInsert.error;
}

export async function loadProjectPricingItems(projectId: string): Promise<ProjectPricingItem[]> {
  const query = supabase
    .from("tablero_cdc_project_pricing_items")
    .select(
      "id, project_id, source, category, service_name, work_type, complexity_level, reference_label, reference_min, reference_max, amount, description, tariff_section_id, tariff_catalog_id, created_at",
    )
    .eq("project_id", projectId)
    .order("sort_order", { ascending: true })
    .order("created_at", { ascending: true });

  const result = await query;

  if (!result.error) {
    return ((result.data || []) as DbProjectPricingItem[]).map(mapDbProjectPricingItem);
  }

  if (!isMissingTariffTraceColumns(result.error)) throw result.error;

  const legacyResult = await supabase
    .from("tablero_cdc_project_pricing_items")
    .select(
      "id, project_id, source, category, service_name, work_type, complexity_level, reference_label, reference_min, reference_max, amount, description, created_at",
    )
    .eq("project_id", projectId)
    .order("sort_order", { ascending: true })
    .order("created_at", { ascending: true });

  if (legacyResult.error) throw legacyResult.error;

  return ((legacyResult.data || []) as DbProjectPricingItem[]).map(mapDbProjectPricingItem);
}

export async function loadProjectActivities(
  projectId: string,
  limit = 20,
  offset = 0,
): Promise<ProjectActivity[]> {
  const safeLimit = Math.max(1, Math.min(50, Math.floor(limit)));
  const safeOffset = Math.max(0, Math.floor(offset));

  const { data, error } = await supabase
    .from("tablero_cdc_project_activity")
    .select(
      "id, project_id, activity_type, title, description, actor_name, actor_email, created_at",
    )
    .eq("project_id", projectId)
    .order("created_at", { ascending: false })
    .range(safeOffset, safeOffset + safeLimit - 1);

  if (error) throw error;

  return ((data || []) as DbProjectActivity[]).map(mapDbProjectActivity);
}

const PROJECT_TIME_PAGE_SIZE = 500;

export async function loadProjectTimeEntries(projectId: string): Promise<ProjectTimeEntry[]> {
  const cleanProjectId = String(projectId || "").trim();

  if (!cleanProjectId) return [];

  const allRows: DbProjectTimeEntry[] = [];
  let offset = 0;

  while (true) {
    const { data, error } = await supabase
      .from("tablero_cdc_project_time_entries")
      .select(
        "id, project_id, country, task_name, duration_minutes, work_date, notes, created_by_name, created_by_email, created_at, updated_at",
      )
      .eq("project_id", cleanProjectId)
      .order("work_date", { ascending: false })
      .order("created_at", { ascending: false })
      .range(offset, offset + PROJECT_TIME_PAGE_SIZE - 1);

    if (error) throw error;

    const rows = (data || []) as DbProjectTimeEntry[];
    allRows.push(...rows);

    if (rows.length < PROJECT_TIME_PAGE_SIZE) break;
    offset += PROJECT_TIME_PAGE_SIZE;
  }

  return allRows.map(mapDbProjectTimeEntry);
}

export async function addProjectTimeEntry(
  projectId: string,
  input: ProjectTimeEntryInput,
): Promise<ProjectTimeEntry> {
  const cleanProjectId = String(projectId || "").trim();
  const country = String(input.country || "").trim();
  const taskName = String(input.taskName || "").trim();
  const durationMinutes = Math.round(Number(input.durationMinutes || 0));
  const workDate = String(input.workDate || "").trim();
  const notes = String(input.notes || "").trim();

  if (!cleanProjectId) throw new Error("No se encontró el proyecto.");
  if (!country) throw new Error("Selecciona el país.");
  if (!taskName) throw new Error("Escribe la tarea realizada.");
  if (!Number.isFinite(durationMinutes) || durationMinutes <= 0) {
    throw new Error("La duración debe ser mayor que 0 minutos.");
  }
  if (!/^\d{4}-\d{2}-\d{2}$/.test(workDate)) {
    throw new Error("Selecciona una fecha válida.");
  }

  const user = await getCurrentUser();
  const actorName = getUserDisplayName(user);
  const actorEmail = user.email || "";

  const { data, error } = await supabase
    .from("tablero_cdc_project_time_entries")
    .insert({
      project_id: cleanProjectId,
      country,
      task_name: taskName,
      duration_minutes: durationMinutes,
      work_date: workDate,
      notes,
      created_by: user.id,
      created_by_email: actorEmail || null,
      created_by_name: actorName,
    })
    .select(
      "id, project_id, country, task_name, duration_minutes, work_date, notes, created_by_name, created_by_email, created_at, updated_at",
    )
    .single();

  if (error) throw error;

  return mapDbProjectTimeEntry(data as DbProjectTimeEntry);
}

export async function updateProjectTimeEntry(
  id: string,
  input: ProjectTimeEntryInput,
): Promise<ProjectTimeEntry> {
  const cleanId = String(id || "").trim();
  const country = String(input.country || "").trim();
  const taskName = String(input.taskName || "").trim();
  const durationMinutes = Math.round(Number(input.durationMinutes || 0));
  const workDate = String(input.workDate || "").trim();
  const notes = String(input.notes || "").trim();

  if (!cleanId) throw new Error("No se encontró el registro de tiempo.");
  if (!country) throw new Error("Selecciona el país.");
  if (!taskName) throw new Error("Escribe la tarea realizada.");
  if (!Number.isFinite(durationMinutes) || durationMinutes <= 0) {
    throw new Error("La duración debe ser mayor que 0 minutos.");
  }
  if (!/^\d{4}-\d{2}-\d{2}$/.test(workDate)) {
    throw new Error("Selecciona una fecha válida.");
  }

  const { data, error } = await supabase
    .from("tablero_cdc_project_time_entries")
    .update({
      country,
      task_name: taskName,
      duration_minutes: durationMinutes,
      work_date: workDate,
      notes,
    })
    .eq("id", cleanId)
    .select(
      "id, project_id, country, task_name, duration_minutes, work_date, notes, created_by_name, created_by_email, created_at, updated_at",
    )
    .single();

  if (error) throw error;

  return mapDbProjectTimeEntry(data as DbProjectTimeEntry);
}

export async function deleteProjectTimeEntry(id: string): Promise<void> {
  const cleanId = String(id || "").trim();

  if (!cleanId) throw new Error("No se encontró el registro de tiempo.");

  const { error } = await supabase
    .from("tablero_cdc_project_time_entries")
    .delete()
    .eq("id", cleanId);

  if (error) throw error;
}

function formatCommentForSheet(
  description: string,
  actorName: string,
  actorEmail: string,
  date: Date,
) {
  const timestamp = new Intl.DateTimeFormat("es-DO", {
    day: "2-digit",
    month: "2-digit",
    year: "numeric",
    hour: "2-digit",
    minute: "2-digit",
  }).format(date);

  const actor = actorEmail ? `${actorName} (${actorEmail})` : actorName;

  return `[${timestamp}] ${actor}:\n${description}`;
}

async function appendActivityNoteToProjectComments(
  projectId: string,
  description: string,
  actorName: string,
  actorEmail: string,
  actorId: string,
  createdAt: Date,
) {
  const { data, error: readError } = await supabase
    .from("tablero_cdc_projects")
    .select("description")
    .eq("id", projectId)
    .single();

  if (readError) throw readError;

  const existingDescription = String(data?.description || "").trim();
  const noteForSheet = formatCommentForSheet(description, actorName, actorEmail, createdAt);
  const nextDescription = existingDescription
    ? `${existingDescription}\n\n${noteForSheet}`
    : noteForSheet;

  const { error: updateError } = await supabase
    .from("tablero_cdc_projects")
    .update({
      description: nextDescription,
      updated_by: actorId,
    })
    .eq("id", projectId);

  if (updateError) throw updateError;

  const current = cache.find((project) => project.id === projectId);

  if (current) {
    replaceProjectInCache({ ...current, comentarios: nextDescription });
  }

  return nextDescription;
}

export async function addProjectActivityNote(projectId: string, description: string) {
  const cleanDescription = description.trim();

  if (!cleanDescription) {
    throw new Error("Escribe una nota antes de agregarla al historial.");
  }

  const user = await getCurrentUser();
  const actorName = getUserDisplayName(user);
  const actorEmail = user.email || "";
  const createdAt = new Date();

  const { error: activityError } = await supabase.from("tablero_cdc_project_activity").insert({
    project_id: projectId,
    activity_type: "note",
    title: "Comentario agregado",
    description: cleanDescription,
    actor_id: user.id,
    actor_email: actorEmail || null,
    actor_name: actorName,
  });

  if (activityError) throw activityError;

  const comments = await appendActivityNoteToProjectComments(
    projectId,
    cleanDescription,
    actorName,
    actorEmail,
    user.id,
    createdAt,
  );
  const sheetSync = await syncProjectToSheet(projectId, "update");

  return {
    comments,
    ...sheetSync,
  };
}

async function logProjectActivity(
  projectId: string,
  type: ProjectActivityType,
  title: string,
  description = "",
  shouldThrow = false,
) {
  try {
    const user = await getCurrentUser();
    const actorName = getUserDisplayName(user);

    const { error } = await supabase.from("tablero_cdc_project_activity").insert({
      project_id: projectId,
      activity_type: type,
      title,
      description,
      actor_id: user.id,
      actor_email: user.email || null,
      actor_name: actorName,
    });

    if (error) throw error;
  } catch (error) {
    console.warn("No se pudo registrar la actividad del proyecto:", error);

    if (shouldThrow) {
      throw error instanceof Error
        ? error
        : new Error("No se pudo registrar la actividad del proyecto.");
    }
  }
}

function getSelectColumns(includeInternalAmount: boolean) {
  return `
        id,
        title,
        client,
        brand,
        country,
        requested_by,
        country_manager,
        description,
        status,
        ${includeInternalAmount ? "internal_amount," : ""}
        brief_link,
        created_at,
        extra_data,
        project_links:tablero_cdc_project_links (
          id,
          link_type,
          url,
          label
        )
      `;
}

function getErrorMessage(error: unknown) {
  if (error instanceof Error) return error.message;

  if (error && typeof error === "object" && "message" in error) {
    return String((error as { message?: unknown }).message || "");
  }

  return String(error || "");
}

function isMissingRpcFunction(error: unknown) {
  const message = getErrorMessage(error);
  return (
    message.includes("tablero_cdc_get_projects_paginated") ||
    message.includes("Could not find the function")
  );
}

function isMissingReplaceLinksRpc(error: unknown) {
  const message = getErrorMessage(error);
  return (
    message.includes("tablero_cdc_replace_project_links") ||
    message.includes("Could not find the function")
  );
}

function isMissingPricingSummaryRpc(error: unknown) {
  const message = getErrorMessage(error);
  return (
    message.includes("tablero_cdc_get_pricing_summary") ||
    message.includes("Could not find the function")
  );
}

function escapePostgrestFilterValue(value: string) {
  return value.replace(/[%_]/g, "\\$&").replace(/,/g, " ");
}

function applyDirectBaseFilters(query: DirectProjectQuery, params: ProjectQueryParams) {
  let nextQuery = query;

  if (params.tab === "abiertos") {
    nextQuery = nextQuery.or("status.is.null,status.eq.,status.eq.Activo");
  }

  if (params.tab === "cerrados") {
    nextQuery = nextQuery.not("status", "is", null).neq("status", "").neq("status", OPEN_STATUS);
  }

  const search = params.search.trim();

  if (search) {
    const escapedSearch = escapePostgrestFilterValue(search);
    const pattern = `%${escapedSearch}%`;
    nextQuery = nextQuery.or(
      `title.ilike.${pattern},client.ilike.${pattern},brand.ilike.${pattern},country.ilike.${pattern},requested_by.ilike.${pattern},country_manager.ilike.${pattern},description.ilike.${pattern}`,
    );
  }

  return nextQuery;
}

function applyDirectFilters(query: DirectProjectQuery, params: ProjectQueryParams) {
  let nextQuery = applyDirectBaseFilters(query, params);

  if (params.country) {
    const pattern = `%${escapePostgrestFilterValue(params.country)}%`;
    nextQuery = nextQuery.or(`country.ilike.${pattern}`);
  }

  if (params.brand) {
    const pattern = `%${escapePostgrestFilterValue(params.brand)}%`;
    nextQuery = nextQuery.or(`brand.ilike.${pattern}`);
  }

  if (params.client) {
    const pattern = `%${escapePostgrestFilterValue(params.client)}%`;
    nextQuery = nextQuery.or(`client.ilike.${pattern}`);
  }

  if (params.cm) {
    const pattern = `%${escapePostgrestFilterValue(params.cm)}%`;
    nextQuery = nextQuery.or(`country_manager.ilike.${pattern}`);
  }

  return nextQuery;
}

async function loadProjectsDirect(
  params: ProjectQueryParams,
  includeInternalAmount: boolean,
): Promise<ProjectLoadResult> {
  const from = (params.page - 1) * params.pageSize;
  const to = from + params.pageSize - 1;

  let query = supabase
    .from("tablero_cdc_projects")
    .select(getSelectColumns(includeInternalAmount), { count: "exact" })
    .order("created_at", { ascending: false }) as unknown as DirectProjectQuery;

  query = applyDirectFilters(query, params);

  const { data, error, count } = await query.range(from, to);

  if (error) throw error;

  const projects = (data || []).map((row: unknown) => mapDbProject(row as DbProject));

  return {
    projects,
    totalProjects: count ?? 0,
    filterOptions: buildFilterOptionsFromProjects(projects),
  };
}

async function loadProjectsSmartDirect(
  params: ProjectQueryParams,
  includeInternalAmount: boolean,
): Promise<ProjectLoadResult> {
  const pageSize = 1000;
  let from = 0;
  let totalCount: number | null = null;
  const allProjects: Project[] = [];

  while (totalCount === null || from < totalCount) {
    let query = supabase
      .from("tablero_cdc_projects")
      .select(getSelectColumns(includeInternalAmount), { count: "exact" })
      .order("created_at", { ascending: false }) as unknown as DirectProjectQuery;

    query = applyDirectBaseFilters(query, params);

    const { data, error, count } = await query.range(from, from + pageSize - 1);

    if (error) throw error;

    if (totalCount === null) {
      totalCount = count ?? (data || []).length;
    }

    const rows = (data || []).map((row: unknown) => mapDbProject(row as DbProject));
    allProjects.push(...rows);

    if (rows.length < pageSize) break;
    from += pageSize;
  }

  const smartOptions = buildSmartFilterOptionsFromProjects(allProjects);
  const filteredProjects = allProjects.filter((project) =>
    projectMatchesSmartFilters(project, params, smartOptions),
  );
  const pageFrom = (params.page - 1) * params.pageSize;
  const pageTo = pageFrom + params.pageSize;

  return {
    projects: filteredProjects.slice(pageFrom, pageTo),
    totalProjects: filteredProjects.length,
    filterOptions: smartOptions,
  };
}

async function loadProjectsFromRpc(params: ProjectQueryParams): Promise<ProjectLoadResult> {
  const { data, error } = await supabase.rpc("tablero_cdc_get_projects_paginated", {
    p_tab: params.tab,
    p_search: params.search,
    p_page: params.page,
    p_page_size: params.pageSize,
    p_country: params.country || "",
    p_brand: params.brand || "",
    p_client: params.client || "",
    p_cm: params.cm || "",
  });

  if (error) throw error;

  const result = (data || {}) as RpcProjectsResponse;
  const rows = Array.isArray(result.projects) ? result.projects : [];

  return {
    totalProjects: Number(result.total_count || 0),
    projects: rows.map((row) => mapDbProject(row)),
    filterOptions: {
      countries: cleanFilterOptionArray(result.filter_options?.countries),
      brands: cleanFilterOptionArray(result.filter_options?.brands),
      clients: cleanFilterOptionArray(result.filter_options?.clients),
      cms: cleanFilterOptionArray(result.filter_options?.cms),
    },
  };
}

async function loadProjects(params?: Partial<ProjectQueryParams>) {
  const nextParams = normalizeProjectParams(params);
  const requestId = (loadRequestSeq += 1);

  currentParams = nextParams;
  loading = true;
  setError(null);
  notify();

  try {
    const canManageInternalPricing = await currentUserCanManageInternalPricing();
    let result: ProjectLoadResult;

    if (hasSmartFilter(nextParams) || canManageInternalPricing) {
      result = await loadProjectsSmartDirect(nextParams, canManageInternalPricing);
    } else {
      try {
        result = await loadProjectsFromRpc(nextParams);
      } catch (rpcError) {
        if (!isMissingRpcFunction(rpcError)) throw rpcError;

        console.warn(
          "RPC tablero_cdc_get_projects_paginated no existe todavía. Usando consulta directa como respaldo temporal.",
          rpcError,
        );
        result = await loadProjectsDirect(nextParams, canManageInternalPricing);
      }
    }

    if (requestId !== loadRequestSeq) return;

    totalProjects = result.totalProjects;
    cache = result.projects;
    filterOptions = result.filterOptions;
    initialized = true;
  } catch (error) {
    if (requestId !== loadRequestSeq) return;

    console.error("Error cargando proyectos desde Supabase:", error);
    errorMessage =
      error instanceof Error
        ? error.message
        : "No se pudieron cargar los proyectos desde Supabase.";
  } finally {
    if (requestId === loadRequestSeq) {
      loading = false;
      notify();
    }
  }
}

async function addProject(project: Project & { pricingItems?: ProjectPricingItemInput[] }) {
  setError(null);
  writeInProgress += 1;

  try {
    const access = await getCurrentTableroCdcAccess();
    if (!access.canCreateProjects) {
      throw new Error("No tienes permiso para crear proyectos.");
    }

    const userId = await getCurrentUserId();
    const insertPayload = {
      ...toProjectRow(project),
      created_by: userId,
      updated_by: userId,
    };

    const { data, error } = await supabase
      .from("tablero_cdc_projects")
      .insert(insertPayload)
      .select("id")
      .single();

    if (error) {
      setError(error.message);
      throw error;
    }

    await replaceProjectLinks(data.id, project);
    await replaceProjectPricingItems(data.id, project.pricingItems);
    await logProjectActivity(
      data.id,
      "created",
      "Proyecto creado",
      `${project.nombre || "Proyecto sin título"} quedó registrado en el tablero.`,
    );
    const sheetSync = await syncProjectToSheet(data.id, "create");
    await loadProjects(currentParams);

    return {
      projectId: data.id,
      ...sheetSync,
    } satisfies SaveProjectResult;
  } finally {
    writeInProgress = Math.max(0, writeInProgress - 1);
  }
}

async function updateProject(
  id: string,
  patch: Partial<Project> & { pricingItems?: ProjectPricingItemInput[] },
) {
  setError(null);
  writeInProgress += 1;

  try {
    const userId = await getCurrentUserId();
    const isGerardo = await currentUserIsGerardo();
    const canManageInternalPricing = await currentUserCanManageInternalPricing();
    const current = cache.find((project) => project.id === id);

    if (!current) {
      const message =
        "No se encontró el proyecto en la página actual. Recarga el tablero antes de guardar para evitar sobrescribir datos.";
      setError(message);
      throw new Error(message);
    }

    const nextProject = { ...current, ...patch };
    const rowPayload = toProjectRow(nextProject);
    const updatePayload = isGerardo
      ? {
          ...rowPayload,
          updated_by: userId,
        }
      : canManageInternalPricing
        ? {
            title: rowPayload.title,
            client: rowPayload.client,
            brand: rowPayload.brand,
            country: rowPayload.country,
            requested_by: rowPayload.requested_by,
            country_manager: rowPayload.country_manager,
            description: rowPayload.description,
            status: current.status,
            internal_amount: rowPayload.internal_amount,
            brief_link: rowPayload.brief_link,
            extra_data: rowPayload.extra_data,
            updated_by: userId,
          }
        : {
            title: rowPayload.title,
            client: rowPayload.client,
            brand: rowPayload.brand,
            country: rowPayload.country,
            requested_by: rowPayload.requested_by,
            country_manager: rowPayload.country_manager,
            description: rowPayload.description,
            status: rowPayload.status,
            brief_link: rowPayload.brief_link,
            extra_data: rowPayload.extra_data,
            updated_by: userId,
          };

    const { error } = await supabase
      .from("tablero_cdc_projects")
      .update(updatePayload)
      .eq("id", id);

    if (error) {
      setError(error.message);
      throw error;
    }

    replaceProjectInCache(nextProject);
    await replaceProjectLinks(id, nextProject);
    if (canManageInternalPricing) {
      await replaceProjectPricingItems(id, patch.pricingItems);
    }

    const changes = buildProjectChangeSummary(current, nextProject, canManageInternalPricing);
    if (changes.length > 0) {
      await logProjectActivity(
        id,
        "updated",
        "Proyecto actualizado",
        changes.slice(0, 8).join("\n"),
      );
    }

    const sheetSync = await syncProjectToSheet(id, "update");
    await loadProjects(currentParams);

    return {
      projectId: id,
      ...sheetSync,
    } satisfies SaveProjectResult;
  } finally {
    writeInProgress = Math.max(0, writeInProgress - 1);
  }
}

async function deleteProject(id: string) {
  setError(null);
  writeInProgress += 1;

  try {
    const canDelete = await currentUserCanDeleteProjects();

    if (!canDelete) {
      const message = "No tienes permiso para eliminar proyectos.";
      setError(message);
      throw new Error(message);
    }

    const sheetSync = await syncProjectToSheet(id, "delete");

    if (sheetSync.sheetSyncStatus !== "synced") {
      const message =
        sheetSync.sheetSyncMessage || "No se pudo eliminar el proyecto del Google Sheet.";
      setError(message);
      throw new Error(message);
    }

    const { error: linksError } = await supabase
      .from("tablero_cdc_project_links")
      .delete()
      .eq("project_id", id);

    if (linksError) {
      setError(linksError.message);
      throw linksError;
    }

    const { error: projectError } = await supabase
      .from("tablero_cdc_projects")
      .delete()
      .eq("id", id);

    if (projectError) {
      setError(projectError.message);
      throw projectError;
    }

    removeProjectFromCache(id);
    await loadProjects(currentParams);
  } finally {
    writeInProgress = Math.max(0, writeInProgress - 1);
  }
}

async function loadProjectPricingSummaryFromRpc(
  params: ProjectQueryParams,
): Promise<ProjectPricingSummary> {
  const { data, error } = await supabase.rpc("tablero_cdc_get_pricing_summary", {
    p_tab: params.tab,
    p_search: params.search,
    p_country: params.country || "",
    p_brand: params.brand || "",
    p_client: params.client || "",
    p_cm: params.cm || "",
  });

  if (error) throw error;

  const result = (data || {}) as RpcPricingSummaryResponse;
  const totalAmount = Number(result.total_amount || 0);
  const projectCount = Number(result.project_count || 0);

  return {
    totalAmount: Number.isFinite(totalAmount) ? totalAmount : 0,
    projectCount: Number.isFinite(projectCount) ? projectCount : 0,
    loadedAt: Date.now(),
  };
}

async function loadProjectPricingSummaryDirect(
  params: ProjectQueryParams,
): Promise<ProjectPricingSummary> {
  const pageSize = 1000;
  let from = 0;
  let totalCount: number | null = null;
  let totalAmount = 0;

  while (totalCount === null || from < totalCount) {
    let query = supabase
      .from("tablero_cdc_projects")
      .select("id, internal_amount", { count: "exact" })
      .order("created_at", { ascending: false }) as unknown as DirectProjectQuery;

    query = applyDirectFilters(query, params);

    const { data, error, count } = await query.range(from, from + pageSize - 1);

    if (error) throw error;

    if (totalCount === null) {
      totalCount = count ?? (data || []).length;
    }

    const rows = (data || []) as DirectProjectSummaryRow[];

    rows.forEach((row) => {
      const amount = Number(row.internal_amount ?? 0);
      if (Number.isFinite(amount) && amount > 0) {
        totalAmount += amount;
      }
    });

    if (rows.length < pageSize) break;
    from += pageSize;
  }

  return {
    totalAmount,
    projectCount: totalCount ?? 0,
    loadedAt: Date.now(),
  };
}

async function loadProjectPricingSummarySmartDirect(
  params: ProjectQueryParams,
): Promise<ProjectPricingSummary> {
  const pageSize = 1000;
  let from = 0;
  let totalCount: number | null = null;
  const allProjects: Project[] = [];

  while (totalCount === null || from < totalCount) {
    let query = supabase
      .from("tablero_cdc_projects")
      .select(
        "id, title, client, brand, country, requested_by, country_manager, description, status, internal_amount, brief_link, created_at, extra_data",
        { count: "exact" },
      )
      .order("created_at", { ascending: false }) as unknown as DirectProjectQuery;

    query = applyDirectBaseFilters(query, params);

    const { data, error, count } = await query.range(from, from + pageSize - 1);

    if (error) throw error;

    if (totalCount === null) {
      totalCount = count ?? (data || []).length;
    }

    const rows = (data || []).map((row: unknown) => mapDbProject(row as DbProject));
    allProjects.push(...rows);

    if (rows.length < pageSize) break;
    from += pageSize;
  }

  const smartOptions = buildSmartFilterOptionsFromProjects(allProjects);
  const filteredProjects = allProjects.filter((project) =>
    projectMatchesSmartFilters(project, params, smartOptions),
  );

  const totalAmount = filteredProjects.reduce((sum, project) => {
    const amount = Number(project.monto ?? 0);
    return Number.isFinite(amount) && amount > 0 ? sum + amount : sum;
  }, 0);

  return {
    totalAmount,
    projectCount: filteredProjects.length,
    loadedAt: Date.now(),
  };
}

async function loadProjectPricingSummary(
  params?: Partial<ProjectQueryParams>,
): Promise<ProjectPricingSummary> {
  const nextParams = normalizeProjectParams({
    ...params,
    page: 1,
    pageSize: 1000,
  });

  if (hasSmartFilter(nextParams)) {
    return loadProjectPricingSummarySmartDirect(nextParams);
  }

  try {
    return await loadProjectPricingSummaryFromRpc(nextParams);
  } catch (rpcError) {
    if (!isMissingPricingSummaryRpc(rpcError)) throw rpcError;

    console.warn(
      "RPC tablero_cdc_get_pricing_summary no existe todavía. Usando suma directa como respaldo temporal.",
      rpcError,
    );
    return loadProjectPricingSummaryDirect(nextParams);
  }
}

export function useProjectPricingSummary(params: ProjectQueryParams, enabled: boolean) {
  const [summary, setSummary] = useState<ProjectPricingSummary>({
    totalAmount: 0,
    projectCount: 0,
    loadedAt: null,
  });
  const [summaryLoading, setSummaryLoading] = useState(false);
  const [summaryError, setSummaryError] = useState<string | null>(null);

  const paramsTab = params.tab;
  const paramsSearch = params.search;
  const paramsCountry = params.country;
  const paramsBrand = params.brand;
  const paramsClient = params.client;
  const paramsCm = params.cm;

  useEffect(() => {
    if (!enabled) {
      setSummary({ totalAmount: 0, projectCount: 0, loadedAt: null });
      setSummaryError(null);
      setSummaryLoading(false);
      return;
    }

    let cancelled = false;
    setSummaryLoading(true);
    setSummaryError(null);

    loadProjectPricingSummary({
      tab: paramsTab,
      search: paramsSearch,
      country: paramsCountry,
      brand: paramsBrand,
      client: paramsClient,
      cm: paramsCm,
      page: 1,
      pageSize: 1000,
    })
      .then((result) => {
        if (!cancelled) setSummary(result);
      })
      .catch((error) => {
        if (cancelled) return;
        console.error("No se pudo calcular el total tarifado filtrado:", error);
        setSummaryError(
          error instanceof Error
            ? error.message
            : "No se pudo calcular el total tarifado filtrado.",
        );
      })
      .finally(() => {
        if (!cancelled) setSummaryLoading(false);
      });

    return () => {
      cancelled = true;
    };
  }, [enabled, paramsTab, paramsSearch, paramsCountry, paramsBrand, paramsClient, paramsCm]);

  return {
    summary,
    loading: summaryLoading,
    error: summaryError,
    refresh: () =>
      loadProjectPricingSummary(params).then((result) => {
        setSummary(result);
        return result;
      }),
  };
}

export function useProjects(params?: ProjectQueryParams) {
  const [, force] = useState(0);
  const paramsTab = params?.tab;
  const paramsSearch = params?.search;
  const paramsPage = params?.page;
  const paramsPageSize = params?.pageSize;
  const paramsCountry = params?.country;
  const paramsBrand = params?.brand;
  const paramsClient = params?.client;
  const paramsCm = params?.cm;

  useEffect(() => {
    const listener = () => force((n) => n + 1);
    listeners.push(listener);
    activeHookInstances += 1;

    startProjectsRealtime();

    return () => {
      listeners = listeners.filter((x) => x !== listener);
      activeHookInstances = Math.max(0, activeHookInstances - 1);

      if (activeHookInstances === 0) {
        stopProjectsRealtime();
      }
    };
  }, []);

  useEffect(() => {
    if (paramsTab && paramsPage && paramsPageSize) {
      void loadProjects({
        tab: paramsTab,
        search: paramsSearch ?? "",
        page: paramsPage,
        pageSize: paramsPageSize,
        country: paramsCountry ?? "",
        brand: paramsBrand ?? "",
        client: paramsClient ?? "",
        cm: paramsCm ?? "",
      });
      return;
    }

    if (!initialized && !loading) {
      void loadProjects();
    }
  }, [
    paramsTab,
    paramsSearch,
    paramsPage,
    paramsPageSize,
    paramsCountry,
    paramsBrand,
    paramsClient,
    paramsCm,
  ]);

  return {
    projects: cache,
    totalProjects,
    filterOptions,
    loading,
    error: errorMessage,
    refresh: () => loadProjects(params ?? currentParams),
    add: addProject,
    update: updateProject,
    remove: deleteProject,
  };
}

// NOTE: useRole() was removed in favour of useAuth() from AuthContext.
// Role logic (isGerardo) is now derived from the real Supabase authenticated user.
