import { queryOptions } from "@tanstack/react-query";

import { apiClient } from "./api-client";
import type { SuccessEnvelope } from "./api-error";

// ── Shapes ────────────────────────────────────────────────────────────────────

export type SprintStatus = "planned" | "active" | "completed";

export interface Sprint {
	id: string;
	project_id: string;
	name: string;
	start_date?: string | null;
	end_date?: string | null;
	goal?: string | null;
	status: SprintStatus;
	created_at: string;
	updated_at: string;
}

export interface SprintListResult {
	items: Sprint[];
}

export interface Task {
	id: string;
	project_id: string;
	title: string;
	task_number: number;
	task_type_id?: string | null;
	status_id?: string | null;
	sprint_id?: string | null;
	parent_task_id?: string | null;
	description?: unknown[] | null;
	importance: number;
	story_points?: number | null;
	assignee_id?: string | null;
	reporter_id?: string | null;
	custom_fields: Record<string, unknown>;
	start_date?: string | null;
	due_date?: string | null;
	tags?: string[];
	view_position?: number | null;
	view_group_key?: string | null;
	created_at: string;
	updated_at: string;
}

export interface TaskListResult {
	items: Task[];
	page_size: number;
	next_cursor?: string | null;
}

export interface TaskPosition {
	view_id: string;
	task_id: string;
	position: number;
	group_key?: string | null;
}

interface TaskPositionListResult {
	items: TaskPosition[];
}

// ── View types ─────────────────────────────────────────────────────────────────
export type ViewType = "table" | "board" | "roadmap" | "plugin";

/**
 * A single entry in a FilterConfig's `items` map.
 * - `true`  → include this item / group
 * - `false` → exclude this item / group
 * - `FilterConfig` → recursively apply a sub-selector (used for named groups
 *   such as `"normal"` in task-type filters)
 */
export type FilterEntry = boolean | FilterConfig;

/**
 * A generic, recursive filter selector stored per view dimension.
 *
 * - `all: true`  → include everything by default; `items` act as exclusion overrides.
 * - `all: false` → include nothing by default; `items` act as inclusions.
 *
 * Item keys are either entity IDs (UUIDs) or named virtual groups.
 * The `"normal"` key in a task-types filter expands client-side to all
 * non-system type IDs, enabling dynamic inclusion without hard-coded snapshots.
 */
export interface FilterConfig {
	all: boolean;
	items?: Record<string, FilterEntry>;
}

/**
 * Per-view filter configuration stored in the database.
 * Each dimension uses an optional FilterConfig selector; absent means no filter
 * (include everything) for that dimension.
 */
export interface ViewFilters {
	task_types?: FilterConfig;
	statuses?: FilterConfig;
	assignees?: FilterConfig;
	sprints?: FilterConfig;
}

export interface ViewConfig {
	fields?: string[];
	column_by?: string;
	swimlanes?: string;
	sort_by?: string;
	field_sum?: string;
	slice_by?: string;
	filters?: ViewFilters;
	collapsed_columns?: string[];
	/** Populated only for plugin views (view_type = "plugin") */
	plugin_manifest_id?: string;
	plugin_component?: string;
}

export type ViewLayout = "Board" | "Table" | "Roadmap" | "Plugin";

// ── Filter resolver helpers ───────────────────────────────────────────────────

/**
 * Resolves a generic FilterConfig to an array of IDs from `allIds`.
 * Does not handle virtual group keys — use domain-specific resolvers for those.
 */
export function resolveFilterConfig(
	config: FilterConfig,
	allIds: string[],
): string[] {
	const included = new Set<string>(config.all ? allIds : []);
	for (const [key, entry] of Object.entries(config.items ?? {})) {
		const include = entry === true || (typeof entry === "object" && entry.all);
		if (include) included.add(key);
		else included.delete(key);
	}
	return Array.from(included);
}

/**
 * Resolves a task-types FilterConfig to an explicit list of task type IDs.
 *
 * The virtual key `"normal"` expands to all non-system type IDs so newly
 * created task types are automatically included without updating stored views.
 */
