// Planning functions for the per-user portfolio workspace (starred portfolios
// and personal folders — user_portfolio_config / user_portfolio_folders).
// Pure data-in/plan-out: route handlers and server components apply the plans
// with drizzle. See docs/design/home-redesign/ for the interaction design.

export type FolderReorderPlan =
  | { updates: Array<{ id: bigint; position: number }> }
  | { error: string };

// The drag-drop reorder PATCH sends the full ordered id list; the plan
// renumbers every folder to its index so positions stay dense (0..n-1).
export function planFolderReorder({
  currentIds,
  orderedIds,
}: {
  currentIds: bigint[];
  orderedIds: bigint[];
}): FolderReorderPlan {
  const current = new Set(currentIds);
  const requested = new Set(orderedIds);
  const sameSet =
    orderedIds.length === currentIds.length &&
    current.size === requested.size &&
    [...requested].every((id) => current.has(id));
  if (!sameSet) {
    return { error: "Folder order does not match your folders" };
  }
  return {
    updates: orderedIds.map((id, index) => ({ id, position: index })),
  };
}

// starred_at doubles as the flag (non-null = starred) and the "recently
// starred" sort key; this is the one place that convention is written down.
export function buildStarChange({
  starred,
  now,
}: {
  starred: boolean;
  now: Date;
}): { starredAt: Date | null } {
  return { starredAt: starred ? now : null };
}

// The same-owner FK on user_portfolio_config is NO ACTION, so a folder can
// only be deleted after its configs are unfiled — both steps must run in one
// transaction, unfile first. This plan makes that ordering explicit.
export function planFolderDeletion({
  folderId,
  configs,
}: {
  folderId: bigint;
  configs: Array<{ id: bigint; folderId: bigint | null }>;
}): { unfileConfigIds: bigint[]; deleteFolderId: bigint } {
  return {
    unfileConfigIds: configs
      .filter((c) => c.folderId === folderId)
      .map((c) => c.id),
    deleteFolderId: folderId,
  };
}

type ConfigRow<Id> = {
  portfolioId: Id;
  folderId: Id | null;
  starredAt: Date | string | null;
};

type FolderRow<Id> = { id: Id; name: string; position: number };

// Assembles the home page's render model from the three per-user reads.
// Generic over the portfolio payload and the id type: the server groups with
// bigints; the client re-groups optimistically with wire-format string ids.
export function groupPortfoliosForHome<Id, P extends { id: Id }>({
  portfolios,
  configs,
  folders,
}: {
  portfolios: P[];
  configs: Array<ConfigRow<Id>>;
  folders: Array<FolderRow<Id>>;
}): {
  folders: Array<{ id: Id; name: string; portfolios: P[] }>;
  unfiled: P[];
  starred: P[];
} {
  const configByPortfolio = new Map(configs.map((c) => [c.portfolioId, c]));
  const folderOf = (p: P) => configByPortfolio.get(p.id)?.folderId ?? null;
  const starredAt = (p: P) => configByPortfolio.get(p.id)?.starredAt ?? null;
  const starredTime = (p: P) => new Date(starredAt(p)!).getTime();

  // Starred float above unstarred; original relative order is otherwise kept.
  const floatStarred = (group: P[]) =>
    [...group].sort(
      (a, b) => Number(starredAt(b) !== null) - Number(starredAt(a) !== null),
    );

  const orderedFolders = [...folders].sort((a, b) => a.position - b.position);
  return {
    folders: orderedFolders.map((f) => ({
      id: f.id,
      name: f.name,
      portfolios: floatStarred(portfolios.filter((p) => folderOf(p) === f.id)),
    })),
    unfiled: floatStarred(portfolios.filter((p) => folderOf(p) === null)),
    starred: portfolios
      .filter((p) => starredAt(p) !== null)
      .sort((a, b) => starredTime(b) - starredTime(a)),
  };
}
