'use client';

import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { IProjectPhase, IProjectTask, ITaskChecklistItem, ProjectStatus, IProjectTicketLinkWithDetails, IProjectTaskDependency } from '@alga-psa/types';
import { IUser } from '@shared/interfaces/user.interfaces';
import { IPriority } from '@alga-psa/types';
import { ITag } from '@alga-psa/types';
import UserAvatar from '@alga-psa/ui/components/UserAvatar';
import TeamAvatar from '@alga-psa/ui/components/TeamAvatar';
import { Tooltip } from '@alga-psa/ui/components/Tooltip';
import { getProjectTreeData, getProjectDetails } from '../actions/projectActions';
import { getAllPriorities } from '@alga-psa/reference-data/actions';
import { getServices } from '@alga-psa/projects/actions/serviceCatalogActions';
import { IService } from '@alga-psa/types';
import {
  updateTaskWithChecklist,
  addTaskToPhase,
  getTaskChecklistItems,
  moveTaskToPhase,
  deleteTask,
  addTaskResourcesAction,
  removeTaskResourceAction,
  getTaskResourcesAction,
  assignTeamToProjectTask,
  removeTeamFromProjectTask,
  addTicketLinkAction,
  deleteTaskTicketLinksByTicketIdAction,
  duplicateTaskToPhase,
  getTaskDependencies,
  addTaskDependency
} from '../actions/projectTaskActions';
import { getCurrentUser, getUserAvatarUrlsBatchAction, searchUsersForMentions } from '@alga-psa/user-composition/actions';
import { findTagsByEntityId, createTagsForEntity } from '@alga-psa/tags/actions';
import { QuickAddTagPicker, TagManager } from '@alga-psa/tags/components';
import type { PendingTag } from '@alga-psa/types';
import { Dialog, DialogContent } from '@alga-psa/ui/components/Dialog';
import { Button } from '@alga-psa/ui/components/Button';
import { TextArea } from '@alga-psa/ui/components/TextArea';
import { TextEditor } from '@alga-psa/ui/editor';
import type { BlockNoteEditor } from '@blocknote/core';
import { PartialBlock } from '@blocknote/core';
import { ListChecks, Pencil, Plus, Trash2, Clock, Ticket, GripVertical } from 'lucide-react';
import { DatePicker } from '@alga-psa/ui/components/DatePicker';
import UserAndTeamPicker from '@alga-psa/ui/components/UserAndTeamPicker';
import MultiUserAndTeamPicker from '@alga-psa/ui/components/MultiUserAndTeamPicker';
import { ConfirmationDialog } from '@alga-psa/ui/components/ConfirmationDialog';
import DuplicateTaskDialog, { DuplicateOptions } from './DuplicateTaskDialog';
import RemoveTeamDialog from './RemoveTeamDialog';
import { Input } from '@alga-psa/ui/components/Input';
import { toast } from 'react-hot-toast';
import { handleError, isActionPermissionError } from '@alga-psa/ui/lib/errorHandling';
import { TaskTypeSelector } from './TaskTypeSelector';
import { getTaskTypes } from '../actions/projectTaskActions';
import { ITaskType } from '@alga-psa/types';
import { Alert, AlertDescription } from '@alga-psa/ui/components/Alert';
import TaskTicketLinks, { TaskTicketLinksRef } from './TaskTicketLinks';
import { TaskDependencies, TaskDependenciesRef } from './TaskDependencies';
import TaskDocumentsSimple, { PendingTaskDocument } from './TaskDocumentsSimple';
import TaskCommentThread from './TaskCommentThread';
import { useDocumentsCrossFeature } from '@alga-psa/core/context/DocumentsCrossFeatureContext';
import { SearchableSelect } from '@alga-psa/ui/components/SearchableSelect';
import TreeSelect, { TreeSelectOption, TreeSelectPath } from '@alga-psa/ui/components/TreeSelect';
import { useTicketIntegration } from '../context/TicketIntegrationContext';
import { Checkbox } from '@alga-psa/ui/components/Checkbox';
import { useDrawer } from '@alga-psa/ui';
import { useSchedulingCallbacks } from '@alga-psa/ui/context';
import { IExtendedWorkItem, WorkItemType } from '@alga-psa/types';
import TaskStatusSelect from './TaskStatusSelect';
import PrefillFromTicketDialog from './PrefillFromTicketDialog';
import { getTeams, getTeamAvatarUrlsBatchAction } from '@alga-psa/teams/actions';
import type { ITeam } from '@alga-psa/types';
import { TaskPrefillFields } from '../lib/taskTicketMapping';
import { buildTaskTimeEntryContext } from '../lib/timeEntryContext';
import {
  parseTaskRichTextContent,
  serializeTaskRichTextContent,
  serializeTaskDescriptions,
  isTaskRichTextEmpty,
} from '../lib/taskRichText';
import { useTranslation } from 'react-i18next';
import checklistDnd from './ChecklistDragDrop.module.css';

type ProjectTreeTypes = 'project' | 'phase' | 'status';

export interface TaskFormPrefillData extends TaskPrefillFields {
  pendingTicketLink?: IProjectTicketLinkWithDetails | null;
}

interface TaskFormProps {
  task?: IProjectTask;
  phase: IProjectPhase;
  phases?: IProjectPhase[];
  onClose: () => void;
  onSubmit: (task: IProjectTask | null) => void;
  projectStatuses: ProjectStatus[];
  defaultStatus?: ProjectStatus;
  users: IUser[];
  mode: 'create' | 'edit';
  onPhaseChange: (phaseId: string) => void;
  inDrawer?: boolean;
  projectTreeData?: any[]; // Add projectTreeData prop
  prefillData?: TaskFormPrefillData;
  onCommentCountChange?: (taskId: string, count: number) => void;
  printButton?: React.ReactNode;
  printableHeader?: React.ReactNode;
  printTitle?: string;
}

