import type { Template } from '@pdfme/common';
import { formatTopicItems } from './pdfme-transforms';
import type { PdfTopic, PdfTopicEngineOptions } from './pdf-topic.type';

type TemplateSchema = {
  name?: string;
  position?: { x?: number; y?: number };
  width?: number;
  height?: number;
  content?: string;
  [key: string]: unknown;
};

const DEFAULT_TOPIC_ENGINE_OPTIONS: Required<PdfTopicEngineOptions> = {
  targetPageIndex: 1,
  pageStartY: 35,
  pageBottomY: 250,
  topicSpacing: 10,
  keepTogetherNames: [
    'Please_do_not',
    'yours_faithfuly',
    'app1',
    'app1_position',
    'app2',
    'app2_position',
    'app3',
    'app3_position'
  ],
  topicTemplateName: 'topic',
  topicDataTemplateName: 'data_topic',
  rowHeight: 7,
  keepTogetherMinSpace: 60
};

function cloneTemplate<T>(value: T): T {
  return JSON.parse(JSON.stringify(value)) as T;
}

function getFieldY(field: TemplateSchema) {
  return typeof field.position?.y === 'number' ? field.position.y : 0;
}

function getFieldBottom(field: TemplateSchema) {
  return getFieldY(field) + (typeof field.height === 'number' ? field.height : 0);
}

function setFieldY(field: TemplateSchema, y: number) {
  field.position = {
    ...(field.position ?? {}),
    y
  };
}

function sortTopics(topics: PdfTopic[]) {
  return [...topics].sort((left, right) => {
    const sortDelta = (left.sortOrder ?? Number.MAX_SAFE_INTEGER) - (right.sortOrder ?? Number.MAX_SAFE_INTEGER);

    if (sortDelta !== 0) {
      return sortDelta;
    }

    return String(left.id ?? left.topicType).localeCompare(String(right.id ?? right.topicType));
  });
}

