import { z } from 'zod';

import { baseListQuerySchema } from '@/lib/api/list-query';
import { VALID_MERGE_TOKENS, isCustomMergeToken } from '@/lib/templates/merge-fields';
import { fieldMapSchema } from '@/lib/templates/field-map';

// A token is acceptable if it's in the static catalog OR matches the
// dynamic `{{custom.<fieldName>}}` shape. The resolver checks the actual
// per-port custom-field definition at expand time and substitutes the
// stored value (or leaves the token unresolved if no definition matches).
function isAcceptableMergeToken(token: string): boolean {
  return VALID_MERGE_TOKENS.has(token) || isCustomMergeToken(token);
}

const mergeFieldsSchema = z
  .array(z.string())
  .optional()
  .default([])
  .refine((tokens) => tokens.every(isAcceptableMergeToken), {
    error: (issue) => {
      const tokens = issue.input as string[] | undefined;
      const unknown = tokens?.filter((t) => !isAcceptableMergeToken(t)) ?? [];
      return `Unknown merge tokens: ${unknown.join(', ')}`;
    },
  });

export const templateFormats = ['html', 'pdf_form', 'pdf_overlay', 'documenso_render'] as const;

const createTemplateBaseSchema = z.object({
  name: z.string().min(1).max(200),
  description: z.string().max(500).optional(),
  templateType: z.enum([
    'eoi',
    'welcome_letter',
    'handover_checklist',
    'acknowledgment',
    'correspondence',
    'custom',
  ]),
  templateFormat: z.enum(templateFormats).default('html'),
  bodyHtml: z.string().min(1).optional(),
  mergeFields: mergeFieldsSchema,
  isActive: z.boolean().default(true),
  // Phase 7.1 - PDF overlay markers (percent-coord) saved by the
  // in-app editor. Reused by templateFormat='pdf_overlay' at fill time.
  // Stays optional so legacy html/pdf_form templates can be PATCHed
  // without an empty array round-trip.
  overlayPositions: fieldMapSchema.optional(),
});

export const createTemplateSchema = createTemplateBaseSchema.refine(
  (data) => data.templateFormat !== 'html' || (data.bodyHtml && data.bodyHtml.length > 0),
  { path: ['bodyHtml'], message: 'bodyHtml is required when templateFormat is html' },
);

export const updateTemplateSchema = createTemplateBaseSchema.partial();

export const listTemplatesSchema = baseListQuerySchema.extend({
  templateType: z.string().optional(),
  isActive: z
    .enum(['true', 'false'])
    .transform((v) => v === 'true')
    .optional(),
});

export const generateSchema = z.object({
  clientId: z.string().optional(),
  interestId: z.string().optional(),
  berthId: z.string().optional(),
});

/**
 * Phase 3b - per-field override descriptor used by the EOI dialog.
 *
 * Three modes:
 *   - `useOnlyForThisEoi=true` → write the value to documents.override_*
 *     and stop; client_contacts is untouched.
 *   - `setAsDefault=true` + new value → insert a client_contacts row
 *     (source='eoi-custom-input', source_document_id=this EOI), promote
 *     to primary inside a transaction (demote the prior primary first).
 *   - `setAsDefault=true` + existing `contactId` → promote that row.
 *   - both flags false + new value → insert a non-primary client_contacts
 *     row only (rep typed a fresh value but doesn't want it as default).
 */
const fieldOverrideSchema = z.object({
  value: z.string().min(1).max(500),
  useOnlyForThisEoi: z.boolean().default(false),
  setAsDefault: z.boolean().default(false),
  /** When the value comes from an existing client_contacts row, the rep
   *  picked it from the combobox - pass the id so the service can skip
   *  re-inserting and just promote it (when setAsDefault is set). */
  contactId: z.string().uuid().optional(),
});

