/**
 * Project-workflow — task view-model (pure domain).
 *
 * The server owns the pipeline state machine (be0 imagehub_task_pipeline); this module owns the
 * *display* derivations the Data Page needs: the Vietnamese label + badge tone for each state, and
 * which actions are valid for a task in a given state. Pure + framework-free so it is unit-tested
 * with plain data (mirrors the data-import feature's pure domain layer).
 */
import type { DatasetTask, TaskPipelineState, TaskQueueStatus } from '@ump/shared';

export type BadgeTone = 'label' | 'review' | 'done' | 'issue' | 'warn' | 'muted';

export const PIPELINE_STATE_META: Record<TaskPipelineState, { label: string; tone: BadgeTone }> = {
  inLabel: { label: 'Gán nhãn', tone: 'label' },
  inReview: { label: 'Rà soát', tone: 'review' },
  groundTruth: { label: 'Hoàn tất', tone: 'done' },
  issue: { label: 'Sự cố', tone: 'issue' },
};

export const QUEUE_STATUS_META: Record<TaskQueueStatus, { label: string; tone: BadgeTone }> = {
  assigned: { label: 'Đã giao', tone: 'muted' },
  saved: { label: 'Đang làm', tone: 'label' },
  pendingFinalization: { label: 'Chờ hoàn tất', tone: 'warn' },
  skipped: { label: 'Bỏ qua', tone: 'muted' },
};

/** The minimal task shape the action rules read. */
export type TaskActionInput = {
  pipelineState: TaskPipelineState;
  queueStatus: TaskQueueStatus;
  assigneeUserId: string | null;
};

export type TaskActions = {
  canFinalize: boolean;
  canReview: boolean;
  canSkip: boolean;
  canAssign: boolean;
  canUnassign: boolean;
  canSetReference: boolean;
  /** Ground Truth — terminal; the pipeline is done. */
  isTerminal: boolean;
};

/** Which controls a task row should offer, derived purely from its state. */
export function allowedActions(task: TaskActionInput): TaskActions {
  const inLabel = task.pipelineState === 'inLabel';
  const inReview = task.pipelineState === 'inReview';
  const isGroundTruth = task.pipelineState === 'groundTruth';
  const skipped = task.queueStatus === 'skipped';
  return {
    canFinalize: inLabel,
    canReview: inReview,
    canSkip: (inLabel || inReview) && !skipped,
    canAssign: !task.assigneeUserId && !isGroundTruth,
    canUnassign: !!task.assigneeUserId,
    canSetReference: isGroundTruth,
    isTerminal: isGroundTruth,
  };
}

/** Clamp a user-entered priority to the server's accepted range [0, 1]. */
export function clampPriority(value: number): number {
  if (Number.isNaN(value)) return 0;
  return Math.max(0, Math.min(1, value));
}

// ---------------------------------------------------------------------------
// Data Page list view-model — scope / search / assignee / grouping / paging.
//
// The server returns the dataset's whole task array (no server-side filtering,
// grouping or paging yet), so the Data Page derives every view here, purely, on
// the client. Fine at current dataset sizes; if a dataset outgrows one page,
// promote these to `?q=&offset=&limit=` query params on GET /tasks.
// ---------------------------------------------------------------------------

/** Region 2 "Assigned tasks" scope: everything / my queue / unassigned / by pipeline state. */
export type TaskScope = 'all' | 'mine' | 'unassigned' | TaskPipelineState;

export const SCOPE_OPTIONS: Array<{ value: TaskScope; label: string }> = [
  { value: 'all', label: 'Tất cả công việc' },
  { value: 'mine', label: 'Việc của tôi' },
  { value: 'unassigned', label: 'Chưa giao' },
  { value: 'inLabel', label: 'Đang gán nhãn' },
  { value: 'inReview', label: 'Đang rà soát' },
  { value: 'groundTruth', label: 'Đã hoàn tất' },
];

export function matchesScope(task: DatasetTask, scope: TaskScope, currentUserId: string | null): boolean {
  switch (scope) {
    case 'all':
      return true;
    case 'mine':
      return !!currentUserId && task.assigneeUserId === currentUserId;
    case 'unassigned':
      return !task.assigneeUserId;
    default:
      return task.pipelineState === scope; // any TaskPipelineState
  }
}