export function resolveTaskTypeFilter(
	config: FilterConfig,
	taskTypes: { id: string; is_system?: boolean }[],
): string[] {
	const normalTypeIds = taskTypes.filter((t) => !t.is_system).map((t) => t.id);
	const allIds = taskTypes.map((t) => t.id);

	const included = new Set<string>(config.all ? allIds : []);

	for (const [key, entry] of Object.entries(config.items ?? {})) {
		if (key === "normal") {
			if (entry === true) {
				for (const id of normalTypeIds) included.add(id);
			} else if (entry === false) {
				for (const id of normalTypeIds) included.delete(id);
			} else {
				// Nested FilterConfig: resolve within the normal-type subset
				const subIds = resolveFilterConfig(entry, normalTypeIds);
				if (config.all) {
					// Replace the normal-type portion with the sub-config result
					for (const id of normalTypeIds) included.delete(id);
				}
				for (const id of subIds) included.add(id);
			}
		} else {
			// Direct UUID key
			const include =
				entry === true || (typeof entry === "object" && entry.all);
			if (include) included.add(key);
			else included.delete(key);
		}
	}

	return Array.from(included);
}

export interface InteractionView {
	id: string;
	name: string;
	view_type: ViewType;
	layout: ViewLayout;
	config?: ViewConfig;
	position: number;
}

// ── View shape helpers ─────────────────────────────────────────────────────────
function viewTypeToLayout(vt: ViewType): ViewLayout {
	if (vt === "board") return "Board";
	if (vt === "roadmap") return "Roadmap";
	if (vt === "plugin") return "Plugin";
	return "Table";
}

export function layoutToViewType(l: ViewLayout): ViewType {
	if (l === "Board") return "board";
	if (l === "Roadmap") return "roadmap";
	if (l === "Plugin") return "plugin";
	return "table";
}

function mapView(raw: Omit<InteractionView, "layout">): InteractionView {
	return { ...raw, layout: viewTypeToLayout(raw.view_type) };
}

// ── View API ──────────────────────────────────────────────────────────────────
interface ViewListResult {
	items: Omit<InteractionView, "layout">[];
}

// ── Unified context-based View API ───────────────────────────────────────────

export type ViewsContext = "sprint" | "backlog" | "timeline";

function viewsContextQuery(
	context: ViewsContext,
	sprintId?: string | null,
): string {
	if (context === "sprint" && sprintId)
		return `context=sprint&sprint_id=${sprintId}`;
	return `context=${context}`;
}

export async function listViewsByContext(
	projectId: string,
	context: ViewsContext,
	sprintId?: string | null,
): Promise<InteractionView[]> {
	const { data } = await apiClient.instance.get<
		SuccessEnvelope<ViewListResult>
	>(`/projects/${projectId}/views?${viewsContextQuery(context, sprintId)}`);
	return data.data.items.map(mapView);
}

export async function createViewByContext(
	projectId: string,
	context: ViewsContext,
	payload: { name: string; view_type: ViewType; config?: ViewConfig },
	sprintId?: string | null,
): Promise<InteractionView> {
	const { data } = await apiClient.instance.post<
		SuccessEnvelope<Omit<InteractionView, "layout">>
	>(
		`/projects/${projectId}/views?${viewsContextQuery(context, sprintId)}`,
		payload,
	);
	return mapView(data.data);
}

export async function updateViewById(
	projectId: string,
	viewId: string,
	payload: Partial<{
		name: string;
		view_type: ViewType;
		config: ViewConfig;
		position: number;
	}>,
): Promise<InteractionView> {
	const { data } = await apiClient.instance.patch<
		SuccessEnvelope<Omit<InteractionView, "layout">>
	>(`/projects/${projectId}/views/${viewId}`, payload);
	return mapView(data.data);
}

export async function deleteViewById(
	projectId: string,
	viewId: string,
): Promise<void> {
	await apiClient.instance.delete(`/projects/${projectId}/views/${viewId}`);
}

export async function reorderViewsByContext(
	projectId: string,
	context: ViewsContext,
	viewIds: string[],
	sprintId?: string | null,
): Promise<void> {
	await apiClient.instance.put(
		`/projects/${projectId}/views/positions?${viewsContextQuery(context, sprintId)}`,
		{ view_ids: viewIds },
	);
}

