import { db } from "@/db";
import {
  clients,
  projects,
  payments,
  phases,
  tasks,
  deliverables,
  comments,
  documents,
  notes,
  time_entries,
  quote_items,
  service_catalog,
  services,
  settings,
  offer_micros,
  offer_macros,
  project_offers,
  offer_phases,
  offer_phase_services,
  quotes,
  leads,
  tags,
  clientTranscripts,
} from "@/db/schema";
import { eq, inArray, asc, desc, isNull, sql, and } from "drizzle-orm";
import { getPool } from "@/lib/taxonomy";
import { LEAD_STAGES } from "@/lib/lead-validators";
import type {
  Client,
  Project,
  Phase,
  Task,
  Deliverable,
  Payment,
  Document,
  Note,
  Comment,
  ServiceCatalog,
  Service,
  OfferMicro,
  OfferMacro,
  ProjectOffer,
  OfferPhase,
  OfferPhaseService,
  Quote,
  Lead,
} from "@/db/schema";

// ── ClientWithPayments — used by /admin clients list ─────────────────────────

export type ClientWithPayments = {
  id: string;
  name: string;
  brand_name: string;
  token: string;
  slug: string | null;
  accepted_total: string;
  archived: boolean;
  created_at: Date;
  payments: Array<{ id: string; label: string; status: string; amount: string }>;
  activeTimerEntryId: string | null;
  activeTimerStartedAt: Date | null;
  totalTrackedSeconds: number;
  projectBrands: string[];
  ltv: string;
};

export async function getAllClientsWithPayments(
  includeArchived = false
): Promise<ClientWithPayments[]> {
  const allClients = await db
    .select()
    .from(clients)
    .orderBy(clients.created_at);

  const visible = includeArchived
    ? allClients
    : allClients.filter((c) => !c.archived);

  if (visible.length === 0) return [];

  const clientIds = visible.map((c) => c.id);

  // Get all projects for these clients (payments/time_entries now scoped to project)
  const clientProjects = await db
    .select({
      id: projects.id,
      client_id: projects.client_id,
      name: projects.name,
      accepted_total: projects.accepted_total,
      archived: projects.archived,
    })
    .from(projects)
    .where(inArray(projects.client_id, clientIds))
    .orderBy(asc(projects.created_at));

  const projectIds = clientProjects.map((p) => p.id);
  const projectToClient = new Map(clientProjects.map((p) => [p.id, p.client_id]));

  if (projectIds.length === 0) {
    return visible.map((c) => ({
      id: c.id,
      name: c.name,
      brand_name: c.brand_name,
      token: c.token,
      slug: c.slug ?? null,
      accepted_total: c.accepted_total ?? "0",
      archived: c.archived ?? false,
      created_at: c.created_at,
      payments: [],
      activeTimerEntryId: null,
      activeTimerStartedAt: null,
      totalTrackedSeconds: 0,
      projectBrands: [],
      ltv: "0.00",
    }));
  }

  const [allPayments, activeEntries, totals, offerTotals] = await Promise.all([
    db.select().from(payments).where(inArray(payments.project_id, projectIds)),

    db
      .select({
        id: time_entries.id,
        project_id: time_entries.project_id,
        started_at: time_entries.started_at,
      })
      .from(time_entries)
      .where(isNull(time_entries.ended_at)),

    db
      .select({
        project_id: time_entries.project_id,
        total: sql<string>`coalesce(sum(${time_entries.duration_seconds}), 0)`,
      })
      .from(time_entries)
      .where(inArray(time_entries.project_id, projectIds))
      .groupBy(time_entries.project_id),

    db
      .select({
        project_id: project_offers.project_id,
        total: sql<string>`coalesce(sum(${project_offers.accepted_total}), 0)`,
      })
      .from(project_offers)
      .where(inArray(project_offers.project_id, projectIds))
      .groupBy(project_offers.project_id),
  ]);

  // Somma offerte accettate per progetto (per arricchire l'LTV)
  const projectOffersTotalMap = new Map<string, number>();
  for (const row of offerTotals) {
    projectOffersTotalMap.set(row.project_id, parseFloat(row.total ?? "0") || 0);
  }

  // Aggregate project-scoped data back to client level
  const clientPaymentsMap = new Map<string, typeof allPayments>();
  for (const payment of allPayments) {
    const clientId = projectToClient.get(payment.project_id);
    if (!clientId) continue;
    if (!clientPaymentsMap.has(clientId)) clientPaymentsMap.set(clientId, []);
    clientPaymentsMap.get(clientId)!.push(payment);
  }

  const clientTimerMap = new Map<string, { id: string; started_at: Date }>();
  for (const entry of activeEntries) {
    const clientId = projectToClient.get(entry.project_id);
    if (!clientId || clientTimerMap.has(clientId)) continue;
    clientTimerMap.set(clientId, { id: entry.id, started_at: entry.started_at });
  }

  const clientTotalsMap = new Map<string, number>();
  for (const row of totals) {
    const clientId = projectToClient.get(row.project_id);
    if (!clientId) continue;
    clientTotalsMap.set(clientId, (clientTotalsMap.get(clientId) ?? 0) + parseInt(row.total));
  }

  return visible.map((c) => {
    const active = clientTimerMap.get(c.id);
    const clientPayments = clientPaymentsMap.get(c.id) ?? [];
    const clientProjectsList = clientProjects.filter((p) => p.client_id === c.id);
    const projectBrands = clientProjectsList
      .filter((p) => !p.archived)
      .map((p) => p.name);
    // LTV per progetto = max(totale preventivo impostato, somma offerte assegnate).
    // Copre il caso "preventivo non impostato + offerta attiva" senza doppi conteggi.
    const ltv = clientProjectsList
      .reduce((sum, p) => {
        const acceptedTotal = parseFloat(p.accepted_total ?? "0") || 0;
        const offersTotal = projectOffersTotalMap.get(p.id) ?? 0;
        return sum + Math.max(acceptedTotal, offersTotal);
      }, 0)
      .toFixed(2);
    return {
      id: c.id,
      name: c.name,
      brand_name: c.brand_name,
      token: c.token,
      slug: c.slug ?? null,
      accepted_total: c.accepted_total ?? "0",
      archived: c.archived ?? false,
      created_at: c.created_at,
      payments: clientPayments.map((p) => ({
        id: p.id,
        label: p.label,
        status: p.status,
        amount: String(p.amount),
      })),
      activeTimerEntryId: active?.id ?? null,
      activeTimerStartedAt: active?.started_at ?? null,
      totalTrackedSeconds: clientTotalsMap.get(c.id) ?? 0,
      projectBrands,
      ltv,
    };
  });
}

