import { NodeApiError, NodeOperationError } from 'n8n-workflow';
import type {
  IDataObject,
  IExecuteFunctions,
  ILoadOptionsFunctions,
  INodeExecutionData,
  INodeListSearchResult,
  INodePropertyOptions,
  INodeType,
  INodeTypeDescription,
  JsonObject,
} from 'n8n-workflow';
import {
  buildContactCreatePayload,
  buildContactListQuery,
  buildContactUpdatePayload,
  buildProjectTaskCreatePayload,
  buildProjectTaskListQuery,
  buildProjectTaskUpdatePayload,
  buildTicketCommentListQuery,
  buildTicketCommentPayload,
  buildTicketCreatePayload,
  buildTicketListQuery,
  buildTicketSearchQuery,
  buildTicketUpdatePayload,
  compactObject,
  ensureNonEmpty,
  ensureUuid,
  extractResourceLocatorValue,
  formatAlgaApiError,
  normalizeDeleteSuccess,
  normalizeSuccessResponse,
  parseCsvList,
  UUID_REGEX,
} from './helpers';
import { algaApiRequest } from './transport';

type Resource =
  | 'ticket'
  | 'contact'
  | 'projectTask'
  | 'client'
  | 'board'
  | 'status'
  | 'priority';
type StatusType = 'ticket' | 'project' | 'project_task' | 'interaction';
type ContactOperation = 'create' | 'get' | 'list' | 'update' | 'delete';
type ProjectTaskOperation = 'create' | 'get' | 'list' | 'update' | 'delete';
type TicketOperation =
  | 'create'
  | 'get'
  | 'list'
  | 'listComments'
  | 'search'
  | 'update'
  | 'addComment'
  | 'updateStatus'
  | 'updateAssignment'
  | 'delete';

const LOOKUP_PAGE_SIZE = 100;
type HelperResource = Exclude<Resource, 'ticket' | 'contact' | 'projectTask'>;

function ensureDataArray(response: unknown): IDataObject[] {
  const normalized = normalizeSuccessResponse(response);
  const data = normalized.data;
  if (Array.isArray(data)) {
    return data as IDataObject[];
  }

  return [];
}

function getLookupLabel(record: IDataObject, candidates: string[]): string {
  for (const candidate of candidates) {
    const value = record[candidate];
    if (value !== undefined && value !== null && String(value).trim()) {
      return String(value);
    }
  }

  return '';
}

async function loadLookup(
  context: ILoadOptionsFunctions,
  endpoint: string,
  idField: string,
  labelFields: string[],
  filter?: string,
  extraQuery?: IDataObject,
): Promise<INodeListSearchResult> {
  try {
    const query = compactObject({
      page: 1,
      limit: LOOKUP_PAGE_SIZE,
      search: filter?.trim(),
      ...(extraQuery ?? {}),
    });

    const response = await algaApiRequest(context, 'GET', endpoint, query);
    const records = ensureDataArray(response);

    return {
      results: records
        .map((record) => {
          const id = record[idField];
          if (!id) {
            return null;
          }

          const label = getLookupLabel(record, labelFields) || String(id);
          return {
            name: label,
            value: String(id),
          };
        })
        .filter((entry): entry is { name: string; value: string } => entry !== null),
    };
  } catch {
    // Allow manual ID fallback when lookups are unavailable.
    return { results: [] };
  }
}

function getCurrentStatusLookupType(context: ILoadOptionsFunctions): StatusType | undefined {
  const currentParams = context.getCurrentNodeParameters?.();
  const resource = currentParams?.resource as Resource | undefined;

  if (resource === 'ticket') {
    return 'ticket';
  }

  if (resource === 'status') {
    return currentParams?.helperStatusType as StatusType | undefined;
  }

  return undefined;
}

function getCurrentProjectLookupId(context: ILoadOptionsFunctions): string | undefined {
  const currentParams = context.getCurrentNodeParameters?.();
  const value = extractResourceLocatorValue(currentParams?.projectTaskProjectId);
  return value && UUID_REGEX.test(value) ? value : undefined;
}

function getCurrentStatusBoardId(context: ILoadOptionsFunctions): string | undefined {
  const currentParams = context.getCurrentNodeParameters?.();
  if (!currentParams) {
    return undefined;
  }

  const resource = currentParams.resource as Resource | undefined;

  if (resource === 'ticket') {
    const ticketOperation = currentParams.ticketOperation as TicketOperation | undefined;

    if (ticketOperation === 'create') {
      return extractResourceLocatorValue(currentParams.board_id) || undefined;
    }

    if (ticketOperation === 'update') {
      const updateAdditional = currentParams.updateAdditionalFields as IDataObject | undefined;
      return extractResourceLocatorValue(updateAdditional?.board_id) || undefined;
    }

    if (ticketOperation === 'updateStatus') {
      return extractResourceLocatorValue(currentParams.statusFilterBoardId) || undefined;
    }

    return undefined;
  }

  if (resource === 'status') {
    return extractResourceLocatorValue(currentParams.helperBoardId) || undefined;
  }

  return undefined;
}

function getOperationParameterName(resource: Resource): string {
  switch (resource) {
    case 'ticket':
      return 'ticketOperation';
    case 'contact':
      return 'contactOperation';
    case 'projectTask':
      return 'projectTaskOperation';
    case 'client':
      return 'clientOperation';
    case 'board':
      return 'boardOperation';
    case 'status':
      return 'statusOperation';
    case 'priority':
      return 'priorityOperation';
  }
}

function getHelperEndpoint(resource: HelperResource): string {
  switch (resource) {
    case 'client':
      return '/api/v1/clients';
    case 'board':
      return '/api/v1/boards';
    case 'status':
      return '/api/v1/statuses';
    case 'priority':
      return '/api/v1/priorities';
  }
}