export async function bulkMoveViewTaskPositions(
	projectId: string,
	viewId: string,
	items: Array<{
		task_id: string;
		position: number;
		group_key?: string | null;
	}>,
): Promise<void> {
	await apiClient.instance.put(
		`/projects/${projectId}/views/${viewId}/task-positions`,
		{ items },
	);
}

export const viewsByContextQueryOptions = (
	projectId: string,
	context: ViewsContext,
	sprintId?: string | null,
) => {
	const queryKey =
		context === "sprint" && sprintId
			? (["projects", projectId, "sprints", sprintId, "views"] as const)
			: context === "backlog"
				? (["projects", projectId, "backlog-views"] as const)
				: (["projects", projectId, "timeline-views"] as const);
	return queryOptions({
		queryKey,
		queryFn: () => listViewsByContext(projectId, context, sprintId),
		staleTime: 30_000,
	});
};

// ── Sprint API ────────────────────────────────────────────────────────────────

export async function listSprints(projectId: string): Promise<Sprint[]> {
	const { data } = await apiClient.instance.get<
		SuccessEnvelope<SprintListResult>
	>(`/projects/${projectId}/sprints`);
	return data.data.items;
}

export async function getSprint(
	projectId: string,
	sprintId: string,
): Promise<Sprint> {
	const { data } = await apiClient.instance.get<SuccessEnvelope<Sprint>>(
		`/projects/${projectId}/sprints/${sprintId}`,
	);
	return data.data;
}

export interface CreateSprintPayload {
	name: string;
	status?: SprintStatus;
	goal?: string | null;
	start_date?: string | null;
	end_date?: string | null;
}

export async function createSprint(
	projectId: string,
	payload: CreateSprintPayload,
): Promise<Sprint> {
	const { data } = await apiClient.instance.post<SuccessEnvelope<Sprint>>(
		`/projects/${projectId}/sprints`,
		payload,
	);
	return data.data;
}

export interface UpdateSprintPayload {
	name?: string;
	status?: SprintStatus;
	goal?: string | null;
	start_date?: string | null;
	end_date?: string | null;
}

export async function updateSprint(
	projectId: string,
	sprintId: string,
	payload: UpdateSprintPayload,
): Promise<Sprint> {
	const { data } = await apiClient.instance.patch<SuccessEnvelope<Sprint>>(
		`/projects/${projectId}/sprints/${sprintId}`,
		payload,
	);
	return data.data;
}

export interface CompleteSprintPayload {
	move_to_sprint_id?: string | null;
}

export async function completeSprint(
	projectId: string,
	sprintId: string,
	payload: CompleteSprintPayload = {},
): Promise<Sprint> {
	const { data } = await apiClient.instance.post<SuccessEnvelope<Sprint>>(
		`/projects/${projectId}/sprints/${sprintId}/complete`,
		payload,
	);
	return data.data;
}

// ── Task API ──────────────────────────────────────────────────────────────────

export interface ListTasksOptions {
	sprintId?: string | null;
	sprintIds?: string[];
	statusId?: string;
	statusIds?: string[];
	assigneeId?: string;
	assigneeIds?: string[];
	assigneeNull?: boolean;
	taskTypeIds?: string[];
	taskTypeNull?: boolean;
	parentTaskId?: string;
	pageSize?: number;
	cursor?: string;
}