export async function getClientById(id: string) {
  const rows = await db.select().from(clients).where(eq(clients.id, id)).limit(1);
  return rows[0] ?? null;
}

// ── ProjectOfferWithMicro — used by OffersTab ────────────────────────────────

export type ProjectOfferWithMicro = {
  id: string;
  project_id: string;
  micro_id: string;
  micro_internal_name: string;
  micro_public_name: string;
  micro_duration_months: number;
  tier_letter: string | null;
  macro_internal_name: string;
  macro_offer_type: string;
  start_date: Date;
  accepted_total: string | null;
  created_at: Date;
};

// ── ClientFullDetail — used by /admin/clients/[id] workspace ─────────────────

// quote_items NEVER exposed via client API — admin workspace query only
export type QuoteItemWithLabel = {
  id: string;
  label: string;        // COALESCE(service_catalog.name, quote_items.custom_label)
  custom_label: string | null;
  service_id: string | null;
  quantity: string;
  unit_price: string;   // snapshotted — never joined back to service_catalog.unit_price
  subtotal: string;
};

export type ClientFullDetail = {
  client: Client;
  phases: Array<Phase & { tasks: Array<Task & { deliverables: Deliverable[] }> }>;
  payments: Payment[];
  documents: Document[];
  notes: Note[];
  comments: Comment[];
  quoteItems: QuoteItemWithLabel[];
  activeServices: Service[];
};

