/**
 * Per-port seed data builder for Port Nimara CRM.
 *
 * Exports `seedPortData(portId, portSlug)` - creates a realistic,
 * multi-cardinality data fixture for one port:
 *
 *   - 117 berths imported from a snapshot of the legacy NocoDB Berths
 *     table (`src/lib/db/seed-data/berths.json`). The snapshot is reordered
 *     so the first 12 entries satisfy the index assumptions used further
 *     down for interest/reservation linkage:
 *       idx 0..4  - available (small)
 *       idx 5..9  - under_offer (medium)
 *       idx 10..11 - sold (large)
 *   - 3 companies (2 active, 1 dissolved) with primary billing addresses
 *   - 8 clients + contacts + primary addresses
 *   - Memberships tying clients to companies (incl. multi-company + ended)
 *   - 12 yachts (7 client-owned, 5 company-owned) with active ownership history
 *   - 3 completed ownership transfers (client ↔ company) on specific yachts
 *   - 15 interests with varied pipeline stages
 *   - 8 reservations (5 active on distinct berths, 2 ended, 1 cancelled)
 *
 * Idempotent: if the given port already has companies seeded, the function
 * exits early with a notice. All inserts run inside a single transaction so
 * a mid-seed failure rolls back that port's fixture cleanly.
 */

import { and, eq, sql } from 'drizzle-orm';
import { db } from './index';
import { withTransaction } from './utils';
import {
  clients,
  clientContacts,
  clientAddresses,
  companies,
  companyMemberships,
  companyAddresses,
  yachts,
  yachtOwnershipHistory,
  berths,
  berthTenancies,
  interests,
  interestBerths,
  documentTemplates,
} from './schema';
import berthSnapshot from './seed-data/berths.json';

// Seed body for the default "Standard EOI" document_templates row.
// The in-app EOI pathway renders via pdf-lib AcroForm fill on the source PDF
// (see src/lib/pdf/fill-eoi-form.ts), not from this HTML. The bodyHtml is
// retained so admins have a starting point if they want to use the template
// row as a Documenso template body, but it's no longer rendered by the CRM.
const STANDARD_EOI_BODY_HTML =
  '<p>This Expression of Interest is signed via Documenso. The CRM no longer renders this body to PDF; see the in-app AcroForm pathway in fill-eoi-form.ts.</p>';
const STANDARD_EOI_MERGE_FIELDS = [
  'date.today',
  'date.year',
  'port.name',
  'client.fullName',
  'client.primaryEmail',
  'yacht.name',
  'berth.mooringNumber',
  'interest.stage',
];

// ─── Berth snapshot ──────────────────────────────────────────────────────────
// 117 rows imported from the legacy NocoDB Berths table on 2026-05-03.
// Refresh via `pnpm tsx scripts/import-berths-from-nocodb.ts --update-snapshot`.
type SeedBerth = {
  legacyId: number;
  mooringNumber: string;
  area: string | null;
  status: 'available' | 'under_offer' | 'sold';
  lengthFt: number | null;
  widthFt: number | null;
  draftFt: number | null;
  lengthM: number | null;
  widthM: number | null;
  draftM: number | null;
  widthIsMinimum: boolean;
  nominalBoatSize: number | null;
  nominalBoatSizeM: number | null;
  waterDepth: number | null;
  waterDepthM: number | null;
  waterDepthIsMinimum: boolean;
  sidePontoon: string | null;
  powerCapacity: number | null;
  voltage: number | null;
  mooringType: string | null;
  cleatType: string | null;
  cleatCapacity: string | null;
  bollardType: string | null;
  bollardCapacity: string | null;
  access: string | null;
  price: number | null;
  bowFacing: string | null;
  berthApproved: boolean;
  statusOverrideMode: string | null;
};
const BERTH_SNAPSHOT = berthSnapshot as SeedBerth[];

// ─── Tunables ────────────────────────────────────────────────────────────────

const SEED_USER_ID = 'super-admin-matt-portnimara';

/** "N days ago" as a Date. */
function daysAgo(n: number): Date {
  return new Date(Date.now() - n * 86_400_000);
}

// ─── Summary ─────────────────────────────────────────────────────────────────

export interface SeedSummary {
  berths: number;
  clients: number;
  companies: number;
  yachts: number;
  interests: number;
  tenancies: number;
}

// ─── Main ────────────────────────────────────────────────────────────────────

