/**
 * @file documentTemplate.ts - API wrapper for IWS5 document template objects and functions.
 */

import type {
  DocumentFieldType,
  DocumentFieldTypeName,
} from '../types/documentField.ts';
import { FetchError } from '../utils/fetchHelper.ts';
import { getAccessToken } from '../services/authService.ts';
import { StatusCodes } from 'http-status-codes';

/** Base URL for the IWS5 document templates API endpoint */
export const DOCUMENT_TEMPLATES_API_URL =
  window.__APP_CONFIG__?.IWS5_API_URL + '/api/DocumentTemplate';

// Discriminated type unions to handle API return formatting inconsistencies
// The IWS5 API uses "cabinet" and "template" in different endpoints to represent the same thing
// These unions allow either variant
/** Template ID: returned as either `cabinetId` or `templateId` by IWS5 */
type TemplateId =
  | { cabinetId: number; templateId?: never }
  | { cabinetId?: never; templateId: number };
/** Template name: returned as either `cabinetName` or `templateName` by IWS5 */
type TemplateName =
  | { cabinetName: string; templateName?: never }
  | { cabinetName?: never; templateName: string };
/** Template description: returned as either `cabinetDescription` or `templateDescription` by IWS5 */
type TemplateDescription =
  | { cabinetDescription: string; templateDescription?: never }
  | { cabinetDescription?: never; templateDescription: string };
/** Template field ID: returned as either `fieldId` or `id` by IWS5 */
type TemplateFieldId =
  | { fieldId: number; id?: never }
  | { fieldId?: never; id: number };
/** Template field name: returned as either `fieldName` or `name` by IWS5 */
type TemplateFieldName =
  | { fieldName: string; name?: never }
  | { fieldName?: never; name: string };
/** Template field type name: returned as either `fieldTypeName` or `dataType` by IWS5 */
type TemplateFieldTypeName =
  | { fieldTypeName: DocumentFieldTypeName; dataType?: never }
  | { fieldTypeName?: never; dataType: DocumentFieldTypeName };
/** Template field size: returned as either `size` or `fieldSize` by IWS5 */
type TemplateFieldSize =
  | { size: number; fieldSize?: never }
  | { size?: never; fieldSize: number };

/**
 * Represents a document template defining the structure for document fields.
 * Composed of discriminated union types to handle IWS5 API naming inconsistencies.
 */
export type DocumentTemplate = TemplateId &
  TemplateName &
  TemplateDescription & {
    /** Numeric identifier for the template type */
    templateTypeId: number;

    /** Human-readable name for the template type */
    templateTypeName: string;

    /** ISO date string when the template was created */
    createdDate?: string;

    /** ISO date string when the template was last modified */
    modifiedDate?: string;

    /** Database connection ID, if the template links to an external data source */
    connectionId?: number | null;

    /** Database name, if the template links to an external data source */
    databaseName?: string | null;

    /** Whether this is the default template for a given process */
    isDefault?: boolean;

    /** List of fields defined by this template */
    fields?: DocumentTemplateField[];
  };

/**
 * Represents a single document field within a document template.
 * Composed of discriminated union types to handle IWS5 API naming inconsistencies.
 */
export type DocumentTemplateField = TemplateFieldId &
  TemplateFieldName &
  TemplateFieldTypeName &
  TemplateFieldSize & {
    /** Human-readable description of the field's purpose */
    description?: string;

    /** Numeric field type identifier (maps to DocumentFieldType enum values) */
    fieldType?: DocumentFieldType;

    /** Current value of the field for a specific document instance */
    fieldValue?: string;

    /** Default value assigned to the field when a new document is created */
    defaultValue?: string;

    /** Whether the field can be edited by the current user */
    isEditable?: boolean;

    /** Whether the field should be displayed in the UI */
    display?: boolean;
  };

/**
 * Retrieves a document template.
 *
 * @param templateId ID of document template
 * @returns Retrieved document template
 * @throws FetchError If response is not 200-class
 */
export async function getDocumentTemplate(
  templateId: number
): Promise<DocumentTemplate> {
  const _url = DOCUMENT_TEMPLATES_API_URL + '/' + templateId;

  // Attempt GET /api/DocumentTemplate/{templateId}
  try {
    const response = await fetch(_url.toString(), {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${getAccessToken()}`,
      },
    });
    if (!response.ok) {
      const errorText = await response.text();
      throw new FetchError(
        errorText || 'Failed to retrieve document template',
        response
      );
    }
    if (response.status === StatusCodes.NO_CONTENT) {
      return {} as DocumentTemplate;
    }

    return (await response.json()).responseModel as DocumentTemplate;
  } catch (error) {
    throw new FetchError((error as Error).message);
  }
}

/**
 * Retrieves the fields of a document template.
 *
 * @param templateId ID of document template
 * @returns List of fields for document template
 * @throws FetchError If response is not 200-class
 */
export async function listTemplateFields(
  templateId: number
): Promise<DocumentTemplateField[]> {
  const _url = DOCUMENT_TEMPLATES_API_URL + '/' + templateId + '/fields';

  // Attempt GET /api/DocumentTemplate/{templateId}/fields
  try {
    const response = await fetch(_url.toString(), {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${getAccessToken()}`,
      },
    });
    if (!response.ok) {
      const errorText = await response.text();
      throw new FetchError(
        errorText || 'Failed to retrieve document template fields',
        response
      );
    }
    if (response.status === StatusCodes.NO_CONTENT) {
      return [] as DocumentTemplateField[];
    }

    return (await response.json()).responseModel
      .fields as DocumentTemplateField[];
  } catch (error) {
    throw new FetchError((error as Error).message);
  }
}