export async function getClientFullDetail(id: string): Promise<ClientFullDetail | null> {
  const clientRows = await db.select().from(clients).where(eq(clients.id, id)).limit(1);
  if (clientRows.length === 0) return null;
  const client = clientRows[0];

  // Get all projects for this client (data is now project-scoped)
  const projectRows = await db
    .select({ id: projects.id })
    .from(projects)
    .where(eq(projects.client_id, id));
  const projectIds = projectRows.map((p) => p.id);

  const activeServiceRows = await db
    .select()
    .from(services)
    .where(eq(services.active, true))
    .orderBy(asc(services.name));

  if (projectIds.length === 0) {
    return {
      client,
      phases: [],
      payments: [],
      documents: [],
      notes: [],
      comments: [],
      quoteItems: [],
      activeServices: activeServiceRows,
    };
  }

  const phasesRows = await db
    .select()
    .from(phases)
    .where(inArray(phases.project_id, projectIds))
    .orderBy(asc(phases.sort_order));

  const phaseIds = phasesRows.map((p) => p.id);

  const tasksRows =
    phaseIds.length === 0
      ? []
      : await db
          .select()
          .from(tasks)
          .where(inArray(tasks.phase_id, phaseIds))
          .orderBy(asc(tasks.sort_order));

  const taskIds = tasksRows.map((t) => t.id);

  const deliverablesRows =
    taskIds.length === 0
      ? []
      : await db.select().from(deliverables).where(inArray(deliverables.task_id, taskIds));

  const paymentsRows = await db
    .select()
    .from(payments)
    .where(inArray(payments.project_id, projectIds));

  const documentsRows = await db
    .select()
    .from(documents)
    .where(inArray(documents.project_id, projectIds))
    .orderBy(asc(documents.created_at));

  const notesRows = await db
    .select()
    .from(notes)
    .where(inArray(notes.project_id, projectIds))
    .orderBy(asc(notes.created_at));

  const allEntityIds = [id, ...taskIds, ...deliverablesRows.map((d) => d.id)];
  const commentsRows =
    allEntityIds.length === 0
      ? []
      : await db
          .select()
          .from(comments)
          .where(inArray(comments.entity_id, allEntityIds))
          .orderBy(asc(comments.created_at));

  const phasesWithTasks = phasesRows.map((phase) => ({
    ...phase,
    tasks: tasksRows
      .filter((t) => t.phase_id === phase.id)
      .map((task) => ({
        ...task,
        deliverables: deliverablesRows.filter((d) => d.task_id === task.id),
      })),
  }));

  // quote_items NEVER exposed via client API — admin workspace query only
  const quoteItemRows: QuoteItemWithLabel[] = await db
    .select({
      id: quote_items.id,
      label: sql<string>`COALESCE(${service_catalog.name}, ${quote_items.custom_label})`,
      custom_label: quote_items.custom_label,
      service_id: quote_items.service_id,
      quantity: quote_items.quantity,
      unit_price: quote_items.unit_price,
      subtotal: quote_items.subtotal,
    })
    .from(quote_items)
    .leftJoin(service_catalog, eq(quote_items.service_id, service_catalog.id))
    .where(inArray(quote_items.project_id, projectIds))
    .orderBy(asc(quote_items.id));

  return {
    client,
    phases: phasesWithTasks,
    payments: paymentsRows,
    documents: documentsRows,
    notes: notesRows,
    comments: commentsRows,
    quoteItems: quoteItemRows,
    activeServices: activeServiceRows,
  };
}

// ── ServiceWithTags — services + multi-select options (Phase 11 database-view) ─
// "tag" and "pacchetto" are multi-select pools stored in the polymorphic `tags`
// table, distinguished by entity_type: "services" (tag) | "services.pacchetto".
// "category" and "fase" are single-select values stored directly on the row.

const TAG_ENTITY = "services";
const PACCHETTO_ENTITY = "services.pacchetto";

export type ServiceWithTags = Service & { tags: string[]; pacchetto: string[] };

export async function getAllServices(): Promise<ServiceWithTags[]> {
  const rows = await db
    .select({
      id: services.id,
      name: services.name,
      description: services.description,
      unit_price: services.unit_price,
      category: services.category,
      fase: services.fase,
      active: services.active,
      migrated_from: services.migrated_from,
      migrated_id: services.migrated_id,
      created_at: services.created_at,
      tag_name: tags.name,
      tag_type: tags.entity_type,
    })
    .from(services)
    .leftJoin(
      tags,
      and(
        eq(tags.entity_id, services.id),
        inArray(tags.entity_type, [TAG_ENTITY, PACCHETTO_ENTITY])
      )
    )
    .orderBy(asc(services.name), asc(tags.name));

  const serviceMap = new Map<string, ServiceWithTags>();
  for (const row of rows) {
    const { tag_name, tag_type, ...serviceFields } = row;
    if (!serviceMap.has(row.id)) {
      serviceMap.set(row.id, { ...serviceFields, tags: [], pacchetto: [] });
    }
    if (tag_name) {
      const target = serviceMap.get(row.id)!;
      if (tag_type === PACCHETTO_ENTITY) target.pacchetto.push(tag_name);
      else target.tags.push(tag_name);
    }
  }
  return Array.from(serviceMap.values());
}

// ── Shared option pools for the catalog select fields (Notion-style dropdowns) ─
// Each pool is the set of distinct values currently in use, sorted alphabetically.

export type CatalogFieldOptions = {
  tag: string[];
  pacchetto: string[];
  categoria: string[];
  fase: string[];
};