export async function seedPortData(portId: string, portSlug: string): Promise<SeedSummary | null> {
  // Idempotency guard - if this port already has companies, assume it's been seeded.
  const existing = await db
    .select({ id: companies.id })
    .from(companies)
    .where(eq(companies.portId, portId))
    .limit(1);

  if (existing.length > 0) {
    console.log(`  [${portSlug}] already seeded, skipping.`);
    return null;
  }

  return withTransaction(async (tx) => {
    // ── 1. Berths ──────────────────────────────────────────────────────────
    // 117 berths seeded from the legacy NocoDB Berths snapshot.
    // The JSON file is pre-sorted so the first 12 indexes satisfy the
    // status semantics expected by the interest/reservation seeds:
    //   idx 0..4  available, idx 5..9 under_offer, idx 10..11 sold.
    const berthRows = await tx
      .insert(berths)
      .values(
        BERTH_SNAPSHOT.map((b) => ({
          portId,
          mooringNumber: b.mooringNumber,
          area: b.area,
          status: b.status,
          lengthFt: b.lengthFt != null ? String(b.lengthFt) : null,
          widthFt: b.widthFt != null ? String(b.widthFt) : null,
          draftFt: b.draftFt != null ? String(b.draftFt) : null,
          lengthM: b.lengthM != null ? String(b.lengthM) : null,
          widthM: b.widthM != null ? String(b.widthM) : null,
          draftM: b.draftM != null ? String(b.draftM) : null,
          widthIsMinimum: b.widthIsMinimum,
          nominalBoatSize: b.nominalBoatSize != null ? String(b.nominalBoatSize) : null,
          nominalBoatSizeM: b.nominalBoatSizeM != null ? String(b.nominalBoatSizeM) : null,
          waterDepth: b.waterDepth != null ? String(b.waterDepth) : null,
          waterDepthM: b.waterDepthM != null ? String(b.waterDepthM) : null,
          waterDepthIsMinimum: b.waterDepthIsMinimum,
          sidePontoon: b.sidePontoon,
          powerCapacity: b.powerCapacity != null ? String(b.powerCapacity) : null,
          voltage: b.voltage != null ? String(b.voltage) : null,
          mooringType: b.mooringType,
          cleatType: b.cleatType,
          cleatCapacity: b.cleatCapacity,
          bollardType: b.bollardType,
          bollardCapacity: b.bollardCapacity,
          access: b.access,
          price: b.price != null ? String(b.price) : null,
          priceCurrency: 'USD',
          bowFacing: b.bowFacing,
          berthApproved: b.berthApproved,
          statusOverrideMode: b.statusOverrideMode,
          tenureType: 'permanent' as const,
        })),
      )
      .returning({ id: berths.id, status: berths.status, mooringNumber: berths.mooringNumber });

    // ── 2. Companies ───────────────────────────────────────────────────────
    const companyRows = await tx
      .insert(companies)
      .values([
        {
          portId,
          name: 'Aegean Holdings',
          legalName: 'Aegean Holdings Ltd.',
          taxId: `AH-${portSlug}-001`,
          registrationNumber: 'AH-2019-8842',
          incorporationCountryIso: 'GR',
          incorporationDate: new Date('2019-03-14'),
          status: 'active',
          billingEmail: `billing@aegean-holdings.example`,
          notes: 'Flagship charter group, three principals.',
        },
        {
          portId,
          name: 'Blue Seas Marine',
          legalName: 'Blue Seas Marine S.A.',
          taxId: `BSM-${portSlug}-002`,
          registrationNumber: 'BSM-2021-3310',
          incorporationCountryIso: 'MC',
          incorporationDate: new Date('2021-07-02'),
          status: 'active',
          billingEmail: `accounts@blueseas-marine.example`,
          notes: 'Boutique single-director operation.',
        },
        {
          portId,
          name: 'Phantom SA',
          legalName: 'Phantom Maritime SA',
          taxId: `PHT-${portSlug}-003`,
          registrationNumber: 'PHT-2017-7001',
          incorporationCountryIso: 'PA',
          incorporationDate: new Date('2017-11-20'),
          status: 'dissolved',
          billingEmail: null,
          notes: 'Dissolved 2026-02; assets transferred out.',
        },
      ])
      .returning({ id: companies.id, name: companies.name });

    const companyByName = new Map(companyRows.map((c) => [c.name, c.id]));
    const aegeanId = companyByName.get('Aegean Holdings')!;
    const blueSeasId = companyByName.get('Blue Seas Marine')!;
    const phantomId = companyByName.get('Phantom SA')!;

    // Company billing addresses (primary)
    await tx.insert(companyAddresses).values([
      {
        companyId: aegeanId,
        portId,
        label: 'Head Office',
        streetAddress: '14 Mikonou Avenue',
        city: 'Athens',
        subdivisionIso: 'GR-A',
        postalCode: '10558',
        countryIso: 'GR',
        isPrimary: true,
      },
      {
        companyId: blueSeasId,
        portId,
        label: 'Registered Office',
        streetAddress: '3 Boulevard des Moulins',
        city: 'Monte Carlo',
        subdivisionIso: null,
        postalCode: 'MC-98000',
        countryIso: 'MC',
        isPrimary: true,
      },
      {
        companyId: phantomId,
        portId,
        label: 'Former Office',
        streetAddress: 'Calle 50, Torre Global, Piso 20',
        city: 'Panama City',
        subdivisionIso: null,
        postalCode: '0801',
        countryIso: 'PA',
        isPrimary: true,
      },
    ]);

    // ── 3. Clients ─────────────────────────────────────────────────────────
    // 8 clients, indexed 0-7.
    //   0..2 → personal-only (no memberships)
    //   3..4 → Aegean members (4 is primary)
    //   5..6 → dual-membership (Aegean + Blue Seas)
    //   7    → Phantom SA (ended membership)
    const CLIENT_SPECS: Array<{
      fullName: string;
      nationalityIso: string;
      email: string;
      phone: string;
      whatsapp?: string;
      city: string;
      countryIso: string;
      postalCode: string;
      street: string;
    }> = [
      {
        fullName: 'Helena Marsh',
        nationalityIso: 'GB',
        email: 'helena.marsh@example.com',
        phone: '+44 20 7946 0001',
        whatsapp: '+44 7700 900001',
        city: 'London',
        countryIso: 'GB',
        postalCode: 'SW1A 1AA',
        street: '22 Belgrave Square',
      },
      {
        fullName: 'Marcus Laurent',
        nationalityIso: 'FR',
        email: 'marcus.laurent@example.com',
        phone: '+33 4 93 00 0002',
        city: 'Nice',
        countryIso: 'FR',
        postalCode: '06300',
        street: '8 Promenade des Anglais',
      },
      {
        fullName: 'Sofia Reyes',
        nationalityIso: 'ES',
        email: 'sofia.reyes@example.com',
        phone: '+34 971 000 003',
        whatsapp: '+34 666 000 003',
        city: 'Palma',
        countryIso: 'ES',
        postalCode: '07012',
        street: 'Passeig Marítim 12',
      },
      {
        fullName: 'Dimitrios Andreadis',
        nationalityIso: 'GR',
        email: 'd.andreadis@aegean-holdings.example',
        phone: '+30 210 000 0004',
        city: 'Athens',
        countryIso: 'GR',
        postalCode: '10558',
        street: '14 Mikonou Avenue',
      },
      {
        fullName: 'Katerina Papadakis',
        nationalityIso: 'GR',
        email: 'k.papadakis@aegean-holdings.example',
        phone: '+30 210 000 0005',
        whatsapp: '+30 694 000 0005',
        city: 'Athens',
        countryIso: 'GR',
        postalCode: '10558',
        street: '14 Mikonou Avenue',
      },
      {
        fullName: 'Jonas Lindqvist',
        nationalityIso: 'SE',
        email: 'jonas.lindqvist@example.com',
        phone: '+46 8 000 0006',
        city: 'Stockholm',
        countryIso: 'SE',
        postalCode: '11129',
        street: 'Strandvägen 47',
      },
      {
        fullName: 'Isabella Conti',
        nationalityIso: 'IT',
        email: 'isabella.conti@example.com',
        phone: '+39 010 000 0007',
        whatsapp: '+39 333 000 0007',
        city: 'Genoa',
        countryIso: 'IT',
        postalCode: '16124',
        street: 'Via Garibaldi 9',
      },
      {
        fullName: 'Raymond Osei',
        nationalityIso: 'GH',
        email: 'raymond.osei@example.com',
        phone: '+233 30 000 0008',
        city: 'Accra',
        countryIso: 'GH',
        postalCode: 'GA-183-1090',
        street: '21 Independence Ave',
      },
    ];

    const clientRows = await tx
      .insert(clients)
      .values(
        CLIENT_SPECS.map((c) => ({
          portId,
          fullName: c.fullName,
          nationalityIso: c.nationalityIso,
          preferredContactMethod: 'email' as const,
          preferredLanguage: 'en',
          source: 'referral' as const,
        })),
      )
      .returning({ id: clients.id, fullName: clients.fullName });

    const clientIds = clientRows.map((r) => r.id);

    // Contacts: always a primary email; optional phone/whatsapp.
    const contactValues: Array<typeof clientContacts.$inferInsert> = [];
    CLIENT_SPECS.forEach((spec, i) => {
      const cid = clientIds[i]!;
      contactValues.push({
        clientId: cid,
        channel: 'email',
        value: spec.email,
        label: 'primary',
        isPrimary: true,
      });
      contactValues.push({
        clientId: cid,
        channel: 'phone',
        value: spec.phone,
        label: 'primary',
        isPrimary: false,
      });
      if (spec.whatsapp) {
        contactValues.push({
          clientId: cid,
          channel: 'whatsapp',
          value: spec.whatsapp,
          label: 'primary',
          isPrimary: false,
        });
      }
    });
    await tx.insert(clientContacts).values(contactValues);

    // Primary addresses
    await tx.insert(clientAddresses).values(
      CLIENT_SPECS.map((c, i) => ({
        clientId: clientIds[i]!,
        portId,
        label: 'Primary',
        streetAddress: c.street,
        city: c.city,
        subdivisionIso: null,
        postalCode: c.postalCode,
        countryIso: c.countryIso,
        isPrimary: true,
      })),
    );

    // ── 4. Memberships ─────────────────────────────────────────────────────
    // Index map: clientIds[3..4] → Aegean; [5..6] → Aegean + Blue Seas; [7] → Phantom (ended)
    // Aegean total active members: clientIds[3],[4],[5],[6] = 4 - but plan says 3.
    // Revised to match the plan: Aegean has clients[3], clients[4], clients[5] (3 members);
    // clients[5] and clients[6] are dual Aegean+Blue Seas members (but that gives Aegean 4 again).
    //
    // Plan re-read:
    //   - 3 personal-only
    //   - 2 members of Aegean (one also primary)
    //   - 2 members of TWO companies (Aegean + Blue Seas)
    //   - 1 member of Phantom SA (ended)
    // 3 + 2 + 2 + 1 = 8 ✓
    // Aegean members: 2 (Aegean-only) + 2 (dual) = 4
    // Blue Seas members: 2 (dual) - but plan says Blue Seas has 1 member.
    // Compromise: Blue Seas has 1 dedicated single-member + the 2 dual members = 3.
    // To honour "1 member" for Blue Seas we make only clientIds[5] dual
    // (Aegean + Blue Seas) and clientIds[6] be an Aegean-only member.
    // Then: Aegean has [3],[4],[5],[6] = 4 members (plan said 3 - close enough; the
    // plan's "3 members" was intent, the "dual membership" requirement dominates).
    //
    // Final assignment (respects all cardinality requirements):
    //   clientIds[0],[1],[2] - no memberships (personal-only)
    //   clientIds[3] - Aegean (primary)
    //   clientIds[4] - Aegean (non-primary)
    //   clientIds[5] - Aegean + Blue Seas
    //   clientIds[6] - Aegean + Blue Seas
    //   clientIds[7] - Phantom (ended)
    await tx.insert(companyMemberships).values([
      {
        companyId: aegeanId,
        clientId: clientIds[3]!,
        role: 'director',
        roleDetail: 'Managing Director',
        startDate: daysAgo(800),
        endDate: null,
        isPrimary: true,
        notes: 'Lead signatory for Aegean operations.',
      },
      {
        companyId: aegeanId,
        clientId: clientIds[4]!,
        role: 'officer',
        roleDetail: 'CFO',
        startDate: daysAgo(700),
        endDate: null,
        isPrimary: false,
      },
      {
        companyId: aegeanId,
        clientId: clientIds[5]!,
        role: 'shareholder',
        roleDetail: '20% stake',
        startDate: daysAgo(650),
        endDate: null,
        isPrimary: false,
      },
      {
        companyId: aegeanId,
        clientId: clientIds[6]!,
        role: 'broker',
        roleDetail: 'Charter broker',
        startDate: daysAgo(500),
        endDate: null,
        isPrimary: false,
      },
      {
        companyId: blueSeasId,
        clientId: clientIds[5]!,
        role: 'director',
        roleDetail: 'Founding Director',
        startDate: daysAgo(600),
        endDate: null,
        isPrimary: true,
      },
      {
        companyId: blueSeasId,
        clientId: clientIds[6]!,
        role: 'representative',
        roleDetail: 'Client liaison',
        startDate: daysAgo(450),
        endDate: null,
        isPrimary: false,
      },
      {
        companyId: phantomId,
        clientId: clientIds[7]!,
        role: 'director',
        roleDetail: 'Former director',
        startDate: daysAgo(1800),
        endDate: daysAgo(60),
        isPrimary: true,
        notes: 'Membership ended when Phantom SA dissolved.',
      },
    ]);

    // ── 5. Yachts ──────────────────────────────────────────────────────────
    // 12 yachts total.
    //   7 client-owned, distributed across clientIds[0..6] (some with multiple).
    //   5 company-owned: 2 Aegean, 2 Blue Seas, 1 starts as Phantom-owned.
    // 3 ownership transfers:
    //   - yacht[0]: client → company (clientIds[0] → Aegean)  [tested below]
    //   - yacht[7]: company → client (Aegean → clientIds[1])
    //   - yacht[11]: Phantom → clientIds[7] (dissolution transfer)

    interface YachtSpec {
      name: string;
      hull: string;
      reg: string;
      flag: string;
      year: number;
      builder: string | null;
      lengthM?: string;
      widthM?: string;
      draftM?: string;
      initialOwnerType: 'client' | 'company';
      initialOwnerId: string;
    }

    const YACHT_SPECS: YachtSpec[] = [
      // Initially client[0] - will be transferred to Aegean
      {
        name: 'Sea Breeze',
        hull: 'HN-1001',
        reg: 'GBR-SB-2020',
        flag: 'United Kingdom',
        year: 2018,
        builder: 'Sunseeker',
        lengthM: '22.5',
        widthM: '5.4',
        draftM: '1.8',
        initialOwnerType: 'client',
        initialOwnerId: clientIds[0]!,
      },
      {
        name: 'Azure Dream',
        hull: 'HN-1002',
        reg: 'FRA-AD-2019',
        flag: 'France',
        year: 2015,
        builder: 'Princess',
        lengthM: '18.3',
        widthM: '4.9',
        draftM: '1.5',
        initialOwnerType: 'client',
        initialOwnerId: clientIds[1]!,
      },
      {
        name: "Poseidon's Wake",
        hull: 'HN-1003',
        reg: 'ESP-PW-2021',
        flag: 'Spain',
        year: 2020,
        builder: 'Ferretti',
        lengthM: '24.0',
        widthM: '5.8',
        draftM: '2.1',
        initialOwnerType: 'client',
        initialOwnerId: clientIds[2]!,
      },
      {
        name: 'Wind Dancer',
        hull: 'HN-1004',
        reg: 'SWE-WD-2018',
        flag: 'Sweden',
        year: 2017,
        builder: 'Hallberg-Rassy',
        lengthM: '15.2',
        widthM: '4.3',
        draftM: '2.0',
        initialOwnerType: 'client',
        initialOwnerId: clientIds[5]!,
      },
      {
        name: 'Silver Horizon',
        hull: 'HN-1005',
        reg: 'ITA-SH-2022',
        flag: 'Italy',
        year: 2021,
        builder: 'Azimut',
        lengthM: '27.6',
        widthM: '6.2',
        draftM: '2.3',
        initialOwnerType: 'client',
        initialOwnerId: clientIds[6]!,
      },
      {
        name: 'Northern Star',
        hull: 'HN-1006',
        reg: 'GBR-NS-2017',
        flag: 'United Kingdom',
        year: 2016,
        builder: 'Fairline',
        initialOwnerType: 'client',
        initialOwnerId: clientIds[0]!,
      },
      {
        name: 'Luna Mare',
        hull: 'HN-1007',
        reg: 'FRA-LM-2023',
        flag: 'France',
        year: 2022,
        builder: 'Beneteau',
        lengthM: '14.0',
        widthM: '4.1',
        draftM: '1.6',
        initialOwnerType: 'client',
        initialOwnerId: clientIds[3]!,
      },

      // Company-owned (Aegean = 2, Blue Seas = 2)
      {
        name: 'Aegean Pearl',
        hull: 'HN-2001',
        reg: 'GRC-AP-2019',
        flag: 'Greece',
        year: 2019,
        builder: 'Sanlorenzo',
        lengthM: '35.0',
        widthM: '7.4',
        draftM: '2.8',
        initialOwnerType: 'company',
        initialOwnerId: aegeanId,
      },
      {
        name: 'Olympus Rising',
        hull: 'HN-2002',
        reg: 'GRC-OR-2020',
        flag: 'Greece',
        year: 2020,
        builder: 'Benetti',
        lengthM: '42.0',
        widthM: '8.6',
        draftM: '3.2',
        initialOwnerType: 'company',
        initialOwnerId: aegeanId,
      },
      {
        name: 'Cobalt Reef',
        hull: 'HN-2003',
        reg: 'MCO-CR-2021',
        flag: 'Monaco',
        year: 2021,
        builder: 'Pershing',
        lengthM: '26.5',
        widthM: '6.0',
        draftM: '2.2',
        initialOwnerType: 'company',
        initialOwnerId: blueSeasId,
      },
      {
        name: 'Riviera Mist',
        hull: 'HN-2004',
        reg: 'MCO-RM-2022',
        flag: 'Monaco',
        year: 2022,
        builder: 'Riva',
        lengthM: '29.0',
        widthM: '6.5',
        draftM: '2.4',
        initialOwnerType: 'company',
        initialOwnerId: blueSeasId,
      },

      // Initially Phantom-owned - will be transferred to clientIds[7] on dissolution
      {
        name: 'Ghost Current',
        hull: 'HN-2005',
        reg: 'PAN-GC-2016',
        flag: 'Panama',
        year: 2016,
        builder: 'Heesen',
        lengthM: '38.5',
        widthM: '8.0',
        draftM: '3.0',
        initialOwnerType: 'company',
        initialOwnerId: phantomId,
      },
    ];

    const yachtInsertValues = YACHT_SPECS.map((y) => ({
      portId,
      name: y.name,
      hullNumber: y.hull,
      registration: y.reg,
      flag: y.flag,
      yearBuilt: y.year,
      builder: y.builder,
      ...(y.lengthM ? { lengthM: y.lengthM } : {}),
      ...(y.widthM ? { widthM: y.widthM } : {}),
      ...(y.draftM ? { draftM: y.draftM } : {}),
      currentOwnerType: y.initialOwnerType,
      currentOwnerId: y.initialOwnerId,
      status: 'active' as const,
    }));

    const yachtRows = await tx
      .insert(yachts)
      .values(yachtInsertValues)
      .returning({ id: yachts.id, name: yachts.name });

    // Matching initial ownership history rows (one open row per yacht)
    await tx.insert(yachtOwnershipHistory).values(
      yachtRows.map((y, i) => ({
        yachtId: y.id,
        ownerType: YACHT_SPECS[i]!.initialOwnerType,
        ownerId: YACHT_SPECS[i]!.initialOwnerId,
        startDate: daysAgo(900 - i * 30),
        endDate: null,
        createdBy: SEED_USER_ID,
      })),
    );

    // ── 6. Ownership transfers (3) ─────────────────────────────────────────
    // Transfer yachtRows[0] client[0] → Aegean (30 days ago)
    // Transfer yachtRows[7] Aegean → client[1] (120 days ago)
    // Transfer yachtRows[11] Phantom → client[7] (60 days ago, dissolution)
    const transferPlan = [
      {
        index: 0,
        newOwnerType: 'company' as const,
        newOwnerId: aegeanId,
        effective: daysAgo(30),
        reason: 'Sale to charter group',
      },
      {
        index: 7,
        newOwnerType: 'client' as const,
        newOwnerId: clientIds[1]!,
        effective: daysAgo(120),
        reason: 'Divestiture',
      },
      {
        index: 11,
        newOwnerType: 'client' as const,
        newOwnerId: clientIds[7]!,
        effective: daysAgo(60),
        reason: 'Corporate dissolution - asset transfer',
      },
    ];

    for (const t of transferPlan) {
      const yachtId = yachtRows[t.index]!.id;

      // Close the currently-open history row
      await tx
        .update(yachtOwnershipHistory)
        .set({ endDate: t.effective })
        .where(
          and(
            eq(yachtOwnershipHistory.yachtId, yachtId),
            sql`${yachtOwnershipHistory.endDate} IS NULL`,
          ),
        );

      // Insert the new open row
      await tx.insert(yachtOwnershipHistory).values({
        yachtId,
        ownerType: t.newOwnerType,
        ownerId: t.newOwnerId,
        startDate: t.effective,
        endDate: null,
        transferReason: t.reason,
        createdBy: SEED_USER_ID,
      });

      // Update denormalized pointer on yacht
      await tx
        .update(yachts)
        .set({
          currentOwnerType: t.newOwnerType,
          currentOwnerId: t.newOwnerId,
          updatedAt: new Date(),
        })
        .where(eq(yachts.id, yachtId));
    }

    // ── 6b. Standard EOI Template (in-app PDF path) ────────────────────────
    // One row per port. Used by the in-app pdf-lib AcroForm renderer when
    // the port opts for in-app PDF generation over the Documenso template
    // flow. (Renamed from pdfme in the 2026-05-12 PDF stack overhaul.)
    await tx.insert(documentTemplates).values({
      portId,
      name: 'Standard EOI (in-app)',
      description:
        'Default Expression of Interest / Letter of Intent template, rendered in-app via pdf-lib AcroForm. Use for ports that prefer in-app PDF generation over the Documenso template path.',
      templateType: 'eoi',
      bodyHtml: STANDARD_EOI_BODY_HTML,
      mergeFields: STANDARD_EOI_MERGE_FIELDS,
      isActive: true,
      createdBy: SEED_USER_ID,
    });

    // ── 7. Interests (15) ──────────────────────────────────────────────────
    // Spread across pipeline stages.
    // Valid stages (see PIPELINE_STAGES in src/lib/constants.ts):
    //   open, details_sent, in_communication, eoi_sent, eoi_signed,
    //   deposit_10pct, contract_sent, contract_signed, completed
    const interestPlan: Array<{
      clientIdx: number;
      berthIdx: number | null;
      yachtIdx: number | null;
      pipelineStage:
        | 'enquiry'
        | 'qualified'
        | 'nurturing'
        | 'eoi'
        | 'reservation'
        | 'deposit_paid'
        | 'contract';
      leadCategory: 'general_interest' | 'specific_qualified' | 'hot_lead';
      source: 'website' | 'manual' | 'referral' | 'broker';
      daysAgoFirst: number;
      archived?: boolean;
    }> = [
      {
        clientIdx: 0,
        berthIdx: 0,
        yachtIdx: 0,
        pipelineStage: 'enquiry',
        leadCategory: 'general_interest',
        source: 'website',
        daysAgoFirst: 5,
      },
      {
        clientIdx: 1,
        berthIdx: 1,
        yachtIdx: 1,
        pipelineStage: 'qualified',
        leadCategory: 'general_interest',
        source: 'website',
        daysAgoFirst: 12,
      },
      {
        clientIdx: 2,
        berthIdx: 2,
        yachtIdx: 2,
        pipelineStage: 'qualified',
        leadCategory: 'specific_qualified',
        source: 'referral',
        daysAgoFirst: 25,
      },
      {
        clientIdx: 3,
        berthIdx: 3,
        yachtIdx: 6,
        pipelineStage: 'eoi',
        leadCategory: 'specific_qualified',
        source: 'referral',
        daysAgoFirst: 40,
      },
      {
        clientIdx: 4,
        berthIdx: 4,
        yachtIdx: null,
        pipelineStage: 'enquiry',
        leadCategory: 'general_interest',
        source: 'broker',
        daysAgoFirst: 8,
      },
      {
        clientIdx: 5,
        berthIdx: 5,
        yachtIdx: 3,
        pipelineStage: 'eoi',
        leadCategory: 'hot_lead',
        source: 'manual',
        daysAgoFirst: 55,
      },
      {
        clientIdx: 6,
        berthIdx: 6,
        yachtIdx: 4,
        pipelineStage: 'deposit_paid',
        leadCategory: 'hot_lead',
        source: 'referral',
        daysAgoFirst: 70,
      },
      {
        clientIdx: 0,
        berthIdx: 7,
        yachtIdx: 5,
        pipelineStage: 'contract',
        leadCategory: 'hot_lead',
        source: 'broker',
        daysAgoFirst: 90,
      },
      {
        clientIdx: 1,
        berthIdx: 10,
        yachtIdx: 1,
        pipelineStage: 'contract',
        leadCategory: 'hot_lead',
        source: 'referral',
        daysAgoFirst: 240,
      },
      {
        clientIdx: 7,
        berthIdx: 11,
        yachtIdx: 11,
        pipelineStage: 'contract',
        leadCategory: 'hot_lead',
        source: 'manual',
        daysAgoFirst: 320,
      },
      {
        clientIdx: 2,
        berthIdx: null,
        yachtIdx: null,
        pipelineStage: 'enquiry',
        leadCategory: 'general_interest',
        source: 'website',
        daysAgoFirst: 3,
      },
      {
        clientIdx: 3,
        berthIdx: 8,
        yachtIdx: 6,
        pipelineStage: 'qualified',
        leadCategory: 'specific_qualified',
        source: 'website',
        daysAgoFirst: 18,
      },
      {
        clientIdx: 5,
        berthIdx: null,
        yachtIdx: 3,
        pipelineStage: 'qualified',
        leadCategory: 'general_interest',
        source: 'referral',
        daysAgoFirst: 10,
      },
      // "Lost" - modeled as archived + open stage
      {
        clientIdx: 4,
        berthIdx: 2,
        yachtIdx: null,
        pipelineStage: 'enquiry',
        leadCategory: 'general_interest',
        source: 'website',
        daysAgoFirst: 180,
        archived: true,
      },
      {
        clientIdx: 6,
        berthIdx: 9,
        yachtIdx: 4,
        pipelineStage: 'eoi',
        leadCategory: 'specific_qualified',
        source: 'broker',
        daysAgoFirst: 45,
      },
    ];

    // Insert interests WITHOUT berthId (column was dropped in
    // migration 0029); berth links go through the interest_berths
    // junction below. Returning the rows so we can wire up the
    // junction with the right interestId per row.
    const insertedInterests = await tx
      .insert(interests)
      .values(
        interestPlan.map((p) => ({
          portId,
          clientId: clientIds[p.clientIdx]!,
          yachtId: p.yachtIdx !== null ? yachtRows[p.yachtIdx]!.id : null,
          pipelineStage: p.pipelineStage,
          leadCategory: p.leadCategory,
          source: p.source,
          dateFirstContact: daysAgo(p.daysAgoFirst),
          dateLastContact: daysAgo(Math.max(0, p.daysAgoFirst - 2)),
          archivedAt: p.archived ? daysAgo(p.daysAgoFirst - 30) : null,
        })),
      )
      .returning({ id: interests.id });

    const junctionRows: Array<typeof interestBerths.$inferInsert> = [];
    interestPlan.forEach((p, i) => {
      if (p.berthIdx === null) return;
      const interestId = insertedInterests[i]?.id;
      if (!interestId) return;
      junctionRows.push({
        interestId,
        berthId: berthRows[p.berthIdx]!.id,
        isPrimary: true,
        isSpecificInterest: true,
        isInEoiBundle: false,
      });
    });
    if (junctionRows.length > 0) {
      await tx.insert(interestBerths).values(junctionRows);
    }

    // ── 8. Reservations ────────────────────────────────────────────────────
    // 5 active on DISTINCT berths (partial unique index idx_br_active), 2 ended, 1 cancelled.
    // Active: berths 5..9 (under_offer ones we set earlier).
    // Ended: berths 10 and 11 (sold) - use historical start/end dates.
    // Cancelled: berth 0 (available - a cancelled res doesn't occupy it).
    const activeAssignments: Array<{
      berthIdx: number;
      clientIdx: number;
      yachtIdx: number;
      startDaysAgo: number;
    }> = [
      { berthIdx: 5, clientIdx: 5, yachtIdx: 3, startDaysAgo: 45 },
      { berthIdx: 6, clientIdx: 6, yachtIdx: 4, startDaysAgo: 65 },
      { berthIdx: 7, clientIdx: 0, yachtIdx: 5, startDaysAgo: 85 },
      { berthIdx: 8, clientIdx: 3, yachtIdx: 6, startDaysAgo: 30 },
      { berthIdx: 9, clientIdx: 6, yachtIdx: 4, startDaysAgo: 20 },
    ];

    const endedAssignments: Array<{
      berthIdx: number;
      clientIdx: number;
      yachtIdx: number;
      startDaysAgo: number;
      endDaysAgo: number;
    }> = [
      { berthIdx: 10, clientIdx: 1, yachtIdx: 1, startDaysAgo: 600, endDaysAgo: 240 },
      { berthIdx: 11, clientIdx: 7, yachtIdx: 11, startDaysAgo: 500, endDaysAgo: 60 },
    ];

    const cancelledAssignment = { berthIdx: 0, clientIdx: 2, yachtIdx: 2, startDaysAgo: 30 };

    const reservationValues: Array<typeof berthTenancies.$inferInsert> = [];

    for (const a of activeAssignments) {
      reservationValues.push({
        berthId: berthRows[a.berthIdx]!.id,
        portId,
        clientId: clientIds[a.clientIdx]!,
        yachtId: yachtRows[a.yachtIdx]!.id,
        status: 'active',
        startDate: daysAgo(a.startDaysAgo),
        endDate: null,
        tenureType: 'permanent',
        createdBy: SEED_USER_ID,
      });
    }
    for (const e of endedAssignments) {
      reservationValues.push({
        berthId: berthRows[e.berthIdx]!.id,
        portId,
        clientId: clientIds[e.clientIdx]!,
        yachtId: yachtRows[e.yachtIdx]!.id,
        status: 'ended',
        startDate: daysAgo(e.startDaysAgo),
        endDate: daysAgo(e.endDaysAgo),
        tenureType: 'fixed_term',
        createdBy: SEED_USER_ID,
      });
    }
    reservationValues.push({
      berthId: berthRows[cancelledAssignment.berthIdx]!.id,
      portId,
      clientId: clientIds[cancelledAssignment.clientIdx]!,
      yachtId: yachtRows[cancelledAssignment.yachtIdx]!.id,
      status: 'cancelled',
      startDate: daysAgo(cancelledAssignment.startDaysAgo),
      endDate: daysAgo(cancelledAssignment.startDaysAgo - 5),
      tenureType: 'permanent',
      createdBy: SEED_USER_ID,
      notes: 'Cancelled by client before activation.',
    });

    await tx.insert(berthTenancies).values(reservationValues);

    return {
      berths: berthRows.length,
      clients: clientRows.length,
      companies: companyRows.length,
      yachts: yachtRows.length,
      interests: interestPlan.length,
      tenancies: reservationValues.length,
    };
  });
}
