/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import type { Config } from '../config/config.js';
import type { MessageBus } from '../confirmation-bus/message-bus.js';
import {
  TRACKER_ADD_DEPENDENCY_DEFINITION,
  TRACKER_CREATE_TASK_DEFINITION,
  TRACKER_GET_TASK_DEFINITION,
  TRACKER_LIST_TASKS_DEFINITION,
  TRACKER_UPDATE_TASK_DEFINITION,
  TRACKER_VISUALIZE_DEFINITION,
} from './definitions/trackerTools.js';
import { resolveToolDeclaration } from './definitions/resolver.js';
import {
  TRACKER_ADD_DEPENDENCY_TOOL_NAME,
  TRACKER_CREATE_TASK_TOOL_NAME,
  TRACKER_GET_TASK_TOOL_NAME,
  TRACKER_LIST_TASKS_TOOL_NAME,
  TRACKER_UPDATE_TASK_TOOL_NAME,
  TRACKER_VISUALIZE_TOOL_NAME,
} from './tool-names.js';
import type {
  ToolResult,
  TodoList,
  TodoStatus,
  ExecuteOptions,
} from './tools.js';
import { BaseDeclarativeTool, BaseToolInvocation, Kind } from './tools.js';
import { ToolErrorType } from './tool-error.js';
import type { TrackerTask, TaskType } from '../services/trackerTypes.js';
import { TaskStatus, TASK_TYPE_LABELS } from '../services/trackerTypes.js';
import type { TrackerService } from '../services/trackerService.js';

export async function buildTodosReturnDisplay(
  service: TrackerService,
): Promise<TodoList> {
  const tasks = await service.listTasks();
  const childrenMap = new Map<string, TrackerTask[]>();
  const roots: TrackerTask[] = [];

  for (const task of tasks) {
    if (task.parentId) {
      if (!childrenMap.has(task.parentId)) {
        childrenMap.set(task.parentId, []);
      }
      childrenMap.get(task.parentId)!.push(task);
    } else {
      roots.push(task);
    }
  }

  const statusOrder: Record<TaskStatus, number> = {
    [TaskStatus.IN_PROGRESS]: 0,
    [TaskStatus.OPEN]: 1,
    [TaskStatus.BLOCKED]: 2,
    [TaskStatus.CLOSED]: 3,
  };

  const sortTasks = (a: TrackerTask, b: TrackerTask) => {
    if (statusOrder[a.status] !== statusOrder[b.status]) {
      return statusOrder[a.status] - statusOrder[b.status];
    }
    return a.id.localeCompare(b.id);
  };

  roots.sort(sortTasks);

  const todos: TodoList['todos'] = [];

  const addTask = (task: TrackerTask, depth: number, visited: Set<string>) => {
    if (visited.has(task.id)) {
      todos.push({
        description: `${'  '.repeat(depth)}[CYCLE DETECTED: ${task.id}]`,
        status: 'cancelled',
      });
      return;
    }
    visited.add(task.id);

    let status: TodoStatus = 'pending';
    if (task.status === TaskStatus.IN_PROGRESS) {
      status = 'in_progress';
    } else if (task.status === TaskStatus.CLOSED) {
      status = 'completed';
    } else if (task.status === TaskStatus.BLOCKED) {
      status = 'blocked';
    }

    const indent = '  '.repeat(depth);
    const description = `${indent}${task.type}: ${task.title} (${task.id})`;

    todos.push({ description, status });

    const children = childrenMap.get(task.id) ?? [];
    children.sort(sortTasks);
    for (const child of children) {
      addTask(child, depth + 1, visited);
    }
    visited.delete(task.id);
  };

  for (const root of roots) {
    addTask(root, 0, new Set());
  }

  return { todos };
}

// --- tracker_create_task ---

interface CreateTaskParams {
  title: string;
  description: string;
  type: TaskType;
  parentId?: string;
  dependencies?: string[];
}

class TrackerCreateTaskInvocation extends BaseToolInvocation<
  CreateTaskParams,
  ToolResult
> {
  constructor(
    private readonly config: Config,
    params: CreateTaskParams,
    messageBus: MessageBus,
    toolName: string,
  ) {
    super(params, messageBus, toolName);
  }

  private get service() {
    return this.config.getTrackerService();
  }
  getDescription(): string {
    return `Creating task: ${this.params.title}`;
  }

  override async execute({
    abortSignal: _signal,
  }: ExecuteOptions): Promise<ToolResult> {
    try {
      const task = await this.service.createTask({
        title: this.params.title,
        description: this.params.description,
        type: this.params.type,
        status: TaskStatus.OPEN,
        parentId: this.params.parentId,
        dependencies: this.params.dependencies ?? [],
      });
      return {
        llmContent: `Created task ${task.id}: ${task.title}`,
        returnDisplay: await buildTodosReturnDisplay(this.service),
      };
    } catch (error) {
      const errorMessage =
        error instanceof Error ? error.message : String(error);
      return {
        llmContent: `Error creating task: ${errorMessage}`,
        returnDisplay: 'Failed to create task.',
        error: {
          message: errorMessage,
          type: ToolErrorType.EXECUTION_FAILED,
        },
      };
    }
  }
}