export class AlgaPsa implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'Alga PSA',
    name: 'algaPsa',
    icon: 'file:avatar-purple.png',
    group: ['transform'],
    version: 1,
    subtitle:
      '={{$parameter["resource"] + ": " + ($parameter["ticketOperation"] || $parameter["contactOperation"] || $parameter["projectTaskOperation"] || $parameter["clientOperation"] || $parameter["boardOperation"] || $parameter["statusOperation"] || $parameter["priorityOperation"])}}',
    description: 'Create and manage Alga PSA tickets, contacts, project tasks, and lookup resources',
    defaults: {
      name: 'Alga PSA',
    },
    inputs: ['main'],
    outputs: ['main'],
    credentials: [
      {
        name: 'algaPsaApi',
        required: true,
      },
    ],
    properties: [
      {
        displayName: 'Resource',
        name: 'resource',
        type: 'options',
        noDataExpression: true,
        options: [
          { name: 'Ticket', value: 'ticket' },
          { name: 'Contact', value: 'contact' },
          { name: 'Project Task', value: 'projectTask' },
          { name: 'Client', value: 'client' },
          { name: 'Board', value: 'board' },
          { name: 'Status', value: 'status' },
          { name: 'Priority', value: 'priority' },
        ],
        default: 'ticket',
      },
      {
        displayName: 'Operation',
        name: 'ticketOperation',
        type: 'options',
        noDataExpression: true,
        displayOptions: {
          show: {
            resource: ['ticket'],
          },
        },
        options: [
          { name: 'Create', value: 'create', action: 'Create a ticket' },
          { name: 'Get', value: 'get', action: 'Get a ticket' },
          { name: 'List', value: 'list', action: 'List tickets' },
          {
            name: 'List Comments',
            value: 'listComments',
            action: 'List comments for a ticket',
          },
          { name: 'Search', value: 'search', action: 'Search tickets' },
          { name: 'Update', value: 'update', action: 'Update a ticket' },
          {
            name: 'Add Comment',
            value: 'addComment',
            action: 'Add a comment to a ticket',
          },
          {
            name: 'Update Status',
            value: 'updateStatus',
            action: 'Update ticket status',
          },
          {
            name: 'Update Assignment',
            value: 'updateAssignment',
            action: 'Update ticket assignment',
          },
          { name: 'Delete', value: 'delete', action: 'Delete a ticket' },
        ],
        default: 'create',
      },
      {
        displayName: 'Operation',
        name: 'contactOperation',
        type: 'options',
        noDataExpression: true,
        displayOptions: {
          show: {
            resource: ['contact'],
          },
        },
        options: [
          { name: 'Create', value: 'create', action: 'Create a contact' },
          { name: 'Get', value: 'get', action: 'Get a contact' },
          { name: 'List', value: 'list', action: 'List contacts' },
          { name: 'Update', value: 'update', action: 'Update a contact' },
          { name: 'Delete', value: 'delete', action: 'Delete a contact' },
        ],
        default: 'create',
      },
      {
        displayName: 'Operation',
        name: 'projectTaskOperation',
        type: 'options',
        noDataExpression: true,
        displayOptions: {
          show: {
            resource: ['projectTask'],
          },
        },
        options: [
          { name: 'Create', value: 'create', action: 'Create a project task' },
          { name: 'Get', value: 'get', action: 'Get a project task' },
          { name: 'List', value: 'list', action: 'List project tasks' },
          { name: 'Update', value: 'update', action: 'Update a project task' },
          { name: 'Delete', value: 'delete', action: 'Delete a project task' },
        ],
        default: 'create',
      },
      {
        displayName: 'Operation',
        name: 'clientOperation',
        type: 'options',
        noDataExpression: true,
        displayOptions: {
          show: {
            resource: ['client'],
          },
        },
        options: [{ name: 'List', value: 'list', action: 'List clients' }],
        default: 'list',
      },
      {
        displayName: 'Operation',
        name: 'boardOperation',
        type: 'options',
        noDataExpression: true,
        displayOptions: {
          show: {
            resource: ['board'],
          },
        },
        options: [{ name: 'List', value: 'list', action: 'List boards' }],
        default: 'list',
      },
      {
        displayName: 'Operation',
        name: 'statusOperation',
        type: 'options',
        noDataExpression: true,
        displayOptions: {
          show: {
            resource: ['status'],
          },
        },
        options: [{ name: 'List', value: 'list', action: 'List statuses' }],
        default: 'list',
      },
      {
        displayName: 'Operation',
        name: 'priorityOperation',
        type: 'options',
        noDataExpression: true,
        displayOptions: {
          show: {
            resource: ['priority'],
          },
        },
        options: [{ name: 'List', value: 'list', action: 'List priorities' }],
        default: 'list',
      },

      // Ticket fields
      {
        displayName: 'Title',
        name: 'title',
        type: 'string',
        required: true,
        default: '',
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['create'],
          },
        },
      },
      {
        displayName: 'Client ID',
        name: 'client_id',
        type: 'resourceLocator',
        default: { mode: 'list', value: '' },
        required: true,
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['create'],
          },
        },
        modes: [
          {
            displayName: 'From List',
            name: 'list',
            type: 'list',
            typeOptions: {
              searchListMethod: 'searchClients',
            },
          },
          {
            displayName: 'By ID',
            name: 'id',
            type: 'string',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
        ],
        description: 'Select a client or enter a client UUID manually',
      },
      {
        displayName: 'Board ID',
        name: 'board_id',
        type: 'resourceLocator',
        default: { mode: 'list', value: '' },
        required: true,
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['create'],
          },
        },
        modes: [
          {
            displayName: 'From List',
            name: 'list',
            type: 'list',
            typeOptions: {
              searchListMethod: 'searchBoards',
            },
          },
          {
            displayName: 'By ID',
            name: 'id',
            type: 'string',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
        ],
        description: 'Select a board or enter a board UUID manually',
      },
      {
        displayName: 'Board ID (for Status Picker)',
        name: 'statusFilterBoardId',
        type: 'resourceLocator',
        default: { mode: 'list', value: '' },
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['updateStatus'],
          },
        },
        modes: [
          {
            displayName: 'From List',
            name: 'list',
            type: 'list',
            typeOptions: {
              searchListMethod: 'searchBoards',
            },
          },
          {
            displayName: 'By ID',
            name: 'id',
            type: 'string',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
        ],
        description:
          "Optional — scopes the Status ID dropdown to one board. Ticket statuses are board-owned, so the picker needs a board to list options. Not sent in the request; the server validates against the ticket's existing board.",
      },
      {
        displayName: 'Status ID',
        name: 'status_id',
        type: 'resourceLocator',
        default: { mode: 'list', value: '' },
        required: true,
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['create', 'updateStatus'],
          },
        },
        modes: [
          {
            displayName: 'From List',
            name: 'list',
            type: 'list',
            typeOptions: {
              searchListMethod: 'searchStatuses',
            },
          },
          {
            displayName: 'By ID',
            name: 'id',
            type: 'string',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
        ],
        description:
          'Select a status or enter a status UUID manually. Ticket statuses are board-owned, so the dropdown is filtered by the selected board',
      },
      {
        displayName: 'Priority ID',
        name: 'priority_id',
        type: 'resourceLocator',
        default: { mode: 'list', value: '' },
        required: true,
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['create'],
          },
        },
        modes: [
          {
            displayName: 'From List',
            name: 'list',
            type: 'list',
            typeOptions: {
              searchListMethod: 'searchPriorities',
            },
          },
          {
            displayName: 'By ID',
            name: 'id',
            type: 'string',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
        ],
        description: 'Select a priority or enter a priority UUID manually',
      },
      {
        displayName: 'Ticket ID',
        name: 'ticketId',
        type: 'string',
        required: true,
        default: '',
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: [
              'get',
              'update',
              'listComments',
              'addComment',
              'updateStatus',
              'updateAssignment',
              'delete',
            ],
          },
        },
      },
      {
        displayName: 'Comment List Options',
        name: 'commentListOptions',
        type: 'collection',
        default: {},
        placeholder: 'Add Option',
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['listComments'],
          },
        },
        options: [
          {
            displayName: 'Limit',
            name: 'limit',
            type: 'number',
            default: 50,
            typeOptions: {
              minValue: 1,
              maxValue: 200,
              numberPrecision: 0,
            },
          },
          {
            displayName: 'Offset',
            name: 'offset',
            type: 'number',
            default: 0,
            typeOptions: {
              minValue: 0,
              numberPrecision: 0,
            },
          },
          {
            displayName: 'Order',
            name: 'order',
            type: 'options',
            default: 'asc',
            options: [
              { name: 'Ascending', value: 'asc' },
              { name: 'Descending', value: 'desc' },
            ],
          },
        ],
      },
      {
        displayName: 'Comment Text',
        name: 'commentText',
        type: 'string',
        required: true,
        default: '',
        typeOptions: {
          rows: 4,
        },
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['addComment'],
          },
        },
      },
      {
        displayName: 'Comment Additional Fields',
        name: 'commentAdditionalFields',
        type: 'collection',
        default: {},
        placeholder: 'Add Field',
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['addComment'],
          },
        },
        options: [
          {
            displayName: 'Is Internal',
            name: 'is_internal',
            type: 'boolean',
            default: false,
          },
        ],
      },
      {
        displayName: 'Assignment Action',
        name: 'assignmentAction',
        type: 'options',
        default: 'assign',
        options: [
          { name: 'Assign User', value: 'assign' },
          { name: 'Clear Assignment', value: 'clear' },
        ],
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['updateAssignment'],
          },
        },
      },
      {
        displayName: 'Assigned To (User ID)',
        name: 'assigned_to',
        type: 'string',
        required: true,
        default: '',
        placeholder: '00000000-0000-0000-0000-000000000000',
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['updateAssignment'],
            assignmentAction: ['assign'],
          },
        },
      },
      {
        displayName: 'Page',
        name: 'page',
        type: 'number',
        default: 1,
        typeOptions: {
          minValue: 1,
          numberPrecision: 0,
        },
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['list'],
          },
        },
      },
      {
        displayName: 'Limit',
        name: 'limit',
        type: 'number',
        default: 25,
        typeOptions: {
          minValue: 1,
          maxValue: 100,
          numberPrecision: 0,
        },
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['list'],
          },
        },
      },
      {
        displayName: 'Sort',
        name: 'sort',
        type: 'string',
        default: 'entered_at',
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['list'],
          },
        },
      },
      {
        displayName: 'Order',
        name: 'order',
        type: 'options',
        default: 'desc',
        options: [
          { name: 'Ascending', value: 'asc' },
          { name: 'Descending', value: 'desc' },
        ],
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['list'],
          },
        },
      },
      {
        displayName: 'List Filters',
        name: 'listFilters',
        type: 'collection',
        default: {},
        placeholder: 'Add Filter',
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['list'],
          },
        },
        options: [
          { displayName: 'Title', name: 'title', type: 'string', default: '' },
          {
            displayName: 'Ticket Number',
            name: 'ticket_number',
            type: 'string',
            default: '',
          },
          { displayName: 'Client ID', name: 'client_id', type: 'string', default: '' },
          { displayName: 'Board ID', name: 'board_id', type: 'string', default: '' },
          { displayName: 'Status ID', name: 'status_id', type: 'string', default: '' },
          {
            displayName: 'Priority ID',
            name: 'priority_id',
            type: 'string',
            default: '',
          },
          {
            displayName: 'Assigned To',
            name: 'assigned_to',
            type: 'string',
            default: '',
          },
          {
            displayName: 'Is Open',
            name: 'is_open',
            type: 'boolean',
            default: false,
          },
          {
            displayName: 'Is Closed',
            name: 'is_closed',
            type: 'boolean',
            default: false,
          },
        ],
      },
      {
        displayName: 'Search Query',
        name: 'query',
        type: 'string',
        required: true,
        default: '',
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['search'],
          },
        },
      },
      {
        displayName: 'Search Limit',
        name: 'searchLimit',
        type: 'number',
        default: 25,
        typeOptions: {
          minValue: 1,
          maxValue: 100,
          numberPrecision: 0,
        },
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['search'],
          },
        },
      },
      {
        displayName: 'Search Filters',
        name: 'searchAdditionalFields',
        type: 'collection',
        default: {},
        placeholder: 'Add Search Filter',
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['search'],
          },
        },
        options: [
          {
            displayName: 'Include Closed',
            name: 'include_closed',
            type: 'boolean',
            default: false,
          },
          {
            displayName: 'Fields',
            name: 'fields',
            type: 'multiOptions',
            default: [],
            options: [
              { name: 'Title', value: 'title' },
              { name: 'Ticket Number', value: 'ticket_number' },
              { name: 'Client Name', value: 'client_name' },
              { name: 'Contact Name', value: 'contact_name' },
            ],
          },
          {
            displayName: 'Status IDs',
            name: 'status_ids',
            type: 'string',
            default: '',
            description: 'Comma-separated UUID list',
          },
          {
            displayName: 'Priority IDs',
            name: 'priority_ids',
            type: 'string',
            default: '',
            description: 'Comma-separated UUID list',
          },
          {
            displayName: 'Client IDs',
            name: 'client_ids',
            type: 'string',
            default: '',
            description: 'Comma-separated UUID list',
          },
          {
            displayName: 'Assigned To IDs',
            name: 'assigned_to_ids',
            type: 'string',
            default: '',
            description: 'Comma-separated UUID list',
          },
        ],
      },
      {
        displayName: 'Create Additional Fields',
        name: 'createAdditionalFields',
        type: 'collection',
        default: {},
        placeholder: 'Add Field',
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['create'],
          },
        },
        options: [
          { displayName: 'URL', name: 'url', type: 'string', default: '' },
          {
            displayName: 'Location ID',
            name: 'location_id',
            type: 'string',
            default: '',
          },
          {
            displayName: 'Contact Name ID',
            name: 'contact_name_id',
            type: 'string',
            default: '',
          },
          {
            displayName: 'Category ID',
            name: 'category_id',
            type: 'string',
            default: '',
          },
          {
            displayName: 'Subcategory ID',
            name: 'subcategory_id',
            type: 'string',
            default: '',
          },
          {
            displayName: 'Assigned To',
            name: 'assigned_to',
            type: 'string',
            default: '',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
          {
            displayName: 'Attributes (JSON)',
            name: 'attributes',
            type: 'json',
            default: '{}',
          },
          {
            displayName: 'Tags',
            name: 'tags',
            type: 'string',
            default: '',
            description: 'Comma-separated tags',
          },
        ],
      },
      {
        displayName: 'Update Additional Fields',
        name: 'updateAdditionalFields',
        type: 'collection',
        default: {},
        placeholder: 'Add Field',
        displayOptions: {
          show: {
            resource: ['ticket'],
            ticketOperation: ['update'],
          },
        },
        options: [
          { displayName: 'Title', name: 'title', type: 'string', default: '' },
          { displayName: 'URL', name: 'url', type: 'string', default: '' },
          {
            displayName: 'Client ID',
            name: 'client_id',
            type: 'resourceLocator',
            default: { mode: 'list', value: '' },
            modes: [
              {
                displayName: 'From List',
                name: 'list',
                type: 'list',
                typeOptions: {
                  searchListMethod: 'searchClients',
                },
              },
              {
                displayName: 'By ID',
                name: 'id',
                type: 'string',
                placeholder: '00000000-0000-0000-0000-000000000000',
              },
            ],
          },
          {
            displayName: 'Board ID',
            name: 'board_id',
            type: 'resourceLocator',
            default: { mode: 'list', value: '' },
            modes: [
              {
                displayName: 'From List',
                name: 'list',
                type: 'list',
                typeOptions: {
                  searchListMethod: 'searchBoards',
                },
              },
              {
                displayName: 'By ID',
                name: 'id',
                type: 'string',
                placeholder: '00000000-0000-0000-0000-000000000000',
              },
            ],
          },
          {
            displayName: 'Status ID',
            name: 'status_id',
            type: 'resourceLocator',
            default: { mode: 'list', value: '' },
            modes: [
              {
                displayName: 'From List',
                name: 'list',
                type: 'list',
                typeOptions: {
                  searchListMethod: 'searchStatuses',
                },
              },
              {
                displayName: 'By ID',
                name: 'id',
                type: 'string',
                placeholder: '00000000-0000-0000-0000-000000000000',
              },
            ],
          },
          {
            displayName: 'Priority ID',
            name: 'priority_id',
            type: 'resourceLocator',
            default: { mode: 'list', value: '' },
            modes: [
              {
                displayName: 'From List',
                name: 'list',
                type: 'list',
                typeOptions: {
                  searchListMethod: 'searchPriorities',
                },
              },
              {
                displayName: 'By ID',
                name: 'id',
                type: 'string',
                placeholder: '00000000-0000-0000-0000-000000000000',
              },
            ],
          },
          {
            displayName: 'Location ID',
            name: 'location_id',
            type: 'string',
            default: '',
          },
          {
            displayName: 'Contact Name ID',
            name: 'contact_name_id',
            type: 'string',
            default: '',
          },
          {
            displayName: 'Category ID',
            name: 'category_id',
            type: 'string',
            default: '',
          },
          {
            displayName: 'Subcategory ID',
            name: 'subcategory_id',
            type: 'string',
            default: '',
          },
          {
            displayName: 'Assigned To',
            name: 'assigned_to',
            type: 'string',
            default: '',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
          {
            displayName: 'Attributes (JSON)',
            name: 'attributes',
            type: 'json',
            default: '{}',
          },
          {
            displayName: 'Tags',
            name: 'tags',
            type: 'string',
            default: '',
            description: 'Comma-separated tags',
          },
        ],
      },

      // Contact fields
      {
        displayName: 'Full Name',
        name: 'full_name',
        type: 'string',
        required: true,
        default: '',
        displayOptions: {
          show: {
            resource: ['contact'],
            contactOperation: ['create'],
          },
        },
      },
      {
        displayName: 'Contact ID',
        name: 'contactId',
        type: 'string',
        required: true,
        default: '',
        displayOptions: {
          show: {
            resource: ['contact'],
            contactOperation: ['get', 'update', 'delete'],
          },
        },
      },
      {
        displayName: 'Create Additional Fields',
        name: 'contactCreateAdditionalFields',
        type: 'collection',
        default: {},
        placeholder: 'Add Field',
        displayOptions: {
          show: {
            resource: ['contact'],
            contactOperation: ['create'],
          },
        },
        options: [
          { displayName: 'Email', name: 'email', type: 'string', default: '' },
          {
            displayName: 'Primary Email Canonical Type',
            name: 'primary_email_canonical_type',
            type: 'options',
            default: '',
            options: [
              { name: 'Work', value: 'work' },
              { name: 'Personal', value: 'personal' },
              { name: 'Billing', value: 'billing' },
              { name: 'Other', value: 'other' },
            ],
          },
          {
            displayName: 'Primary Email Custom Type',
            name: 'primary_email_custom_type',
            type: 'string',
            default: '',
          },
          {
            displayName: 'Additional Email Addresses (JSON)',
            name: 'additional_email_addresses',
            type: 'json',
            default: '[]',
            description: 'Array of objects with required email_address and optional label metadata',
          },
          {
            displayName: 'Client ID',
            name: 'client_id',
            type: 'resourceLocator',
            default: { mode: 'list', value: '' },
            modes: [
              {
                displayName: 'From List',
                name: 'list',
                type: 'list',
                typeOptions: {
                  searchListMethod: 'searchClients',
                },
              },
              {
                displayName: 'By ID',
                name: 'id',
                type: 'string',
                placeholder: '00000000-0000-0000-0000-000000000000',
              },
            ],
          },
          { displayName: 'Role', name: 'role', type: 'string', default: '' },
          {
            displayName: 'Notes',
            name: 'notes',
            type: 'string',
            default: '',
            typeOptions: {
              rows: 4,
            },
          },
          {
            displayName: 'Is Inactive',
            name: 'is_inactive',
            type: 'boolean',
            default: false,
          },
          {
            displayName: 'Phone Numbers (JSON)',
            name: 'phone_numbers',
            type: 'json',
            default: '[]',
            description: 'Array of objects with required phone_number and optional metadata',
          },
        ],
      },
      {
        displayName: 'Update Additional Fields',
        name: 'contactUpdateAdditionalFields',
        type: 'collection',
        default: {},
        placeholder: 'Add Field',
        displayOptions: {
          show: {
            resource: ['contact'],
            contactOperation: ['update'],
          },
        },
        options: [
          { displayName: 'Full Name', name: 'full_name', type: 'string', default: '' },
          { displayName: 'Email', name: 'email', type: 'string', default: '' },
          {
            displayName: 'Primary Email Canonical Type',
            name: 'primary_email_canonical_type',
            type: 'options',
            default: '',
            options: [
              { name: 'Work', value: 'work' },
              { name: 'Personal', value: 'personal' },
              { name: 'Billing', value: 'billing' },
              { name: 'Other', value: 'other' },
            ],
          },
          {
            displayName: 'Primary Email Custom Type',
            name: 'primary_email_custom_type',
            type: 'string',
            default: '',
          },
          {
            displayName: 'Additional Email Addresses (JSON)',
            name: 'additional_email_addresses',
            type: 'json',
            default: '[]',
            description: 'Array of objects with required email_address and optional label metadata',
          },
          {
            displayName: 'Client ID',
            name: 'client_id',
            type: 'resourceLocator',
            default: { mode: 'list', value: '' },
            modes: [
              {
                displayName: 'From List',
                name: 'list',
                type: 'list',
                typeOptions: {
                  searchListMethod: 'searchClients',
                },
              },
              {
                displayName: 'By ID',
                name: 'id',
                type: 'string',
                placeholder: '00000000-0000-0000-0000-000000000000',
              },
            ],
          },
          { displayName: 'Role', name: 'role', type: 'string', default: '' },
          {
            displayName: 'Notes',
            name: 'notes',
            type: 'string',
            default: '',
            typeOptions: {
              rows: 4,
            },
          },
          {
            displayName: 'Is Inactive',
            name: 'is_inactive',
            type: 'boolean',
            default: false,
          },
          {
            displayName: 'Phone Numbers (JSON)',
            name: 'phone_numbers',
            type: 'json',
            default: '[]',
            description: 'Array of objects with required phone_number and optional metadata',
          },
        ],
      },
      {
        displayName: 'Contact List Filters',
        name: 'contactListFilters',
        type: 'collection',
        default: {},
        placeholder: 'Add Filter',
        displayOptions: {
          show: {
            resource: ['contact'],
            contactOperation: ['list'],
          },
        },
        options: [
          { displayName: 'Client ID', name: 'client_id', type: 'string', default: '' },
          { displayName: 'Search Term', name: 'search_term', type: 'string', default: '' },
          {
            displayName: 'Is Inactive',
            name: 'is_inactive',
            type: 'boolean',
            default: false,
          },
        ],
      },
      {
        displayName: 'Page',
        name: 'contactPage',
        type: 'number',
        default: 1,
        typeOptions: {
          minValue: 1,
          numberPrecision: 0,
        },
        displayOptions: {
          show: {
            resource: ['contact'],
            contactOperation: ['list'],
          },
        },
      },
      {
        displayName: 'Limit',
        name: 'contactLimit',
        type: 'number',
        default: 25,
        typeOptions: {
          minValue: 1,
          maxValue: 100,
          numberPrecision: 0,
        },
        displayOptions: {
          show: {
            resource: ['contact'],
            contactOperation: ['list'],
          },
        },
      },

      // Project Task fields
      {
        displayName: 'Task Name',
        name: 'task_name',
        type: 'string',
        required: true,
        default: '',
        displayOptions: {
          show: {
            resource: ['projectTask'],
            projectTaskOperation: ['create'],
          },
        },
      },
      {
        displayName: 'Project ID',
        name: 'projectTaskProjectId',
        type: 'resourceLocator',
        default: { mode: 'list', value: '' },
        required: true,
        displayOptions: {
          show: {
            resource: ['projectTask'],
            projectTaskOperation: ['create', 'list'],
          },
        },
        modes: [
          {
            displayName: 'From List',
            name: 'list',
            type: 'list',
            typeOptions: {
              searchListMethod: 'searchProjects',
            },
          },
          {
            displayName: 'By ID',
            name: 'id',
            type: 'string',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
        ],
        description: 'Select a project or enter a project UUID manually',
      },
      {
        displayName: 'Phase ID',
        name: 'projectTaskPhaseId',
        type: 'resourceLocator',
        default: { mode: 'list', value: '' },
        required: true,
        displayOptions: {
          show: {
            resource: ['projectTask'],
            projectTaskOperation: ['create'],
          },
        },
        modes: [
          {
            displayName: 'From List',
            name: 'list',
            type: 'list',
            typeOptions: {
              searchListMethod: 'searchProjectPhases',
            },
          },
          {
            displayName: 'By ID',
            name: 'id',
            type: 'string',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
        ],
        description: 'Select a phase within the selected project, or enter a phase UUID manually',
      },
      {
        displayName: 'Status Mapping ID',
        name: 'projectTaskStatusMappingId',
        type: 'resourceLocator',
        default: { mode: 'list', value: '' },
        required: true,
        displayOptions: {
          show: {
            resource: ['projectTask'],
            projectTaskOperation: ['create'],
          },
        },
        modes: [
          {
            displayName: 'From List',
            name: 'list',
            type: 'list',
            typeOptions: {
              searchListMethod: 'searchProjectTaskStatusMappings',
            },
          },
          {
            displayName: 'By ID',
            name: 'id',
            type: 'string',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
        ],
        description:
          'Project-specific task status mapping UUID. Use the lookup to see valid options for the selected project.',
      },
      {
        displayName: 'Task ID',
        name: 'projectTaskId',
        type: 'string',
        required: true,
        default: '',
        displayOptions: {
          show: {
            resource: ['projectTask'],
            projectTaskOperation: ['get', 'update', 'delete'],
          },
        },
      },
      {
        displayName: 'Create Additional Fields',
        name: 'projectTaskCreateAdditionalFields',
        type: 'collection',
        default: {},
        placeholder: 'Add Field',
        displayOptions: {
          show: {
            resource: ['projectTask'],
            projectTaskOperation: ['create'],
          },
        },
        options: [
          {
            displayName: 'Description',
            name: 'description',
            type: 'string',
            default: '',
            typeOptions: {
              rows: 4,
            },
          },
          {
            displayName: 'Assigned To',
            name: 'assigned_to',
            type: 'string',
            default: '',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
          {
            displayName: 'Estimated Hours',
            name: 'estimated_hours',
            type: 'number',
            default: 0,
            typeOptions: {
              minValue: 0,
            },
          },
          {
            displayName: 'Due Date',
            name: 'due_date',
            type: 'string',
            default: '',
            placeholder: 'YYYY-MM-DD or ISO 8601 timestamp',
          },
          {
            displayName: 'Priority ID',
            name: 'priority_id',
            type: 'string',
            default: '',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
          {
            displayName: 'Task Type Key',
            name: 'task_type_key',
            type: 'string',
            default: '',
            description: 'Optional task type key; server defaults to "general"',
          },
          {
            displayName: 'WBS Code',
            name: 'wbs_code',
            type: 'string',
            default: '',
          },
          {
            displayName: 'Tags',
            name: 'tags',
            type: 'string',
            default: '',
            description: 'Comma-separated tags',
          },
        ],
      },
      {
        displayName: 'Update Additional Fields',
        name: 'projectTaskUpdateAdditionalFields',
        type: 'collection',
        default: {},
        placeholder: 'Add Field',
        displayOptions: {
          show: {
            resource: ['projectTask'],
            projectTaskOperation: ['update'],
          },
        },
        options: [
          { displayName: 'Task Name', name: 'task_name', type: 'string', default: '' },
          {
            displayName: 'Description',
            name: 'description',
            type: 'string',
            default: '',
            typeOptions: {
              rows: 4,
            },
          },
          {
            displayName: 'Assigned To',
            name: 'assigned_to',
            type: 'string',
            default: '',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
          {
            displayName: 'Estimated Hours',
            name: 'estimated_hours',
            type: 'number',
            default: 0,
            typeOptions: {
              minValue: 0,
            },
          },
          {
            displayName: 'Due Date',
            name: 'due_date',
            type: 'string',
            default: '',
            placeholder: 'YYYY-MM-DD or ISO 8601 timestamp',
          },
          {
            displayName: 'Priority ID',
            name: 'priority_id',
            type: 'string',
            default: '',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
          {
            displayName: 'Task Type Key',
            name: 'task_type_key',
            type: 'string',
            default: '',
          },
          {
            displayName: 'Status Mapping ID',
            name: 'project_status_mapping_id',
            type: 'string',
            default: '',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
          {
            displayName: 'WBS Code',
            name: 'wbs_code',
            type: 'string',
            default: '',
          },
          {
            displayName: 'Tags',
            name: 'tags',
            type: 'string',
            default: '',
            description: 'Comma-separated tags',
          },
        ],
      },
      {
        displayName: 'Page',
        name: 'projectTaskPage',
        type: 'number',
        default: 1,
        typeOptions: {
          minValue: 1,
          numberPrecision: 0,
        },
        displayOptions: {
          show: {
            resource: ['projectTask'],
            projectTaskOperation: ['list'],
          },
        },
      },
      {
        displayName: 'Limit',
        name: 'projectTaskLimit',
        type: 'number',
        default: 25,
        typeOptions: {
          minValue: 1,
          maxValue: 100,
          numberPrecision: 0,
        },
        displayOptions: {
          show: {
            resource: ['projectTask'],
            projectTaskOperation: ['list'],
          },
        },
      },

      // Helper list parameters
      {
        displayName: 'Page',
        name: 'helperPage',
        type: 'number',
        default: 1,
        typeOptions: {
          minValue: 1,
          numberPrecision: 0,
        },
        displayOptions: {
          show: {
            resource: ['client', 'board', 'status', 'priority'],
          },
        },
      },
      {
        displayName: 'Limit',
        name: 'helperLimit',
        type: 'number',
        default: 25,
        typeOptions: {
          minValue: 1,
          maxValue: 100,
          numberPrecision: 0,
        },
        displayOptions: {
          show: {
            resource: ['client', 'board', 'status', 'priority'],
          },
        },
      },
      {
        displayName: 'Search',
        name: 'helperSearch',
        type: 'string',
        default: '',
        displayOptions: {
          show: {
            resource: ['client', 'board', 'status', 'priority'],
          },
        },
      },
      {
        displayName: 'Status Type',
        name: 'helperStatusType',
        type: 'options',
        default: 'ticket',
        options: [
          { name: 'Ticket', value: 'ticket' },
          { name: 'Project', value: 'project' },
          { name: 'Project Task', value: 'project_task' },
          { name: 'Interaction', value: 'interaction' },
        ],
        displayOptions: {
          show: {
            resource: ['status'],
            statusOperation: ['list'],
          },
        },
        description: 'Filter statuses by entity type',
      },
      {
        displayName: 'Board ID',
        name: 'helperBoardId',
        type: 'resourceLocator',
        default: { mode: 'list', value: '' },
        required: true,
        displayOptions: {
          show: {
            resource: ['status'],
            statusOperation: ['list'],
            helperStatusType: ['ticket'],
          },
        },
        modes: [
          {
            displayName: 'From List',
            name: 'list',
            type: 'list',
            typeOptions: {
              searchListMethod: 'searchBoards',
            },
          },
          {
            displayName: 'By ID',
            name: 'id',
            type: 'string',
            placeholder: '00000000-0000-0000-0000-000000000000',
          },
        ],
        description:
          'Required — ticket statuses are board-owned, so listing them requires a board_id',
      },
    ],
  };

  methods = {
    listSearch: {
      async searchClients(this: ILoadOptionsFunctions, filter?: string): Promise<INodeListSearchResult> {
        return loadLookup(this, '/api/v1/clients', 'client_id', ['client_name', 'name'], filter);
      },
      async searchBoards(this: ILoadOptionsFunctions, filter?: string): Promise<INodeListSearchResult> {
        return loadLookup(this, '/api/v1/boards', 'board_id', ['board_name', 'name'], filter);
      },
      async searchStatuses(this: ILoadOptionsFunctions, filter?: string): Promise<INodeListSearchResult> {
        const type = getCurrentStatusLookupType(this);
        const boardId = getCurrentStatusBoardId(this);

        // Ticket statuses are board-scoped; without a board_id the server returns 400.
        // Return an empty list so the user can fall back to manual UUID entry.
        if (type === 'ticket' && !boardId) {
          return { results: [] };
        }

        return loadLookup(
          this,
          '/api/v1/statuses',
          'status_id',
          ['name', 'status_name'],
          filter,
          compactObject({ type, board_id: boardId }),
        );
      },
      async searchPriorities(this: ILoadOptionsFunctions, filter?: string): Promise<INodeListSearchResult> {
        return loadLookup(
          this,
          '/api/v1/priorities',
          'priority_id',
          ['priority_name', 'name'],
          filter,
        );
      },
      async searchProjects(this: ILoadOptionsFunctions, filter?: string): Promise<INodeListSearchResult> {
        return loadLookup(
          this,
          '/api/v1/projects',
          'project_id',
          ['project_name', 'name'],
          filter,
        );
      },
      async searchProjectPhases(this: ILoadOptionsFunctions, filter?: string): Promise<INodeListSearchResult> {
        const projectId = getCurrentProjectLookupId(this);
        if (!projectId) {
          return { results: [] };
        }

        return loadLookup(
          this,
          `/api/v1/projects/${projectId}/phases`,
          'phase_id',
          ['phase_name', 'name'],
          filter,
        );
      },
      async searchProjectTaskStatusMappings(
        this: ILoadOptionsFunctions,
        filter?: string,
      ): Promise<INodeListSearchResult> {
        const projectId = getCurrentProjectLookupId(this);
        if (!projectId) {
          return { results: [] };
        }

        return loadLookup(
          this,
          `/api/v1/projects/${projectId}/task-status-mappings`,
          'project_status_mapping_id',
          ['custom_name', 'status_name', 'name'],
          filter,
        );
      },
    },
  };

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const items = this.getInputData();
    const returnData: INodeExecutionData[] = [];

    for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
      try {
        const resource = this.getNodeParameter('resource', itemIndex) as Resource;
        const operationParamName = getOperationParameterName(resource);
        const operation = this.getNodeParameter(operationParamName, itemIndex) as string;

        const responseJson =
          resource === 'ticket'
            ? await executeTicketOperation(this, itemIndex, operation as TicketOperation)
            : resource === 'contact'
              ? await executeContactOperation(this, itemIndex, operation as ContactOperation)
              : resource === 'projectTask'
                ? await executeProjectTaskOperation(
                    this,
                    itemIndex,
                    operation as ProjectTaskOperation,
                  )
            : await executeHelperOperation(
                this,
                resource as HelperResource,
                itemIndex,
                operation,
              );

        returnData.push({
          json: responseJson,
          pairedItem: { item: itemIndex },
        });
      } catch (error) {
        const mappedError = formatAlgaApiError(error);
        const errorPayload = {
          error: {
            code: mappedError.code,
            message: mappedError.message,
            ...(mappedError.details !== undefined ? { details: mappedError.details } : {}),
            ...(mappedError.statusCode !== undefined
              ? { statusCode: mappedError.statusCode }
              : {}),
          },
        } as IDataObject;

        if (this.continueOnFail()) {
          returnData.push({
            json: errorPayload,
            pairedItem: { item: itemIndex },
          });
          continue;
        }

        if (error instanceof NodeApiError || error instanceof NodeOperationError) {
          throw error;
        }

        throw new NodeApiError(this.getNode(), (error ?? {}) as JsonObject, {
          itemIndex,
          message: mappedError.message,
          description: JSON.stringify({
            code: mappedError.code,
            details: mappedError.details,
          }),
          ...(mappedError.statusCode ? { httpCode: String(mappedError.statusCode) } : {}),
        });
      }
    }

    return [returnData];
  }
}

async function executeHelperOperation(
  context: IExecuteFunctions,
  resource: HelperResource,
  itemIndex: number,
  operation: string,
): Promise<IDataObject> {
  if (operation !== 'list') {
    throw new NodeOperationError(context.getNode(), `Unsupported operation: ${operation}`, {
      itemIndex,
    });
  }

  const endpoint = getHelperEndpoint(resource);
  const page = context.getNodeParameter('helperPage', itemIndex, 1) as number;
  const limit = context.getNodeParameter('helperLimit', itemIndex, 25) as number;
  const search = context.getNodeParameter('helperSearch', itemIndex, '') as string;
  const statusType =
    resource === 'status'
      ? (context.getNodeParameter('helperStatusType', itemIndex, 'ticket') as StatusType)
      : undefined;

  let statusBoardId: string | undefined;
  if (resource === 'status' && statusType === 'ticket') {
    const rawHelperBoard = context.getNodeParameter('helperBoardId', itemIndex, {}) as unknown;
    statusBoardId = requireUuid(
      context,
      extractResourceLocatorValue(rawHelperBoard),
      'helperBoardId',
      itemIndex,
    );
  }

  const query = compactObject({
    page,
    limit,
    search,
    type: statusType,
    board_id: statusBoardId,
  });

  const response = await algaApiRequest(context, 'GET', endpoint, query);
  return normalizeSuccessResponse(response);
}

async function executeContactOperation(
  context: IExecuteFunctions,
  itemIndex: number,
  operation: ContactOperation,
): Promise<IDataObject> {
  switch (operation) {
    case 'create': {
      const fullName = requireNonEmpty(
        context,
        context.getNodeParameter('full_name', itemIndex) as string,
        'full_name',
        itemIndex,
      );
      const rawAdditionalFields = context.getNodeParameter(
        'contactCreateAdditionalFields',
        itemIndex,
        {},
      ) as IDataObject;
      const additionalFields = {
        ...rawAdditionalFields,
        client_id: extractResourceLocatorValue(rawAdditionalFields.client_id),
      } as IDataObject;

      const payload = buildWithOperationValidation(context, itemIndex, () =>
        buildContactCreatePayload({
          fullName,
          additionalFields,
        }),
      );

      const response = await algaApiRequest(context, 'POST', '/api/v1/contacts', undefined, payload);
      return normalizeSuccessResponse(response);
    }

    case 'get': {
      const contactId = requireUuid(
        context,
        context.getNodeParameter('contactId', itemIndex) as string,
        'contactId',
        itemIndex,
      );

      const response = await algaApiRequest(context, 'GET', `/api/v1/contacts/${contactId}`);
      return normalizeSuccessResponse(response);
    }

    case 'list': {
      const page = context.getNodeParameter('contactPage', itemIndex, 1) as number;
      const limit = context.getNodeParameter('contactLimit', itemIndex, 25) as number;
      const filters = context.getNodeParameter('contactListFilters', itemIndex, {}) as IDataObject;

      const query = buildWithOperationValidation(context, itemIndex, () =>
        buildContactListQuery({
          page,
          limit,
          filters,
        }),
      );

      const response = await algaApiRequest(context, 'GET', '/api/v1/contacts', query);
      return normalizeSuccessResponse(response);
    }

    case 'update': {
      const contactId = requireUuid(
        context,
        context.getNodeParameter('contactId', itemIndex) as string,
        'contactId',
        itemIndex,
      );
      const rawAdditionalFields = context.getNodeParameter(
        'contactUpdateAdditionalFields',
        itemIndex,
        {},
      ) as IDataObject;
      const additionalFields = {
        ...rawAdditionalFields,
        client_id: extractResourceLocatorValue(rawAdditionalFields.client_id),
      } as IDataObject;

      const payload = buildWithOperationValidation(context, itemIndex, () =>
        buildContactUpdatePayload(additionalFields),
      );

      if (Object.keys(payload).length === 0) {
        throw new NodeOperationError(context.getNode(), 'At least one update field is required', {
          itemIndex,
        });
      }

      const response = await algaApiRequest(
        context,
        'PUT',
        `/api/v1/contacts/${contactId}`,
        undefined,
        payload,
      );
      return normalizeSuccessResponse(response);
    }

    case 'delete': {
      const contactId = requireUuid(
        context,
        context.getNodeParameter('contactId', itemIndex) as string,
        'contactId',
        itemIndex,
      );

      const response = await algaApiRequest(context, 'DELETE', `/api/v1/contacts/${contactId}`);
      return normalizeDeleteSuccess(contactId, response);
    }
  }
}

async function executeProjectTaskOperation(
  context: IExecuteFunctions,
  itemIndex: number,
  operation: ProjectTaskOperation,
): Promise<IDataObject> {
  switch (operation) {
    case 'create': {
      const taskName = requireNonEmpty(
        context,
        context.getNodeParameter('task_name', itemIndex) as string,
        'task_name',
        itemIndex,
      );
      const projectId = requireUuid(
        context,
        getResourceLocatorId(context, 'projectTaskProjectId', itemIndex),
        'projectTaskProjectId',
        itemIndex,
      );
      const phaseId = requireUuid(
        context,
        getResourceLocatorId(context, 'projectTaskPhaseId', itemIndex),
        'projectTaskPhaseId',
        itemIndex,
      );
      const statusMappingId = requireUuid(
        context,
        getResourceLocatorId(context, 'projectTaskStatusMappingId', itemIndex),
        'projectTaskStatusMappingId',
        itemIndex,
      );

      const additionalFields = context.getNodeParameter(
        'projectTaskCreateAdditionalFields',
        itemIndex,
        {},
      ) as IDataObject;

      const payload = buildWithOperationValidation(context, itemIndex, () =>
        buildProjectTaskCreatePayload({
          taskName,
          statusMappingId,
          additionalFields,
        }),
      );

      const response = await algaApiRequest(
        context,
        'POST',
        `/api/v1/projects/${projectId}/phases/${phaseId}/tasks`,
        undefined,
        payload,
      );
      return normalizeSuccessResponse(response);
    }

    case 'get': {
      const taskId = requireUuid(
        context,
        context.getNodeParameter('projectTaskId', itemIndex) as string,
        'projectTaskId',
        itemIndex,
      );

      const response = await algaApiRequest(
        context,
        'GET',
        `/api/v1/projects/tasks/${taskId}`,
      );
      return normalizeSuccessResponse(response);
    }

    case 'list': {
      const projectId = requireUuid(
        context,
        getResourceLocatorId(context, 'projectTaskProjectId', itemIndex),
        'projectTaskProjectId',
        itemIndex,
      );
      const page = context.getNodeParameter('projectTaskPage', itemIndex, 1) as number;
      const limit = context.getNodeParameter('projectTaskLimit', itemIndex, 25) as number;

      const query = buildProjectTaskListQuery({ page, limit });

      const response = await algaApiRequest(
        context,
        'GET',
        `/api/v1/projects/${projectId}/tasks`,
        query,
      );
      return normalizeSuccessResponse(response);
    }

    case 'update': {
      const taskId = requireUuid(
        context,
        context.getNodeParameter('projectTaskId', itemIndex) as string,
        'projectTaskId',
        itemIndex,
      );
      const additionalFields = context.getNodeParameter(
        'projectTaskUpdateAdditionalFields',
        itemIndex,
        {},
      ) as IDataObject;

      const payload = buildWithOperationValidation(context, itemIndex, () =>
        buildProjectTaskUpdatePayload(additionalFields),
      );

      if (Object.keys(payload).length === 0) {
        throw new NodeOperationError(context.getNode(), 'At least one update field is required', {
          itemIndex,
        });
      }

      const response = await algaApiRequest(
        context,
        'PUT',
        `/api/v1/projects/tasks/${taskId}`,
        undefined,
        payload,
      );
      return normalizeSuccessResponse(response);
    }

    case 'delete': {
      const taskId = requireUuid(
        context,
        context.getNodeParameter('projectTaskId', itemIndex) as string,
        'projectTaskId',
        itemIndex,
      );

      const response = await algaApiRequest(
        context,
        'DELETE',
        `/api/v1/projects/tasks/${taskId}`,
      );
      return normalizeDeleteSuccess(taskId, response);
    }
  }
}

async function executeTicketOperation(
  context: IExecuteFunctions,
  itemIndex: number,
  operation: TicketOperation,
): Promise<IDataObject> {
  switch (operation) {
    case 'create': {
      const title = requireNonEmpty(
        context,
        context.getNodeParameter('title', itemIndex) as string,
        'title',
        itemIndex,
      );

      const clientId = requireUuid(
        context,
        getResourceLocatorId(context, 'client_id', itemIndex),
        'client_id',
        itemIndex,
      );
      const boardId = requireUuid(
        context,
        getResourceLocatorId(context, 'board_id', itemIndex),
        'board_id',
        itemIndex,
      );
      const statusId = requireUuid(
        context,
        getResourceLocatorId(context, 'status_id', itemIndex),
        'status_id',
        itemIndex,
      );
      const priorityId = requireUuid(
        context,
        getResourceLocatorId(context, 'priority_id', itemIndex),
        'priority_id',
        itemIndex,
      );

      const additionalFields = context.getNodeParameter(
        'createAdditionalFields',
        itemIndex,
        {},
      ) as IDataObject;

      const payload = buildTicketCreatePayload({
        title,
        clientId,
        boardId,
        statusId,
        priorityId,
        additionalFields,
      });

      const response = await algaApiRequest(context, 'POST', '/api/v1/tickets', undefined, payload);
      return normalizeSuccessResponse(response);
    }

    case 'get': {
      const ticketId = requireUuid(
        context,
        context.getNodeParameter('ticketId', itemIndex) as string,
        'ticketId',
        itemIndex,
      );

      const response = await algaApiRequest(context, 'GET', `/api/v1/tickets/${ticketId}`);
      return normalizeSuccessResponse(response);
    }

    case 'list': {
      const page = context.getNodeParameter('page', itemIndex, 1) as number;
      const limit = context.getNodeParameter('limit', itemIndex, 25) as number;
      const sort = context.getNodeParameter('sort', itemIndex, 'entered_at') as string;
      const order = context.getNodeParameter('order', itemIndex, 'desc') as string;
      const listFilters = context.getNodeParameter('listFilters', itemIndex, {}) as IDataObject;

      const query = buildTicketListQuery({
        page,
        limit,
        sort,
        order,
        filters: compactObject(listFilters),
      });

      const response = await algaApiRequest(context, 'GET', '/api/v1/tickets', query);
      return normalizeSuccessResponse(response);
    }

    case 'listComments': {
      const ticketId = requireUuid(
        context,
        context.getNodeParameter('ticketId', itemIndex) as string,
        'ticketId',
        itemIndex,
      );
      const commentListOptions = context.getNodeParameter(
        'commentListOptions',
        itemIndex,
        {},
      ) as IDataObject;

      const query = buildTicketCommentListQuery(commentListOptions);
      const response = await algaApiRequest(
        context,
        'GET',
        `/api/v1/tickets/${ticketId}/comments`,
        query,
      );

      return normalizeSuccessResponse(response);
    }

    case 'search': {
      const queryText = requireNonEmpty(
        context,
        context.getNodeParameter('query', itemIndex) as string,
        'query',
        itemIndex,
      );

      const searchLimit = context.getNodeParameter('searchLimit', itemIndex, 25) as number;
      const searchFields = context.getNodeParameter(
        'searchAdditionalFields',
        itemIndex,
        {},
      ) as IDataObject;

      const query = buildTicketSearchQuery({
        query: queryText,
        limit: searchLimit,
        includeClosed: searchFields.include_closed as boolean | undefined,
        fields: (searchFields.fields as string[] | undefined) ?? [],
        statusIds: parseCsvList(searchFields.status_ids),
        priorityIds: parseCsvList(searchFields.priority_ids),
        clientIds: parseCsvList(searchFields.client_ids),
        assignedToIds: parseCsvList(searchFields.assigned_to_ids),
      });

      const response = await algaApiRequest(context, 'GET', '/api/v1/tickets/search', query);
      return normalizeSuccessResponse(response);
    }

    case 'update': {
      const ticketId = requireUuid(
        context,
        context.getNodeParameter('ticketId', itemIndex) as string,
        'ticketId',
        itemIndex,
      );
      const rawAdditionalFields = context.getNodeParameter(
        'updateAdditionalFields',
        itemIndex,
        {},
      ) as IDataObject;

      const normalizedAdditionalFields = {
        ...rawAdditionalFields,
        client_id: extractResourceLocatorValue(rawAdditionalFields.client_id),
        board_id: extractResourceLocatorValue(rawAdditionalFields.board_id),
        status_id: extractResourceLocatorValue(rawAdditionalFields.status_id),
        priority_id: extractResourceLocatorValue(rawAdditionalFields.priority_id),
      } as IDataObject;

      const payload = buildTicketUpdatePayload(normalizedAdditionalFields);

      if (Object.keys(payload).length === 0) {
        throw new NodeOperationError(context.getNode(), 'At least one update field is required', {
          itemIndex,
        });
      }

      const response = await algaApiRequest(
        context,
        'PUT',
        `/api/v1/tickets/${ticketId}`,
        undefined,
        payload,
      );

      return normalizeSuccessResponse(response);
    }

    case 'addComment': {
      const ticketId = requireUuid(
        context,
        context.getNodeParameter('ticketId', itemIndex) as string,
        'ticketId',
        itemIndex,
      );
      const commentText = requireNonEmpty(
        context,
        context.getNodeParameter('commentText', itemIndex) as string,
        'commentText',
        itemIndex,
      );
      const commentAdditionalFields = context.getNodeParameter(
        'commentAdditionalFields',
        itemIndex,
        {},
      ) as IDataObject;

      const payload = buildTicketCommentPayload(commentText, commentAdditionalFields);
      const response = await algaApiRequest(
        context,
        'POST',
        `/api/v1/tickets/${ticketId}/comments`,
        undefined,
        payload,
      );

      return normalizeSuccessResponse(response);
    }

    case 'updateStatus': {
      const ticketId = requireUuid(
        context,
        context.getNodeParameter('ticketId', itemIndex) as string,
        'ticketId',
        itemIndex,
      );
      const statusId = requireUuid(
        context,
        getResourceLocatorId(context, 'status_id', itemIndex),
        'status_id',
        itemIndex,
      );

      const response = await algaApiRequest(
        context,
        'PUT',
        `/api/v1/tickets/${ticketId}/status`,
        undefined,
        { status_id: statusId },
      );

      return normalizeSuccessResponse(response);
    }

    case 'updateAssignment': {
      const ticketId = requireUuid(
        context,
        context.getNodeParameter('ticketId', itemIndex) as string,
        'ticketId',
        itemIndex,
      );

      const assignmentAction = context.getNodeParameter(
        'assignmentAction',
        itemIndex,
      ) as 'assign' | 'clear';
      const assignedTo =
        assignmentAction === 'assign'
          ? requireUuid(
              context,
              context.getNodeParameter('assigned_to', itemIndex) as string,
              'assigned_to',
              itemIndex,
            )
          : null;

      const response = await algaApiRequest(
        context,
        'PUT',
        `/api/v1/tickets/${ticketId}/assignment`,
        undefined,
        { assigned_to: assignedTo },
      );

      return normalizeSuccessResponse(response);
    }

    case 'delete': {
      const ticketId = requireUuid(
        context,
        context.getNodeParameter('ticketId', itemIndex) as string,
        'ticketId',
        itemIndex,
      );

      const response = await algaApiRequest(context, 'DELETE', `/api/v1/tickets/${ticketId}`);
      return normalizeDeleteSuccess(ticketId, response);
    }
  }
}

function getResourceLocatorId(
  context: IExecuteFunctions,
  parameterName: string,
  itemIndex: number,
): string {
  const value = context.getNodeParameter(parameterName, itemIndex) as INodePropertyOptions | IDataObject;
  return extractResourceLocatorValue(value);
}

function requireNonEmpty(
  context: IExecuteFunctions,
  value: unknown,
  fieldName: string,
  itemIndex: number,
): string {
  try {
    return ensureNonEmpty(value, fieldName);
  } catch (error) {
    throw new NodeOperationError(context.getNode(), (error as Error).message, {
      itemIndex,
    });
  }
}

function requireUuid(
  context: IExecuteFunctions,
  value: unknown,
  fieldName: string,
  itemIndex: number,
): string {
  try {
    return ensureUuid(value, fieldName);
  } catch (error) {
    throw new NodeOperationError(context.getNode(), (error as Error).message, {
      itemIndex,
    });
  }
}

function buildWithOperationValidation<T>(
  context: IExecuteFunctions,
  itemIndex: number,
  builder: () => T,
): T {
  try {
    return builder();
  } catch (error) {
    throw new NodeOperationError(context.getNode(), (error as Error).message, {
      itemIndex,
    });
  }
}