function buildTaskQueryParams(opts: ListTasksOptions = {}) {
	const params: Record<string, string | number | boolean> = {};
	params.page_size = opts.pageSize ?? 20;
	if (opts.cursor) params.cursor = opts.cursor;
	if (opts.sprintId === null) params.sprint_id = "null";
	else if (opts.sprintId) params.sprint_id = opts.sprintId;
	if (opts.sprintIds && opts.sprintIds.length > 0)
		params.sprint_ids = opts.sprintIds.join(",");
	if (opts.statusId) params.status_id = opts.statusId;
	if (opts.statusIds && opts.statusIds.length > 0)
		params.status_ids = opts.statusIds.join(",");
	if (opts.assigneeNull) {
		params.assignee_id = "null";
		if (opts.assigneeIds && opts.assigneeIds.length > 0)
			params.assignee_ids = opts.assigneeIds.join(",");
	} else if (opts.assigneeId) {
		params.assignee_id = opts.assigneeId;
	} else if (opts.assigneeIds && opts.assigneeIds.length > 0) {
		params.assignee_ids = opts.assigneeIds.join(",");
	}
	if (opts.taskTypeNull) params.task_type_id = "null";
	else if (opts.taskTypeIds && opts.taskTypeIds.length > 0)
		params.task_type_ids = opts.taskTypeIds.join(",");
	if (opts.parentTaskId) params.parent_task_id = opts.parentTaskId;
	return params;
}

export async function listAllTasks(
	projectId: string,
	opts: ListTasksOptions = {},
): Promise<TaskListResult> {
	const params = buildTaskQueryParams(opts);
	const { data } = await apiClient.instance.get<
		SuccessEnvelope<TaskListResult>
	>(`/projects/${projectId}/tasks`, { params });
	return data.data;
}

export async function listSprintTasks(
	projectId: string,
	sprintId: string,
	opts: ListTasksOptions = {},
): Promise<TaskListResult> {
	return listAllTasks(projectId, {
		...opts,
		sprintId,
	});
}

export async function createTask(
	projectId: string,
	payload: {
		title: string;
		status_id?: string | null;
		sprint_id?: string | null;
		task_type_id?: string | null;
		assignee_id?: string | null;
		parent_task_id?: string | null;
	},
): Promise<Task> {
	const { data } = await apiClient.instance.post<SuccessEnvelope<Task>>(
		`/projects/${projectId}/tasks`,
		payload,
	);
	return data.data;
}

export async function getTask(
	projectId: string,
	taskId: string,
): Promise<Task> {
	const { data } = await apiClient.instance.get<SuccessEnvelope<Task>>(
		`/projects/${projectId}/tasks/${taskId}`,
	);
	return data.data;
}

export async function updateTask(
	projectId: string,
	taskId: string,
	payload: Partial<{
		title: string;
		status_id: string | null;
		sprint_id: string | null;
		task_type_id: string | null;
		assignee_id: string | null;
		reporter_id: string | null;
		parent_task_id: string | null;
		description: unknown[] | null;
		importance: number;
		story_points?: number | null;
		start_date: string | null;
		due_date: string | null;
		tags: string[];
		custom_fields: Record<string, unknown>;
	}>,
): Promise<Task> {
	const { data } = await apiClient.instance.patch<SuccessEnvelope<Task>>(
		`/projects/${projectId}/tasks/${taskId}`,
		payload,
	);
	return data.data;
}

export async function listSubtasks(
	projectId: string,
	parentTaskId: string,
): Promise<Task[]> {
	const { data } = await apiClient.instance.get<
		SuccessEnvelope<TaskListResult>
	>(`/projects/${projectId}/tasks`, {
		params: { parent_task_id: parentTaskId, page: 1, page_size: 200 },
	});
	return data.data.items;
}

export async function listViewTaskPositions(
	projectId: string,
	viewId: string,
): Promise<TaskPosition[]> {
	const { data } = await apiClient.instance.get<
		SuccessEnvelope<TaskPositionListResult>
	>(`/projects/${projectId}/views/${viewId}/task-positions`);
	return data.data.items;
}

// ── Query Options ─────────────────────────────────────────────────────────────

export const taskQueryOptions = (projectId: string, taskId: string) =>
	queryOptions({
		queryKey: ["projects", projectId, "tasks", taskId],
		queryFn: () => getTask(projectId, taskId),
		staleTime: 15_000,
	});

export const subtasksQueryOptions = (projectId: string, parentTaskId: string) =>
	queryOptions({
		queryKey: ["projects", projectId, "tasks", parentTaskId, "subtasks"],
		queryFn: () => listSubtasks(projectId, parentTaskId),
		staleTime: 15_000,
	});

export const sprintsQueryOptions = (projectId: string) =>
	queryOptions({
		queryKey: ["projects", projectId, "sprints"],
		queryFn: () => listSprints(projectId),
		staleTime: 30_000,
	});