export class TrackerCreateTaskTool extends BaseDeclarativeTool<
  CreateTaskParams,
  ToolResult
> {
  static readonly Name = TRACKER_CREATE_TASK_TOOL_NAME;
  constructor(
    private config: Config,
    messageBus: MessageBus,
  ) {
    super(
      TrackerCreateTaskTool.Name,
      'Create Task',
      TRACKER_CREATE_TASK_DEFINITION.base.description!,
      Kind.Edit,
      TRACKER_CREATE_TASK_DEFINITION.base.parametersJsonSchema,
      messageBus,
    );
  }
  protected createInvocation(params: CreateTaskParams, messageBus: MessageBus) {
    return new TrackerCreateTaskInvocation(
      this.config,
      params,
      messageBus,
      this.name,
    );
  }
  override getSchema(modelId?: string) {
    return resolveToolDeclaration(TRACKER_CREATE_TASK_DEFINITION, modelId);
  }
}

// --- tracker_update_task ---

interface UpdateTaskParams {
  id: string;
  title?: string;
  description?: string;
  status?: TaskStatus;
  dependencies?: string[];
}

class TrackerUpdateTaskInvocation extends BaseToolInvocation<
  UpdateTaskParams,
  ToolResult
> {
  constructor(
    private readonly config: Config,
    params: UpdateTaskParams,
    messageBus: MessageBus,
    toolName: string,
  ) {
    super(params, messageBus, toolName);
  }

  private get service() {
    return this.config.getTrackerService();
  }
  getDescription(): string {
    return `Updating task ${this.params.id}`;
  }

  override async execute({
    abortSignal: _signal,
  }: ExecuteOptions): Promise<ToolResult> {
    const { id, ...updates } = this.params;
    try {
      const task = await this.service.updateTask(id, updates);
      return {
        llmContent: `Updated task ${task.id}. Status: ${task.status}`,
        returnDisplay: await buildTodosReturnDisplay(this.service),
      };
    } catch (error) {
      const errorMessage =
        error instanceof Error ? error.message : String(error);
      return {
        llmContent: `Error updating task: ${errorMessage}`,
        returnDisplay: 'Failed to update task.',
        error: {
          message: errorMessage,
          type: ToolErrorType.EXECUTION_FAILED,
        },
      };
    }
  }
}

export class TrackerUpdateTaskTool extends BaseDeclarativeTool<
  UpdateTaskParams,
  ToolResult
> {
  static readonly Name = TRACKER_UPDATE_TASK_TOOL_NAME;
  constructor(
    private config: Config,
    messageBus: MessageBus,
  ) {
    super(
      TrackerUpdateTaskTool.Name,
      'Update Task',
      TRACKER_UPDATE_TASK_DEFINITION.base.description!,
      Kind.Edit,
      TRACKER_UPDATE_TASK_DEFINITION.base.parametersJsonSchema,
      messageBus,
    );
  }
  protected createInvocation(params: UpdateTaskParams, messageBus: MessageBus) {
    return new TrackerUpdateTaskInvocation(
      this.config,
      params,
      messageBus,
      this.name,
    );
  }
  override getSchema(modelId?: string) {
    return resolveToolDeclaration(TRACKER_UPDATE_TASK_DEFINITION, modelId);
  }
}

// --- tracker_get_task ---

interface GetTaskParams {
  id: string;
}

class TrackerGetTaskInvocation extends BaseToolInvocation<
  GetTaskParams,
  ToolResult
> {
  constructor(
    private readonly config: Config,
    params: GetTaskParams,
    messageBus: MessageBus,
    toolName: string,
  ) {
    super(params, messageBus, toolName);
  }

  private get service() {
    return this.config.getTrackerService();
  }
  getDescription(): string {
    return `Retrieving task ${this.params.id}`;
  }

  override async execute({
    abortSignal: _signal,
  }: ExecuteOptions): Promise<ToolResult> {
    const task = await this.service.getTask(this.params.id);
    if (!task) {
      return {
        llmContent: `Task ${this.params.id} not found.`,
        returnDisplay: 'Task not found.',
      };
    }
    return {
      llmContent: JSON.stringify(task, null, 2),
      returnDisplay: await buildTodosReturnDisplay(this.service),
    };
  }
}

export class TrackerGetTaskTool extends BaseDeclarativeTool<
  GetTaskParams,
  ToolResult
