import type { Channel, ContentPayload } from '@zyphon/shared-types';
import type { MessageTemplateModel } from '@zyphon/config-schema';

export type TemplateRenderOk = {
  ok: true;
  content: ContentPayload;
  metadata: Record<string, unknown>;
};

export type TemplateRenderErr = {
  ok: false;
  code: 'TEMPLATE_NOT_FOUND' | 'TEMPLATE_CHANNEL_MISMATCH' | 'TEMPLATE_VARIABLES_MISSING';
  message: string;
};

export type TemplateRenderResult = TemplateRenderOk | TemplateRenderErr;

const VAR_PATTERN = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;

export function renderTemplateString(
  source: string,
  variables: Record<string, string>,
): string {
  return source.replace(VAR_PATTERN, (_match, key: string) => {
    if (Object.prototype.hasOwnProperty.call(variables, key)) {
      return variables[key] ?? '';
    }
    return `{{${key}}}`;
  });
}

export function missingTemplateVariables(
  declared: string[],
  provided: Record<string, string> | undefined,
): string[] {
  const vars = provided || {};
  return declared.filter((name) => {
    const value = vars[name];
    return value === undefined || value === null || String(value).length === 0;
  });
}

/**
 * Resolve a Zyphon template id into rendered body/subject and DLT metadata.
 * No-op when the request is not template-based.
 */
export function applyMessageTemplate(
  content: ContentPayload,
  channel: Channel,
  templates: MessageTemplateModel[],
  existingMetadata: Record<string, unknown> = {},
): TemplateRenderResult {
  const wantsTemplate =
    content.type === 'template' || Boolean(content.template && String(content.template).trim());

  if (!wantsTemplate) {
    return { ok: true, content, metadata: { ...existingMetadata } };
  }

  const templateId = String(content.template || '').trim();
  if (!templateId) {
    return {
      ok: false,
      code: 'TEMPLATE_NOT_FOUND',
      message: 'content.template is required when content.type is "template"',
    };
  }

  const template = templates.find((t) => t.id === templateId);
  if (!template) {
    return {
      ok: false,
      code: 'TEMPLATE_NOT_FOUND',
      message: `Unknown template '${templateId}'`,
    };
  }

  if (template.channel !== channel) {
    return {
      ok: false,
      code: 'TEMPLATE_CHANNEL_MISMATCH',
      message: `Template '${templateId}' is for ${template.channel}, not ${channel}`,
    };
  }

  const provided = content.variables || {};
  const missing = missingTemplateVariables(template.variables || [], provided);
  if (missing.length > 0) {
    return {
      ok: false,
      code: 'TEMPLATE_VARIABLES_MISSING',
      message: `Missing template variables: ${missing.join(', ')}`,
    };
  }

  const renderedBody = renderTemplateString(template.body, provided);
  const renderedSubject = template.subject
    ? renderTemplateString(template.subject, provided)
    : content.subject;

  const declared = template.variables || [];
  const metadata: Record<string, unknown> = {
    ...existingMetadata,
    zyphon_template_id: template.id,
    template_variables: { ...provided },
    template_variable_values: orderedTemplateVariableValues(declared, provided),
  };

  if (template.dlt_id) metadata.dlt_template_id = template.dlt_id;
  if (template.header) metadata.dlt_header = template.header;
  if (template.category) metadata.dlt_category = template.category;

  return {
    ok: true,
    content: {
      ...content,
      type: content.type === 'template' ? 'template' : content.type,
      body: renderedBody,
      subject: renderedSubject,
      // Keep Zyphon id for audit; adapters must prefer metadata.dlt_template_id.
      template: template.id,
      variables: provided,
    },
    metadata,
  };
}

/** Ordered variable values for providers that expect pipe-separated DLT vars. */
export function orderedTemplateVariableValues(
  declared: string[],
  variables: Record<string, string> | undefined,
): string[] {
  const vars = variables || {};
  return declared.map((name) => String(vars[name] ?? ''));
}
