import type { Template } from '@pdfme/common';
import type {
  AssembledTemplate,
  BuiltSection,
  RuntimeIssue,
} from './runtime-contracts';

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

function mergeTemplateInputPatch(
  target: Record<string, unknown>,
  patch: Record<string, unknown>,
): Record<string, unknown> {
  for (const [key, value] of Object.entries(patch)) {
    if (value === null || value === undefined) {
      continue;
    }

    if (typeof value === 'string' && value.trim().length === 0) {
      continue;
    }

    target[key] = value;
  }

  return target;
}

export function assembleTemplate(args: {
  template: Template;
  templateInput: Record<string, unknown>;
  staticInputPatch?: Record<string, unknown>;
  sections: BuiltSection[];
}): AssembledTemplate {
  const baseTemplate = cloneTemplate(args.template);
  const replacementByAnchor = new Map<number, BuiltSection>();
  const issues: RuntimeIssue[] = [];

  for (const section of args.sections) {
    if (section.anchorPageIndex === null) {
      issues.push(...section.issues);
      continue;
    }

    if (replacementByAnchor.has(section.anchorPageIndex)) {
      issues.push({
        code: 'INVALID_TEMPLATE',
        severity: 'error',
        message: 'Multiple built sections attempted to replace the same page.',
        details: {
          anchorPageIndex: section.anchorPageIndex,
          roles: [
            replacementByAnchor.get(section.anchorPageIndex)?.role,
            section.role,
          ],
        },
      });
      continue;
    }

    replacementByAnchor.set(section.anchorPageIndex, section);
    issues.push(...section.issues);
  }

  const nextPages: Template['schemas'] = [];

  for (const [pageIndex, page] of baseTemplate.schemas.entries()) {
    const replacement = replacementByAnchor.get(pageIndex);
    if (!replacement) {
      nextPages.push(cloneTemplate(page));
      continue;
    }

    if (!replacement.rendered || replacement.pages.length === 0) {
      continue;
    }

    nextPages.push(...cloneTemplate(replacement.pages));
  }

  baseTemplate.schemas = nextPages;

  const templateInput = mergeTemplateInputPatch({}, args.templateInput);

  if (args.staticInputPatch) {
    mergeTemplateInputPatch(templateInput, args.staticInputPatch);
  }

  for (const section of args.sections) {
    mergeTemplateInputPatch(templateInput, section.templateInputPatch);
  }

  return {
    template: baseTemplate,
    templateInput,
    issues,
  };
}