> {
  static readonly Name = TRACKER_GET_TASK_TOOL_NAME;
  constructor(
    private config: Config,
    messageBus: MessageBus,
  ) {
    super(
      TrackerGetTaskTool.Name,
      'Get Task',
      TRACKER_GET_TASK_DEFINITION.base.description!,
      Kind.Read,
      TRACKER_GET_TASK_DEFINITION.base.parametersJsonSchema,
      messageBus,
    );
  }
  protected createInvocation(params: GetTaskParams, messageBus: MessageBus) {
    return new TrackerGetTaskInvocation(
      this.config,
      params,
      messageBus,
      this.name,
    );
  }
  override getSchema(modelId?: string) {
    return resolveToolDeclaration(TRACKER_GET_TASK_DEFINITION, modelId);
  }
}

// --- tracker_list_tasks ---

interface ListTasksParams {
  status?: TaskStatus;
  type?: TaskType;
  parentId?: string;
}

class TrackerListTasksInvocation extends BaseToolInvocation<
  ListTasksParams,
  ToolResult
> {
  constructor(
    private readonly config: Config,
    params: ListTasksParams,
    messageBus: MessageBus,
    toolName: string,
  ) {
    super(params, messageBus, toolName);
  }

  private get service() {
    return this.config.getTrackerService();
  }
  getDescription(): string {
    return 'Listing tasks.';
  }

  override async execute({
    abortSignal: _signal,
  }: ExecuteOptions): Promise<ToolResult> {
    let tasks = await this.service.listTasks();
    if (this.params.status) {
      tasks = tasks.filter((t) => t.status === this.params.status);
    }
    if (this.params.type) {
      tasks = tasks.filter((t) => t.type === this.params.type);
    }
    if (this.params.parentId) {
      tasks = tasks.filter((t) => t.parentId === this.params.parentId);
    }

    if (tasks.length === 0) {
      return {
        llmContent: 'No tasks found matching the criteria.',
        returnDisplay: 'No matching tasks.',
      };
    }

    const content = tasks
      .map((t) => `- [${t.id}] ${t.title} (${t.status})`)
      .join('\n');
    return {
      llmContent: content,
      returnDisplay: await buildTodosReturnDisplay(this.service),
    };
  }
}

export class TrackerListTasksTool extends BaseDeclarativeTool<
  ListTasksParams,
  ToolResult
> {
  static readonly Name = TRACKER_LIST_TASKS_TOOL_NAME;
  constructor(
    private config: Config,
    messageBus: MessageBus,
  ) {
    super(
      TrackerListTasksTool.Name,
      'List Tasks',
      TRACKER_LIST_TASKS_DEFINITION.base.description!,
      Kind.Search,
      TRACKER_LIST_TASKS_DEFINITION.base.parametersJsonSchema,
      messageBus,
    );
  }
  protected createInvocation(params: ListTasksParams, messageBus: MessageBus) {
    return new TrackerListTasksInvocation(
      this.config,
      params,
      messageBus,
      this.name,
    );
  }
  override getSchema(modelId?: string) {
    return resolveToolDeclaration(TRACKER_LIST_TASKS_DEFINITION, modelId);
  }
}

// --- tracker_add_dependency ---

interface AddDependencyParams {
  taskId: string;
  dependencyId: string;
}

class TrackerAddDependencyInvocation extends BaseToolInvocation<
  AddDependencyParams,
  ToolResult
> {
  constructor(
    private readonly config: Config,
    params: AddDependencyParams,
    messageBus: MessageBus,
    toolName: string,
  ) {
    super(params, messageBus, toolName);
  }

  private get service() {
    return this.config.getTrackerService();
  }
  getDescription(): string {
    return `Adding dependency: ${this.params.taskId} depends on ${this.params.dependencyId}`;
  }

  override async execute({
    abortSignal: _signal,
  }: ExecuteOptions): Promise<ToolResult> {
    if (this.params.taskId === this.params.dependencyId) {
      return {
        llmContent: `Error: Task ${this.params.taskId} cannot depend on itself.`,
        returnDisplay: 'Self-referential dependency rejected.',
        error: {
          message: 'Task cannot depend on itself',
          type: ToolErrorType.EXECUTION_FAILED,
        },
      };
    }

    const [task, dep] = await Promise.all([
      this.service.getTask(this.params.taskId),
      this.service.getTask(this.params.dependencyId),
    ]);

    if (!task) {
      return {
        llmContent: `Task ${this.params.taskId} not found.`,
        returnDisplay: 'Task not found.',
      };
    }
    if (!dep) {
      return {
        llmContent: `Dependency task ${this.params.dependencyId} not found.`,
        returnDisplay: 'Dependency not found.',
      };
    }

    const newDeps = Array.from(
      new Set([...task.dependencies, this.params.dependencyId]),
    );
    try {
      await this.service.updateTask(task.id, { dependencies: newDeps });
      return {
        llmContent: `Linked ${task.id} -> ${dep.id}.`,
        returnDisplay: await buildTodosReturnDisplay(this.service),
      };
    } catch (error) {
      const errorMessage =
        error instanceof Error ? error.message : String(error);
      return {
        llmContent: `Error adding dependency: ${errorMessage}`,
        returnDisplay: 'Failed to add dependency.',
        error: {
          message: errorMessage,
          type: ToolErrorType.EXECUTION_FAILED,
        },
      };
    }
  }
}