export async function getCatalogFieldOptions(): Promise<CatalogFieldOptions> {
  // fase / Offerta (tag) / Pacchetto read from the centralized, persistent
  // taxonomy pools (src/lib/taxonomy.ts). `categoria` (services.category) is
  // hidden in the catalog UI and stays derived from in-use values.
  const [tag, pacchetto, fase, catRows] = await Promise.all([
    getPool("service_offerta"),
    getPool("service_pacchetto"),
    getPool("service_fase"),
    db.selectDistinct({ value: services.category }).from(services),
  ]);

  const categoria = Array.from(
    new Set(catRows.map((r) => r.value).filter((v): v is string => !!v && v.trim().length > 0))
  ).sort((a, b) => a.localeCompare(b, "it"));

  return { tag, pacchetto, categoria, fase };
}

// ── ProjectWithPayments — used by /admin/projects list ───────────────────────

export type ProjectWithPayments = {
  id: string;
  name: string;
  client: { id: string; name: string; slug: string | null };
  accepted_total: string;
  archived: boolean;
  created_at: Date;
  payments: Array<{ id: string; label: string; status: string; amount: string }>;
  activeTimerEntryId: string | null;
  activeTimerStartedAt: Date | null;
  totalTrackedSeconds: number;
};

export async function getAllProjectsWithPayments(
  includeArchived = false
): Promise<ProjectWithPayments[]> {
  const allProjects = await db
    .select({
      id: projects.id,
      name: projects.name,
      client_id: projects.client_id,
      accepted_total: projects.accepted_total,
      archived: projects.archived,
      created_at: projects.created_at,
    })
    .from(projects)
    .orderBy(projects.created_at);

  const visible = includeArchived
    ? allProjects
    : allProjects.filter((p) => !p.archived);

  if (visible.length === 0) return [];

  const projectIds = visible.map((p) => p.id);
  const clientIds = [...new Set(visible.map((p) => p.client_id))];

  const [allPayments, activeEntries, totals, parentClients] = await Promise.all([
    db
      .select()
      .from(payments)
      .where(inArray(payments.project_id, projectIds)),

    db
      .select({
        id: time_entries.id,
        project_id: time_entries.project_id,
        started_at: time_entries.started_at,
      })
      .from(time_entries)
      .where(isNull(time_entries.ended_at)),

    db
      .select({
        project_id: time_entries.project_id,
        total: sql<string>`coalesce(sum(${time_entries.duration_seconds}), 0)`,
      })
      .from(time_entries)
      .where(inArray(time_entries.project_id, projectIds))
      .groupBy(time_entries.project_id),

    db
      .select({ id: clients.id, name: clients.name, slug: clients.slug })
      .from(clients)
      .where(inArray(clients.id, clientIds)),
  ]);

  return visible.map((project) => {
    const projectPayments = allPayments.filter((p) => p.project_id === project.id);
    const activeEntry = activeEntries.find((e) => e.project_id === project.id);
    const totalRow = totals.find((t) => t.project_id === project.id);
    const parentClient = parentClients.find((c) => c.id === project.client_id);

    return {
      id: project.id,
      name: project.name,
      client: parentClient ?? { id: project.client_id, name: "—", slug: null },
      accepted_total: project.accepted_total ?? "0",
      archived: project.archived,
      created_at: project.created_at,
      payments: projectPayments.map((p) => ({
        id: p.id,
        label: p.label,
        status: p.status,
        amount: String(p.amount),
      })),
      activeTimerEntryId: activeEntry?.id ?? null,
      activeTimerStartedAt: activeEntry?.started_at ?? null,
      totalTrackedSeconds: totalRow ? parseInt(totalRow.total) : 0,
    };
  });
}

// ── ProjectFullDetail — used by /admin/projects/[id] workspace ───────────────

export type ProjectFullDetail = {
  project: Project & { client: { id: string; name: string; brand_name: string; slug: string | null } };
  phases: Array<Phase & { tasks: Array<Task & { deliverables: Deliverable[] }> }>;
  payments: Payment[];
  documents: Document[];
  notes: Note[];
  comments: Comment[];
  quoteItems: QuoteItemWithLabel[];
  activeServices: Service[];
  activeTimerEntryId: string | null;
  activeTimerStartedAt: Date | null;
  totalTrackedSeconds: number;
  projectOffers: ProjectOfferWithMicro[];
  /** Sum of accepted_total across all active project offers — used as default for payment plan */
  offersAcceptedTotal: number;
  availableMicros: Array<{
    id: string;
    internal_name: string;
    public_name: string;
    duration_months: number;
    tier_letter: string | null;
    public_price: string | null;
    macro_id: string;
    macro_internal_name: string;
    macro_category: string | null;
    macro_offer_type: string;
  }>;
  transcripts: Array<{
    id: string;
    title: string | null;
    call_date: string;
    content: string;
    created_at: Date;
  }>;
};