/** Region 3 search — case-insensitive substring over name, logical path, and task id. */
export function searchTasks(tasks: DatasetTask[], query: string): DatasetTask[] {
  const q = query.trim().toLowerCase();
  if (!q) return tasks;
  return tasks.filter(
    (t) =>
      (t.name ?? '').toLowerCase().includes(q) ||
      (t.fileLogicalPath ?? '').toLowerCase().includes(q) ||
      t.id.toLowerCase().includes(q),
  );
}

/** Region 2 "All Users": every assignee / unassigned only / a specific member id. */
export type AssigneeFilter = 'all' | 'unassigned' | string;

export function matchesAssignee(task: DatasetTask, filter: AssigneeFilter): boolean {
  if (filter === 'all') return true;
  if (filter === 'unassigned') return !task.assigneeUserId;
  return task.assigneeUserId === filter;
}

/** Region 2 "No grouping" → group the visible rows by a facet. */
export type GroupBy = 'none' | 'stage' | 'assignee' | 'state';

export const GROUP_OPTIONS: Array<{ value: GroupBy; label: string }> = [
  { value: 'none', label: 'Không nhóm' },
  { value: 'stage', label: 'Theo giai đoạn' },
  { value: 'assignee', label: 'Theo phụ trách' },
  { value: 'state', label: 'Theo trạng thái' },
];

export type TaskGroup = { key: string; label: string; tasks: DatasetTask[] };

/** Group tasks for display. 'none' yields a single unlabelled group (caller renders it flat). */
export function groupTasks(tasks: DatasetTask[], groupBy: GroupBy): TaskGroup[] {
  if (groupBy === 'none') return [{ key: '__all__', label: '', tasks }];
  const groups = new Map<string, TaskGroup>();
  for (const t of tasks) {
    let key: string;
    let label: string;
    if (groupBy === 'stage') {
      key = t.currentStageId ?? '__none__';
      label = t.currentStageName ?? 'Chưa có giai đoạn';
    } else if (groupBy === 'assignee') {
      key = t.assigneeUserId ?? '__none__';
      label = t.assigneeName ?? 'Chưa giao';
    } else {
      key = t.pipelineState;
      label = PIPELINE_STATE_META[t.pipelineState].label;
    }
    const g = groups.get(key);
    if (g) g.tasks.push(t);
    else groups.set(key, { key, label, tasks: [t] });
  }
  return Array.from(groups.values());
}

/** Region 7 pagination over a flat list. Page is 1-based and clamped to [1, pageCount]. */
export type PageSlice<T> = {
  items: T[];
  page: number;
  pageCount: number;
  total: number;
  /** 1-based index of the first item shown (0 when empty). */
  from: number;
  /** 1-based index of the last item shown (0 when empty). */
  to: number;
};

export function paginate<T>(items: T[], page: number, pageSize: number): PageSlice<T> {
  const total = items.length;
  const size = Math.max(1, pageSize);
  const pageCount = Math.max(1, Math.ceil(total / size));
  const safePage = Math.min(Math.max(1, page), pageCount);
  const start = (safePage - 1) * size;
  const slice = items.slice(start, start + size);
  return {
    items: slice,
    page: safePage,
    pageCount,
    total,
    from: total === 0 ? 0 : start + 1,
    to: start + slice.length,
  };
}

/** Region 1 sidebar "Tasks assigned" — count a user's tasks by current stage. */
export type StageCount = { stageId: string; stageName: string; count: number };

export function tasksAssignedByStage(
  tasks: DatasetTask[],
  userId: string | null,
): { rows: StageCount[]; total: number } {
  if (!userId) return { rows: [], total: 0 };
  const mine = tasks.filter((t) => t.assigneeUserId === userId);
  const byStage = new Map<string, StageCount>();
  for (const t of mine) {
    const stageId = t.currentStageId ?? '__none__';
    const stageName = t.currentStageName ?? 'Hoàn tất';
    const row = byStage.get(stageId);
    if (row) row.count += 1;
    else byStage.set(stageId, { stageId, stageName, count: 1 });
  }
  return { rows: Array.from(byStage.values()), total: mine.length };
}

/** Region 5 — a short, stable prefix of a task UUID for the "Task Id" column. */
export function shortId(id: string, len = 18): string {
  return id.length > len ? `${id.slice(0, len)}…` : id;
}

/** Tone a stage chip by its kind (label vs review); used when the stage's kind is known. */
export function stageTone(kind: 'label' | 'review' | undefined): BadgeTone {
  return kind === 'review' ? 'review' : 'label';
}