export default function TaskForm({
  task,
  phase,
  phases,
  onClose,
  onSubmit,
  projectStatuses,
  defaultStatus,
  users,
  mode,
  onPhaseChange,
  inDrawer = false,
  projectTreeData = [],
  prefillData,
  onCommentCountChange,
  printButton,
  printableHeader,
  printTitle,
}: TaskFormProps): React.JSX.Element {
  const { t } = useTranslation(['features/projects', 'common']);
  const { createDocumentAssociations, deleteDocument, removeDocumentAssociations } = useDocumentsCrossFeature();
  const dependenciesRef = useRef<TaskDependenciesRef>(null);
  const ticketLinksRef = useRef<TaskTicketLinksRef>(null);
  // Ref to the BlockNote editor instance; used to read the normalized document
  // for dirty-check comparisons so they're immune to BlockNote adding default
  // props / block IDs on initial load.
  const blockNoteEditorRef = useRef<BlockNoteEditor<any, any, any> | null>(null);
  // Serialized baseline captured after the editor normalizes initial content.
  // Compared against the live editor.document in hasChanges().
  const initialDescriptionSerializedRef = useRef<string | null>(null);
  const [currentUserId, setCurrentUserId] = useState<string>('');
  const [taskName, setTaskName] = useState(task?.task_name || prefillData?.task_name || '');
  const [descriptionContent, setDescriptionContent] = useState<PartialBlock[]>(() =>
    parseTaskRichTextContent(task?.description_rich_text ?? task?.description ?? prefillData?.description ?? null)
  );
  const [projectTreeOptions, setProjectTreeOptions] = useState<Array<TreeSelectOption<'project' | 'phase' | 'status'>>>([]);
  const [selectedPhaseId, setSelectedPhaseId] = useState<string>(phase.phase_id);
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [checklistItems, setChecklistItems] = useState<Omit<ITaskChecklistItem, 'tenant'>[]>(task?.checklist_items || []);
  const [isEditingChecklist, setIsEditingChecklist] = useState(false);
  const [assignedUser, setAssignedUser] = useState<string | null>(task?.assigned_to ?? prefillData?.assigned_to ?? null);
  const [assignedTeamId, setAssignedTeamId] = useState<string | null>(task?.assigned_team_id ?? null);
  const [teamAvatarUrl, setTeamAvatarUrl] = useState<string | null>(null);
  const [isRemoveTeamDialogOpen, setIsRemoveTeamDialogOpen] = useState(false);
  const [pendingSwitchTeamId, setPendingSwitchTeamId] = useState<string | null>(null);
  const [descriptionEditorKey, setDescriptionEditorKey] = useState(0);
  const [selectedPhase, setSelectedPhase] = useState<IProjectPhase>(phase);
  const [showMoveConfirmation, setShowMoveConfirmation] = useState(false);
  const [showCancelConfirm, setShowCancelConfirm] = useState(false);
  const [showDocumentCleanupConfirm, setShowDocumentCleanupConfirm] = useState(false);
  const [isDeletingDocuments, setIsDeletingDocuments] = useState(false);
  const [showTicketCleanupConfirm, setShowTicketCleanupConfirm] = useState(false);
  const [isDeletingTickets, setIsDeletingTickets] = useState(false);
  const [sessionCreatedTickets, setSessionCreatedTickets] = useState<{ ticket_id: string; ticket_number: string; title: string }[]>([]);
  const [tempTaskId] = useState<string>(`temp-${Date.now()}`);
  const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
  const { launchTimeEntry } = useSchedulingCallbacks();
  // Convert from minutes to hours for display
  const [estimatedHours, setEstimatedHours] = useState<number>(
    task?.estimated_hours !== undefined && task?.estimated_hours !== null
      ? Number(task?.estimated_hours) / 60
      : prefillData?.estimated_hours ?? 0
  );
  const [actualHours, setActualHours] = useState<number>(Number(task?.actual_hours) / 60 || 0);
  const [dueDate, setDueDate] = useState<Date | undefined>(
    task?.due_date
      ? new Date(task.due_date)
      : prefillData?.due_date ?? undefined
  );
  const [taskResources, setTaskResources] = useState<any[]>(task?.task_id ? [] : []);
  const [teams, setTeams] = useState<ITeam[]>([]);
  const [initialTaskResources, setInitialTaskResources] = useState<any[]>([]);
  const [resourcesLoaded, setResourcesLoaded] = useState(false); // Track if resources have been loaded
  const [tempTaskResources, setTempTaskResources] = useState<any[]>(() => {
    if (prefillData?.additional_agents?.length) {
      return prefillData.additional_agents.map(agent => {
        const nameParts = agent.name?.split(' ') ?? [];
        return {
          additional_user_id: agent.user_id,
          first_name: nameParts[0] || '',
          last_name: nameParts.slice(1).join(' ') || '',
          assignment_id: `temp-${Date.now()}-${agent.user_id}`
        };
      });
    }
    return [];
  });
  const [pendingDocuments, setPendingDocuments] = useState<PendingTaskDocument[]>([]);
  // Track documents added during edit session (for cleanup on cancel)
  const [sessionAddedDocuments, setSessionAddedDocuments] = useState<PendingTaskDocument[]>([]);

  // Ref to prevent race conditions when rapidly adding/removing agents
  const isProcessingAgentsRef = useRef(false);
  const [taskTags, setTaskTags] = useState<ITag[]>([]);
  const [pendingTags, setPendingTags] = useState<PendingTag[]>([]);
  const [pendingTicketLinks, setPendingTicketLinks] = useState<IProjectTicketLinkWithDetails[]>(() => {
    if (task?.ticket_links?.length) return task.ticket_links;
    if (prefillData?.pendingTicketLink) return [prefillData.pendingTicketLink];
    return [];
  });
  // Dirty-check baseline for ticket links. Initialized from the prop, then
  // replaced with the post-fetch result so we don't flag a "change" just
  // because TaskTicketLinks refreshed possibly-stale cached data from the
  // kanban board.
  const initialTicketLinkIdsRef = useRef<Set<string>>(
    new Set(task?.ticket_links?.map((link) => link.ticket_id) ?? [])
  );
  const handleInitialTicketLinksLoaded = useCallback((links: IProjectTicketLinkWithDetails[]) => {
    initialTicketLinkIdsRef.current = new Set(links.map((link) => link.ticket_id));
  }, []);
  const [editingChecklistItemId, setEditingChecklistItemId] = useState<string | null>(null);
  const [draggedChecklistId, setDraggedChecklistId] = useState<string | null>(null);
  const [dragOverChecklistId, setDragOverChecklistId] = useState<string | null>(null);
  const [checklistDropPosition, setChecklistDropPosition] = useState<'before' | 'after' | null>(null);
  const [recentlyDroppedChecklistId, setRecentlyDroppedChecklistId] = useState<string | null>(null);
  // Tracks whether the latest mousedown landed inside a checklist drag handle.
  // dragstart fires on the wrapper (the draggable element) and its e.target is
  // the wrapper, not the original mousedown target — so we capture that origin
  // here on mousedown and use it to gate dragstart.
  const checklistDragOriginIsHandleRef = useRef(false);
  const [isCrossProjectMove, setIsCrossProjectMove] = useState<boolean>(false);
  const [selectedDuplicatePhaseId, setSelectedDuplicatePhaseId] = useState<string | null>(null);
  const [showDuplicateConfirm, setShowDuplicateConfirm] = useState(false); // State for duplicate dialog
  const [showDependencyConfirmation, setShowDependencyConfirmation] = useState(false);
  const [pendingSubmit, setPendingSubmit] = useState<React.FormEvent | null>(null);
  const [showPrefillDialog, setShowPrefillDialog] = useState(false);
  const [taskTypes, setTaskTypes] = useState<ITaskType[]>([]);
  const [selectedTaskType, setSelectedTaskType] = useState<string>(task?.task_type_key || 'task');
  const [initialTaskType] = useState<string>(task?.task_type_key || 'task');
  const [allProjectTasks, setAllProjectTasks] = useState<IProjectTask[]>([]);
  const [duplicateTaskDetails, setDuplicateTaskDetails] = useState<{
    originalTaskId: string;
    originalTaskName: string;
    targetPhaseId: string;
    targetPhaseName: string;
    targetStatusId: string | null;
    hasChecklist: boolean;
    hasPrimaryAssignee: boolean;
    additionalAssigneeCount: number;
    ticketLinkCount: number;
  } | null>(null);
  const [priorities, setPriorities] = useState<IPriority[]>([]);
  const [selectedPriorityId, setSelectedPriorityId] = useState<string | null>(task?.priority_id ?? null);
  const [availableServices, setAvailableServices] = useState<IService[]>([]);
  const [selectedServiceId, setSelectedServiceId] = useState<string | null>(task?.service_id ?? null);
  const [taskDependencies, setTaskDependencies] = useState<{
    predecessors: IProjectTaskDependency[];
    successors: IProjectTaskDependency[];
  }>({ predecessors: [], successors: [] });

  const { openDrawer, closeDrawer } = useDrawer();
  const { renderPrioritySelect, deleteTicket } = useTicketIntegration();

  const [selectedStatusId, setSelectedStatusId] = useState<string>(
    task?.project_status_mapping_id ||
    defaultStatus?.project_status_mapping_id ||
    projectStatuses[0]?.project_status_mapping_id
  );
  const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState(false);
  const [validationErrors, setValidationErrors] = useState<string[]>([]);

  // Capture the BlockNote-normalized description as the dirty-check baseline
  // after the editor has finished normalizing initial content. Without this,
  // opening a task and changing nothing can flag "unsaved changes" because
  // parseTaskRichTextContent's output differs from the editor's normalized
  // form (BlockNote assigns block IDs, adds default props, etc.).
  useEffect(() => {
    initialDescriptionSerializedRef.current = null;
    let frameId: number | null = null;
    const capture = () => {
      const editor = blockNoteEditorRef.current;
      if (editor) {
        const blocks = editor.document as PartialBlock[];
        initialDescriptionSerializedRef.current = isTaskRichTextEmpty(blocks)
          ? ''
          : serializeTaskRichTextContent(blocks);
        frameId = null;
        return;
      }
      // Editor not yet attached — try again on the next frame.
      frameId = requestAnimationFrame(capture);
    };
    frameId = requestAnimationFrame(capture);
    return () => {
      if (frameId !== null) cancelAnimationFrame(frameId);
    };
  }, [task?.task_id, descriptionEditorKey]);

  const taskFormT = (key: string, fallback: string, options?: Record<string, unknown>) =>
    t(`taskForm.${key}`, { defaultValue: fallback, ...(options ?? {}) });

  const handlePrefillFromTicket = (payload: {
    prefillData: TaskFormPrefillData;
    ticket: {
      ticket_id: string;
      ticket_number: string;
      title: string;
      status_name?: string;
      is_closed?: boolean;
      closed_at?: string | null;
    };
    shouldLink: boolean;
  }) => {
    const { prefillData, ticket, shouldLink } = payload;
    setTaskName(prefillData.task_name);
    setDescriptionContent(parseTaskRichTextContent(prefillData.description || null));
    setDescriptionEditorKey(prev => prev + 1);
    setAssignedUser(prefillData.assigned_to);
    setDueDate(prefillData.due_date ?? undefined);
    setEstimatedHours(prefillData.estimated_hours);

    if (prefillData.additional_agents?.length) {
      setTempTaskResources(prefillData.additional_agents.map(agent => {
        const nameParts = agent.name?.split(' ') ?? [];
        return {
          additional_user_id: agent.user_id,
          first_name: nameParts[0] || '',
          last_name: nameParts.slice(1).join(' ') || '',
          assignment_id: `temp-${Date.now()}-${agent.user_id}`
        };
      }));
    }

    if (shouldLink) {
      const newLink: IProjectTicketLinkWithDetails = {
        link_id: `temp-${Date.now()}`,
        task_id: 'temp',
        ticket_id: ticket.ticket_id,
        ticket_number: ticket.ticket_number,
        title: ticket.title,
        created_at: new Date(),
        project_id: phase.project_id,
        phase_id: phase.phase_id,
        status_name: ticket.status_name || 'New',
        is_closed: ticket.is_closed ?? ticket.closed_at != null
      };
      // Use the ref to inject the link into TaskTicketLinks' internal state,
      // which also calls onLinksChange to keep pendingTicketLinks in sync
      if (ticketLinksRef.current) {
        ticketLinksRef.current.addExternalLink(newLink);
      } else {
        // Fallback: directly update pendingTicketLinks
        setPendingTicketLinks((prev) => {
          if (prev.some(link => link.ticket_id === ticket.ticket_id)) return prev;
          return [...prev, newLink];
        });
      }
    }
  };

  useEffect(() => {
    const fetchInitialData = async () => {
      try {
        const user = await getCurrentUser();
        if (user) {
          setCurrentUserId(user.user_id);
        }

        // Fetch priorities for project tasks
        const allPriorities = await getAllPriorities('project_task');
        setPriorities(allPriorities);

        // Fetch task types
        const types = await getTaskTypes();
        setTaskTypes(types);

        // Fetch services for time entry prefill
        const servicesResponse = await getServices(1, 999);
        setAvailableServices(servicesResponse.services);

        // Fetch all tasks in the project
        if (phase.project_id) {
          try {
            const projectDetailsResult = await getProjectDetails(phase.project_id);
            if (isActionPermissionError(projectDetailsResult)) {
              handleError(projectDetailsResult.permissionError);
            } else {
              setAllProjectTasks(projectDetailsResult.tasks);
            }
          } catch (error) {
            console.error('Error fetching project tasks:', error);
          }
        }

        if (task?.task_id) {
          // Use checklist items and resources from the task object if they exist
          if (task.checklist_items !== undefined) {
            console.log('Using checklist items from task object');
            setChecklistItems(task.checklist_items);
          } else {
            // Only fetch if not available on the task object
            console.log('Fetching checklist items from API');
            const existingChecklistItems = await getTaskChecklistItems(task.task_id);
            setChecklistItems(existingChecklistItems);
          }

          // Always fetch resources to ensure we have the latest data
          const resources = await getTaskResourcesAction(task.task_id);
          setTaskResources(resources);
          setInitialTaskResources(resources); // Track initial state for hasChanges()
          setResourcesLoaded(true); // Mark resources as loaded

          // Fetch tags
          const tags = await findTagsByEntityId(task.task_id, 'project_task');
          setTaskTags(tags);
        }
      } catch (error) {
        console.error('Error fetching initial data:', error);
      }
    };
    fetchInitialData();
  }, [task?.task_id]);

  useEffect(() => {
    const loadTeams = async () => {
      try {
        const fetchedTeams = await getTeams();
        setTeams(fetchedTeams);
      } catch (error) {
        console.error('Failed to load teams:', error);
      }
    };
    loadTeams();
  }, []);

  // Fetch team avatar URL when assigned team changes
  useEffect(() => {
    if (!assignedTeamId) {
      setTeamAvatarUrl(null);
      return;
    }
    const team = teams.find(t => t.team_id === assignedTeamId);
    if (!team?.tenant) return;

    getTeamAvatarUrlsBatchAction([assignedTeamId], team.tenant)
      .then(result => {
        if (result instanceof Map) {
          setTeamAvatarUrl(result.get(assignedTeamId) ?? null);
        } else {
          setTeamAvatarUrl((result as Record<string, string | null>)[assignedTeamId] ?? null);
        }
      })
      .catch(() => setTeamAvatarUrl(null));
  }, [assignedTeamId, teams]);

  const teamMembersOnTask = useMemo(() => {
    return taskResources.filter(r => r.role === 'team_member');
  }, [taskResources]);

  // Separate effect for loading task dependencies
  useEffect(() => {
    const loadDependencies = async () => {
      if (task?.task_id && mode === 'edit') {
        try {
          console.log('Fetching task dependencies from API');
          const dependencies = await getTaskDependencies(task.task_id);
          setTaskDependencies(dependencies);
        } catch (error) {
          console.error('Error fetching task dependencies:', error);
          // Set empty dependencies on error to avoid breaking the UI
          setTaskDependencies({ predecessors: [], successors: [] });
        }
      }
    };

    loadDependencies();
  }, [task?.task_id, mode]);

  // Use provided projectTreeData if available, otherwise fetch it
  useEffect(() => {
    const fetchProjectsData = async () => {
      if (mode === 'edit') {
        if (projectTreeData && projectTreeData.length > 0) {
          // Use the provided project tree data
          setProjectTreeOptions(projectTreeData);
        } else {
          // Fall back to fetching the data if not provided
          try {
            const treeData = await getProjectTreeData();
            if (isActionPermissionError(treeData)) {
              handleError(treeData.permissionError);
              setProjectTreeOptions([]);
            } else if (treeData && Array.isArray(treeData) && treeData.length > 0) {
              setProjectTreeOptions(treeData);
            } else {
              console.error('Invalid or empty tree data received:', treeData);
              toast.error(t('taskForm.noValidProjects', 'No projects available with valid phases and statuses'));
              setProjectTreeOptions([]);
            }
          } catch (error) {
            handleError(error, t('taskForm.loadProjectDataFailed', 'Error loading project data. Please try again.'));
            setProjectTreeOptions([]);
          }
        }
      }
    };

    fetchProjectsData();
  }, [mode, projectTreeData, t]);

  const handleTreeSelectChange = async (
    value: string,
    type: ProjectTreeTypes,
    excluded: boolean,
    path?: TreeSelectPath
  ) => {
    if (!path) {
      console.error('Path is undefined in tree select change');
      return;
    }

    // Get IDs from the path
    const phaseId = path['phase'];
    const statusId = path['status'];

    if (!phaseId) {
      console.error('Phase ID is missing from path');
      return;
    }

    // Find the selected phase from tree options
    const findPhaseInTree = (options: TreeSelectOption<ProjectTreeTypes>[]): TreeSelectOption<ProjectTreeTypes> | undefined => {
      for (const opt of options) {
        if (opt.type === 'phase' && opt.value === phaseId) {
          return opt;
        }
        if (opt.children) {
          const found = findPhaseInTree(opt.children);
          if (found) return found;
        }
      }
      return undefined;
    };

    const selectedPhaseOption = findPhaseInTree(projectTreeOptions);
    if (!selectedPhaseOption) return;

    // Update phase ID
    setSelectedPhaseId(phaseId);

    // Find the project ID of the selected phase
    const findProjectId = (options: TreeSelectOption<ProjectTreeTypes>[]): string | undefined => {
      for (const opt of options) {
        if (opt.type === 'project' && opt.children?.some(child => child.value === phaseId)) {
          return opt.value;
        }
        if (opt.children) {
          const found = findProjectId(opt.children);
          if (found) return found;
        }
      }
      return undefined;
    };

    const newProjectId = findProjectId(projectTreeOptions);
    const currentProjectId = phase.project_id;
    const isMovingToNewProject = Boolean(newProjectId && currentProjectId !== newProjectId);
    setIsCrossProjectMove(isMovingToNewProject);

    // Update status ID based on the following priority:
    // 1. Status from path (if explicitly selected)
    // 2. For same-project moves:
    //    a. Current task's status (if valid)
    //    b. Default status
    //    c. First available status
    // 3. For cross-project moves:
    //    Let moveTaskToPhase handle status mapping
    if (statusId) {
      // Status explicitly selected
      setSelectedStatusId(statusId);
    } else if (!isCrossProjectMove) {
      // Same project move - try to keep current status if valid
      const currentStatusId = task?.project_status_mapping_id;
      const currentStatusValid = currentStatusId && projectStatuses.some(s => s.project_status_mapping_id === currentStatusId);

      if (currentStatusValid) {
        setSelectedStatusId(currentStatusId);
      } else if (defaultStatus?.project_status_mapping_id) {
        setSelectedStatusId(defaultStatus.project_status_mapping_id);
      } else if (projectStatuses.length > 0) {
        setSelectedStatusId(projectStatuses[0].project_status_mapping_id);
      }
    } else {
      // Cross-project move - let moveTaskToPhase handle status mapping
      // Keep the current status ID until moveTaskToPhase determines the new one
      setSelectedStatusId(task?.project_status_mapping_id || projectStatuses[0]?.project_status_mapping_id);
    }

    // Show move confirmation if it's a different phase
    if (phaseId !== phase.phase_id) {
      setSelectedPhase({ ...phase, phase_id: phaseId });
      setShowMoveConfirmation(true);
    }

    handlePhaseChange(phaseId);
    onPhaseChange(phaseId);
  };

  const handleMoveConfirm = async () => {
    if (!task) return;

    setIsSubmitting(true);
    try {
      // For cross-project moves, let moveTaskToPhase handle status mapping
      const movedTask = await moveTaskToPhase(
        task.task_id,
        selectedPhaseId,
        isCrossProjectMove ? undefined : selectedStatusId // Only pass status ID for same-project moves
      );

      if (movedTask) {
        // For cross-project moves, use the status mapping that moveTaskToPhase determined
        // Update our local state to match the new status
        if (isCrossProjectMove) {
          setSelectedStatusId(movedTask.project_status_mapping_id);
        }

        // Update task with all fields preserved
        const taskData: Partial<IProjectTask> = {
          task_name: taskName,
          ...serializeTaskDescriptions(descriptionContent),
          assigned_to: assignedUser || null,
          assigned_team_id: assignedTeamId || null,
          estimated_hours: Math.round(estimatedHours * 60), // Convert hours to minutes for storage
          actual_hours: Math.round(actualHours * 60), // Convert hours to minutes for storage
          due_date: dueDate || null,
          checklist_items: checklistItems,
          phase_id: selectedPhaseId,
          project_status_mapping_id: movedTask.project_status_mapping_id, // Always use the mapping from moveTaskToPhase
          task_type_key: selectedTaskType
        };
        const updatedTask = await updateTaskWithChecklist(movedTask.task_id, taskData);
        onSubmit(updatedTask);
      }

      toast.success(taskFormT('movedSuccess', 'Task moved successfully'));
      onClose();
    } catch (error) {
      handleError(error, taskFormT('moveFailed', 'Failed to move task'));
    } finally {
      setIsSubmitting(false);
      setShowMoveConfirmation(false);
    }
  };

  const performTeamAssign = async (teamId: string) => {
    setAssignedTeamId(teamId);
    const team = teams.find(t => t.team_id === teamId);
    const leadId = team?.manager_id || team?.members?.find(member => member.role === 'lead')?.user_id || null;

    // Always set team lead as primary agent
    if (leadId) {
      setAssignedUser(leadId);
    }

    if (task?.task_id) {
      try {
        await assignTeamToProjectTask(task.task_id, teamId);
        const resources = await getTaskResourcesAction(task.task_id);
        setTaskResources(resources);
        setInitialTaskResources(resources);
        toast.success(taskFormT('teamAssignedSuccess', 'Team assigned successfully'));
      } catch (error) {
        console.error('Failed to assign team:', error);
        toast.error(taskFormT('assignTeamFailed', 'Failed to assign team'));
      }
    } else {
      // New task: populate tempTaskResources with team members (excluding lead who becomes primary)
      const primaryId = leadId || assignedUser;
      const memberIds = (team?.members || [])
        .map(m => m.user_id)
        .filter(uid => uid && uid !== primaryId);

      const newResources = memberIds.map(uid => {
        const memberUser = users.find(u => u.user_id === uid);
        return {
          additional_user_id: uid,
          first_name: memberUser?.first_name || '',
          last_name: memberUser?.last_name || '',
          assignment_id: `temp-${Date.now()}-${uid}`,
          role: 'team_member'
        };
      });

      // Replace existing temp resources with team members
      setTempTaskResources(newResources);
    }
  };

  const handleAssignTeam = async (teamId: string) => {
    // If a team is already assigned to an existing task, prompt for the
    // remove/keep mode before swapping. The existing team's resources are
    // cleaned per the chosen mode, then the new team is assigned.
    if (task?.task_id && assignedTeamId && assignedTeamId !== teamId) {
      setPendingSwitchTeamId(teamId);
      setIsRemoveTeamDialogOpen(true);
      return;
    }

    await performTeamAssign(teamId);
  };

  const handleRemoveTeamAssignment = async (
    mode: 'remove_all' | 'keep_all' | 'selective',
    keepUserIds?: string[]
  ) => {
    // 'remove_all' takes the team's lead off the task too, so mirror the server
    // and clear the primary agent when it's that lead.
    const removedTeam = teams.find(t => t.team_id === assignedTeamId);
    const removedLeadId =
      removedTeam?.manager_id ||
      removedTeam?.members?.find(m => m.role === 'lead')?.user_id ||
      null;
    const shouldClearPrimary =
      mode === 'remove_all' && !!removedLeadId && assignedUser === removedLeadId;

    if (!task?.task_id) {
      // New task: just clear local state (nothing persisted yet)
      setAssignedTeamId(null);
      setTempTaskResources(prev => prev.filter(r => r.role !== 'team_member'));
      if (shouldClearPrimary) setAssignedUser(null);
      return;
    }
    try {
      await removeTeamFromProjectTask(task.task_id, { mode, keepUserIds });
      setAssignedTeamId(null);
      if (shouldClearPrimary) setAssignedUser(null);
      const resources = await getTaskResourcesAction(task.task_id);
      setTaskResources(resources);
      setInitialTaskResources(resources);
      toast.success(taskFormT('teamRemovedSuccess', 'Team removed successfully'));
    } catch (error) {
      console.error('Failed to remove team assignment:', error);
      toast.error(taskFormT('teamRemoveFailed', 'Failed to remove team assignment'));
      throw error;
    }
  };

  const clearErrorIfSubmitted = () => {
    if (hasAttemptedSubmit) {
      setValidationErrors([]);
    }
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setHasAttemptedSubmit(true);

    const errors: string[] = [];
    if (!taskName.trim()) errors.push(taskFormT('taskNameRequired', 'Task name'));
    const currentAgents = [...taskResources, ...tempTaskResources];
    if (currentAgents.length > 0 && !assignedUser) {
      toast.error(taskFormT('primaryAgentRequired', 'Primary agent is required when additional agents are assigned'));
      return;
    }

    if (errors.length > 0) {
      setValidationErrors(errors);
      return;
    }

    setValidationErrors([]);

    // Check for unsaved dependency selection
    if (dependenciesRef.current?.hasPendingChanges()) {
      setPendingSubmit(e);
      setShowDependencyConfirmation(true);
      return;
    }

    await performSubmit();
  };

  const handleDependencyConfirm = async () => {
    // "Discard changes" — discard the pending dependency selection and save the task
    setShowDependencyConfirmation(false);
    setPendingSubmit(null);
    await performSubmit();
  };

  const handleDependencyCancel = () => {
    // "Continue editing" — go back so user can click the + button
    setShowDependencyConfirmation(false);
    setPendingSubmit(null);
  };

  const performSubmit = async () => {
    setIsSubmitting(true);

    try {
      let resultTask: IProjectTask | null = null;

      // Convert empty string to null for database
      const finalAssignedTo = !assignedUser || assignedUser === '' ? null : assignedUser;

      if (mode === 'edit' && task?.task_id) {
        // Check if phase or status actually changed
        const phaseChanged = task.phase_id !== selectedPhaseId;
        const statusChanged = task.project_status_mapping_id !== selectedStatusId;

        let taskToUpdate = task;

        // Only call moveTaskToPhase if phase or status actually changed
        if (phaseChanged || statusChanged) {
          // Phase or status changed - handle the move
          const movedTask = await moveTaskToPhase(
            task.task_id,
            selectedPhaseId,
            isCrossProjectMove ? undefined : selectedStatusId
          );

          if (movedTask) {
            taskToUpdate = movedTask;
            // For cross-project moves, use the status mapping that moveTaskToPhase determined
            if (isCrossProjectMove) {
              setSelectedStatusId(movedTask.project_status_mapping_id);
            }
          }
        }

        // Always update the task data (whether moved or not)
        const taskData: Partial<IProjectTask> = {
          task_name: taskName,
          ...serializeTaskDescriptions(descriptionContent),
          assigned_to: finalAssignedTo,
          assigned_team_id: assignedTeamId || null,
          estimated_hours: Math.round(estimatedHours * 60), // Convert hours to minutes for storage
          actual_hours: Math.round(actualHours * 60), // Convert hours to minutes for storage
          due_date: dueDate || null,
          priority_id: selectedPriorityId,
          checklist_items: checklistItems,
          project_status_mapping_id: statusChanged ? taskToUpdate.project_status_mapping_id : task.project_status_mapping_id,
          task_type_key: selectedTaskType,
          order_key: task.order_key, // Always preserve the original order_key
          service_id: selectedServiceId
        };
        resultTask = await updateTaskWithChecklist(taskToUpdate.task_id, taskData);

        // Save any temporarily stored additional agents (added while task had no primary agent)
        if (tempTaskResources.length > 0) {
          try {
            await addTaskResourcesAction(taskToUpdate.task_id, tempTaskResources.map(r => r.additional_user_id));
          } catch (agentError) {
            console.error('Failed to add additional agents:', agentError);
          }
        }

        onSubmit(resultTask);
        onClose();
      } else {
        // Create mode
        const taskData = {
          task_name: taskName,
          project_status_mapping_id: selectedStatusId,
          wbs_code: `${phase.wbs_code}.0`,
          ...serializeTaskDescriptions(descriptionContent),
          assigned_to: finalAssignedTo,
          assigned_team_id: assignedTeamId || null,
          estimated_hours: Math.round(estimatedHours * 60), // Convert hours to minutes for storage
          actual_hours: Math.round(actualHours * 60), // Convert hours to minutes for storage
          due_date: dueDate || null, // Use selected due date or null
          priority_id: selectedPriorityId,
          phase_id: phase.phase_id,
          task_type_key: selectedTaskType,
          service_id: selectedServiceId
        };

        // Create the task first
        resultTask = await addTaskToPhase(phase.phase_id, taskData, checklistItems);

        if (resultTask) {
          let linkingFailed = false;
          try {
            if (assignedTeamId) {
              await assignTeamToProjectTask(resultTask.task_id, assignedTeamId);
            }
            // Add task resources
            if (tempTaskResources.length > 0) {
              await addTaskResourcesAction(resultTask.task_id, tempTaskResources.map(r => r.additional_user_id));
            }

            // Add ticket links using the actual task ID and phase ID
            for (const link of pendingTicketLinks) {
              await addTicketLinkAction(phase.project_id, resultTask.task_id, link.ticket_id, phase.phase_id);
            }

            // Create document associations for pending documents
            if (pendingDocuments.length > 0) {
              const documentIds = pendingDocuments.map(d => d.document_id);
              await createDocumentAssociations(resultTask.task_id, 'project_task', documentIds);
            }

            // Save pending dependencies
            // Always pass new task as predecessor — addTaskDependency handles the swap for 'blocked_by'
            if (dependenciesRef.current) {
              const pendingDeps = dependenciesRef.current.getPendingDependencies();
              for (const dep of pendingDeps) {
                await addTaskDependency(resultTask.task_id, dep.targetTaskId, dep.dependencyType, 0, undefined);
              }
            }
          } catch (error) {
            handleError(error, taskFormT('linkingPartialFailure', 'Task created but failed to link some items'));
            linkingFailed = true;
          }

          // Create tags for the new task (always attempt, even if linking failed)
          let createdTags: typeof resultTask.tags = [];
          if (pendingTags.length > 0) {
            try {
              createdTags = await createTagsForEntity(resultTask.task_id, 'project_task', pendingTags);
              if (createdTags.length < pendingTags.length) {
                toast.error(taskFormT('tagCreationPartialFailure', '{{count}} tag(s) could not be created', {
                  count: pendingTags.length - createdTags.length,
                }));
              }
            } catch (tagError) {
              console.error("Error creating task tags:", tagError);
            }
          }

          // Submit task with tags
          onSubmit({ ...resultTask, tags: createdTags });
          onClose();
        }
      }
    } catch (error) {
      handleError(error, taskFormT('saveFailed', 'Failed to save task'));
    } finally {
      setIsSubmitting(false);
    }
  };

  const handlePhaseChange = (phaseId: string) => {
    if (!phases) return;

    const newPhase = phases.find(p => p.phase_id === phaseId);
    if (newPhase && newPhase.phase_id !== phase.phase_id) {
      setSelectedPhase(newPhase);
      setShowMoveConfirmation(true);
    }
  };

  const hasChanges = (): boolean => {
    if (mode === 'create') {
      // For new tasks, only show confirmation if user has entered any data
      if (taskName.trim() !== '') return true;
      if (!isTaskRichTextEmpty(descriptionContent)) return true;
      if (assignedUser !== null && assignedUser !== currentUserId) return true; // Only if explicitly selected
      if (assignedTeamId !== null) return true;
      if (checklistItems.length > 0) return true;
      if (estimatedHours > 0) return true; // Only if actually entered a value
      if (actualHours > 0) return true; // Only if actually entered a value
      if (dueDate !== undefined) return true;
      if (tempTaskResources.length > 0) return true;
      if (pendingTicketLinks.length > 0) return true;
      if (selectedPriorityId !== null) return true; // User explicitly selected a priority
      if (selectedTaskType !== initialTaskType) return true; // Only if changed from initial value
      if (selectedServiceId !== null) return true; // User explicitly selected a service
      if (pendingTags.length > 0) return true;
      if (pendingDocuments.length > 0) return true; // User has added pending documents
      if (dependenciesRef.current?.getPendingDependencies()?.length) return true; // User has added pending dependencies
      return false; // No changes detected
    }

    // Compare all form fields with their original values for edit mode
    if (!task) return false;

    // Helper to normalize null/undefined/empty string for comparison
    const normalizeString = (val: string | null | undefined): string => val || '';
    const normalizeNullable = <T,>(val: T | null | undefined): T | null => val ?? null;

    if (taskName !== (task.task_name || '')) return true;
    // Read the current description from the live BlockNote editor when
    // available so both sides of the comparison use BlockNote's normalized
    // form. Fall back to React state + raw parse when the editor hasn't
    // initialized yet (e.g. very early dirty-checks before first paint).
    const liveBlocks = (blockNoteEditorRef.current?.document as PartialBlock[] | undefined) ?? descriptionContent;
    const currentDescriptionSerialized = isTaskRichTextEmpty(liveBlocks)
      ? ''
      : serializeTaskRichTextContent(liveBlocks);
    const originalDescriptionSerialized = initialDescriptionSerializedRef.current
      ?? (!(task.description_rich_text ?? task.description)
        ? ''
        : serializeTaskRichTextContent(parseTaskRichTextContent(task.description_rich_text ?? task.description)));
    if (currentDescriptionSerialized !== originalDescriptionSerialized) return true;
    if (selectedPhaseId !== task.phase_id) return true;
    if (selectedStatusId !== task.project_status_mapping_id) return true;
    // Use || 0 to handle null/undefined consistently with initial state
    if (estimatedHours !== (Number(task.estimated_hours) / 60 || 0)) return true;
    if (actualHours !== (Number(task.actual_hours) / 60 || 0)) return true;
    if (normalizeNullable(assignedUser) !== normalizeNullable(task.assigned_to)) return true;
    if (normalizeNullable(assignedTeamId) !== normalizeNullable(task.assigned_team_id)) return true;
    if (normalizeNullable(selectedPriorityId) !== normalizeNullable(task.priority_id)) return true;
    if (normalizeNullable(selectedServiceId) !== normalizeNullable(task.service_id)) return true;

    // Compare checklist items - use 0 as fallback for undefined length
    if (checklistItems.length !== (task.checklist_items?.length ?? 0)) return true;
    for (let i = 0; i < checklistItems.length; i++) {
      const current = checklistItems[i];
      const original = task.checklist_items?.[i];
      if (!original) return true;
      if (current.item_name !== original.item_name) return true;
      if (current.completed !== original.completed) return true;
    }

    // Compare resources - use initialTaskResources to compare against the loaded state
    // Only compare if resources have been loaded to avoid false positives during initial load
    if (task.task_id && resourcesLoaded) {
      // For existing tasks, compare current resources with initial resources
      if (taskResources.length !== initialTaskResources.length) return true;

      const currentUserIds = new Set(taskResources.map(r => r.additional_user_id));
      const initialUserIds = new Set(initialTaskResources.map(r => r.additional_user_id));

      if (currentUserIds.size !== initialUserIds.size) return true;
      for (const id of currentUserIds) {
        if (!initialUserIds.has(id)) return true;
      }
    }

    // Compare ticket links - only compare ticket IDs since other fields might differ in format.
    // Baseline is the post-fetch result captured by handleInitialTicketLinksLoaded so a stale
    // task.ticket_links prop (e.g. from cached kanban state) doesn't trigger a false dirty flag.
    const currentTicketIds = new Set(pendingTicketLinks.map((link): string => link.ticket_id));
    const originalTicketIds = initialTicketLinkIdsRef.current;

    if (currentTicketIds.size !== originalTicketIds.size) return true;
    for (const id of currentTicketIds) {
      if (!originalTicketIds.has(id)) return true;
    }

    // Check if documents were added during this edit session
    if (sessionAddedDocuments.length > 0) return true;

    return false;
  };

  const handleCancelClick = (e?: React.MouseEvent) => {
    if (e) {
      e.preventDefault();
    }

    if (hasChanges()) {
      setShowCancelConfirm(true);
    } else {
      onClose();
    }
  };

  const proceedAfterDocumentCleanup = () => {
    // After documents are handled, check for tickets that need cleanup
    if (sessionCreatedTickets.length > 0) {
      setShowTicketCleanupConfirm(true);
    } else {
      onClose();
    }
  };

  const handleCancelConfirm = () => {
    setShowCancelConfirm(false);
    // Check for NEW documents (uploaded or created) that need cleanup prompt
    // Linked documents already existed in the system, so they don't need cleanup
    const docsToCleanup = mode === 'create' ? pendingDocuments : sessionAddedDocuments;
    const newDocsToCleanup = docsToCleanup.filter(d => d.type === 'uploaded' || d.type === 'block');
    if (newDocsToCleanup.length > 0) {
      setShowDocumentCleanupConfirm(true);
    } else {
      proceedAfterDocumentCleanup();
    }
  };

  const handleCancelDismiss = () => {
    setShowCancelConfirm(false);
  };

  const handleDocumentCleanupKeep = () => {
    // Keep documents in the system, proceed to ticket cleanup check
    setShowDocumentCleanupConfirm(false);
    proceedAfterDocumentCleanup();
  };

  const handleDocumentCleanupDelete = async () => {
    setIsDeletingDocuments(true);
    try {
      // Only process uploaded and created documents - linked docs are ignored
      const docsToDelete = mode === 'create'
        ? pendingDocuments.filter(d => d.type === 'uploaded' || d.type === 'block')
        : sessionAddedDocuments.filter(d => d.type === 'uploaded' || d.type === 'block');

      let failureCount = 0;
      for (const doc of docsToDelete) {
        try {
          if (mode === 'edit' && task?.task_id) {
            // In edit mode, first remove the association from the task
            await removeDocumentAssociations(task.task_id, 'project_task', [doc.document_id]);
          }
          // Delete the document from the system
          await deleteDocument(doc.document_id, currentUserId);
        } catch (error) {
          console.error(`Failed to delete document ${doc.document_id}:`, error);
          failureCount++;
        }
      }

      if (failureCount > 0) {
        toast.error(taskFormT('documentCleanupFailure', '{{count}} documents could not be deleted and will remain in Documents', {
          count: failureCount,
        }));
      }
    } finally {
      setIsDeletingDocuments(false);
      setShowDocumentCleanupConfirm(false);
      proceedAfterDocumentCleanup();
    }
  };

  const handleTicketCleanupKeep = () => {
    // Keep tickets in the system, just close the form
    setShowTicketCleanupConfirm(false);
    onClose();
  };

  const handleTicketCleanupDelete = async () => {
    setIsDeletingTickets(true);
    try {
      let failureCount = 0;
      for (const ticket of sessionCreatedTickets) {
        try {
          // Remove any ticket links first to avoid FK constraint violations
          await deleteTaskTicketLinksByTicketIdAction(ticket.ticket_id);
          await deleteTicket(ticket.ticket_id);
        } catch (error) {
          console.error(`Failed to delete ticket ${ticket.ticket_id}:`, error);
          failureCount++;
        }
      }

      if (failureCount > 0) {
        toast.error(taskFormT('ticketCleanupFailure', '{{count}} tickets could not be deleted', {
          count: failureCount,
        }));
      }
    } finally {
      setIsDeletingTickets(false);
      setShowTicketCleanupConfirm(false);
      onClose();
    }
  };

  const handleDialogClose = (open: boolean) => {
    if (!open) {
      handleCancelClick();
    }
  };

  const toggleEditChecklist = (e: React.MouseEvent) => {
    e.stopPropagation();
    setIsEditingChecklist(!isEditingChecklist);
  };

  const addChecklistItem = (insertAtIndex?: number): string => {
    const newItemId = `temp-${Date.now()}`;
    const newItem: Omit<ITaskChecklistItem, 'tenant'> = {
      checklist_item_id: newItemId,
      task_id: task?.task_id || tempTaskId,
      item_name: '',
      description: null,
      assigned_to: null,
      completed: false,
      due_date: null,
      created_at: new Date(),
      updated_at: new Date(),
      order_number: 0, // recomputed below
    };
    setChecklistItems((items) => {
      const next = [...items];
      const at = insertAtIndex === undefined ? next.length : Math.max(0, Math.min(insertAtIndex, next.length));
      next.splice(at, 0, newItem);
      return next.map((it, i) => ({ ...it, order_number: i + 1 }));
    });
    return newItemId;
  };

  const handleChecklistItemKeyDown = (
    e: React.KeyboardEvent<HTMLTextAreaElement>,
    index: number
  ) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      const newId = addChecklistItem(index + 1);
      setEditingChecklistItemId(newId);
    }
  };

  const updateChecklistItem = (index: number, field: keyof ITaskChecklistItem, value: any) => {
    const updatedItems = [...checklistItems];
    updatedItems[index] = { ...updatedItems[index], [field]: value };
    setChecklistItems(updatedItems);
  };

  const removeChecklistItem = (index: number) => {
    const updatedItems = checklistItems.filter((_, i) => i !== index);
    setChecklistItems(updatedItems);
  };

  const resetChecklistDragState = () => {
    setDraggedChecklistId(null);
    setDragOverChecklistId(null);
    setChecklistDropPosition(null);
  };

  const handleChecklistDragStart = (e: React.DragEvent, id: string) => {
    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/plain', id);
    requestAnimationFrame(() => setDraggedChecklistId(id));
  };

  const handleChecklistDragOver = (e: React.DragEvent, id: string) => {
    if (!draggedChecklistId || draggedChecklistId === id) return;
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
    const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
    const position: 'before' | 'after' =
      e.clientY < rect.top + rect.height / 2 ? 'before' : 'after';
    if (dragOverChecklistId !== id || checklistDropPosition !== position) {
      setDragOverChecklistId(id);
      setChecklistDropPosition(position);
    }
  };

  const handleChecklistDrop = (e: React.DragEvent, targetId: string) => {
    e.preventDefault();
    const fromId = draggedChecklistId;
    const position = checklistDropPosition;
    resetChecklistDragState();
    if (!fromId || fromId === targetId || position === null) return;

    let didMove = false;
    setChecklistItems((prev) => {
      const items = [...prev];
      const fromIndex = items.findIndex((item) => item.checklist_item_id === fromId);
      const targetIndex = items.findIndex((item) => item.checklist_item_id === targetId);
      if (fromIndex === -1 || targetIndex === -1) return prev;

      let insertAt = position === 'after' ? targetIndex + 1 : targetIndex;
      if (fromIndex < insertAt) insertAt -= 1;
      if (insertAt === fromIndex) return prev;

      const [moved] = items.splice(fromIndex, 1);
      items.splice(insertAt, 0, moved);
      didMove = true;
      return items.map((it, i) => ({ ...it, order_number: i + 1 }));
    });

    if (didMove) {
      setRecentlyDroppedChecklistId(fromId);
      window.setTimeout(() => setRecentlyDroppedChecklistId((curr) => (curr === fromId ? null : curr)), 400);
    }
  };

  const handleDeleteConfirm = async () => {
    if (!task?.task_id) return;

    setIsSubmitting(true);
    try {
      await deleteTask(task.task_id);
      toast.success(taskFormT('deletedSuccess', 'Task deleted successfully'));
      onSubmit(null);
      onClose();
    } catch (error) {
      handleError(error, taskFormT('deleteFailed', 'Failed to delete task'));
    } finally {
      setIsSubmitting(false);
      setShowDeleteConfirm(false);
    }
  };

  const handleDeleteDismiss = () => {
    setShowDeleteConfirm(false);
  };

  const handleAddTimeEntry = async () => {
    if (!task?.task_id) {
      toast.error(taskFormT('saveBeforeTimeEntry', 'Please save the task before adding time entries'));
      return;
    }

    try {
      const findProjectName = (options: Array<TreeSelectOption<'project' | 'phase' | 'status'>>, phaseId: string): string | undefined => {
        for (const option of options) {
          if (option.type === 'project' && option.children?.some(child => child.value === phaseId)) {
            return typeof option.label === 'string' ? option.label : undefined;
          }
          if (option.children) {
            const nested = findProjectName(option.children, phaseId);
            if (nested) return nested;
          }
        }
        return undefined;
      };

      const projectName = findProjectName(projectTreeOptions, selectedPhaseId);
      const serviceName = availableServices.find(service => service.service_id === selectedServiceId)?.service_name ?? null;

      await launchTimeEntry({
        openDrawer,
        closeDrawer,
        context: buildTaskTimeEntryContext({
          taskId: task.task_id,
          taskName: taskName || task.task_name,
          projectName,
          phaseName: selectedPhase.phase_name,
          serviceId: selectedServiceId,
          serviceName,
        }),
      });
    } catch (error) {
      handleError(error, taskFormT('prepareTimeEntryFailed', 'Failed to prepare time entry. Please try again.'));
    }
  };

  // Add one or more users to the task as additional agents. Persists in a single
  // batched request when the task exists with an unchanged primary; otherwise
  // stages them locally to be saved with the task (deferred to avoid the
  // assigned_to != additional_user_id CHECK while the primary is mid-change).
  const addAgents = async (userIds: string[]) => {
    const existingIds = new Set(
      [...taskResources, ...tempTaskResources].map(r => r.additional_user_id)
    );
    const toAdd = Array.from(new Set(userIds))
      .filter(id => id && id !== assignedUser && !existingIds.has(id));
    if (toAdd.length === 0) return;

    const primaryChanged = task?.assigned_to !== assignedUser;
    if (task?.task_id && assignedUser && !primaryChanged) {
      // Existing task with unchanged primary agent: one batched insert + refetch.
      try {
        const updatedResources = await addTaskResourcesAction(task.task_id, toAdd);
        setTaskResources(updatedResources);
        toast.success(taskFormT('agentAddedSuccess', 'Agent added successfully'));
      } catch (error: any) {
        handleError(error, taskFormT('addAgentFailed', 'Failed to add agent'));
      }
    } else {
      // New task, no primary agent, or primary has changed: stage locally and
      // persist on save (makes no requests, so no per-member round-trips).
      const tempResources = toAdd
        .map(userId => {
          const selectedUser = users.find(u => u.user_id === userId);
          return selectedUser
            ? {
                additional_user_id: userId,
                first_name: selectedUser.first_name,
                last_name: selectedUser.last_name,
                assignment_id: `temp-${Date.now()}-${userId}`
              }
            : null;
        })
        .filter((r): r is NonNullable<typeof r> => r !== null);
      if (tempResources.length > 0) {
        setTempTaskResources(prev => [...prev, ...tempResources]);
        toast.success(taskFormT('agentPendingSave', 'Agent will be added when task is saved'));
      }
    }
  };

  const handleAddAgent = async (userId: string) => {
    await addAgents([userId]);
  };

  // Add every member of a team (including its lead) onto the task as additional
  // agents, leaving the primary agent untouched. Used by the Additional Agents
  // picker; team *assignment* (lead → primary) goes through performTeamAssign.
  const addTeamMembersAsAgents = async (teamId: string) => {
    const team = teams.find(t => t.team_id === teamId);
    if (!team) return;
    const memberIds = [...(team.members || []).map(m => m.user_id), team.manager_id || null]
      .filter((id): id is string => Boolean(id));
    // addAgents drops the primary (CHECK constraint) and anyone already present.
    await addAgents(memberIds);
  };

  const handleRemoveAgent = async (assignmentId: string) => {
    try {
      if (assignmentId.startsWith('temp-')) {
        setTempTaskResources(prev => prev.filter(r => r.assignment_id !== assignmentId));
      } else {
        await removeTaskResourceAction(assignmentId);
        setTaskResources(taskResources.filter(r => r.assignment_id !== assignmentId));
      }
    } catch (error) {
      handleError(error, taskFormT('removeAgentFailed', 'Failed to remove agent'));
    }
  };

  // Placeholder handler for the duplicate dropdown
  const handleDuplicateTreeSelectChange = (
    value: string,
    type: ProjectTreeTypes,
    excluded: boolean,
    path?: TreeSelectPath
  ) => {
    if (!path || !task) return; // Need the original task in edit mode
    const targetPhaseId = path['phase'];
    const targetStatusId = path['status'] || null;

    if (targetPhaseId) {
      console.log("Duplicate destination selected:", targetPhaseId, "Status:", targetStatusId);
      setSelectedDuplicatePhaseId(targetPhaseId);

      // Find target phase name from tree data (similar to move logic)
      const findPhaseName = (options: TreeSelectOption<ProjectTreeTypes>[], id: string): string | undefined => {
        for (const opt of options) {
          if (opt.type === 'phase' && opt.value === id) {
            // Handle both string and ReactNode labels
            if (typeof opt.label === 'string') {
              return opt.label;
            } else if (React.isValidElement(opt.label) && (opt.label.props as { children?: React.ReactNode }).children) {
              // Extract text from JSX element
              const children = (opt.label.props as { children?: React.ReactNode }).children;
              if (typeof children === 'string') {
                return children;
              } else if (Array.isArray(children)) {
                const textContent = children.find(child => typeof child === 'string');
                if (textContent) return textContent;
              }
            }
            return taskFormT('unknownPhase', 'Unknown Phase');
          }
          if (opt.children) {
            const found = findPhaseName(opt.children, id);
            if (found) return found;
          }
        }
        return undefined;
      };
      const targetPhaseName = findPhaseName(projectTreeOptions, targetPhaseId) || taskFormT('unknownPhase', 'Unknown Phase');

      // Prepare details for the confirmation dialog
      const details = {
        originalTaskId: task.task_id,
        originalTaskName: task.task_name,
        targetPhaseId: targetPhaseId,
        targetPhaseName: targetPhaseName,
        targetStatusId: targetStatusId, // Store the target status ID
        hasChecklist: (task.checklist_items?.length || checklistItems.length) > 0,
        hasPrimaryAssignee: !!(task.assigned_to || assignedUser),
        additionalAssigneeCount: ([...taskResources, ...tempTaskResources]).length,
        ticketLinkCount: pendingTicketLinks.length,
      };

      console.log("Duplicate Task Details:", details);
      setDuplicateTaskDetails(details);
      setShowDuplicateConfirm(true);

      setTimeout(() => {
        setSelectedDuplicatePhaseId(null);
      }, 0);
    }
  };

  // Action buttons used in both dialog footer and drawer inline
  const renderActionButtons = (outsideForm: boolean) => (
    <div className="flex justify-between">
      <div className="flex gap-2">
        {!inDrawer && (
          <Button
            id='cancel-button'
            type="button"
            variant="ghost"
            onClick={handleCancelClick}
            disabled={isSubmitting}
          >
            {t('common:actions.cancel', 'Cancel')}
          </Button>
        )}
        {mode === 'edit' && !inDrawer && (
          <Button
            id='delete-button'
            type="button"
            variant="destructive"
            onClick={() => setShowDeleteConfirm(true)}
            disabled={isSubmitting}
          >
            {t('common:actions.delete', 'Delete')}
          </Button>
        )}
      </div>
      <div className="flex gap-2">
        {mode === 'edit' && (
          <Button
            id='add-time-entry-button'
            type="button"
            variant="soft"
            onClick={handleAddTimeEntry}
            disabled={isSubmitting || !task?.task_id}
          >
            <Clock className="h-4 w-4 mr-2" />
            {taskFormT('addTimeEntry', 'Add Time Entry')}
          </Button>
        )}
        <Button
          id='save-button'
          type={outsideForm ? "button" : "submit"}
          disabled={isSubmitting}
          className={!taskName.trim() ? 'opacity-50' : ''}
          {...(outsideForm ? { onClick: () => (document.getElementById('task-form') as HTMLFormElement | null)?.requestSubmit() } : {})}
        >
          {isSubmitting
            ? (mode === 'edit'
              ? taskFormT('updating', 'Updating...')
              : taskFormT('adding', 'Adding...'))
            : (mode === 'edit'
              ? taskFormT('update', 'Update')
              : taskFormT('save', 'Save'))}
        </Button>
      </div>
    </div>
  );

  const renderContent = () => (
    <div className="h-full">
      {mode === 'create' && (
        <PrefillFromTicketDialog
          open={showPrefillDialog}
          onOpenChange={setShowPrefillDialog}
          onPrefill={handlePrefillFromTicket}
          users={users}
        />
      )}
      <form id="task-form" onSubmit={handleSubmit} className="flex flex-col h-full overflow-hidden" noValidate>
        {hasAttemptedSubmit && validationErrors.length > 0 && (
          <Alert variant="destructive" className="mb-4">
            <AlertDescription>
              <p className="font-medium mb-2">{taskFormT('validationTitle', 'Please fill in the required fields:')}</p>
              <ul className="list-disc list-inside space-y-1">
                {validationErrors.map((err, index) => (
                  <li key={index}>{err}</li>
                ))}
              </ul>
            </AlertDescription>
          </Alert>
        )}
        <div
          className="space-y-4 flex-1 overflow-y-auto"
          data-print-region
          data-print-title={printTitle}
        >
          {printableHeader && (
            <div className="app-print-section">{printableHeader}</div>
          )}
          {/* Full width Title with Status dropdown */}
          <div>
            <div className="flex items-center justify-between mb-1">
              <div className="flex items-center gap-2">
                <label className="block text-sm font-medium text-gray-700">{taskFormT('taskNameLabel', 'Task Name *')}</label>
                {mode === 'create' && (
                  <Button
                    id="task-create-from-ticket"
                    type="button"
                    variant="ghost"
                    size="sm"
                    onClick={() => setShowPrefillDialog(true)}
                    title={taskFormT('createFromTicket', 'Create from ticket')}
                  >
                    <Ticket className="h-4 w-4" />
                  </Button>
                )}
              </div>
              <div className="flex items-center gap-2">
                {printButton && (
                  <span data-print-hide className="flex items-center">
                    {printButton}
                  </span>
                )}
                <TaskStatusSelect
                  id="task-status-select"
                  value={selectedStatusId}
                  statuses={projectStatuses}
                  onValueChange={setSelectedStatusId}
                  disabled={isSubmitting}
                />
              </div>
            </div>
            <TextArea
              value={taskName}
              onChange={(e) => {
                setTaskName(e.target.value);
                clearErrorIfSubmitted();
              }}
              placeholder={taskFormT('taskNamePlaceholder', 'Enter task name...')}
              className={`w-full text-2xl font-bold p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500 ${
                hasAttemptedSubmit && !taskName.trim() ? 'border-destructive' : 'border-gray-300'
              }`}
              rows={1}
            />
          </div>

          {/* Full width Description */}
          <div>
            <label className="block text-sm font-medium text-gray-700 mb-1">{taskFormT('descriptionLabel', 'Description')}</label>
            <TextEditor
              key={descriptionEditorKey}
              id={`task-description-${task?.task_id || 'new'}`}
              initialContent={descriptionContent}
              onContentChange={setDescriptionContent}
              editorRef={blockNoteEditorRef}
              searchMentions={searchUsersForMentions}
              placeholder={taskFormT('descriptionPlaceholder', 'Add task description...')}
            />
          </div>

          {/* Service (for time entry prefill) - right under description */}
          <div>
            <label className="block text-sm font-medium text-gray-700 mb-1">
              {taskFormT('serviceLabel', 'Service (for time entries)')}
            </label>
            <SearchableSelect
              id="task-service-select"
              value={selectedServiceId || ''}
              onChange={(value) => setSelectedServiceId(value || null)}
              options={[
                { value: '', label: taskFormT('noService', 'No service') },
                ...availableServices.map(s => ({
                  value: s.service_id,
                  label: s.service_name
                }))
              ]}
              placeholder={taskFormT('servicePlaceholder', 'Select service for time entry prefill...')}
              className="w-full"
              dropdownMode="overlay"
            />
            <p className="text-xs text-gray-500 mt-1">
              {taskFormT('serviceHelp', 'When set, this service will be automatically selected when creating time entries from this task.')}
            </p>
          </div>

          {/* 2 Column Grid Section */}
          <div className="grid grid-cols-2 gap-4">
            {/* Row 1: Task Type and Priority */}
            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">{taskFormT('taskTypeLabel', 'Task Type')}</label>
              <TaskTypeSelector
                value={selectedTaskType}
                taskTypes={taskTypes}
                onChange={setSelectedTaskType}
                disabled={isSubmitting}
              />
            </div>
            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">{taskFormT('priorityLabel', 'Priority')}</label>
              {renderPrioritySelect({
                value: selectedPriorityId,
                options: priorities
                  .sort((a, b) => a.order_number - b.order_number)
                  .map(p => ({
                    value: p.priority_id,
                    label: p.priority_name,
                    color: p.color
                  })),
                onValueChange: (value) => setSelectedPriorityId(value || null),
                placeholder: taskFormT('selectPriorityPlaceholder', 'Select priority'),
                className: 'w-full',
              })}
            </div>

            {/* Row 2: Move To and Duplicate To (Edit mode only) */}
            {mode === 'edit' && (
              <>
                <div>
                  <label className="block text-sm font-medium text-gray-700 mb-1">{taskFormT('moveToLabel', 'Move to')}</label>
                  {projectTreeOptions.length > 0 ? (
                    <TreeSelect<ProjectTreeTypes>
                      value={selectedPhaseId}
                      onValueChange={handleTreeSelectChange}
                      options={projectTreeOptions}
                      placeholder={taskFormT('moveToPlaceholder', 'Select move destination...')}
                      className="w-full"
                      multiSelect={false}
                      showExclude={false}
                      showReset={false}
                      allowEmpty={false}
                    />
                  ) : (
                    <div className="text-sm text-gray-500">{taskFormT('loading', 'Loading...')}</div>
                  )}
                </div>
                <div>
                  <label className="block text-sm font-medium text-gray-700 mb-1">{taskFormT('duplicateToLabel', 'Duplicate to')}</label>
                  {projectTreeOptions.length > 0 ? (
                    <TreeSelect<ProjectTreeTypes>
                      value={selectedDuplicatePhaseId || ''}
                      onValueChange={handleDuplicateTreeSelectChange}
                      options={projectTreeOptions}
                      placeholder={taskFormT('duplicateToPlaceholder', 'Select duplicate destination...')}
                      className="w-full"
                      multiSelect={false}
                      showExclude={false}
                      showReset={false}
                      allowEmpty={true}
                    />
                  ) : (
                    <div className="text-sm text-gray-500">{taskFormT('loading', 'Loading...')}</div>
                  )}
                </div>
              </>
            )}

            {/* Row 3: Created At (Edit mode only) and Due Date */}
            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">{taskFormT('createdAtLabel', 'Created At')}</label>
              {mode === 'edit' && task ? (
                <div className="p-2 bg-gray-50 border border-gray-200 rounded-md text-gray-700">
                  {new Date(task.created_at).toLocaleDateString('en-US', {
                    month: 'short',
                    day: 'numeric',
                    year: 'numeric',
                    hour: '2-digit',
                    minute: '2-digit'
                  })}
                </div>
              ) : (
                <div className="p-2 bg-gray-50 border border-gray-200 rounded-md text-gray-500">
                  {taskFormT('willBeSetOnCreate', 'Will be set on creation')}
                </div>
              )}
            </div>
            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">{taskFormT('dueDateLabel', 'Due Date')}</label>
              <DatePicker
                value={dueDate}
                onChange={setDueDate}
                id="task-due-date-picker"
                label={taskFormT('taskDueDateLabel', 'Task Due Date')}
                placeholder={taskFormT('dueDatePlaceholder', 'Select due date')}
                required={true}
                disabled={isSubmitting}
              />
            </div>

            {/* Row 4: Estimated Hours and Actual Hours */}
            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                {taskFormT('estimatedHoursLabel', 'Estimated Hours')}
              </label>
              <Input
                type="number"
                min="0"
                step="0.5"
                value={estimatedHours}
                onChange={(e) => setEstimatedHours(Number(e.target.value))}
                className="w-full"
              />
            </div>
            <div>
              <label className="block text-sm font-medium text-gray-700 mb-1">
                {taskFormT('actualHoursLabel', 'Actual Hours')}
              </label>
              <Input
                type="number"
                min="0"
                step="0.5"
                value={actualHours}
                onChange={(e) => setActualHours(Number(e.target.value))}
                className="w-full"
              />
            </div>
            {/* Row 5: Assigned To and Additional Agents in one row */}
            <div className="col-span-2">
              <div className="grid grid-cols-2 gap-4">
                <div>
                  <label className="block text-sm font-medium text-gray-700 mb-1">{taskFormT('assignedToLabel', 'Assigned To')}</label>
                  <UserAndTeamPicker
                    label=""
                    value={assignedUser ?? ''}
                    onValueChange={(value) => {
                      setAssignedUser(value === '' ? null : value);
                    }}
                    onTeamSelect={handleAssignTeam}
                    size="sm"
                    users={users.filter(u =>
                      !([...taskResources, ...tempTaskResources])
                        .some(r => r.additional_user_id === u.user_id)
                    )}
                    teams={teams}
                    getUserAvatarUrlsBatch={getUserAvatarUrlsBatchAction}
                    getTeamAvatarUrlsBatch={getTeamAvatarUrlsBatchAction}
                  />
                  {assignedTeamId && (() => {
                    const assignedTeam = teams.find(t => t.team_id === assignedTeamId);
                    return assignedTeam ? (
                      <div className="flex items-center gap-1.5 mt-1.5">
                        <TeamAvatar
                          teamId={assignedTeam.team_id}
                          teamName={assignedTeam.team_name}
                          avatarUrl={teamAvatarUrl}
                          size="xs"
                        />
                        <span className="text-xs text-gray-500 truncate">{assignedTeam.team_name}</span>
                        <button
                          type="button"
                          id="task-remove-team-btn"
                          className="text-xs text-gray-400 hover:text-red-500 ml-1"
                          onClick={() => {
                            setPendingSwitchTeamId(null);
                            setIsRemoveTeamDialogOpen(true);
                          }}
                          aria-label={taskFormT('removeTeamAssignment', 'Remove team assignment')}
                        >
                          <Trash2 className="h-3 w-3" />
                        </button>
                      </div>
                    ) : null;
                  })()}
                </div>
                <div>
                  <label className="block text-sm font-medium text-gray-700 mb-1">{taskFormT('additionalAgentsLabel', 'Additional Agents')}</label>
                  <MultiUserAndTeamPicker
                    id="task-additional-agents"
                    values={([...taskResources, ...tempTaskResources]).map(r => r.additional_user_id)}
                    getUserAvatarUrlsBatch={getUserAvatarUrlsBatchAction}
                    getTeamAvatarUrlsBatch={getTeamAvatarUrlsBatchAction}
                    teams={teams}
                    teamSectionLabel={taskFormT('addTeamMembers', 'Add Team Members')}
                    onTeamValuesChange={(selectedTeamIds) => {
                      // Selecting a team here drops its members (including the
                      // lead) onto the task as additional agents and leaves the
                      // primary agent alone. Team assignment (lead → primary) is
                      // done via the Assigned To picker above.
                      for (const teamId of selectedTeamIds) {
                        void addTeamMembersAsAgents(teamId);
                      }
                    }}
                    onValuesChange={async (newUserIds) => {
                      // Prevent race conditions from rapid clicks
                      if (isProcessingAgentsRef.current) {
                        return;
                      }
                      isProcessingAgentsRef.current = true;

                      try {
                        const currentResources = [...taskResources, ...tempTaskResources];
                        const currentUserIds = currentResources.map(r => r.additional_user_id);

                        // Find added users
                        const addedUserIds = newUserIds.filter(id => !currentUserIds.includes(id));
                        // Find removed users
                        const removedUserIds = currentUserIds.filter(id => !newUserIds.includes(id));

                        // Add all new agents in a single batched request
                        if (addedUserIds.length > 0) {
                          await addAgents(addedUserIds);
                        }

                        // Process all removals sequentially
                        for (const userId of removedUserIds) {
                          const resource = currentResources.find(r => r.additional_user_id === userId);
                          if (resource) {
                            await handleRemoveAgent(resource.assignment_id);
                          }
                        }
                      } finally {
                        isProcessingAgentsRef.current = false;
                      }
                    }}
                    users={users.filter(u => u.user_id !== assignedUser)}
                    size="sm"
                    placeholder={taskFormT('additionalAgentsPlaceholder', 'Select additional agents...')}
                  />
                </div>
              </div>
            </div>
          </div>

          {/* Full width Tags section */}
          {mode === 'edit' && task?.task_id ? (
            <div>
              <h3 className="font-semibold mb-2">{taskFormT('tagsTitle', 'Tags')}</h3>
              <TagManager
                id="task-tags-edit"
                entityId={task.task_id}
                entityType="project_task"
                initialTags={taskTags}
                onTagsChange={setTaskTags}
                useInlineInput={true}
              />
            </div>
          ) : mode === 'create' && (
            <div>
              <h3 className="font-semibold mb-2">{taskFormT('tagsTitle', 'Tags')}</h3>
              <QuickAddTagPicker
                id="task-tags-create"
                entityType="project_task"
                pendingTags={pendingTags}
                onPendingTagsChange={setPendingTags}
                disabled={isSubmitting}
              />
            </div>
          )}

          {/* Full width Checklist section */}
          <div>
            <div className="flex items-center gap-2 mb-2">
              <ListChecks className="h-5 w-5 text-gray-500" />
              <h3 className='font-semibold'>{taskFormT('checklistTitle', 'Checklist')}</h3>
              <Button
                id="add-checklist-item-header"
                type="button"
                variant="soft"
                size="sm"
                onClick={() => {
                  const newId = addChecklistItem();
                  setEditingChecklistItemId(newId);
                  setIsEditingChecklist(true);
                }}
              >
                <Plus className="h-4 w-4" />
              </Button>
            </div>

                <div className="flex flex-col space-y-2">
                  {checklistItems.map((item, index): React.JSX.Element => {
                    const isItemEditing = isEditingChecklist || editingChecklistItemId === item.checklist_item_id;
                    const isDragging = draggedChecklistId === item.checklist_item_id;
                    const isDropTarget = dragOverChecklistId === item.checklist_item_id && draggedChecklistId !== item.checklist_item_id;
                    const isEntering = recentlyDroppedChecklistId === item.checklist_item_id;
                    const isAnyDragging = draggedChecklistId !== null;
                    return (
                      <div
                        key={item.checklist_item_id}
                        draggable
                        onMouseDown={(e) => {
                          const target = e.target as HTMLElement | null;
                          checklistDragOriginIsHandleRef.current =
                            !!target && !!target.closest(`.${checklistDnd.dragHandle}`);
                        }}
                        onDragStart={(e) => {
                          if (!checklistDragOriginIsHandleRef.current) {
                            e.preventDefault();
                            return;
                          }
                          handleChecklistDragStart(e, item.checklist_item_id);
                        }}
                        onDragOver={(e) => handleChecklistDragOver(e, item.checklist_item_id)}
                        onDrop={(e) => handleChecklistDrop(e, item.checklist_item_id)}
                        onDragEnd={() => {
                          checklistDragOriginIsHandleRef.current = false;
                          resetChecklistDragState();
                        }}
                      >
                        {isEditingChecklist && (
                          <Tooltip content={taskFormT('insertChecklistItem', 'Insert item here')}>
                            <div
                              className={`${checklistDnd.insertZone} ${isAnyDragging ? checklistDnd.insertZoneHidden : ''}`}
                              role="button"
                              tabIndex={-1}
                              aria-label={taskFormT('insertChecklistItem', 'Insert item here')}
                              onClick={(e) => {
                                e.stopPropagation();
                                const newId = addChecklistItem(index);
                                setEditingChecklistItemId(newId);
                              }}
                            >
                              <div className={checklistDnd.insertZoneLine} />
                              <div className={checklistDnd.insertZoneButton}>
                                <Plus className="h-3 w-3" />
                              </div>
                            </div>
                          </Tooltip>
                        )}
                        {isDropTarget && checklistDropPosition === 'before' && (
                          <div className={`${checklistDnd.dropPlaceholder} ${checklistDnd.visible}`} />
                        )}
                        <div
                          className={`flex items-center gap-2 w-full ${checklistDnd.row} ${
                            isDragging ? checklistDnd.dragging : ''
                          } ${isEntering ? checklistDnd.entering : ''}`}
                        >
                          <Tooltip content={taskFormT('reorderChecklistItem', 'Drag to reorder')}>
                            <div
                              className={`${checklistDnd.dragHandle} cursor-grab text-gray-400 flex-none`}
                              aria-label={taskFormT('reorderChecklistItem', 'Drag to reorder')}
                            >
                              <GripVertical className="h-4 w-4" />
                            </div>
                          </Tooltip>
                        <Checkbox
                          checked={item.completed}
                          onChange={(e) => updateChecklistItem(index, 'completed', e.target.checked)}
                          className="flex-none"
                          containerClassName=""
                        />
                        {isItemEditing ? (
                          <div className="flex-1">
                            <TextArea
                              value={item.item_name}
                              onChange={(e) => updateChecklistItem(index, 'item_name', e.target.value)}
                              placeholder={taskFormT('checklistItemPlaceholder', 'Checklist item')}
                              className="w-full"
                              wrapperClassName="!mb-0 !px-0"
                              onBlur={() => setEditingChecklistItemId(null)} // Stop editing when focus is lost
                              autoFocus={editingChecklistItemId === item.checklist_item_id}
                              onKeyDown={(e) => handleChecklistItemKeyDown(e, index)}
                            />
                          </div>
                        ) : (
                          <span
                            className={`flex-1 whitespace-pre-wrap cursor-text ${item.completed ? 'line-through text-gray-500' : ''}`}
                            onClick={() => setEditingChecklistItemId(item.checklist_item_id)} // Start editing when clicked
                          >
                            {item.item_name}
                          </span>
                        )}
                        <div className="flex items-center gap-1 shrink-0">
                          {!isItemEditing && (
                            <Tooltip content={taskFormT('editChecklistItem', 'Edit checklist item')}>
                              <Button
                                id={`edit-checklist-${item.checklist_item_id}`}
                                type="button"
                                variant="icon"
                                size="icon"
                                onClick={() => setEditingChecklistItemId(item.checklist_item_id)}
                              >
                                <Pencil className="h-3.5 w-3.5" />
                              </Button>
                            </Tooltip>
                          )}
                          <Tooltip content={taskFormT('removeChecklistItem', 'Remove checklist item')}>
                            <Button
                              id={`remove-checklist-${item.checklist_item_id}`}
                              type="button"
                              variant="icon"
                              size="icon"
                              // Fire on mousedown (with preventDefault) so the textarea's
                              // blur handler doesn't unmount this button before click fires.
                              onMouseDown={(e) => {
                                e.preventDefault();
                                removeChecklistItem(index);
                              }}
                              className="text-destructive hover:text-destructive"
                            >
                              <Trash2 className="h-4 w-4" />
                            </Button>
                          </Tooltip>
                        </div>
                        </div>
                        {isDropTarget && checklistDropPosition === 'after' && (
                          <div className={`${checklistDnd.dropPlaceholder} ${checklistDnd.visible}`} />
                        )}
                      </div>
                    );
                  })}
                  {isEditingChecklist && checklistItems.length > 0 && draggedChecklistId === null && (
                    <Tooltip content={taskFormT('insertChecklistItem', 'Insert item here')}>
                      <div
                        className={checklistDnd.insertZone}
                        role="button"
                        tabIndex={-1}
                        aria-label={taskFormT('insertChecklistItem', 'Insert item here')}
                        onClick={(e) => {
                          e.stopPropagation();
                          const newId = addChecklistItem(checklistItems.length);
                          setEditingChecklistItemId(newId);
                        }}
                      >
                        <div className={checklistDnd.insertZoneLine} />
                        <div className={checklistDnd.insertZoneButton}>
                          <Plus className="h-3 w-3" />
                        </div>
                      </div>
                    </Tooltip>
                  )}
                </div>

          </div>

          {/* Full width Dependencies section */}
          {mode === 'edit' && task ? (
            <TaskDependencies
              ref={dependenciesRef}
              task={task}
              allTasksInProject={allProjectTasks}
              taskTypes={taskTypes}
              initialPredecessors={taskDependencies.predecessors}
              initialSuccessors={taskDependencies.successors}
              users={users}
              phases={phases}
              currentPhaseId={task.phase_id}
              refreshDependencies={async () => {
                try {
                  const dependencies = await getTaskDependencies(task.task_id);
                  setTaskDependencies(dependencies);
                } catch (error) {
                  console.error('Error refreshing dependencies:', error);
                }
              }}
            />
          ) : mode === 'create' && allProjectTasks.length > 0 && (
            <TaskDependencies
              ref={dependenciesRef}
              allTasksInProject={allProjectTasks}
              taskTypes={taskTypes}
              users={users}
              phases={phases}
              currentPhaseId={phase.phase_id}
              pendingMode
            />
          )}

          {/* Full width Associated Tickets section */}
          <TaskTicketLinks
            ref={ticketLinksRef}
            taskId={task?.task_id || undefined}
            phaseId={phase.phase_id}
            projectId={phase.project_id}
            initialLinks={task?.ticket_links}
            users={users}
            onLinksChange={setPendingTicketLinks}
            onInitialLinksLoaded={handleInitialTicketLinksLoaded}
            onTicketCreated={(ticket) => setSessionCreatedTickets(prev => [...prev, ticket])}
            taskData={
              mode === 'edit'
                ? {
                    task_name: taskName,
                    description: isTaskRichTextEmpty(descriptionContent) ? '' : serializeTaskRichTextContent(descriptionContent),
                    assigned_to: assignedUser,
                    due_date: dueDate ?? null,
                    additional_agents: ([...taskResources, ...tempTaskResources]).map(r => ({
                      user_id: r.additional_user_id,
                      name: r.first_name ? `${r.first_name} ${r.last_name || ''}`.trim() : undefined
                    }))
                  }
                : undefined
            }
          />

          {/* Full width Attachments section */}
          <div onClick={(e) => e.stopPropagation()} onSubmit={(e) => e.preventDefault()}>
            <TaskDocumentsSimple
              taskId={mode === 'edit' && task ? task.task_id : undefined}
              pendingDocuments={mode === 'create' ? pendingDocuments : undefined}
              onPendingDocumentsChange={mode === 'create' ? setPendingDocuments : undefined}
              onDocumentAdded={mode === 'edit' ? (doc) => setSessionAddedDocuments(prev => [...prev, doc]) : undefined}
            />
          </div>

          {/* Full width Comments section */}
          {mode === 'edit' && task && (
            <div onClick={(e) => e.stopPropagation()} onSubmit={(e) => e.preventDefault()}>
              <TaskCommentThread
                taskId={task.task_id}
                projectId={phase.project_id}
                onCommentCountChange={onCommentCountChange}
              />
            </div>
          )}

        </div>

        {/* Action Buttons - only rendered inside form for drawer mode */}
        {inDrawer && (
          <div className="pt-4 pb-2 border-t bg-white dark:bg-[rgb(var(--color-bg-1))] flex-shrink-0">
            {renderActionButtons(false)}
          </div>
        )}
      </form>
    </div>
  );

  return (
    <>
      {inDrawer ? (
        renderContent()
      ) : (
        <Dialog
          isOpen={true}
          onClose={handleCancelClick}
          className="max-w-3xl"
          title={mode === 'create'
            ? taskFormT('addTitle', 'Add New Task')
            : taskFormT('editTitle', 'Edit Task')}
          disableFocusTrap
          footer={renderActionButtons(true)}
        >
          <DialogContent>
            {renderContent()}
          </DialogContent>
        </Dialog>
      )}

      <ConfirmationDialog
        isOpen={showCancelConfirm}
        onClose={handleCancelDismiss}
        onConfirm={handleCancelConfirm}
        title={mode === 'create'
          ? taskFormT('cancelCreateTitle', 'Cancel Task Creation')
          : taskFormT('cancelEditTitle', 'Cancel Edit')}
        message={taskFormT('cancelMessage', 'Are you sure you want to cancel? Any unsaved changes will be lost.')}
        confirmLabel={taskFormT('discardChanges', 'Discard changes')}
        cancelLabel={taskFormT('continueEditing', 'Continue editing')}
      />

      {/* Document cleanup confirmation - shown when canceling with new documents (uploaded/created) */}
      <ConfirmationDialog
        isOpen={showDocumentCleanupConfirm}
        onClose={() => setShowDocumentCleanupConfirm(false)}
        onConfirm={handleDocumentCleanupDelete}
        onCancel={handleDocumentCleanupKeep}
        title={taskFormT('keepUploadedDocumentsTitle', 'Keep Uploaded Documents?')}
        message={(() => {
          const allDocs = mode === 'create' ? pendingDocuments : sessionAddedDocuments;
          // Only show uploaded and created documents - linked docs don't need cleanup
          const docsToShow = allDocs.filter(d => d.type === 'uploaded' || d.type === 'block');
          const cleanupType = docsToShow.some(d => d.type === 'block') && docsToShow.some(d => d.type === 'uploaded')
            ? taskFormT('uploadedOrCreated', 'uploaded or created')
            : docsToShow.some(d => d.type === 'block')
              ? taskFormT('createdOnly', 'created')
              : taskFormT('uploadedOnly', 'uploaded');

          return (
            <div>
              <p>{taskFormT('documentsCleanupIntro', 'You have {{count}} documents that were {{cleanupType}}:', {
                count: docsToShow.length,
                cleanupType,
              })}</p>
              <ul className="list-disc list-inside mt-2 text-sm">
                {docsToShow.slice(0, 5).map(doc => (
                  <li key={doc.document_id} className="truncate">
                    {doc.document_name}
                    <span className="text-gray-400 ml-1">
                      ({doc.type === 'block'
                        ? taskFormT('createdDocumentTag', 'created')
                        : taskFormT('uploadedDocumentTag', 'uploaded')})
                    </span>
                  </li>
                ))}
                {docsToShow.length > 5 && (
                  <li className="text-gray-500">{taskFormT('andMore', '...and {{count}} more', { count: docsToShow.length - 5 })}</li>
                )}
              </ul>
              <p className="mt-3">{taskFormT('keepUploadedDocumentsMessage', 'Would you like to keep these documents in the Documents section or delete them?')}</p>
            </div>
          );
        })()}
        confirmLabel={isDeletingDocuments
          ? taskFormT('deleting', 'Deleting...')
          : taskFormT('deleteDocumentsLabel', 'Delete documents')}
        cancelLabel={taskFormT('continueEditing', 'Continue editing')}
        thirdButtonLabel={taskFormT('keepDocumentsLabel', 'Keep documents')}
        isConfirming={isDeletingDocuments}
      />

      {/* Ticket cleanup confirmation - shown when canceling with newly created tickets */}
      <ConfirmationDialog
        isOpen={showTicketCleanupConfirm}
        onClose={() => setShowTicketCleanupConfirm(false)}
        onConfirm={handleTicketCleanupDelete}
        onCancel={handleTicketCleanupKeep}
        title={taskFormT('keepCreatedTicketsTitle', 'Keep Created Tickets?')}
        message={(() => {
          return (
            <div>
              <p>{taskFormT('createdTicketsIntro', 'You created {{count}} tickets during this session:', {
                count: sessionCreatedTickets.length,
              })}</p>
              <ul className="list-disc list-inside mt-2 text-sm">
                {sessionCreatedTickets.slice(0, 5).map(ticket => (
                  <li key={ticket.ticket_id} className="truncate">
                    {ticket.ticket_number} - {ticket.title}
                  </li>
                ))}
                {sessionCreatedTickets.length > 5 && (
                  <li className="text-gray-500">{taskFormT('andMore', '...and {{count}} more', { count: sessionCreatedTickets.length - 5 })}</li>
                )}
              </ul>
              <p className="mt-3">{taskFormT('keepCreatedTicketsMessage', 'Would you like to keep these tickets or delete them?')}</p>
            </div>
          );
        })()}
        confirmLabel={isDeletingTickets
          ? taskFormT('deleting', 'Deleting...')
          : taskFormT('deleteTicketsLabel', 'Delete tickets')}
        cancelLabel={taskFormT('continueEditing', 'Continue editing')}
        thirdButtonLabel={taskFormT('keepTicketsLabel', 'Keep tickets')}
        isConfirming={isDeletingTickets}
      />

      <ConfirmationDialog
        isOpen={showDeleteConfirm}
        onClose={handleDeleteDismiss}
        onConfirm={handleDeleteConfirm}
        title={taskFormT('deletingTitle', 'Delete Task')}
        message={taskFormT('deleteMessage', 'Are you sure you want to delete task "{{taskName}}"? This action cannot be undone.', {
          taskName,
        })}
        confirmLabel={t('common:actions.delete', 'Delete')}
        cancelLabel={t('common:actions.cancel', 'Cancel')}
      />

      {mode === 'edit' && (
        <ConfirmationDialog
          isOpen={showMoveConfirmation}
          onClose={() => {
            setShowMoveConfirmation(false);
            setSelectedPhase(phase);
          }}
          onConfirm={handleMoveConfirm}
          title={taskFormT('movingTitle', 'Move Task')}
          message={taskFormT('moveMessage', 'Are you sure you want to move task "{{taskName}}" to phase "{{phaseName}}"?', {
            taskName,
            phaseName: selectedPhase.phase_name,
          })}
          confirmLabel={taskFormT('move', 'Move')}
          cancelLabel={t('common:actions.cancel', 'Cancel')}
        />
      )}

      {/* Render Duplicate Confirmation Dialog */}
      {duplicateTaskDetails && (
        <DuplicateTaskDialog
          isOpen={showDuplicateConfirm}
          onClose={() => {
            setShowDuplicateConfirm(false);
            setDuplicateTaskDetails(null);
          }}
          taskDetails={duplicateTaskDetails}
          projectTreeData={projectTreeOptions}
          initialTargetPhaseId={duplicateTaskDetails.targetPhaseId}
          initialTargetStatusId={duplicateTaskDetails.targetStatusId}
          onConfirm={async (targetPhaseId: string, options: DuplicateOptions) => {
            if (!duplicateTaskDetails) return;
            console.log("Duplicate confirmed for phase:", targetPhaseId, "with options:", options);
            setIsSubmitting(true);
            try {
              const duplicatedTask = await duplicateTaskToPhase(
                duplicateTaskDetails.originalTaskId,
                targetPhaseId,
                options
              );
              toast.success(taskFormT('duplicatedSuccess', 'Task "{{taskName}}" duplicated successfully!', {
                taskName: duplicateTaskDetails.originalTaskName,
              }));
              setShowDuplicateConfirm(false);
              setDuplicateTaskDetails(null);
              onSubmit(duplicatedTask);
              onClose();
            } catch (error) {
              handleError(error, taskFormT('duplicateFailed', 'Failed to duplicate task.'));
              setShowDuplicateConfirm(false);
              setDuplicateTaskDetails(null);
            } finally {
               setIsSubmitting(false);
            }
          }}
        />
      )}

      <ConfirmationDialog
        isOpen={showDependencyConfirmation}
        onClose={handleDependencyCancel}
        onConfirm={handleDependencyConfirm}
        title={taskFormT('unsavedTitle', 'Unsaved Changes')}
        message={taskFormT('dependencyUnsavedMessage', 'You have a dependency selected but not yet added. Click the purple + button to add it, or discard the selection and save.')}
        confirmLabel={taskFormT('discardChanges', 'Discard changes')}
        cancelLabel={taskFormT('continueEditing', 'Continue editing')}
      />

      {assignedTeamId && (
        <RemoveTeamDialog
          id="task-form-remove-team-dialog"
          isOpen={isRemoveTeamDialogOpen}
          onClose={() => {
            setPendingSwitchTeamId(null);
            setIsRemoveTeamDialogOpen(false);
          }}
          isSwitching={!!pendingSwitchTeamId}
          teamMembers={teamMembersOnTask}
          users={users}
          onConfirm={async (mode, keepUserIds) => {
            try {
              await handleRemoveTeamAssignment(mode, keepUserIds);
              if (pendingSwitchTeamId) {
                const nextTeamId = pendingSwitchTeamId;
                setPendingSwitchTeamId(null);
                setIsRemoveTeamDialogOpen(false);
                // Call performTeamAssign directly: handleAssignTeam reads
                // assignedTeamId from the closure of this render, where it
                // still holds the previous team id, so the guard would loop.
                await performTeamAssign(nextTeamId);
                return;
              }
            } catch {
              return;
            }
            setPendingSwitchTeamId(null);
            setIsRemoveTeamDialogOpen(false);
          }}
        />
      )}

    </>
  );
}