export async function getProjectFullDetail(id: string): Promise<ProjectFullDetail | null> {
  const projectRows = await db
    .select()
    .from(projects)
    .where(eq(projects.id, id))
    .limit(1);

  if (projectRows.length === 0) return null;
  const project = projectRows[0];

  const clientRows = await db
    .select({ id: clients.id, name: clients.name, brand_name: clients.brand_name, slug: clients.slug })
    .from(clients)
    .where(eq(clients.id, project.client_id))
    .limit(1);
  const client = clientRows[0] ?? { id: project.client_id, name: "—", brand_name: "—", slug: null };

  const phasesRows = await db
    .select()
    .from(phases)
    .where(eq(phases.project_id, id))
    .orderBy(asc(phases.sort_order));

  const phaseIds = phasesRows.map((p) => p.id);

  const tasksRows =
    phaseIds.length === 0
      ? []
      : await db
          .select()
          .from(tasks)
          .where(inArray(tasks.phase_id, phaseIds))
          .orderBy(asc(tasks.sort_order));

  const taskIds = tasksRows.map((t) => t.id);

  const deliverablesRows =
    taskIds.length === 0
      ? []
      : await db
          .select()
          .from(deliverables)
          .where(inArray(deliverables.task_id, taskIds));

  const [paymentsRows, documentsRows, notesRows, quoteItemRows, activeServiceRows, activeEntryRows, totalRes, projectOffersRows, availableMicrosRows, transcriptsRows] =
    await Promise.all([
      db.select().from(payments).where(eq(payments.project_id, id)),
      db.select().from(documents).where(eq(documents.project_id, id)).orderBy(asc(documents.created_at)),
      db.select().from(notes).where(eq(notes.project_id, id)).orderBy(asc(notes.created_at)),
      db
        .select({
          id: quote_items.id,
          label: sql<string>`COALESCE(${service_catalog.name}, ${quote_items.custom_label})`,
          custom_label: quote_items.custom_label,
          service_id: quote_items.service_id,
          quantity: quote_items.quantity,
          unit_price: quote_items.unit_price,
          subtotal: quote_items.subtotal,
        })
        .from(quote_items)
        .leftJoin(service_catalog, eq(quote_items.service_id, service_catalog.id))
        .where(eq(quote_items.project_id, id))
        .orderBy(asc(quote_items.id)),
      db.select().from(services).where(eq(services.active, true)).orderBy(asc(services.name)),
      db
        .select({ id: time_entries.id, started_at: time_entries.started_at })
        .from(time_entries)
        .where(and(eq(time_entries.project_id, id), isNull(time_entries.ended_at)))
        .limit(1),
      db
        .select({ total: sql<string>`coalesce(sum(${time_entries.duration_seconds}), 0)` })
        .from(time_entries)
        .where(eq(time_entries.project_id, id)),
      // Query A: project offers for this project joined with micro + macro info
      db
        .select({
          id: project_offers.id,
          project_id: project_offers.project_id,
          micro_id: project_offers.micro_id,
          micro_internal_name: offer_micros.internal_name,
          micro_public_name: offer_micros.public_name,
          micro_duration_months: offer_micros.duration_months,
          tier_letter: offer_micros.tier_letter,
          macro_internal_name: offer_macros.internal_name,
          macro_offer_type: offer_macros.offer_type,
          start_date: project_offers.start_date,
          accepted_total: project_offers.accepted_total,
          created_at: project_offers.created_at,
        })
        .from(project_offers)
        .innerJoin(offer_micros, eq(project_offers.micro_id, offer_micros.id))
        .innerJoin(offer_macros, eq(offer_micros.macro_id, offer_macros.id))
        .where(eq(project_offers.project_id, id))
        .orderBy(asc(project_offers.created_at)),
      // Query B: all tiers of non-archived offers (for the assignment dropdown).
      // ordered oldest-first per (macro, tier) so client-side dedup keeps the
      // oldest until the DB cleanup removes genuine duplicates.
      db
        .select({
          id: offer_micros.id,
          internal_name: offer_micros.internal_name,
          public_name: offer_micros.public_name,
          duration_months: offer_micros.duration_months,
          tier_letter: offer_micros.tier_letter,
          public_price: offer_micros.public_price,
          macro_id: offer_micros.macro_id,
          macro_internal_name: offer_macros.internal_name,
          macro_category: offer_macros.category,
          macro_offer_type: offer_macros.offer_type,
        })
        .from(offer_micros)
        .innerJoin(offer_macros, eq(offer_micros.macro_id, offer_macros.id))
        .where(eq(offer_macros.is_archived, false))
        .orderBy(
          asc(offer_macros.internal_name),
          asc(offer_micros.tier_letter),
          asc(offer_micros.id)
        ),
      // Query C: transcripts linked to the project's client
      db
        .select({
          id: clientTranscripts.id,
          title: clientTranscripts.title,
          call_date: clientTranscripts.call_date,
          content: clientTranscripts.content,
          created_at: clientTranscripts.created_at,
        })
        .from(clientTranscripts)
        .where(eq(clientTranscripts.client_id, project.client_id))
        .orderBy(desc(clientTranscripts.call_date)),
    ]);

  const allEntityIds = [id, ...taskIds, ...deliverablesRows.map((d) => d.id)];
  const commentsRows =
    allEntityIds.length === 0
      ? []
      : await db
          .select()
          .from(comments)
          .where(inArray(comments.entity_id, allEntityIds))
          .orderBy(asc(comments.created_at));

  const phasesWithTasks = phasesRows.map((phase) => ({
    ...phase,
    tasks: tasksRows
      .filter((t) => t.phase_id === phase.id)
      .map((task) => ({
        ...task,
        deliverables: deliverablesRows.filter((d) => d.task_id === task.id),
      })),
  }));

  // Defensive dedup: until the DB cleanup removes genuine duplicate tiers,
  // keep one micro per (macro_id, tier_letter) for the assignment dropdown.
  const seenTier = new Set<string>();
  const dedupedMicros = availableMicrosRows.filter((m) => {
    const key = `${m.macro_id}::${m.tier_letter ?? `_null_${m.id}`}`;
    if (seenTier.has(key)) return false;
    seenTier.add(key);
    return true;
  });

  const offersAcceptedTotal = (projectOffersRows as ProjectOfferWithMicro[]).reduce(
    (sum, o) => sum + (o.accepted_total ? parseFloat(String(o.accepted_total)) : 0),
    0
  );

  return {
    project: { ...project, client } as ProjectFullDetail["project"],
    phases: phasesWithTasks,
    payments: paymentsRows,
    documents: documentsRows,
    notes: notesRows,
    comments: commentsRows,
    quoteItems: quoteItemRows as QuoteItemWithLabel[],
    activeServices: activeServiceRows,
    activeTimerEntryId: activeEntryRows[0]?.id ?? null,
    activeTimerStartedAt: activeEntryRows[0]?.started_at ?? null,
    totalTrackedSeconds: totalRes[0] ? parseInt(totalRes[0].total) : 0,
    projectOffers: projectOffersRows as ProjectOfferWithMicro[],
    offersAcceptedTotal,
    availableMicros: dedupedMicros,
    transcripts: transcriptsRows,
  };
}