export class TrackerAddDependencyTool extends BaseDeclarativeTool<
  AddDependencyParams,
  ToolResult
> {
  static readonly Name = TRACKER_ADD_DEPENDENCY_TOOL_NAME;
  constructor(
    private config: Config,
    messageBus: MessageBus,
  ) {
    super(
      TrackerAddDependencyTool.Name,
      'Add Dependency',
      TRACKER_ADD_DEPENDENCY_DEFINITION.base.description!,
      Kind.Edit,
      TRACKER_ADD_DEPENDENCY_DEFINITION.base.parametersJsonSchema,
      messageBus,
    );
  }
  protected createInvocation(
    params: AddDependencyParams,
    messageBus: MessageBus,
  ) {
    return new TrackerAddDependencyInvocation(
      this.config,
      params,
      messageBus,
      this.name,
    );
  }
  override getSchema(modelId?: string) {
    return resolveToolDeclaration(TRACKER_ADD_DEPENDENCY_DEFINITION, modelId);
  }
}

// --- tracker_visualize ---

class TrackerVisualizeInvocation extends BaseToolInvocation<
  Record<string, never>,
  ToolResult
> {
  constructor(
    private readonly config: Config,
    params: Record<string, never>,
    messageBus: MessageBus,
    toolName: string,
  ) {
    super(params, messageBus, toolName);
  }

  private get service() {
    return this.config.getTrackerService();
  }
  getDescription(): string {
    return 'Visualizing the task graph.';
  }

  override async execute({
    abortSignal: _signal,
  }: ExecuteOptions): Promise<ToolResult> {
    const tasks = await this.service.listTasks();
    if (tasks.length === 0) {
      return {
        llmContent: 'No tasks to visualize.',
        returnDisplay: 'Empty tracker.',
      };
    }

    const statusEmojis: Record<TaskStatus, string> = {
      open: '⭕',
      in_progress: '🚧',
      blocked: '⛔',
      closed: '✅',
    };

    const childrenMap = new Map<string, TrackerTask[]>();
    const roots: TrackerTask[] = [];

    for (const task of tasks) {
      if (task.parentId) {
        if (!childrenMap.has(task.parentId)) {
          childrenMap.set(task.parentId, []);
        }
        childrenMap.get(task.parentId)!.push(task);
      } else {
        roots.push(task);
      }
    }

    let output = 'Task Tracker Graph:\n';

    const renderTask = (
      task: TrackerTask,
      depth: number,
      visited: Set<string>,
    ) => {
      if (visited.has(task.id)) {
        output += `${'  '.repeat(depth)}[CYCLE DETECTED: ${task.id}]\n`;
        return;
      }
      visited.add(task.id);

      const indent = '  '.repeat(depth);
      output += `${indent}${statusEmojis[task.status]} ${task.id} ${TASK_TYPE_LABELS[task.type]} ${task.title}\n`;
      if (task.dependencies.length > 0) {
        output += `${indent}  └─ Depends on: ${task.dependencies.join(', ')}\n`;
      }
      const children = childrenMap.get(task.id) ?? [];
      for (const child of children) {
        renderTask(child, depth + 1, visited);
      }
      visited.delete(task.id);
    };

    for (const root of roots) {
      renderTask(root, 0, new Set());
    }

    return {
      llmContent: output,
      returnDisplay: await buildTodosReturnDisplay(this.service),
    };
  }
}

export class TrackerVisualizeTool extends BaseDeclarativeTool<
  Record<string, never>,
  ToolResult
> {
  static readonly Name = TRACKER_VISUALIZE_TOOL_NAME;
  constructor(
    private config: Config,
    messageBus: MessageBus,
  ) {
    super(
      TrackerVisualizeTool.Name,
      'Visualize Tracker',
      TRACKER_VISUALIZE_DEFINITION.base.description!,
      Kind.Read,
      TRACKER_VISUALIZE_DEFINITION.base.parametersJsonSchema,
      messageBus,
    );
  }
  protected createInvocation(
    params: Record<string, never>,
    messageBus: MessageBus,
  ) {
    return new TrackerVisualizeInvocation(
      this.config,
      params,
      messageBus,
      this.name,
    );
  }
  override getSchema(modelId?: string) {
    return resolveToolDeclaration(TRACKER_VISUALIZE_DEFINITION, modelId);
  }
}