export const sprintQueryOptions = (projectId: string, sprintId: string) =>
	queryOptions({
		queryKey: ["projects", projectId, "sprints", sprintId],
		queryFn: () => getSprint(projectId, sprintId),
		staleTime: 30_000,
	});

export const allTasksQueryOptions = (
	projectId: string,
	opts: ListTasksOptions = {},
) =>
	queryOptions({
		queryKey: ["projects", projectId, "tasks", opts],
		queryFn: () => listAllTasks(projectId, opts),
		staleTime: 15_000,
	});

export const viewTaskPositionsQueryOptions = (
	projectId: string,
	viewId: string,
) =>
	queryOptions({
		queryKey: ["projects", projectId, "views", viewId, "task-positions"],
		queryFn: () => listViewTaskPositions(projectId, viewId),
		staleTime: 15_000,
	});

export const sprintTasksQueryOptions = (projectId: string, sprintId: string) =>
	queryOptions({
		queryKey: ["projects", projectId, "sprints", sprintId, "tasks"],
		queryFn: () => listSprintTasks(projectId, sprintId),
		staleTime: 15_000,
	});

/** Fetches all tasks whose task_type is "Epic" for a project. */
export async function listEpicTasks(
	projectId: string,
	epicTypeId: string,
): Promise<Task[]> {
	const result = await listAllTasks(projectId, {
		taskTypeIds: [epicTypeId],
		pageSize: 500,
	});
	return result.items;
}

export const epicTasksQueryOptions = (projectId: string, epicTypeId: string) =>
	queryOptions({
		queryKey: ["projects", projectId, "tasks", "epics", epicTypeId],
		queryFn: () => listEpicTasks(projectId, epicTypeId),
		staleTime: 30_000,
		enabled: !!projectId && !!epicTypeId,
	});

/** Fetches child tasks of an epic (tasks with parent_task_id = epicId). */
export const epicChildTasksQueryOptions = (projectId: string, epicId: string) =>
	queryOptions({
		queryKey: ["projects", projectId, "tasks", epicId, "children"],
		queryFn: () => listSubtasks(projectId, epicId),
		staleTime: 15_000,
	});

// ── Activity & Comments API ────────────────────────────────────────────────────

export interface Activity {
	id: string;
	task_id: string;
	actor_id?: string | null;
	actor_name: string;
	actor_username: string;
	activity_type: string;
	content: Record<string, unknown> | unknown[];
	created_at: string;
	updated_at: string;
}

interface ActivityListResult {
	items: Activity[];
}

export async function listTaskActivities(
	projectId: string,
	taskId: string,
): Promise<Activity[]> {
	const { data } = await apiClient.instance.get<
		SuccessEnvelope<ActivityListResult>
	>(`/projects/${projectId}/tasks/${taskId}/activities`);
	return data.data.items;
}

export async function addComment(
	projectId: string,
	taskId: string,
	content: unknown[],
): Promise<Activity> {
	const { data } = await apiClient.instance.post<SuccessEnvelope<Activity>>(
		`/projects/${projectId}/tasks/${taskId}/activities/comments`,
		{ content },
	);
	return data.data;
}

export async function updateComment(
	projectId: string,
	taskId: string,
	commentId: string,
	content: unknown[],
): Promise<Activity> {
	const { data } = await apiClient.instance.patch<SuccessEnvelope<Activity>>(
		`/projects/${projectId}/tasks/${taskId}/activities/comments/${commentId}`,
		{ content },
	);
	return data.data;
}

export async function deleteComment(
	projectId: string,
	taskId: string,
	commentId: string,
): Promise<void> {
	await apiClient.instance.delete(
		`/projects/${projectId}/tasks/${taskId}/activities/comments/${commentId}`,
	);
}

export const taskActivitiesQueryOptions = (projectId: string, taskId: string) =>
	queryOptions({
		queryKey: ["projects", projectId, "tasks", taskId, "activities"],
		queryFn: () => listTaskActivities(projectId, taskId),
		staleTime: 15_000,
		enabled: !!projectId && !!taskId,
	});