// ── Recent time entries — used by TimerTab for the manual-entry recent list ───

export type RecentTimeEntry = {
  id: string;
  started_at: Date;
  ended_at: Date | null;
  duration_seconds: number | null;
};

export async function getRecentTimeEntries(
  projectId: string,
  limit = 5
): Promise<RecentTimeEntry[]> {
  // sql<boolean>`ended_at IS NOT NULL` used to filter closed entries
  // (Drizzle's isNotNull is available in v0.28+ but we use sql for safety)
  return db
    .select({
      id: time_entries.id,
      started_at: time_entries.started_at,
      ended_at: time_entries.ended_at,
      duration_seconds: time_entries.duration_seconds,
    })
    .from(time_entries)
    .where(and(eq(time_entries.project_id, projectId), sql`${time_entries.ended_at} IS NOT NULL`))
    .orderBy(desc(time_entries.started_at))
    .limit(limit);
}

// ── ClientWithProjects — used by client detail showing project cards ──────────

export type ClientWithProjects = Client & {
  projects: Array<{
    id: string;
    name: string;
    accepted_total: string;
    archived: boolean;
    created_at: Date;
  }>;
};

export async function getClientWithProjects(clientId: string): Promise<ClientWithProjects | null> {
  const clientRows = await db
    .select()
    .from(clients)
    .where(eq(clients.id, clientId))
    .limit(1);

  if (clientRows.length === 0) return null;
  const client = clientRows[0];

  const projectRows = await db
    .select()
    .from(projects)
    .where(eq(projects.client_id, clientId))
    .orderBy(asc(projects.created_at));

  return {
    ...client,
    projects: projectRows.map((p) => ({
      id: p.id,
      name: p.name,
      accepted_total: p.accepted_total ?? "0",
      archived: p.archived,
      created_at: p.created_at,
    })),
  };
}