export function buildPdfTopicTemplate(
  template: Template,
  topics: PdfTopic[],
  options?: PdfTopicEngineOptions
): {
  template: Template;
  topicInputs: Record<string, string[][]>;
} {
  const resolvedOptions = { ...DEFAULT_TOPIC_ENGINE_OPTIONS, ...options };
  const nextTemplate = cloneTemplate(template);
  const pages = Array.isArray(nextTemplate.schemas) ? [...nextTemplate.schemas] : [];
  const targetPage = pages[resolvedOptions.targetPageIndex];

  if (!Array.isArray(targetPage)) {
    return { template: nextTemplate, topicInputs: {} };
  }

  const topicTemplate = targetPage.find(
    (field) => (field as TemplateSchema).name === resolvedOptions.topicTemplateName
  ) as TemplateSchema | undefined;
  const topicDataTemplate = targetPage.find(
    (field) => (field as TemplateSchema).name === resolvedOptions.topicDataTemplateName
  ) as TemplateSchema | undefined;

  if (!topicTemplate || !topicDataTemplate) {
    console.warn('[pdf-topic-engine] Missing topic or data_topic schema template');
    return { template: nextTemplate, topicInputs: {} };
  }

  const keepTogetherSet = new Set(resolvedOptions.keepTogetherNames);
  const pageWithoutDynamicFields = targetPage.filter((field) => {
    const name = (field as TemplateSchema).name;
    return (
      name !== resolvedOptions.topicTemplateName && name !== resolvedOptions.topicDataTemplateName
    );
  }) as TemplateSchema[];
  const keepTogetherFields = pageWithoutDynamicFields
    .filter((field) => keepTogetherSet.has(field.name ?? ''))
    .map((field) => cloneTemplate(field));
  const staticFields = pageWithoutDynamicFields
    .filter((field) => !keepTogetherSet.has(field.name ?? ''))
    .map((field) => cloneTemplate(field));
  const sortedTopics = sortTopics(topics);
  const topicInputs: Record<string, string[][]> = {};
  const generatedPages: Template['schemas'] = [];
  const topicToDataOffset = getFieldY(topicDataTemplate) - getFieldY(topicTemplate);
  const keepTogetherTop =
    keepTogetherFields.length > 0
      ? Math.min(...keepTogetherFields.map((field) => getFieldY(field)))
      : resolvedOptions.pageBottomY;
  const keepTogetherHeight =
    keepTogetherFields.length > 0
      ? Math.max(...keepTogetherFields.map((field) => getFieldBottom(field))) - keepTogetherTop
      : 0;

  let currentPageFields = staticFields.map((field) => cloneTemplate(field));
  let currentY = Math.max(
    resolvedOptions.pageStartY,
    currentPageFields.length > 0
      ? Math.max(...currentPageFields.map((field) => getFieldBottom(field)), resolvedOptions.pageStartY)
      : resolvedOptions.pageStartY
  );

  const pushCurrentPage = () => {
    generatedPages.push(currentPageFields as Template['schemas'][number]);
    currentPageFields = [];
    currentY = resolvedOptions.pageStartY;
  };

  for (const [topicIndex, topic] of sortedTopics.entries()) {
    const topicRows = formatTopicItems({ items: topic.items ?? [] });
    const topicKey = `topic_${resolvedOptions.targetPageIndex}_${topicIndex}`;
    const itemKey = `item_topic_${resolvedOptions.targetPageIndex}_${topicIndex}`;
    const labelHeight =
      typeof topicTemplate.height === 'number' ? topicTemplate.height : resolvedOptions.rowHeight;
    const tableHeight = Math.max(
      typeof topicDataTemplate.height === 'number' ? topicDataTemplate.height : resolvedOptions.rowHeight,
      topicRows.length * resolvedOptions.rowHeight
    );
    const blockHeight = topicToDataOffset + tableHeight;

    if (currentY + blockHeight > resolvedOptions.pageBottomY && currentPageFields.length > 0) {
      pushCurrentPage();
    }

    const topicField = cloneTemplate(topicTemplate);
    topicField.name = topicKey;
    topicField.content = `[[\"{${topicKey}}\"]]`;
    topicField.height = labelHeight;
    setFieldY(topicField, currentY);

    const topicDataField = cloneTemplate(topicDataTemplate);
    topicDataField.name = itemKey;
    topicDataField.content = `[[\"{${itemKey}}\"]]`;
    topicDataField.height = tableHeight;
    setFieldY(topicDataField, currentY + topicToDataOffset);

    topicInputs[topicKey] = [[topic.topicType?.trim() || '-']];
    topicInputs[itemKey] = topicRows;
    currentPageFields.push(topicField, topicDataField);
    currentY = getFieldY(topicDataField) + tableHeight + resolvedOptions.topicSpacing;
  }

  if (keepTogetherFields.length > 0) {
    const remainingSpace = resolvedOptions.pageBottomY - currentY;

    if (
      remainingSpace < resolvedOptions.keepTogetherMinSpace ||
      currentY + keepTogetherHeight > resolvedOptions.pageBottomY
    ) {
      pushCurrentPage();
    }

    const shiftDelta = currentY - keepTogetherTop;

    for (const field of keepTogetherFields) {
      const nextField = cloneTemplate(field);
      setFieldY(nextField, getFieldY(field) + shiftDelta);
      currentPageFields.push(nextField);
    }
  }

  if (currentPageFields.length > 0) {
    generatedPages.push(currentPageFields as Template['schemas'][number]);
  }

  nextTemplate.schemas = [
    ...pages.slice(0, resolvedOptions.targetPageIndex),
    ...(generatedPages.length > 0
      ? generatedPages
      : [pageWithoutDynamicFields as Template['schemas'][number]]),
    ...pages.slice(resolvedOptions.targetPageIndex + 1)
  ];

  return {
    template: nextTemplate,
    topicInputs
  };
}