export const generateAndSignSchema = generateSchema.extend({
  pathway: z.enum(['inapp', 'documenso-template']).default('inapp'),
  signers: z
    .array(
      z.object({
        name: z.string().min(1),
        email: z.string().email(),
        role: z.string().min(1),
        signingOrder: z.number().int().min(1),
      }),
    )
    .optional()
    .default([]),
  /** Which unit (ft|m) of the yacht's stored dimensions flows into the
   *  EOI's Length/Width/Draft formValues. The drawer's toggle drives this;
   *  server defaults to the yacht's `lengthUnit` column when omitted. */
  dimensionUnit: z.enum(['ft', 'm']).optional(),
  /** Optional Section 3 (yacht details) inclusion. Defaults to true when a
   *  yacht is linked. Rep can flip OFF in the dialog to blank out yacht.*
   *  + owner.* merge fields even though a yacht is on file — used when the
   *  yacht is a placeholder, multi-berth deals where yacht-specific dims
   *  don't apply, or the client explicitly asked for the section to stay
   *  off the document. Persisted in `documents.metadata.includeYachtDetails`
   *  for audit. */
  includeYachtDetails: z.boolean().optional(),
  /** Phase 3b/3-follow-up - optional per-field overrides applied at generation. */
  overrides: z
    .object({
      clientEmail: fieldOverrideSchema.optional(),
      clientPhone: fieldOverrideSchema.optional(),
      yachtName: fieldOverrideSchema.optional(),
      // Phase 3 follow-up - multi-component address override. Treated as
      // one logical "field" with one pair of checkboxes (the dialog
      // surfaces it that way, and the side-effects helper applies the
      // intent to the whole address rather than per-component).
      clientAddress: z
        .object({
          line1: z.string().max(500).optional(),
          line2: z.string().max(500).optional(),
          city: z.string().max(200).optional(),
          subdivisionIso: z.string().max(20).optional(),
          postalCode: z.string().max(50).optional(),
          countryIso: z.string().length(2).optional(),
          useOnlyForThisEoi: z.boolean().default(false),
          setAsDefault: z.boolean().default(false),
          addressId: z.string().uuid().optional(),
        })
        .optional(),
    })
    .optional(),
});

export type CreateTemplateInput = z.infer<typeof createTemplateSchema>;
export type UpdateTemplateInput = z.infer<typeof updateTemplateSchema>;
export type ListTemplatesInput = z.infer<typeof listTemplatesSchema>;
export type GenerateInput = z.infer<typeof generateSchema>;
export type GenerateAndSignInput = z.infer<typeof generateAndSignSchema>;

// ─── TipTap-based Admin Template Schemas ─────────────────────────────────────
// Used by /api/v1/admin/templates - the TipTap JSON document store.

export const tiptapDocumentTypes = [
  'eoi',
  'contract',
  'nda',
  'reservation_agreement',
  'letter',
  'other',
] as const;

export const createAdminTemplateSchema = z.object({
  name: z.string().min(1).max(200),
  type: z.enum(tiptapDocumentTypes),
  content: z.record(z.string(), z.unknown()), // TipTap JSON document
});

export const updateAdminTemplateSchema = z.object({
  name: z.string().min(1).max(200).optional(),
  content: z.record(z.string(), z.unknown()).optional(),
  isActive: z.boolean().optional(),
});

export const rollbackAdminTemplateSchema = z.object({
  version: z.number().int().min(1),
});

export const listAdminTemplatesSchema = baseListQuerySchema.extend({
  type: z.enum(tiptapDocumentTypes).optional(),
  isActive: z
    .enum(['true', 'false'])
    .transform((v) => v === 'true')
    .optional(),
});

export type CreateAdminTemplateInput = z.infer<typeof createAdminTemplateSchema>;
export type UpdateAdminTemplateInput = z.infer<typeof updateAdminTemplateSchema>;
export type RollbackAdminTemplateInput = z.infer<typeof rollbackAdminTemplateSchema>;
export type ListAdminTemplatesInput = z.infer<typeof listAdminTemplatesSchema>;