// ── ClientActiveOfferSummary — used by /admin/clients/[id] offers section ─────

export type ClientActiveOfferSummary = {
  offer_id: string;
  project_id: string;
  project_name: string;
  public_name: string;        // offer_micros.public_name — NEVER internal_name
  macro_name: string;         // offer_macros.internal_name — admin-only offer name ("Mantenimento")
  tier_letter: string | null; // A | B | C
  category: string | null;    // Entry | Signature | Retainer (offer_macros.category)
  offer_type: string;         // una_tantum | retainer (fallback for category)
  accepted_total: string | null;
};

export async function getClientActiveOffers(clientId: string): Promise<ClientActiveOfferSummary[]> {
  const projectRows = await db
    .select({ id: projects.id, name: projects.name })
    .from(projects)
    .where(eq(projects.client_id, clientId));

  if (projectRows.length === 0) return [];

  const projectIds = projectRows.map((p) => p.id);
  const projectNameMap = new Map(projectRows.map((p) => [p.id, p.name]));

  // Explicit column selection — public_name only, NEVER internal_name
  const rows = await db
    .select({
      offer_id: project_offers.id,
      project_id: project_offers.project_id,
      public_name: offer_micros.public_name,
      macro_name: offer_macros.internal_name,
      tier_letter: offer_micros.tier_letter,
      category: offer_macros.category,
      offer_type: offer_macros.offer_type,
      accepted_total: project_offers.accepted_total,
    })
    .from(project_offers)
    .innerJoin(offer_micros, eq(project_offers.micro_id, offer_micros.id))
    .innerJoin(offer_macros, eq(offer_micros.macro_id, offer_macros.id))
    .where(inArray(project_offers.project_id, projectIds))
    .orderBy(asc(project_offers.created_at));

  return rows.map((r) => ({
    offer_id: r.offer_id,
    project_id: r.project_id,
    project_name: projectNameMap.get(r.project_id) ?? "—",
    public_name: r.public_name,
    macro_name: r.macro_name,
    tier_letter: r.tier_letter,
    category: r.category,
    offer_type: r.offer_type,
    accepted_total: r.accepted_total ? String(r.accepted_total) : null,
  }));
}

// ── Phase 8 Query Layer: Offer Phases & Quotes ──────────────────────────────

/**
 * Get all phases for a given offer micro (ordered by sort_order)
 * Used by: Quote builder, admin phase editor
 */
export async function getOfferPhases(microId: string): Promise<OfferPhase[]> {
  return await db
    .select()
    .from(offer_phases)
    .where(eq(offer_phases.micro_id, microId))
    .orderBy(asc(offer_phases.sort_order));
}

/**
 * Get all unified services assigned to a phase
 * Used by: Quote builder, public quote page
 */
export async function getOfferPhaseServices(phaseId: string): Promise<Service[]> {
  const phaseServices = await db
    .select({
      service_id: offer_phase_services.service_id,
    })
    .from(offer_phase_services)
    .where(eq(offer_phase_services.phase_id, phaseId));

  if (phaseServices.length === 0) return [];

  const serviceIds = phaseServices.map((ps) => ps.service_id);

  return await db
    .select()
    .from(services)
    .where(inArray(services.id, serviceIds));
}

/**
 * Get full quote by ID with all relations (header + items)
 * Used by: Quote detail page
 */
export async function getQuoteById(quoteId: string): Promise<Quote | null> {
  const quote = await db
    .select()
    .from(quotes)
    .where(eq(quotes.id, quoteId));

  return quote.length > 0 ? quote[0] : null;
}

/**
 * Get quote by public token (for /quote/[token] routes)
 * Used by: Public quote page access validation
 */
export async function getQuoteByToken(token: string): Promise<Quote | null> {
  const quote = await db
    .select()
    .from(quotes)
    .where(eq(quotes.token, token));

  return quote.length > 0 ? quote[0] : null;
}

/**
 * Get all quotes for a client (ordered by created_at DESC)
 * Used by: /admin/clients/[id] detail page
 */
export async function getQuotesByClient(clientId: string): Promise<Quote[]> {
  return await db
    .select()
    .from(quotes)
    .where(eq(quotes.client_id, clientId))
    .orderBy(sql`${quotes.created_at} DESC`);
}

/**
 * Get all offer macros with their micros (ordered by sort_order)
 */
export async function getAllOfferMacrosWithMicros(): Promise<
  (OfferMacro & { micros: OfferMicro[] })[]
> {
  const macros = await db
    .select()
    .from(offer_macros)
    .orderBy(asc(offer_macros.sort_order));

  if (macros.length === 0) return [];

  const allMicros = await db.select().from(offer_micros);

  return macros.map((macro) => ({
    ...macro,
    micros: allMicros
      .filter((micro) => micro.macro_id === macro.id)
      .sort((a, b) => a.sort_order - b.sort_order),
  }));
}

// ── LeadWithTags — leads + tags (Phase 14 database-view) ─────────────────────
// Lead tags are stored in the polymorphic `tags` table, scoped by
// entity_type="leads". `status` is a fixed enum (LEAD_STAGES), not a
// dynamic pool — exposed via getLeadFieldOptions for the OptionSelect UI.

const LEADS_TAG_ENTITY = "leads";

export type LeadWithTags = Lead & { tags: string[] };

export async function getLeadsWithTags(): Promise<LeadWithTags[]> {
  const rows = await db
    .select({
      id: leads.id,
      name: leads.name,
      email: leads.email,
      phone: leads.phone,
      company: leads.company,
      status: leads.status,
      last_contact_date: leads.last_contact_date,
      next_action: leads.next_action,
      next_action_date: leads.next_action_date,
      notes: leads.notes,
      archived: leads.archived,
      created_at: leads.created_at,
      updated_at: leads.updated_at,
      tag_name: tags.name,
    })
    .from(leads)
    .leftJoin(
      tags,
      and(
        eq(tags.entity_id, leads.id),
        eq(tags.entity_type, LEADS_TAG_ENTITY)
      )
    )
    .where(eq(leads.archived, false)) // converted leads are archived → hidden from pipeline
    .orderBy(desc(leads.updated_at), asc(tags.name));

  const leadMap = new Map<string, LeadWithTags>();
  for (const row of rows) {
    const { tag_name, ...leadFields } = row;
    if (!leadMap.has(row.id)) leadMap.set(row.id, { ...leadFields, tags: [] });
    if (tag_name) leadMap.get(row.id)!.tags.push(tag_name);
  }
  return Array.from(leadMap.values());
}

// Single lead by id WITH tags — does NOT filter archived (the detail page must
// still show a converted/archived lead).
export async function getLeadByIdWithTags(leadId: string): Promise<LeadWithTags | null> {
  const rows = await db
    .select({
      id: leads.id,
      name: leads.name,
      email: leads.email,
      phone: leads.phone,
      company: leads.company,
      status: leads.status,
      last_contact_date: leads.last_contact_date,
      next_action: leads.next_action,
      next_action_date: leads.next_action_date,
      notes: leads.notes,
      archived: leads.archived,
      created_at: leads.created_at,
      updated_at: leads.updated_at,
      tag_name: tags.name,
    })
    .from(leads)
    .leftJoin(
      tags,
      and(eq(tags.entity_id, leads.id), eq(tags.entity_type, LEADS_TAG_ENTITY))
    )
    .where(eq(leads.id, leadId));

  if (rows.length === 0) return null;
  let result: LeadWithTags | null = null;
  for (const row of rows) {
    const { tag_name, ...leadFields } = row;
    if (!result) result = { ...leadFields, tags: [] };
    if (tag_name) result.tags.push(tag_name);
  }
  return result;
}

// If this lead was converted, returns the client id of the project created from
// it (projects.created_from_lead_id), else null.
export async function getClientIdFromLead(leadId: string): Promise<string | null> {
  const [row] = await db
    .select({ client_id: projects.client_id })
    .from(projects)
    .where(eq(projects.created_from_lead_id, leadId))
    .limit(1);
  return row?.client_id ?? null;
}

export type LeadFieldOptions = { status: string[]; tags: string[] };

export async function getLeadFieldOptions(): Promise<LeadFieldOptions> {
  const tagRows = await db
    .selectDistinct({ name: tags.name })
    .from(tags)
    .where(eq(tags.entity_type, LEADS_TAG_ENTITY));

  return {
    status: [...LEAD_STAGES],
    tags: tagRows
      .map((r) => r.name)
      .sort((a, b) => a.localeCompare(b, "it")),
  };
}