import fs from "node:fs";
import path from "node:path";
import crypto from "node:crypto";
import { pathToFileURL } from "node:url";
import postgres from "postgres";

type SqlClient = ReturnType<typeof postgres>;

type OrganizationRow = {
  id: string;
  name: string;
  createdBy: string;
};

type UserMembershipRow = {
  userId: string;
  userName: string | null;
  email: string;
  systemRole: string;
  membershipRole: string;
  businessRole: string;
};

type OptionRow = {
  id: string;
  category: string;
  code: string;
  label: string;
  parentId: string | null;
};

type BranchRow = {
  id: string;
  code: string;
  label: string;
};

type StoryConfig = {
  slug: string;
  title: string;
  customerName: string;
  province: string;
  district: string;
  branchCode: string;
  customerGroupCode: string;
  customerSubGroupCode: string;
  productTypeCode: string;
  quotationTypeCode: string;
  priorityCode: string;
  leadChannelCode: string;
  awarenessCode: string;
  leadStatusCode: string;
  leadFollowupStatusCode: string;
  opportunityStatusCode: string;
  chancePercent: number;
  estimatedValue: number;
  projectName: string;
  projectLocation: string;
  leadDescription: string;
  competitor?: string;
  quotePlan:
    | "accepted"
    | "pending_step_1"
    | "pending_step_2"
    | "approved"
    | "rejected_then_revised"
    | "returned_then_revised"
    | "sent_follow_up"
    | "cancelled";
  opportunityOutcome: "won" | "lost" | "cancelled" | "no_quotation" | "open";
  lostReasonCode?: string;
  cancelReasonCode?: string;
  noQuotationReasonCode?: string;
  multiParties?: Array<{
    roleCode: "billing_customer" | "consultant" | "contractor" | "end_customer";
    customerName: string;
  }>;
};

type SeedCustomer = {
  id: string;
  code: string;
  name: string;
  branchId: string | null;
  province: string;
  district: string;
  customerTypeId: string;
  customerStatusId: string;
  customerGroupId: string | null;
  customerSubGroupId: string | null;
  leadChannelId: string | null;
  ownerUserId: string;
  notes: string;
  createdBy: string;
  email: string;
  phone: string;
  website: string;
};

type SeedContact = {
  id: string;
  customerId: string;
  name: string;
  position: string;
  department: string;
  phone: string;
  mobile: string;
  email: string;
  isPrimary: boolean;
  notes: string;
  createdBy: string;
};

type SeedLead = {
  id: string;
  code: string;
  customerId: string | null;
  contactId: string | null;
  branchId: string | null;
  description: string;
  projectName: string;
  projectLocation: string;
  productTypeId: string | null;
  priorityId: string | null;
  estimatedValue: number | null;
  awarenessId: string | null;
  statusId: string;
  followupStatusId: string | null;
  lostReasonId: string | null;
  outcome: "open" | "won" | "lost";
  ownerMarketingUserId: string | null;
  assignedSalesOwnerId: string | null;
  assignedAt: Date | null;
  assignedBy: string | null;
  assignmentRemark: string | null;
  createdBy: string;
  createdAt: Date;
};

type SeedLeadFollowup = {
  id: string;
  leadId: string;
  branchId: string | null;
  userId: string;
  followupDate: Date;
  followupStatusId: string;
  note: string | null;
  nextFollowupDate: Date | null;
  createdAt: Date;
};

type SeedOpportunity = {
  id: string;
  code: string;
  customerId: string;
  contactId: string | null;
  leadId: string | null;
  branchId: string | null;
  title: string;
  description: string | null;
  requirement: string | null;
  projectName: string | null;
  projectLocation: string | null;
  productTypeId: string;
  statusId: string;
  outcomeStatus: "open" | "won" | "lost" | "cancelled" | "no_quotation";
  priorityId: string;
  leadChannelId: string | null;
  estimatedValue: number | null;
  chancePercent: number | null;
  expectedCloseDate: Date | null;
  competitor: string | null;
  source: string;
  notes: string | null;
  isHotProject: boolean;
  isActive: boolean;
  pipelineStage: "lead" | "opportunity" | "closed_won" | "closed_lost";
  closedAt: Date | null;
  closedWonAt: Date | null;
  closedLostAt: Date | null;
  closedByUserId: string | null;
  poNumber: string | null;
  poDate: Date | null;
  poAmount: number | null;
  poCurrencyId: string | null;
  lostReasonId: string | null;
  lostDetail: string | null;
  lostCompetitor: string | null;
  lostRemark: string | null;
  cancelReasonId: string | null;
  noQuotationReasonId: string | null;
  assignedToUserId: string | null;
  assignedAt: Date | null;
  assignedBy: string | null;
  assignmentRemark: string | null;
  createdBy: string;
  updatedBy: string;
  createdAt: Date;
  updatedAt: Date;
};

type SeedOpportunityParty = {
  id: string;
  opportunityId: string;
  customerId: string;
  roleId: string;
  remark: string | null;
};

type SeedOpportunityFollowup = {
  id: string;
  opportunityId: string;
  contactId: string | null;
  followupDate: Date;
  followupTypeId: string;
  outcome: string | null;
  notes: string | null;
  nextFollowupDate: Date | null;
  nextAction: string | null;
  createdBy: string;
  updatedBy: string;
};

type SeedQuotation = {
  id: string;
  code: string;
  opportunityId: string | null;
  customerId: string;
  contactId: string | null;
  branchId: string | null;
  quotationDate: Date;
  validUntil: Date | null;
  quotationTypeId: string;
  projectName: string | null;
  projectLocation: string | null;
  attention: string | null;
  reference: string | null;
  notes: string | null;
  statusId: string;
  revision: number;
  parentQuotationId: string | null;
  revisionRemark: string | null;
  currencyId: string;
  exchangeRate: number;
  subtotal: number;
  discount: number;
  discountTypeId: string | null;
  taxRate: number;
  taxAmount: number;
  totalAmount: number;
  chancePercent: number | null;
  isHotProject: boolean;
  competitor: string | null;
  salesmanId: string | null;
  isSent: boolean;
  sentAt: Date | null;
  sentViaId: string | null;
  approvedAt: Date | null;
  acceptedAt: Date | null;
  rejectedAt: Date | null;
  rejectionReason: string | null;
  createdBy: string;
  updatedBy: string;
  createdAt: Date;
  updatedAt: Date;
};

type SeedQuotationItem = {
  id: string;
  quotationId: string;
  itemNumber: number;
  productTypeId: string;
  description: string;
  quantity: number;
  unitId: string | null;
  unitPrice: number;
  discount: number;
  discountTypeId: string | null;
  taxRate: number;
  totalPrice: number;
  notes: string | null;
  sortOrder: number;
};

type SeedQuotationParty = {
  id: string;
  quotationId: string;
  customerId: string;
  roleId: string;
  isPrimary: boolean;
  remark: string | null;
};

type SeedQuotationTopic = {
  id: string;
  quotationId: string;
  topicTypeId: string;
  title: string;
  sortOrder: number;
  items: Array<{
    id: string;
    content: string;
    sortOrder: number;
  }>;
};

type SeedQuotationFollowup = {
  id: string;
  quotationId: string;
  contactId: string | null;
  followupDate: Date;
  followupTypeId: string;
  outcome: string | null;
  notes: string | null;
  nextFollowupDate: Date | null;
  nextAction: string | null;
  createdBy: string;
  updatedBy: string;
};

type SeedApprovalRequest = {
  id: string;
  workflowId: string;
  entityId: string;
  currentStep: number;
  status: "pending" | "approved" | "rejected" | "returned";
  requestedBy: string;
  requestedAt: Date;
  completedAt: Date | null;
  createdAt: Date;
  updatedAt: Date;
};

type SeedApprovalAction = {
  id: string;
  approvalRequestId: string;
  stepNumber: number;
  action: "submit" | "approve" | "reject" | "return";
  remark: string | null;
  actedBy: string;
  actedAt: Date;
};

type UserContext = {
  admin: UserMembershipRow;
  marketing: UserMembershipRow;
  sales: UserMembershipRow[];
  salesManager: UserMembershipRow;
  departmentManager: UserMembershipRow;
  topManager: UserMembershipRow;
};

const SEED_TAG = "crm-uat-seed-v1";
const PERIOD = "2606";

const STORIES: StoryConfig[] = [
  {
    slug: "website-warehouse-crane-win",
    title: "Lead received from website and won after approval",
    customerName: "บริษัท บางกอก ฟู้ด อินดัสทรี จำกัด",
    province: "Bangkok",
    district: "Min Buri",
    branchCode: "head_office",
    customerGroupCode: "industrial",
    customerSubGroupCode: "factory",
    productTypeCode: "crane",
    quotationTypeCode: "crane",
    priorityCode: "high",
    leadChannelCode: "website",
    awarenessCode: "google_search_website",
    leadStatusCode: "assigned",
    leadFollowupStatusCode: "waiting_po",
    opportunityStatusCode: "negotiation",
    chancePercent: 80,
    estimatedValue: 12400000,
    projectName: "Warehouse Crane Installation Phase 2",
    projectLocation: "Bangkok Food Industrial Estate, Min Buri",
    leadDescription:
      "Customer asked for top-running overhead crane to support raw material warehouse expansion.",
    quotePlan: "accepted",
    opportunityOutcome: "won",
    multiParties: [
      {
        roleCode: "contractor",
        customerName: "บริษัท ซีวิล โปรเจค แอนด์ คอนสตรัคชั่น จำกัด",
      },
      {
        roleCode: "end_customer",
        customerName: "บริษัท บางกอก ฟู้ด โลจิสติกส์ จำกัด",
      },
    ],
  },
  {
    slug: "facebook-lost-price",
    title: "Lead lost to competitor because price too high",
    customerName: "บริษัท ไทย ออโต้ เพรส จำกัด",
    province: "Samut Prakan",
    district: "Bang Phli",
    branchCode: "factory",
    customerGroupCode: "industrial",
    customerSubGroupCode: "factory",
    productTypeCode: "crane",
    quotationTypeCode: "crane",
    priorityCode: "normal",
    leadChannelCode: "facebook",
    awarenessCode: "social_media",
    leadStatusCode: "closed_lost",
    leadFollowupStatusCode: "quotation_preparation",
    opportunityStatusCode: "quotation_created",
    chancePercent: 45,
    estimatedValue: 5800000,
    projectName: "Stamping Line Hoist Replacement",
    projectLocation: "Bang Phli Industrial Estate",
    leadDescription:
      "Customer compared 5 ton monorail hoist replacement between ALLA and Konecranes.",
    competitor: "Konecranes",
    quotePlan: "sent_follow_up",
    opportunityOutcome: "lost",
    lostReasonCode: "price_too_high",
  },
  {
    slug: "revision-r02-approved",
    title: "Customer requested revision and approved revision R02",
    customerName: "บริษัท กรีน โลจิสติกส์ พาร์ค จำกัด",
    province: "Pathum Thani",
    district: "Khlong Luang",
    branchCode: "service",
    customerGroupCode: "industrial",
    customerSubGroupCode: "logistics",
    productTypeCode: "dockdoor",
    quotationTypeCode: "dockdoor",
    priorityCode: "high",
    leadChannelCode: "referral",
    awarenessCode: "word_of_mouth",
    leadStatusCode: "assigned",
    leadFollowupStatusCode: "waiting_po",
    opportunityStatusCode: "negotiation",
    chancePercent: 70,
    estimatedValue: 9300000,
    projectName: "Cold Chain Dock Leveller Upgrade",
    projectLocation: "Khlong Luang Logistics Campus",
    leadDescription:
      "Customer requested revision after consultant changed dock layout and bumper specification.",
    quotePlan: "rejected_then_revised",
    opportunityOutcome: "won",
    multiParties: [
      {
        roleCode: "consultant",
        customerName: "บริษัท เอ็นจิเนียริ่ง คอนเซ็ปท์ คอนซัลแตนท์ จำกัด",
      },
    ],
  },
  {
    slug: "no-quotation-cancelled-project",
    title: "No quotation because customer cancelled project",
    customerName: "มหาวิทยาลัยเทคโนโลยีธนบุรี",
    province: "Bangkok",
    district: "Thung Khru",
    branchCode: "head_office",
    customerGroupCode: "government",
    customerSubGroupCode: "government_agency",
    productTypeCode: "solarcell",
    quotationTypeCode: "solarcell",
    priorityCode: "low",
    leadChannelCode: "referral",
    awarenessCode: "introducing",
    leadStatusCode: "cancel",
    leadFollowupStatusCode: "budget_pending",
    opportunityStatusCode: "qualification",
    chancePercent: 15,
    estimatedValue: 2200000,
    projectName: "Solar Rooftop for Engineering Building",
    projectLocation: "Bangmod Campus",
    leadDescription:
      "Procurement advised project was frozen after university budget allocation changed.",
    quotePlan: "cancelled",
    opportunityOutcome: "no_quotation",
    noQuotationReasonCode: "budget_not_ready",
  },
  {
    slug: "large-project-multi-party",
    title: "Large project with multiple project parties",
    customerName: "บริษัท อีสเทิร์น เพาเวอร์ ซิสเต็ม จำกัด",
    province: "Rayong",
    district: "Pluak Daeng",
    branchCode: "factory",
    customerGroupCode: "industrial",
    customerSubGroupCode: "factory",
    productTypeCode: "crane",
    quotationTypeCode: "crane",
    priorityCode: "urgent",
    leadChannelCode: "sales",
    awarenessCode: "visit",
    leadStatusCode: "assigned",
    leadFollowupStatusCode: "drawing_followup",
    opportunityStatusCode: "proposal_preparation",
    chancePercent: 75,
    estimatedValue: 35200000,
    projectName: "Turbine Hall Double Girder Crane",
    projectLocation: "Rayong Power Plant Block C",
    leadDescription:
      "EPC requested a high-value crane package with consultant review and separate billing customer.",
    quotePlan: "approved",
    opportunityOutcome: "open",
    multiParties: [
      {
        roleCode: "billing_customer",
        customerName: "บริษัท พาวเวอร์ อี.พี.ซี. (ไทยแลนด์) จำกัด",
      },
      {
        roleCode: "contractor",
        customerName: "บริษัท ไทย เทอร์ไบน์ คอนสตรัคชั่น จำกัด",
      },
      {
        roleCode: "consultant",
        customerName: "บริษัท พาวเวอร์ ดีไซน์ แอดไวเซอรี่ จำกัด",
      },
      {
        roleCode: "end_customer",
        customerName: "บริษัท อีสเทิร์น ยูทิลิตี้ โฮลดิ้ง จำกัด",
      },
    ],
  },
  {
    slug: "waiting-approval-sales-manager",
    title: "Quotation waiting approval at sales manager",
    customerName: "บริษัท นครคลังสินค้า จำกัด",
    province: "Ayutthaya",
    district: "Wang Noi",
    branchCode: "service",
    customerGroupCode: "industrial",
    customerSubGroupCode: "warehouse",
    productTypeCode: "dockdoor",
    quotationTypeCode: "dockdoor",
    priorityCode: "normal",
    leadChannelCode: "other",
    awarenessCode: "word_of_mouth",
    leadStatusCode: "assigned",
    leadFollowupStatusCode: "quotation_preparation",
    opportunityStatusCode: "quotation_created",
    chancePercent: 55,
    estimatedValue: 4100000,
    projectName: "Dock Shelter Retrofit Program",
    projectLocation: "Wang Noi Distribution Center",
    leadDescription:
      "Customer asked for fast-track retrofit but margin requires internal approval.",
    quotePlan: "pending_step_1",
    opportunityOutcome: "open",
  },
  {
    slug: "approval-returned-r03",
    title: "Approval returned and revised again until R03",
    customerName: "บริษัท เมโทร โครงการเชิงพาณิชย์ จำกัด",
    province: "Bangkok",
    district: "Huai Khwang",
    branchCode: "head_office",
    customerGroupCode: "construction",
    customerSubGroupCode: "contractor",
    productTypeCode: "dockdoor",
    quotationTypeCode: "dockdoor",
    priorityCode: "high",
    leadChannelCode: "sales",
    awarenessCode: "visit",
    leadStatusCode: "assigned",
    leadFollowupStatusCode: "spec_clarification",
    opportunityStatusCode: "proposal_preparation",
    chancePercent: 62,
    estimatedValue: 6900000,
    projectName: "Commercial Tower Loading Bay Package",
    projectLocation: "Rama 9 Mixed Use Development",
    leadDescription:
      "Manager returned quotation because payment term and warranty wording had to be updated.",
    quotePlan: "returned_then_revised",
    opportunityOutcome: "open",
    multiParties: [
      {
        roleCode: "consultant",
        customerName: "บริษัท ออร์บิท ดีไซน์ คอนซัลแทนท์ จำกัด",
      },
      {
        roleCode: "end_customer",
        customerName: "บริษัท เมโทร แอสเสท ดีเวลลอปเมนท์ จำกัด",
      },
    ],
  },
  {
    slug: "followup-overdue",
    title: "Overdue follow-up for salesperson handoff",
    customerName: "บริษัท สมุทรเครื่องกล จำกัด",
    province: "Samut Sakhon",
    district: "Mueang Samut Sakhon",
    branchCode: "service",
    customerGroupCode: "industrial",
    customerSubGroupCode: "factory",
    productTypeCode: "crane",
    quotationTypeCode: "crane",
    priorityCode: "normal",
    leadChannelCode: "line",
    awarenessCode: "repurchasing",
    leadStatusCode: "follow_up",
    leadFollowupStatusCode: "site_survey_waiting",
    opportunityStatusCode: "site_survey",
    chancePercent: 38,
    estimatedValue: 3600000,
    projectName: "Production Bay Jib Crane",
    projectLocation: "Samut Sakhon Seafood Plant",
    leadDescription:
      "Site survey was discussed but salesperson missed the agreed follow-up date.",
    quotePlan: "sent_follow_up",
    opportunityOutcome: "open",
  },
  {
    slug: "high-value-hot-project",
    title: "High value project tracked as hot project",
    customerName: "บริษัท อยุธยา ออโตโมทีฟ พาร์ท จำกัด",
    province: "Ayutthaya",
    district: "Uthai",
    branchCode: "factory",
    customerGroupCode: "industrial",
    customerSubGroupCode: "factory",
    productTypeCode: "crane",
    quotationTypeCode: "crane",
    priorityCode: "urgent",
    leadChannelCode: "other",
    awarenessCode: "exhibition_event",
    leadStatusCode: "assigned",
    leadFollowupStatusCode: "waiting_po",
    opportunityStatusCode: "negotiation",
    chancePercent: 80,
    estimatedValue: 35000000,
    projectName: "Press Shop Smart Crane Upgrade",
    projectLocation: "Rojana Industrial Park",
    leadDescription:
      "Board approved budget and customer is evaluating final commercial conditions.",
    competitor: "Demag",
    quotePlan: "accepted",
    opportunityOutcome: "won",
  },
  {
    slug: "existing-customer-repeat-order",
    title: "Existing customer repeat order with fast approval",
    customerName: "บริษัท อัลฟ่า โลจิสติกส์ เซอร์วิส จำกัด",
    province: "Chonburi",
    district: "Si Racha",
    branchCode: "service",
    customerGroupCode: "industrial",
    customerSubGroupCode: "logistics",
    productTypeCode: "dockdoor",
    quotationTypeCode: "dockdoor",
    priorityCode: "high",
    leadChannelCode: "sales",
    awarenessCode: "repurchasing",
    leadStatusCode: "assigned",
    leadFollowupStatusCode: "waiting_po",
    opportunityStatusCode: "negotiation",
    chancePercent: 88,
    estimatedValue: 5100000,
    projectName: "Distribution Hub Dock Door Expansion",
    projectLocation: "Laem Chabang Logistics Hub",
    leadDescription:
      "Repeat order from an existing logistics customer who requested similar specification.",
    quotePlan: "approved",
    opportunityOutcome: "won",
  },
  {
    slug: "government-budget-delay",
    title: "Government project stalled by budget delay",
    customerName: "องค์การบริหารส่วนจังหวัดนนทบุรี",
    province: "Nonthaburi",
    district: "Mueang Nonthaburi",
    branchCode: "head_office",
    customerGroupCode: "government",
    customerSubGroupCode: "government_agency",
    productTypeCode: "solarcell",
    quotationTypeCode: "solarcell",
    priorityCode: "low",
    leadChannelCode: "referral",
    awarenessCode: "brochure_catalog",
    leadStatusCode: "follow_up",
    leadFollowupStatusCode: "budget_pending",
    opportunityStatusCode: "qualification",
    chancePercent: 20,
    estimatedValue: 7800000,
    projectName: "Provincial Office Solar Carport",
    projectLocation: "Nonthaburi Government Complex",
    leadDescription:
      "Customer wants to continue but budget approval moved to next fiscal year.",
    quotePlan: "sent_follow_up",
    opportunityOutcome: "open",
  },
  {
    slug: "consultant-spec-mismatch-lost",
    title: "Lost because consultant specification changed",
    customerName: "บริษัท เซ็นทรัล เมดิคอล เซ็นเตอร์ จำกัด",
    province: "Bangkok",
    district: "Chatuchak",
    branchCode: "head_office",
    customerGroupCode: "other",
    customerSubGroupCode: "other",
    productTypeCode: "dockdoor",
    quotationTypeCode: "dockdoor",
    priorityCode: "normal",
    leadChannelCode: "website",
    awarenessCode: "google_search_website",
    leadStatusCode: "closed_lost",
    leadFollowupStatusCode: "spec_clarification",
    opportunityStatusCode: "proposal_preparation",
    chancePercent: 35,
    estimatedValue: 2950000,
    projectName: "Hospital Sterile Delivery Door Package",
    projectLocation: "Central Medical Campus",
    leadDescription:
      "Consultant switched to another system after infection-control requirement changed.",
    quotePlan: "rejected_then_revised",
    opportunityOutcome: "lost",
    lostReasonCode: "spec_not_match",
    multiParties: [
      {
        roleCode: "consultant",
        customerName: "บริษัท เฮลธ์แคร์ โปรเจค คอนซัลแตนท์ จำกัด",
      },
    ],
  },
  {
    slug: "dealer-channel-approved",
    title:
      "Dealer channel opportunity approved and waiting customer acceptance",
    customerName: "บริษัท นอร์ธเทิร์น แมชชีนเนอรี่ ดีลเลอร์ จำกัด",
    province: "Chiang Mai",
    district: "Mueang Chiang Mai",
    branchCode: "head_office",
    customerGroupCode: "dealer",
    customerSubGroupCode: "reseller",
    productTypeCode: "crane",
    quotationTypeCode: "crane",
    priorityCode: "normal",
    leadChannelCode: "referral",
    awarenessCode: "introducing",
    leadStatusCode: "assigned",
    leadFollowupStatusCode: "quotation_preparation",
    opportunityStatusCode: "quotation_created",
    chancePercent: 58,
    estimatedValue: 4700000,
    projectName: "Dealer Stock Crane Package",
    projectLocation: "Chiang Mai Service Warehouse",
    leadDescription:
      "Reseller requested a standard package for a northern distribution project.",
    quotePlan: "approved",
    opportunityOutcome: "open",
  },
  {
    slug: "line-maintenance-renewal",
    title: "Maintenance renewal tracked through quotation and acceptance",
    customerName: "บริษัท เซาท์พอร์ต โลจิสติกส์ จำกัด",
    province: "Songkhla",
    district: "Hat Yai",
    branchCode: "service",
    customerGroupCode: "industrial",
    customerSubGroupCode: "warehouse",
    productTypeCode: "dockdoor",
    quotationTypeCode: "service",
    priorityCode: "normal",
    leadChannelCode: "referral",
    awarenessCode: "repurchasing",
    leadStatusCode: "assigned",
    leadFollowupStatusCode: "waiting_po",
    opportunityStatusCode: "negotiation",
    chancePercent: 76,
    estimatedValue: 1650000,
    projectName: "Annual Dock Door Maintenance Contract",
    projectLocation: "Hat Yai Distribution Center",
    leadDescription:
      "Existing customer requested annual maintenance renewal and safety inspection package.",
    quotePlan: "accepted",
    opportunityOutcome: "won",
  },
  {
    slug: "phone-call-cancelled",
    title: "Phone call lead cancelled after customer internal hold",
    customerName: "บริษัท เวสเทิร์น คอมเมอร์เชียล บิลดิ้ง จำกัด",
    province: "Nakhon Pathom",
    district: "Sam Phran",
    branchCode: "head_office",
    customerGroupCode: "other",
    customerSubGroupCode: "other",
    productTypeCode: "solarcell",
    quotationTypeCode: "solarcell",
    priorityCode: "low",
    leadChannelCode: "other",
    awarenessCode: "word_of_mouth",
    leadStatusCode: "cancel",
    leadFollowupStatusCode: "project_under_review",
    opportunityStatusCode: "qualification",
    chancePercent: 12,
    estimatedValue: 1950000,
    projectName: "Commercial Roof Solar Feasibility",
    projectLocation: "Sam Phran Office Complex",
    leadDescription:
      "Customer requested to pause project indefinitely after holding company review.",
    quotePlan: "cancelled",
    opportunityOutcome: "cancelled",
    cancelReasonCode: "internal_hold",
  },
  {
    slug: "university-lab-crane-approved",
    title: "University lab crane project approved and sent",
    customerName: "มหาวิทยาลัยเทคโนโลยีพระจอมเกล้าพระนครเหนือ",
    province: "Bangkok",
    district: "Bang Sue",
    branchCode: "head_office",
    customerGroupCode: "government",
    customerSubGroupCode: "government_agency",
    productTypeCode: "crane",
    quotationTypeCode: "crane",
    priorityCode: "normal",
    leadChannelCode: "other",
    awarenessCode: "exhibition_event",
    leadStatusCode: "assigned",
    leadFollowupStatusCode: "quotation_preparation",
    opportunityStatusCode: "quotation_created",
    chancePercent: 52,
    estimatedValue: 3200000,
    projectName: "Material Handling Lab Crane",
    projectLocation: "Bang Sue Engineering Faculty",
    leadDescription:
      "Faculty requested teaching-lab crane and asked for formal quotation after exhibition meeting.",
    quotePlan: "pending_step_2",
    opportunityOutcome: "open",
  },
];

function loadEnvFile(filePath: string) {
  if (!fs.existsSync(filePath)) return;

  const content = fs.readFileSync(filePath, "utf8");
  for (const rawLine of content.split(/\r?\n/)) {
    const line = rawLine.trim();
    if (!line || line.startsWith("#")) continue;

    const separatorIndex = line.indexOf("=");
    if (separatorIndex === -1) continue;

    const key = line.slice(0, separatorIndex).trim();
    let value = line.slice(separatorIndex + 1).trim();

    if (
      (value.startsWith('"') && value.endsWith('"')) ||
      (value.startsWith("'") && value.endsWith("'"))
    ) {
      value = value.slice(1, -1);
    }

    if (!(key in process.env)) {
      process.env[key] = value;
    }
  }
}

function loadLocalEnv() {
  loadEnvFile(path.resolve(process.cwd(), ".env.local"));
  loadEnvFile(path.resolve(process.cwd(), ".env"));
}

function requireEnv(name: string) {
  const value = process.env[name]?.trim();
  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }

  return value;
}

function slugify(input: string) {
  return input
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .slice(0, 48);
}

function buildDocumentCode(prefix: string, number: number) {
  return `${prefix}${PERIOD}-${String(number).padStart(3, "0")}`;
}

function seededNote(scope: string) {
  return `[${SEED_TAG}:${scope}]`;
}

function daysAgo(days: number, hour = 9) {
  return new Date(Date.UTC(2026, 5, 25 - days, hour, 0, 0));
}

function daysAhead(days: number, hour = 9) {
  return new Date(Date.UTC(2026, 5, 25 + days, hour, 0, 0));
}

function addDays(base: Date, days: number, hour = 9) {
  return new Date(
    Date.UTC(
      base.getUTCFullYear(),
      base.getUTCMonth(),
      base.getUTCDate() + days,
      hour,
      0,
      0,
    ),
  );
}

function choose<T>(items: readonly T[], index: number) {
  return items[index % items.length];
}

function inferCustomerIndustryLabel(story: StoryConfig) {
  if (story.customerGroupCode === "government") return "Government";
  if (story.customerSubGroupCode === "warehouse") return "Warehouse";
  if (story.customerSubGroupCode === "logistics") return "Logistics";
  if (story.customerSubGroupCode === "factory") return "Factory";
  if (story.customerSubGroupCode === "contractor") return "Contractor";
  if (story.customerSubGroupCode === "consultant") return "Consultant";
  if (story.customerGroupCode === "dealer") return "Dealer";
  return "Commercial";
}

function getOptionId(
  optionMap: Map<string, OptionRow>,
  category: string,
  code: string,
) {
  const key = `${category}:${code}`;
  const value = optionMap.get(key)?.id;
  if (!value) {
    throw new Error(`Missing option ${key}`);
  }

  return value;
}

function getOptionalOptionId(
  optionMap: Map<string, OptionRow>,
  category: string,
  code: string,
) {
  return optionMap.get(`${category}:${code}`)?.id ?? null;
}

async function loadOrganizations(sql: SqlClient): Promise<OrganizationRow[]> {
  return sql<OrganizationRow[]>`
    select id, name, created_by as "createdBy"
    from organizations
    order by name asc
  `;
}

async function loadMemberships(
  sql: SqlClient,
  organizationId: string,
): Promise<UserMembershipRow[]> {
  return sql<UserMembershipRow[]>`
    select
      m.user_id as "userId",
      u.name as "userName",
      u.email,
      u.system_role as "systemRole",
      m.role as "membershipRole",
      m.business_role as "businessRole"
    from memberships m
    inner join users u on u.id = m.user_id
    where m.organization_id = ${organizationId}
    order by u.name asc nulls last, u.email asc
  `;
}

async function loadOptions(
  sql: SqlClient,
  organizationId: string,
): Promise<Map<string, OptionRow>> {
  const rows = await sql<OptionRow[]>`
    select id, category, code, label, parent_id as "parentId"
    from ms_options
    where organization_id = ${organizationId}
      and deleted_at is null
      and is_active = true
  `;

  return new Map(rows.map((row) => [`${row.category}:${row.code}`, row]));
}

async function loadBranches(
  sql: SqlClient,
  organizationId: string,
): Promise<BranchRow[]> {
  return sql<BranchRow[]>`
    select id, code, label
    from ms_options
    where organization_id = ${organizationId}
      and category = 'crm_branch'
      and deleted_at is null
      and is_active = true
    order by sort_order asc, label asc
  `;
}

async function resolveApprovalWorkflow(sql: SqlClient, organizationId: string) {
  const [workflow] = await sql<{ id: string }[]>`
    select id
    from crm_approval_workflows
    where organization_id = ${organizationId}
      and code = 'quotation_standard_approval'
      and deleted_at is null
      and is_active = true
    limit 1
  `;

  if (!workflow) {
    throw new Error(
      "Approval workflow quotation_standard_approval is not configured",
    );
  }

  return workflow.id;
}

function buildUserContext(rows: UserMembershipRow[]): UserContext {
  if (rows.length === 0) {
    throw new Error("Organization has no memberships");
  }

  const first = rows[0];
  const byRole = (role: string) =>
    rows.find((row) => row.businessRole === role) ?? null;
  const salesRows = rows.filter((row) => row.businessRole === "sales");
  const admin =
    rows.find((row) => row.membershipRole === "admin") ??
    rows.find((row) => row.systemRole === "super_admin") ??
    first;

  return {
    admin,
    marketing: byRole("sales_support") ?? admin,
    sales: salesRows.length ? salesRows : [admin],
    salesManager: byRole("sales_manager") ?? admin,
    departmentManager: byRole("department_manager") ?? admin,
    topManager: byRole("top_manager") ?? admin,
  };
}

async function cleanupSeededData(sql: SqlClient, organizationId: string) {
  const quotationRows = await sql<{ id: string }[]>`
    select id
    from crm_quotations
    where organization_id = ${organizationId}
      and reference like ${`%${SEED_TAG}%`}
      and deleted_at is null
  `;
  const opportunityRows = await sql<{ id: string }[]>`
    select id
    from crm_opportunities
    where organization_id = ${organizationId}
      and source like ${`${SEED_TAG}:%`}
      and deleted_at is null
  `;
  const leadRows = await sql<{ id: string }[]>`
    select id
    from crm_leads
    where organization_id = ${organizationId}
      and description like ${`%${SEED_TAG}%`}
      and deleted_at is null
  `;
  const customerRows = await sql<{ id: string }[]>`
    select id
    from crm_customers
    where organization_id = ${organizationId}
      and notes like ${`%${SEED_TAG}%`}
      and deleted_at is null
  `;

  const quotationIds = quotationRows.map((row) => row.id);
  const opportunityIds = opportunityRows.map((row) => row.id);
  const leadIds = leadRows.map((row) => row.id);
  const customerIds = customerRows.map((row) => row.id);

  if (quotationIds.length) {
    await sql`delete from crm_approval_actions where organization_id = ${organizationId} and approval_request_id in (
      select id from crm_approval_requests where organization_id = ${organizationId} and entity_type = 'quotation' and entity_id = any(${sql.array(quotationIds)})
    )`;
    await sql`delete from crm_approval_requests where organization_id = ${organizationId} and entity_type = 'quotation' and entity_id = any(${sql.array(quotationIds)})`;
    await sql`delete from crm_quotation_topic_items where organization_id = ${organizationId} and topic_id in (
      select id from crm_quotation_topics where organization_id = ${organizationId} and quotation_id = any(${sql.array(quotationIds)})
    )`;
    await sql`delete from crm_quotation_topics where organization_id = ${organizationId} and quotation_id = any(${sql.array(quotationIds)})`;
    await sql`delete from crm_quotation_items where organization_id = ${organizationId} and quotation_id = any(${sql.array(quotationIds)})`;
    await sql`delete from crm_quotation_customers where organization_id = ${organizationId} and quotation_id = any(${sql.array(quotationIds)})`;
    await sql`delete from crm_quotation_followups where organization_id = ${organizationId} and quotation_id = any(${sql.array(quotationIds)})`;
    await sql`delete from crm_quotations where organization_id = ${organizationId} and id = any(${sql.array(quotationIds)})`;
  }

  if (opportunityIds.length) {
    await sql`delete from crm_opportunity_customers where organization_id = ${organizationId} and opportunity_id = any(${sql.array(opportunityIds)})`;
    await sql`delete from crm_opportunity_followups where organization_id = ${organizationId} and opportunity_id = any(${sql.array(opportunityIds)})`;
    await sql`delete from crm_opportunities where organization_id = ${organizationId} and id = any(${sql.array(opportunityIds)})`;
  }

  if (leadIds.length) {
    await sql`delete from tr_audit_logs where organization_id = ${organizationId} and entity_type = 'crm_lead' and entity_id = any(${sql.array(leadIds)})`;
    await sql`delete from crm_leads where organization_id = ${organizationId} and id = any(${sql.array(leadIds)})`;
  }

  if (customerIds.length) {
    await sql`delete from crm_contact_shares where organization_id = ${organizationId} and contact_id in (
      select id from crm_customer_contacts where organization_id = ${organizationId} and customer_id = any(${sql.array(customerIds)})
    )`;
    await sql`delete from crm_customer_owner_history where organization_id = ${organizationId} and customer_id = any(${sql.array(customerIds)})`;
    await sql`delete from crm_customer_contacts where organization_id = ${organizationId} and customer_id = any(${sql.array(customerIds)})`;
    await sql`delete from crm_customers where organization_id = ${organizationId} and id = any(${sql.array(customerIds)})`;
  }
}

function buildBaseCustomers(
  optionMap: Map<string, OptionRow>,
  branches: BranchRow[],
  users: UserContext,
) {
  const customers: SeedCustomer[] = [];
  const storyCustomerMap = new Map<string, SeedCustomer>();
  let customerSequence = 801;

  for (const story of STORIES) {
    const branch =
      branches.find((row) => row.code === story.branchCode) ??
      branches[0] ??
      null;
    const customer: SeedCustomer = {
      id: crypto.randomUUID(),
      code: buildDocumentCode("CUS", customerSequence++),
      name: story.customerName,
      branchId: branch?.id ?? null,
      province: story.province,
      district: story.district,
      customerTypeId: getOptionId(optionMap, "crm_customer_type", "company"),
      customerStatusId: getOptionId(optionMap, "crm_customer_status", "active"),
      customerGroupId: getOptionId(
        optionMap,
        "crm_customer_group",
        story.customerGroupCode,
      ),
      customerSubGroupId: getOptionId(
        optionMap,
        "crm_customer_sub_group",
        story.customerSubGroupCode,
      ),
      leadChannelId: getOptionalOptionId(
        optionMap,
        "crm_lead_channel",
        story.leadChannelCode,
      ),
      ownerUserId: choose(users.sales, customers.length).userId,
      notes: `${seededNote(story.slug)} Story: ${story.title}. Industry: ${inferCustomerIndustryLabel(story)}.`,
      createdBy: users.admin.userId,
      email: `${slugify(story.customerName)}@example.co.th`,
      phone: `02-${String(400000 + customerSequence).slice(-6)}`,
      website: `https://${slugify(story.customerName)}.example.co.th`,
    };

    customers.push(customer);
    storyCustomerMap.set(story.slug, customer);

    for (const party of story.multiParties ?? []) {
      customers.push({
        id: crypto.randomUUID(),
        code: buildDocumentCode("CUS", customerSequence++),
        name: party.customerName,
        branchId: branch?.id ?? null,
        province: story.province,
        district: story.district,
        customerTypeId: getOptionId(optionMap, "crm_customer_type", "company"),
        customerStatusId: getOptionId(
          optionMap,
          "crm_customer_status",
          "active",
        ),
        customerGroupId: getOptionId(
          optionMap,
          "crm_customer_group",
          story.customerGroupCode,
        ),
        customerSubGroupId: getOptionId(
          optionMap,
          "crm_customer_sub_group",
          story.customerSubGroupCode,
        ),
        leadChannelId: getOptionalOptionId(
          optionMap,
          "crm_lead_channel",
          story.leadChannelCode,
        ),
        ownerUserId: choose(users.sales, customerSequence).userId,
        notes: `${seededNote(`${story.slug}-${party.roleCode}`)} Project party for ${story.customerName}.`,
        createdBy: users.admin.userId,
        email: `${slugify(party.customerName)}@example.co.th`,
        phone: `02-${String(500000 + customerSequence).slice(-6)}`,
        website: `https://${slugify(party.customerName)}.example.co.th`,
      });
    }
  }

  const extraTemplates = [
    [
      "บริษัท พรีเมียม แมนูแฟคเจอริ่ง โซลูชั่น จำกัด",
      "industrial",
      "factory",
      "factory",
    ],
    [
      "บริษัท สยาม คลังสินค้าสมัยใหม่ จำกัด",
      "industrial",
      "warehouse",
      "service",
    ],
    [
      "บริษัท ไทย รีเทล ซัพพลาย เชน จำกัด",
      "industrial",
      "logistics",
      "service",
    ],
    [
      "บริษัท เมโทร อินฟรา คอนสตรัคชั่น จำกัด",
      "construction",
      "contractor",
      "head_office",
    ],
    [
      "บริษัท แอดวานซ์ โปรเจค คอนซัลแทนท์ จำกัด",
      "construction",
      "consultant",
      "head_office",
    ],
    [
      "การไฟฟ้าส่วนภูมิภาค เขตภาคกลาง",
      "government",
      "state_enterprise",
      "head_office",
    ],
    [
      "บริษัท อินโนเวทีฟ แคมปัส ฟาซิลิตี้ จำกัด",
      "other",
      "other",
      "head_office",
    ],
    ["บริษัท อุตสาหกรรมอาหารเหนือ จำกัด", "industrial", "factory", "factory"],
    [
      "บริษัท เวิลด์ไวด์ ดิสทริบิวชัน เซ็นเตอร์ จำกัด",
      "industrial",
      "logistics",
      "service",
    ],
    ["บริษัท ออโต้โมชั่น ซิสเต็มส์ จำกัด", "industrial", "factory", "factory"],
    ["บริษัท ไททัน พอร์ต เซอร์วิส จำกัด", "industrial", "warehouse", "service"],
    [
      "บริษัท เมืองใหม่ คอมเมอร์เชียล เซ็นเตอร์ จำกัด",
      "other",
      "other",
      "head_office",
    ],
    [
      "บริษัท พรีเมียร์ โพรคิวร์เมนต์ ฮับ จำกัด",
      "dealer",
      "reseller",
      "head_office",
    ],
    ["บริษัท เอเชีย เมทัล เวิร์คส์ จำกัด", "industrial", "factory", "factory"],
    [
      "บริษัท โกลเด้น ฟาร์ม โลจิสติกส์ จำกัด",
      "industrial",
      "logistics",
      "service",
    ],
    [
      "บริษัท บางนา คูลเชน เซอร์วิส จำกัด",
      "industrial",
      "warehouse",
      "service",
    ],
    [
      "บริษัท สยาม โปรเจค ดีไซน์ จำกัด",
      "construction",
      "consultant",
      "head_office",
    ],
    [
      "บริษัท ยูไนเต็ด บิลดิ้ง โปรดักส์ จำกัด",
      "construction",
      "contractor",
      "head_office",
    ],
  ] as const;

  for (const [name, groupCode, subGroupCode, branchCode] of extraTemplates) {
    if (customers.length >= 50) break;
    const branch =
      branches.find((row) => row.code === branchCode) ?? branches[0] ?? null;
    customers.push({
      id: crypto.randomUUID(),
      code: buildDocumentCode("CUS", customerSequence++),
      name,
      branchId: branch?.id ?? null,
      province: choose(STORIES, customerSequence).province,
      district: choose(STORIES, customerSequence).district,
      customerTypeId: getOptionId(optionMap, "crm_customer_type", "company"),
      customerStatusId: getOptionId(optionMap, "crm_customer_status", "active"),
      customerGroupId: getOptionId(optionMap, "crm_customer_group", groupCode),
      customerSubGroupId: getOptionId(
        optionMap,
        "crm_customer_sub_group",
        subGroupCode,
      ),
      leadChannelId: getOptionalOptionId(
        optionMap,
        "crm_lead_channel",
        "referral",
      ),
      ownerUserId: choose(users.sales, customerSequence).userId,
      notes: `${seededNote(`customer-${customerSequence}`)} Supporting UAT account for dashboard and report coverage.`,
      createdBy: users.admin.userId,
      email: `${slugify(name)}@example.co.th`,
      phone: `02-${String(600000 + customerSequence).slice(-6)}`,
      website: `https://${slugify(name)}.example.co.th`,
    });
  }

  return { customers, storyCustomerMap };
}

function buildContacts(customers: SeedCustomer[], users: UserContext) {
  const positions = [
    ["Procurement Manager", "Procurement"],
    ["Project Engineer", "Engineering"],
    ["Plant Manager", "Operations"],
    ["Maintenance Supervisor", "Maintenance"],
    ["Warehouse Manager", "Logistics"],
  ] as const;

  const contacts: SeedContact[] = [];
  const primaryContactByCustomerId = new Map<string, SeedContact>();

  for (const [index, customer] of customers.entries()) {
    const count = 2 + (index % 4);
    for (let offset = 0; offset < count; offset += 1) {
      const [position, department] = choose(positions, index + offset);
      const name = `${offset === 0 ? "คุณ" : "คุณ"}${["กิตติพงศ์", "ศิริพร", "ธนพล", "วราภรณ์", "ปิยะนุช", "จักรพันธ์"][(index * 3 + offset) % 6]} ${["แสงทอง", "สิริวัฒน์", "สุวรรณ", "จิรโรจน์", "อนันต์สุข", "เกษมศรี"][(index * 5 + offset) % 6]}`;
      const contact: SeedContact = {
        id: crypto.randomUUID(),
        customerId: customer.id,
        name,
        position,
        department,
        phone: customer.phone,
        mobile: `08${String(10000000 + index * 10 + offset).slice(-8)}`,
        email: `${slugify(name)}@${slugify(customer.name)}.co.th`,
        isPrimary: offset === 0,
        notes: `${seededNote(`contact-${customer.code}-${offset + 1}`)} ${department} contact for ${customer.name}.`,
        createdBy: users.admin.userId,
      };
      contacts.push(contact);
      if (offset === 0) {
        primaryContactByCustomerId.set(customer.id, contact);
      }
    }
  }

  return { contacts, primaryContactByCustomerId };
}

function buildLeadAndOpportunityData(
  optionMap: Map<string, OptionRow>,
  storyCustomerMap: Map<string, SeedCustomer>,
  allCustomers: SeedCustomer[],
  primaryContactByCustomerId: Map<string, SeedContact>,
  users: UserContext,
) {
  const leads: SeedLead[] = [];
  const leadFollowups: SeedLeadFollowup[] = [];
  const opportunities: SeedOpportunity[] = [];
  const opportunityParties: SeedOpportunityParty[] = [];
  const opportunityFollowups: SeedOpportunityFollowup[] = [];
  const storyOpportunityMap = new Map<string, SeedOpportunity>();

  let leadSequence = 901;
  let opportunitySequence = 1001;

  for (const [storyIndex, story] of STORIES.entries()) {
    const customer = storyCustomerMap.get(story.slug);
    if (!customer) {
      throw new Error(`Story customer missing for ${story.slug}`);
    }
    const contact = primaryContactByCustomerId.get(customer.id) ?? null;
    const createdAt = daysAgo(88 - storyIndex * 4);
    const assignedUser = choose(users.sales, storyIndex);
    const assignedAt = addDays(createdAt, 4);
    const statusId = getOptionId(
      optionMap,
      "crm_lead_status",
      story.leadStatusCode,
    );
    const followupStatusId = getOptionalOptionId(
      optionMap,
      "crm_lead_followup_status",
      story.leadFollowupStatusCode,
    );
    const lostReasonId = story.lostReasonCode
      ? getOptionalOptionId(
          optionMap,
          "crm_lead_lost_reason",
          story.lostReasonCode,
        )
      : null;
    const lead: SeedLead = {
      id: crypto.randomUUID(),
      code: buildDocumentCode("LD", leadSequence++),
      customerId: customer.id,
      contactId: contact?.id ?? null,
      branchId: customer.branchId,
      description: `${story.leadDescription} ${seededNote(story.slug)}`,
      projectName: story.projectName,
      projectLocation: story.projectLocation,
      productTypeId: getOptionalOptionId(
        optionMap,
        "crm_product_type",
        story.productTypeCode,
      ),
      priorityId: getOptionalOptionId(
        optionMap,
        "crm_priority",
        story.priorityCode,
      ),
      estimatedValue: story.estimatedValue,
      awarenessId: getOptionalOptionId(
        optionMap,
        "crm_lead_awareness",
        story.awarenessCode,
      ),
      statusId,
      followupStatusId,
      lostReasonId,
      outcome:
        story.opportunityOutcome === "won"
          ? "won"
          : story.opportunityOutcome === "lost"
            ? "lost"
            : "open",
      ownerMarketingUserId: users.marketing.userId,
      assignedSalesOwnerId: ["assigned", "closed_lost", "cancel"].includes(
        story.leadStatusCode,
      )
        ? assignedUser.userId
        : null,
      assignedAt: ["assigned", "closed_lost", "cancel"].includes(
        story.leadStatusCode,
      )
        ? assignedAt
        : null,
      assignedBy: ["assigned", "closed_lost", "cancel"].includes(
        story.leadStatusCode,
      )
        ? users.marketing.userId
        : null,
      assignmentRemark: ["assigned", "closed_lost", "cancel"].includes(
        story.leadStatusCode,
      )
        ? `Marketing handed over story ${story.slug} to sales.`
        : null,
      createdBy: users.marketing.userId,
      createdAt,
    };
    leads.push(lead);

    const leadFollowupCount = story.leadStatusCode === "new_job" ? 1 : 2;
    for (
      let followupIndex = 0;
      followupIndex < leadFollowupCount;
      followupIndex += 1
    ) {
      const followupDate = addDays(createdAt, 2 + followupIndex * 6);
      leadFollowups.push({
        id: crypto.randomUUID(),
        leadId: lead.id,
        branchId: lead.branchId,
        userId:
          followupIndex === 0 ? users.marketing.userId : assignedUser.userId,
        followupDate,
        followupStatusId:
          followupStatusId ??
          getOptionId(optionMap, "crm_lead_followup_status", "budget_pending"),
        note:
          followupIndex === 0
            ? "Initial qualification completed and customer timeline confirmed."
            : "Followed up on pricing, scope, and next commercial action.",
        nextFollowupDate:
          followupIndex === leadFollowupCount - 1
            ? null
            : addDays(followupDate, 7),
        createdAt: addDays(followupDate, 0, 14),
      });
    }

    const openStage =
      story.opportunityOutcome === "won"
        ? "closed_won"
        : story.opportunityOutcome === "lost"
          ? "closed_lost"
          : story.opportunityOutcome === "open"
            ? storyIndex % 3 === 0
              ? "lead"
              : "opportunity"
            : "opportunity";

    const opportunityCreatedAt = addDays(createdAt, 8);
    const closedAt =
      story.opportunityOutcome === "open"
        ? null
        : addDays(opportunityCreatedAt, 25 + (storyIndex % 8));
    const opportunity: SeedOpportunity = {
      id: crypto.randomUUID(),
      code: buildDocumentCode("EN", opportunitySequence++),
      customerId: customer.id,
      contactId: contact?.id ?? null,
      leadId: lead.id,
      branchId: customer.branchId,
      title: story.projectName,
      description: story.leadDescription,
      requirement: `Need proposal for ${story.projectName.toLowerCase()} with timeline, commercial scope, and installation plan.`,
      projectName: story.projectName,
      projectLocation: story.projectLocation,
      productTypeId: getOptionId(
        optionMap,
        "crm_product_type",
        story.productTypeCode,
      ),
      statusId: getOptionId(
        optionMap,
        "crm_opportunity_stage",
        story.opportunityStatusCode,
      ),
      outcomeStatus: story.opportunityOutcome,
      priorityId: getOptionId(optionMap, "crm_priority", story.priorityCode),
      leadChannelId: getOptionalOptionId(
        optionMap,
        "crm_lead_channel",
        story.leadChannelCode,
      ),
      estimatedValue: story.estimatedValue,
      chancePercent: story.chancePercent,
      expectedCloseDate:
        story.opportunityOutcome === "open"
          ? addDays(opportunityCreatedAt, 21)
          : closedAt,
      competitor: story.competitor ?? null,
      source: `${SEED_TAG}:${story.slug}`,
      notes: `${seededNote(story.slug)} Opportunity seeded for story coverage.`,
      isHotProject:
        story.estimatedValue >= 10000000 || story.priorityCode === "urgent",
      isActive: true,
      pipelineStage: openStage,
      closedAt,
      closedWonAt: story.opportunityOutcome === "won" ? closedAt : null,
      closedLostAt: story.opportunityOutcome === "lost" ? closedAt : null,
      closedByUserId: closedAt ? users.salesManager.userId : null,
      poNumber:
        story.opportunityOutcome === "won"
          ? `PO-${String(2606000 + storyIndex + 1).padStart(7, "0")}`
          : null,
      poDate:
        story.opportunityOutcome === "won" ? addDays(closedAt!, -1) : null,
      poAmount:
        story.opportunityOutcome === "won"
          ? Math.round(story.estimatedValue * (0.95 + (storyIndex % 4) * 0.03))
          : null,
      poCurrencyId:
        story.opportunityOutcome === "won"
          ? getOptionId(optionMap, "crm_currency", "THB")
          : null,
      lostReasonId: story.lostReasonCode
        ? getOptionalOptionId(
            optionMap,
            "crm_opportunity_lost_reason",
            story.lostReasonCode,
          )
        : null,
      lostDetail:
        story.opportunityOutcome === "lost"
          ? "Customer benchmarked price and competitor lead time."
          : null,
      lostCompetitor:
        story.opportunityOutcome === "lost"
          ? (story.competitor ?? "Konecranes")
          : null,
      lostRemark:
        story.opportunityOutcome === "lost"
          ? "Closed after customer confirmed competitor selection."
          : null,
      cancelReasonId: story.cancelReasonCode
        ? getOptionalOptionId(
            optionMap,
            "crm_opportunity_cancel_reason",
            story.cancelReasonCode,
          )
        : null,
      noQuotationReasonId: story.noQuotationReasonCode
        ? getOptionalOptionId(
            optionMap,
            "crm_opportunity_no_quotation_reason",
            story.noQuotationReasonCode,
          )
        : null,
      assignedToUserId: openStage === "lead" ? null : assignedUser.userId,
      assignedAt:
        openStage === "lead" ? null : addDays(opportunityCreatedAt, 0, 11),
      assignedBy: openStage === "lead" ? null : users.salesManager.userId,
      assignmentRemark:
        openStage === "lead" ? null : `Assigned from lead ${lead.code}`,
      createdBy: assignedUser.userId,
      updatedBy: assignedUser.userId,
      createdAt: opportunityCreatedAt,
      updatedAt: closedAt ?? addDays(opportunityCreatedAt, 14),
    };
    opportunities.push(opportunity);
    storyOpportunityMap.set(story.slug, opportunity);

    opportunityFollowups.push({
      id: crypto.randomUUID(),
      opportunityId: opportunity.id,
      contactId: contact?.id ?? null,
      followupDate: addDays(opportunityCreatedAt, 5),
      followupTypeId: getOptionId(optionMap, "crm_followup_type", "meeting"),
      outcome: "Requirement confirmed and next engineering action assigned.",
      notes: "Reviewed layout, lifting capacity, and commercial target.",
      nextFollowupDate: addDays(opportunityCreatedAt, 12),
      nextAction: "Prepare updated commercial proposal",
      createdBy: assignedUser.userId,
      updatedBy: assignedUser.userId,
    });
    opportunityFollowups.push({
      id: crypto.randomUUID(),
      opportunityId: opportunity.id,
      contactId: contact?.id ?? null,
      followupDate:
        story.slug === "followup-overdue"
          ? daysAgo(2, 16)
          : addDays(opportunityCreatedAt, 12),
      followupTypeId: getOptionId(
        optionMap,
        "crm_followup_type",
        story.slug === "followup-overdue" ? "call" : "email",
      ),
      outcome:
        story.slug === "followup-overdue"
          ? "Customer asked for updated date but salesperson missed next promised response."
          : "Commercial follow-up sent and next action agreed.",
      notes:
        story.slug === "followup-overdue"
          ? "Deliberately overdue for dashboard testing."
          : "Discussed approval path, commercial gaps, and expected close timing.",
      nextFollowupDate:
        story.slug === "followup-overdue"
          ? daysAgo(1)
          : story.opportunityOutcome === "open"
            ? daysAhead(2)
            : null,
      nextAction:
        story.slug === "followup-overdue"
          ? "Call customer and re-confirm revised survey date"
          : story.opportunityOutcome === "open"
            ? "Push approval and customer sign-off"
            : null,
      createdBy: assignedUser.userId,
      updatedBy: assignedUser.userId,
    });

    for (const party of story.multiParties ?? []) {
      const relatedCustomer = allCustomers.find(
        (item) => item.name === party.customerName,
      );
      if (!relatedCustomer) continue;
      opportunityParties.push({
        id: crypto.randomUUID(),
        opportunityId: opportunity.id,
        customerId: relatedCustomer.id,
        roleId: getOptionId(
          optionMap,
          "crm_project_party_role",
          party.roleCode,
        ),
        remark: `${party.roleCode.replaceAll("_", " ")} for ${story.projectName}`,
      });
    }
  }

  const extraLeadStatuses = [
    "new_job",
    "new_job",
    "follow_up",
    "follow_up",
    "assigned",
    "assigned",
    "assigned",
    "closed_lost",
    "cancel",
  ] as const;
  const extraFollowupStatuses = [
    "budget_pending",
    "project_under_review",
    "spec_clarification",
    "site_survey_waiting",
    "drawing_followup",
    "quotation_preparation",
  ] as const;
  const extraOpportunityStages = [
    "new",
    "qualification",
    "requirement_gathering",
    "site_survey",
    "proposal_preparation",
    "quotation_created",
    "negotiation",
  ] as const;

  while (leads.length < 80) {
    const customer = allCustomers[leads.length % allCustomers.length];
    const contact = primaryContactByCustomerId.get(customer.id) ?? null;
    const statusCode = choose(extraLeadStatuses, leads.length);
    const createdAt = daysAgo(75 - (leads.length % 45));
    const salesOwner = choose(users.sales, leads.length);
    const lead = {
      id: crypto.randomUUID(),
      code: buildDocumentCode("LD", leadSequence++),
      customerId: customer.id,
      contactId: contact?.id ?? null,
      branchId: customer.branchId,
      description: `General UAT lead for ${customer.name}. ${seededNote(`lead-${leadSequence}`)}`,
      projectName: `${choose(["Warehouse Expansion", "Factory Modernization", "Maintenance Upgrade", "Loading Bay Improvement"], leads.length)} ${Math.floor(leads.length / 4) + 1}`,
      projectLocation: `${customer.district}, ${customer.province}`,
      productTypeId: getOptionId(
        optionMap,
        "crm_product_type",
        choose(["crane", "dockdoor", "solarcell"], leads.length),
      ),
      priorityId: getOptionId(
        optionMap,
        "crm_priority",
        choose(["low", "normal", "high"], leads.length),
      ),
      estimatedValue: 850000 + (leads.length % 9) * 420000,
      awarenessId: getOptionalOptionId(
        optionMap,
        "crm_lead_awareness",
        choose(
          ["social_media", "visit", "brochure_catalog", "repurchasing"],
          leads.length,
        ),
      ),
      statusId: getOptionId(optionMap, "crm_lead_status", statusCode),
      followupStatusId: getOptionalOptionId(
        optionMap,
        "crm_lead_followup_status",
        choose(extraFollowupStatuses, leads.length),
      ),
      lostReasonId:
        statusCode === "closed_lost"
          ? getOptionalOptionId(optionMap, "crm_lead_lost_reason", "price")
          : null,
      outcome: statusCode === "closed_lost" ? "lost" : "open",
      ownerMarketingUserId: users.marketing.userId,
      assignedSalesOwnerId:
        statusCode === "assigned" || statusCode === "closed_lost"
          ? salesOwner.userId
          : null,
      assignedAt:
        statusCode === "assigned" || statusCode === "closed_lost"
          ? addDays(createdAt, 3)
          : null,
      assignedBy:
        statusCode === "assigned" || statusCode === "closed_lost"
          ? users.marketing.userId
          : null,
      assignmentRemark:
        statusCode === "assigned" || statusCode === "closed_lost"
          ? "Qualified and handed over for sales action."
          : null,
      createdBy: users.marketing.userId,
      createdAt,
    } satisfies SeedLead;
    leads.push(lead);
    leadFollowups.push({
      id: crypto.randomUUID(),
      leadId: lead.id,
      branchId: lead.branchId,
      userId: users.marketing.userId,
      followupDate: addDays(createdAt, 2),
      followupStatusId:
        lead.followupStatusId ??
        getOptionId(optionMap, "crm_lead_followup_status", "budget_pending"),
      note: "Seeded follow-up for aging and dashboard validation.",
      nextFollowupDate: statusCode === "new_job" ? daysAhead(1) : null,
      createdAt: addDays(createdAt, 2, 15),
    });
  }

  while (opportunities.length < 50) {
    const customer = allCustomers[opportunities.length % allCustomers.length];
    const contact = primaryContactByCustomerId.get(customer.id) ?? null;
    const createdAt = daysAgo(68 - (opportunities.length % 32));
    const salesOwner = choose(users.sales, opportunities.length);
    const outcomeMode = choose(
      ["open", "won", "lost", "cancelled", "no_quotation"],
      opportunities.length,
    );
    const pipelineStage =
      outcomeMode === "won"
        ? "closed_won"
        : outcomeMode === "lost"
          ? "closed_lost"
          : opportunities.length % 4 === 0
            ? "lead"
            : "opportunity";
    const closedAt =
      pipelineStage === "closed_won" || pipelineStage === "closed_lost"
        ? addDays(createdAt, 18)
        : null;
    opportunities.push({
      id: crypto.randomUUID(),
      code: buildDocumentCode("EN", opportunitySequence++),
      customerId: customer.id,
      contactId: contact?.id ?? null,
      leadId: null,
      branchId: customer.branchId,
      title: `${choose(["Retrofit Program", "Capacity Expansion", "Safety Improvement", "Site Package"], opportunities.length)} ${opportunities.length + 1}`,
      description:
        "Supplemental seeded opportunity for dashboards and reports.",
      requirement:
        "Customer requested commercial proposal and implementation plan.",
      projectName: `${customer.name} - ${choose(["North Plant", "Central Warehouse", "Phase 2", "Service Contract"], opportunities.length)}`,
      projectLocation: `${customer.district}, ${customer.province}`,
      productTypeId: getOptionId(
        optionMap,
        "crm_product_type",
        choose(["crane", "dockdoor", "solarcell"], opportunities.length),
      ),
      statusId: getOptionId(
        optionMap,
        "crm_opportunity_stage",
        choose(extraOpportunityStages, opportunities.length),
      ),
      outcomeStatus: outcomeMode as SeedOpportunity["outcomeStatus"],
      priorityId: getOptionId(
        optionMap,
        "crm_priority",
        choose(["normal", "high", "urgent"], opportunities.length),
      ),
      leadChannelId: getOptionalOptionId(
        optionMap,
        "crm_lead_channel",
        choose(
          ["website", "sales", "referral", "facebook"],
          opportunities.length,
        ),
      ),
      estimatedValue: 1100000 + (opportunities.length % 10) * 540000,
      chancePercent:
        pipelineStage === "lead" ? 25 : 45 + (opportunities.length % 4) * 10,
      expectedCloseDate: closedAt ?? addDays(createdAt, 21),
      competitor:
        outcomeMode === "lost"
          ? choose(["Konecranes", "Demag", "Cibes"], opportunities.length)
          : null,
      source: `${SEED_TAG}:supplemental-${opportunities.length + 1}`,
      notes: `${seededNote(`opportunity-${opportunities.length + 1}`)} Supplemental UAT opportunity.`,
      isHotProject: opportunities.length % 7 === 0,
      isActive: true,
      pipelineStage,
      closedAt,
      closedWonAt: pipelineStage === "closed_won" ? closedAt : null,
      closedLostAt: pipelineStage === "closed_lost" ? closedAt : null,
      closedByUserId: closedAt ? users.salesManager.userId : null,
      poNumber:
        pipelineStage === "closed_won"
          ? `PO-${String(2606200 + opportunities.length).padStart(7, "0")}`
          : null,
      poDate: pipelineStage === "closed_won" ? addDays(closedAt!, -1) : null,
      poAmount:
        pipelineStage === "closed_won"
          ? 1250000 + (opportunities.length % 6) * 320000
          : null,
      poCurrencyId:
        pipelineStage === "closed_won"
          ? getOptionId(optionMap, "crm_currency", "THB")
          : null,
      lostReasonId:
        pipelineStage === "closed_lost"
          ? getOptionalOptionId(
              optionMap,
              "crm_opportunity_lost_reason",
              "competitor_won",
            )
          : null,
      lostDetail:
        pipelineStage === "closed_lost"
          ? "Competitor secured lower commercial package."
          : null,
      lostCompetitor:
        pipelineStage === "closed_lost"
          ? choose(["Konecranes", "Demag"], opportunities.length)
          : null,
      lostRemark:
        pipelineStage === "closed_lost"
          ? "Closed during final commercial comparison."
          : null,
      cancelReasonId:
        outcomeMode === "cancelled"
          ? getOptionalOptionId(
              optionMap,
              "crm_opportunity_cancel_reason",
              "scope_changed",
            )
          : null,
      noQuotationReasonId:
        outcomeMode === "no_quotation"
          ? getOptionalOptionId(
              optionMap,
              "crm_opportunity_no_quotation_reason",
              "customer_stopped",
            )
          : null,
      assignedToUserId: pipelineStage === "lead" ? null : salesOwner.userId,
      assignedAt: pipelineStage === "lead" ? null : addDays(createdAt, 1),
      assignedBy: pipelineStage === "lead" ? null : users.salesManager.userId,
      assignmentRemark:
        pipelineStage === "lead"
          ? null
          : "Seeded assignment for dashboard distribution.",
      createdBy: salesOwner.userId,
      updatedBy: salesOwner.userId,
      createdAt,
      updatedAt: closedAt ?? addDays(createdAt, 9),
    });
    opportunityFollowups.push({
      id: crypto.randomUUID(),
      opportunityId: opportunities[opportunities.length - 1]!.id,
      contactId: contact?.id ?? null,
      followupDate: addDays(createdAt, 4),
      followupTypeId: getOptionId(
        optionMap,
        "crm_followup_type",
        choose(["call", "meeting", "email"], opportunities.length),
      ),
      outcome: "Supplemental follow-up seeded for pipeline analytics.",
      notes: "Used for due and overdue follow-up widgets.",
      nextFollowupDate:
        outcomeMode === "open"
          ? daysAhead((opportunities.length % 4) - 1)
          : null,
      nextAction:
        outcomeMode === "open" ? "Confirm next commercial action" : null,
      createdBy: salesOwner.userId,
      updatedBy: salesOwner.userId,
    });
  }

  return {
    leads,
    leadFollowups,
    opportunities,
    opportunityParties,
    opportunityFollowups,
    storyOpportunityMap,
  };
}

function buildQuotationData(
  optionMap: Map<string, OptionRow>,
  storyOpportunityMap: Map<string, SeedOpportunity>,
  storyCustomerMap: Map<string, SeedCustomer>,
  allCustomers: SeedCustomer[],
  primaryContactByCustomerId: Map<string, SeedContact>,
  users: UserContext,
) {
  const quotations: SeedQuotation[] = [];
  const quotationItems: SeedQuotationItem[] = [];
  const quotationParties: SeedQuotationParty[] = [];
  const quotationTopics: SeedQuotationTopic[] = [];
  const quotationFollowups: SeedQuotationFollowup[] = [];
  const approvalRequests: SeedApprovalRequest[] = [];
  const approvalActions: SeedApprovalAction[] = [];

  let quotationSequence = 1101;

  const acceptedStories = new Set([
    "website-warehouse-crane-win",
    "high-value-hot-project",
    "line-maintenance-renewal",
  ]);

  for (const [storyIndex, story] of STORIES.entries()) {
    const opportunity = storyOpportunityMap.get(story.slug);
    const customer = storyCustomerMap.get(story.slug);
    if (!opportunity || !customer) continue;
    const contact = primaryContactByCustomerId.get(customer.id) ?? null;
    const salesperson = choose(users.sales, storyIndex);
    const baseDate = addDays(opportunity.createdAt, 10);
    const planRevisionCount =
      story.quotePlan === "returned_then_revised"
        ? 4
        : story.quotePlan === "rejected_then_revised"
          ? 3
          : acceptedStories.has(story.slug)
            ? 2
            : 1;

    let familyRootId: string | null = null;
    for (let revision = 0; revision < planRevisionCount; revision += 1) {
      const quotationId = crypto.randomUUID();
      if (!familyRootId) familyRootId = quotationId;

      const statusCode =
        story.quotePlan === "pending_step_1"
          ? "pending_approval"
          : story.quotePlan === "pending_step_2"
            ? revision === planRevisionCount - 1
              ? "pending_approval"
              : "revised"
            : story.quotePlan === "approved"
              ? revision === planRevisionCount - 1
                ? "approved"
                : "revised"
              : story.quotePlan === "accepted"
                ? revision === planRevisionCount - 1
                  ? "accepted"
                  : "revised"
                : story.quotePlan === "rejected_then_revised"
                  ? revision === planRevisionCount - 1
                    ? "accepted"
                    : revision === planRevisionCount - 2
                      ? "rejected"
                      : "revised"
                  : story.quotePlan === "returned_then_revised"
                    ? revision === planRevisionCount - 1
                      ? "approved"
                      : revision === planRevisionCount - 2
                        ? "draft"
                        : "revised"
                    : story.quotePlan === "sent_follow_up"
                      ? revision === planRevisionCount - 1
                        ? "sent"
                        : "revised"
                      : "cancelled";

      const quotationDate = addDays(baseDate, revision * 6);
      const subtotal = Math.round(
        (story.estimatedValue / Math.max(planRevisionCount, 1)) * 0.94,
      );
      const discount =
        revision === planRevisionCount - 1
          ? Math.round(subtotal * 0.03)
          : Math.round(subtotal * 0.01);
      const taxRate = 7;
      const taxAmount = Math.round((subtotal - discount) * (taxRate / 100));
      const totalAmount = subtotal - discount + taxAmount;

      quotations.push({
        id: quotationId,
        code: buildDocumentCode("QT", quotationSequence++),
        opportunityId: opportunity.id,
        customerId: customer.id,
        contactId: contact?.id ?? null,
        branchId: customer.branchId,
        quotationDate,
        validUntil: addDays(quotationDate, 30),
        quotationTypeId: getOptionId(
          optionMap,
          "crm_quotation_type",
          story.quotationTypeCode,
        ),
        projectName: story.projectName,
        projectLocation: story.projectLocation,
        attention: contact?.name ?? null,
        reference: `${SEED_TAG}:${story.slug}`,
        notes: `${story.title} / revision ${revision}`,
        statusId: getOptionId(optionMap, "crm_quotation_status", statusCode),
        revision,
        parentQuotationId: revision === 0 ? null : familyRootId,
        revisionRemark:
          revision === 0
            ? null
            : `Revision R${String(revision).padStart(2, "0")} for ${story.slug}`,
        currencyId: getOptionId(optionMap, "crm_currency", "THB"),
        exchangeRate: 1,
        subtotal,
        discount,
        discountTypeId: getOptionalOptionId(
          optionMap,
          "crm_discount_type",
          "fixed",
        ),
        taxRate,
        taxAmount,
        totalAmount,
        chancePercent: story.chancePercent,
        isHotProject: story.estimatedValue > 10000000,
        competitor: story.competitor ?? null,
        salesmanId: salesperson.userId,
        isSent: ["sent", "approved", "accepted", "rejected"].includes(
          statusCode,
        ),
        sentAt: ["sent", "approved", "accepted", "rejected"].includes(
          statusCode,
        )
          ? addDays(quotationDate, 2, 14)
          : null,
        sentViaId: ["sent", "approved", "accepted", "rejected"].includes(
          statusCode,
        )
          ? (getOptionalOptionId(
              optionMap,
              "crm_sent_via",
              revision % 2 === 0 ? "email" : "line",
            ) ?? getOptionId(optionMap, "crm_sent_via", "email"))
          : null,
        approvedAt: ["approved", "accepted"].includes(statusCode)
          ? addDays(quotationDate, 3, 15)
          : null,
        acceptedAt:
          statusCode === "accepted" ? addDays(quotationDate, 7, 10) : null,
        rejectedAt:
          statusCode === "rejected" ? addDays(quotationDate, 5, 11) : null,
        rejectionReason:
          statusCode === "rejected"
            ? "Customer requested layout revision and commercial warranty adjustment."
            : null,
        createdBy: salesperson.userId,
        updatedBy: salesperson.userId,
        createdAt: quotationDate,
        updatedAt: addDays(quotationDate, 5),
      });

      const itemTemplates = [
        ["Main equipment supply", 1, Math.round(totalAmount * 0.55)],
        ["Installation and commissioning", 1, Math.round(totalAmount * 0.28)],
        [
          "Operator training and documentation",
          1,
          Math.round(totalAmount * 0.1),
        ],
        ["Project management and testing", 1, Math.round(totalAmount * 0.07)],
      ] as const;

      itemTemplates.forEach(([description, quantity, unitPrice], itemIndex) => {
        quotationItems.push({
          id: crypto.randomUUID(),
          quotationId,
          itemNumber: itemIndex + 1,
          productTypeId: getOptionId(
            optionMap,
            "crm_product_type",
            story.productTypeCode,
          ),
          description: `${description} for ${story.projectName}`,
          quantity,
          unitId: getOptionalOptionId(
            optionMap,
            "crm_unit",
            itemIndex === 0 ? "set" : "job",
          ),
          unitPrice,
          discount: 0,
          discountTypeId: null,
          taxRate,
          totalPrice: unitPrice,
          notes: itemIndex === 0 ? "Seeded major commercial line item." : null,
          sortOrder: itemIndex + 1,
        });
      });

      quotationParties.push({
        id: crypto.randomUUID(),
        quotationId,
        customerId: customer.id,
        roleId: getOptionId(optionMap, "crm_project_party_role", "customer"),
        isPrimary: true,
        remark: "Primary customer",
      });

      for (const party of story.multiParties ?? []) {
        const relatedCustomer = allCustomers.find(
          (item) => item.name === party.customerName,
        );
        if (!relatedCustomer) continue;
        quotationParties.push({
          id: crypto.randomUUID(),
          quotationId,
          customerId: relatedCustomer.id,
          roleId: getOptionId(
            optionMap,
            "crm_project_party_role",
            party.roleCode,
          ),
          isPrimary: false,
          remark: `${party.roleCode.replaceAll("_", " ")} relation`,
        });
      }

      const topicTypeCodes = ["scope", "exclusion", "payment"] as const;
      topicTypeCodes.forEach((topicCode, topicIndex) => {
        const title =
          topicCode === "scope"
            ? "Scope of Work"
            : topicCode === "exclusion"
              ? "Exclusions"
              : "Payment Terms";
        quotationTopics.push({
          id: crypto.randomUUID(),
          quotationId,
          topicTypeId: getOptionId(
            optionMap,
            "crm_quotation_topic_type",
            topicCode,
          ),
          title,
          sortOrder: topicIndex + 1,
          items: [
            {
              id: crypto.randomUUID(),
              content:
                topicCode === "scope"
                  ? `Supply and execute ${story.projectName.toLowerCase()} according to approved drawing.`
                  : topicCode === "exclusion"
                    ? "Civil work, main power cable, and customer standby crew are excluded."
                    : "40% deposit, 50% after delivery, 10% after commissioning.",
              sortOrder: 1,
            },
            {
              id: crypto.randomUUID(),
              content:
                topicCode === "scope"
                  ? "Include testing, commissioning, and handover documentation."
                  : topicCode === "exclusion"
                    ? "Work outside normal business hours is not included."
                    : "Price validity 30 days and delivery 60-90 days after order confirmation.",
              sortOrder: 2,
            },
          ],
        });
      });

      quotationFollowups.push({
        id: crypto.randomUUID(),
        quotationId,
        contactId: contact?.id ?? null,
        followupDate: addDays(quotationDate, 4),
        followupTypeId: getOptionId(optionMap, "crm_followup_type", "email"),
        outcome:
          statusCode === "pending_approval"
            ? "Waiting internal approval routing."
            : "Quotation delivered and customer acknowledged receipt.",
        notes: "Seeded quotation follow-up for dashboard and detail tabs.",
        nextFollowupDate:
          statusCode === "sent"
            ? daysAgo(story.slug === "followup-overdue" ? 1 : -2)
            : statusCode === "pending_approval"
              ? daysAhead(1)
              : null,
        nextAction:
          statusCode === "pending_approval"
            ? "Remind internal approver"
            : statusCode === "sent"
              ? "Call customer for quotation review"
              : null,
        createdBy: salesperson.userId,
        updatedBy: salesperson.userId,
      });

      if (
        revision === planRevisionCount - 1 &&
        [
          "pending_step_1",
          "pending_step_2",
          "approved",
          "accepted",
          "rejected_then_revised",
          "returned_then_revised",
        ].includes(story.quotePlan)
      ) {
        const requestId = crypto.randomUUID();
        const requestedAt = addDays(quotationDate, 1, 10);
        const currentStep =
          story.quotePlan === "pending_step_2"
            ? 2
            : story.quotePlan === "pending_step_1"
              ? 1
              : story.quotePlan === "returned_then_revised"
                ? 2
                : 3;
        const requestStatus =
          story.quotePlan === "pending_step_1" ||
          story.quotePlan === "pending_step_2"
            ? "pending"
            : story.quotePlan === "returned_then_revised"
              ? "returned"
              : story.quotePlan === "rejected_then_revised"
                ? "approved"
                : "approved";
        approvalRequests.push({
          id: requestId,
          workflowId: "",
          entityId: quotationId,
          currentStep,
          status: requestStatus,
          requestedBy: salesperson.userId,
          requestedAt,
          completedAt:
            requestStatus === "pending" ? null : addDays(requestedAt, 3, 17),
          createdAt: requestedAt,
          updatedAt:
            requestStatus === "pending"
              ? addDays(requestedAt, currentStep - 1, 16)
              : addDays(requestedAt, 3, 17),
        });
        approvalActions.push({
          id: crypto.randomUUID(),
          approvalRequestId: requestId,
          stepNumber: 1,
          action: "submit",
          remark: "Submitted for commercial approval",
          actedBy: salesperson.userId,
          actedAt: requestedAt,
        });
        if (story.quotePlan !== "pending_step_1") {
          approvalActions.push({
            id: crypto.randomUUID(),
            approvalRequestId: requestId,
            stepNumber: 1,
            action: "approve",
            remark: "Commercial margin acceptable",
            actedBy: users.salesManager.userId,
            actedAt: addDays(requestedAt, 1, 11),
          });
        }
        if (story.quotePlan === "pending_step_2") {
          continue;
        }
        if (story.quotePlan === "returned_then_revised") {
          approvalActions.push({
            id: crypto.randomUUID(),
            approvalRequestId: requestId,
            stepNumber: 2,
            action: "return",
            remark: "Please revise payment term and warranty wording.",
            actedBy: users.departmentManager.userId,
            actedAt: addDays(requestedAt, 2, 14),
          });
          continue;
        }
        approvalActions.push({
          id: crypto.randomUUID(),
          approvalRequestId: requestId,
          stepNumber: 2,
          action: "approve",
          remark: "Department approved commercial assumptions.",
          actedBy: users.departmentManager.userId,
          actedAt: addDays(requestedAt, 2, 10),
        });
        approvalActions.push({
          id: crypto.randomUUID(),
          approvalRequestId: requestId,
          stepNumber: 3,
          action: "approve",
          remark: "Final approval completed.",
          actedBy: users.topManager.userId,
          actedAt: addDays(requestedAt, 3, 9),
        });
      }
    }
  }

  while (quotations.length < 60) {
    const customer = allCustomers[quotations.length % allCustomers.length];
    const contact = primaryContactByCustomerId.get(customer.id) ?? null;
    const salesperson = choose(users.sales, quotations.length);
    const quotationDate = daysAgo(55 - (quotations.length % 28));
    const statusCode = choose(
      [
        "draft",
        "pending_approval",
        "approved",
        "sent",
        "accepted",
        "lost",
        "cancelled",
      ],
      quotations.length,
    );
    const subtotal = 980000 + (quotations.length % 8) * 240000;
    const discount = quotations.length % 3 === 0 ? 25000 : 0;
    const taxRate = 7;
    const taxAmount = Math.round((subtotal - discount) * 0.07);
    const totalAmount = subtotal - discount + taxAmount;
    const quotationId = crypto.randomUUID();
    quotations.push({
      id: quotationId,
      code: buildDocumentCode("QT", quotationSequence++),
      opportunityId: null,
      customerId: customer.id,
      contactId: contact?.id ?? null,
      branchId: customer.branchId,
      quotationDate,
      validUntil: addDays(quotationDate, 30),
      quotationTypeId: getOptionId(
        optionMap,
        "crm_quotation_type",
        choose(["crane", "dockdoor", "solarcell"], quotations.length),
      ),
      projectName: `${customer.name} Supplemental Quotation ${quotations.length + 1}`,
      projectLocation: `${customer.district}, ${customer.province}`,
      attention: contact?.name ?? null,
      reference: `${SEED_TAG}:supplemental-${quotations.length}`,
      notes: "Supplemental UAT quotation for status distribution.",
      statusId: getOptionId(optionMap, "crm_quotation_status", statusCode),
      revision: 0,
      parentQuotationId: null,
      revisionRemark: null,
      currencyId: getOptionId(optionMap, "crm_currency", "THB"),
      exchangeRate: 1,
      subtotal,
      discount,
      discountTypeId:
        discount > 0
          ? getOptionalOptionId(optionMap, "crm_discount_type", "fixed")
          : null,
      taxRate,
      taxAmount,
      totalAmount,
      chancePercent: 25 + (quotations.length % 5) * 10,
      isHotProject: quotations.length % 9 === 0,
      competitor:
        statusCode === "lost"
          ? choose(["Konecranes", "Demag"], quotations.length)
          : null,
      salesmanId: salesperson.userId,
      isSent: [
        "pending_approval",
        "approved",
        "sent",
        "accepted",
        "lost",
      ].includes(statusCode),
      sentAt: [
        "pending_approval",
        "approved",
        "sent",
        "accepted",
        "lost",
      ].includes(statusCode)
        ? addDays(quotationDate, 2, 13)
        : null,
      sentViaId: [
        "pending_approval",
        "approved",
        "sent",
        "accepted",
        "lost",
      ].includes(statusCode)
        ? (getOptionalOptionId(
            optionMap,
            "crm_sent_via",
            choose(["email", "manual"], quotations.length),
          ) ?? getOptionId(optionMap, "crm_sent_via", "email"))
        : null,
      approvedAt: ["approved", "accepted"].includes(statusCode)
        ? addDays(quotationDate, 3, 15)
        : null,
      acceptedAt:
        statusCode === "accepted" ? addDays(quotationDate, 6, 10) : null,
      rejectedAt: statusCode === "lost" ? addDays(quotationDate, 8, 11) : null,
      rejectionReason:
        statusCode === "lost"
          ? "Customer selected another vendor after commercial comparison."
          : null,
      createdBy: salesperson.userId,
      updatedBy: salesperson.userId,
      createdAt: quotationDate,
      updatedAt: addDays(quotationDate, 7),
    });
    quotationItems.push({
      id: crypto.randomUUID(),
      quotationId,
      itemNumber: 1,
      productTypeId: getOptionId(
        optionMap,
        "crm_product_type",
        choose(["crane", "dockdoor", "solarcell"], quotations.length),
      ),
      description: "Supplemental scope item",
      quantity: 1,
      unitId: getOptionalOptionId(optionMap, "crm_unit", "job"),
      unitPrice: totalAmount,
      discount: 0,
      discountTypeId: null,
      taxRate,
      totalPrice: totalAmount,
      notes: null,
      sortOrder: 1,
    });
    quotationParties.push({
      id: crypto.randomUUID(),
      quotationId,
      customerId: customer.id,
      roleId: getOptionId(optionMap, "crm_project_party_role", "customer"),
      isPrimary: true,
      remark: null,
    });
    quotationFollowups.push({
      id: crypto.randomUUID(),
      quotationId,
      contactId: contact?.id ?? null,
      followupDate: addDays(quotationDate, 4),
      followupTypeId: getOptionId(
        optionMap,
        "crm_followup_type",
        choose(["email", "call"], quotations.length),
      ),
      outcome: "Supplemental quotation follow-up",
      notes: "Used for dashboard follow-up distribution.",
      nextFollowupDate:
        statusCode === "sent" ? daysAhead((quotations.length % 3) - 1) : null,
      nextAction: statusCode === "sent" ? "Customer review call" : null,
      createdBy: salesperson.userId,
      updatedBy: salesperson.userId,
    });
  }

  return {
    quotations,
    quotationItems,
    quotationParties,
    quotationTopics,
    quotationFollowups,
    approvalRequests,
    approvalActions,
  };
}

async function insertSeedData(
  sql: SqlClient,
  organization: OrganizationRow,
  workflowId: string,
  customers: SeedCustomer[],
  contacts: SeedContact[],
  leads: SeedLead[],
  leadFollowups: SeedLeadFollowup[],
  opportunities: SeedOpportunity[],
  opportunityParties: SeedOpportunityParty[],
  opportunityFollowups: SeedOpportunityFollowup[],
  quotations: SeedQuotation[],
  quotationItems: SeedQuotationItem[],
  quotationParties: SeedQuotationParty[],
  quotationTopics: SeedQuotationTopic[],
  quotationFollowups: SeedQuotationFollowup[],
  approvalRequests: SeedApprovalRequest[],
  approvalActions: SeedApprovalAction[],
) {
  for (const customer of customers) {
    await sql`
      insert into crm_customers (
        id, organization_id, branch_id, code, name, tax_id, customer_type, customer_status,
        address, province, district, country, phone, email, website, lead_channel,
        customer_group, customer_sub_group, owner_user_id, owner_assigned_at, owner_assigned_by,
        notes, is_active, created_at, updated_at, created_by, updated_by
      ) values (
        ${customer.id}, ${organization.id}, ${customer.branchId}, ${customer.code}, ${customer.name}, null, ${customer.customerTypeId},
        ${customer.customerStatusId}, ${`${customer.district}, ${customer.province}`}, ${customer.province}, ${customer.district},
        ${"Thailand"}, ${customer.phone}, ${customer.email}, ${customer.website}, ${customer.leadChannelId},
        ${customer.customerGroupId}, ${customer.customerSubGroupId}, ${customer.ownerUserId}, ${daysAgo(89)},
        ${organization.createdBy}, ${customer.notes}, ${true}, ${daysAgo(89)}, ${daysAgo(1)}, ${customer.createdBy}, ${customer.createdBy}
      )
    `;
    await sql`
      insert into crm_customer_owner_history (
        id, organization_id, customer_id, old_owner_user_id, new_owner_user_id, changed_by, changed_at, remark
      ) values (
        ${crypto.randomUUID()}, ${organization.id}, ${customer.id}, null, ${customer.ownerUserId}, ${customer.createdBy},
        ${daysAgo(89)}, ${"Initial seeded customer owner"}
      )
    `;
  }

  for (const contact of contacts) {
    await sql`
      insert into crm_customer_contacts (
        id, organization_id, customer_id, name, position, department, phone, mobile, email,
        is_primary, notes, is_active, created_at, updated_at, created_by, updated_by
      ) values (
        ${contact.id}, ${organization.id}, ${contact.customerId}, ${contact.name}, ${contact.position}, ${contact.department},
        ${contact.phone}, ${contact.mobile}, ${contact.email}, ${contact.isPrimary}, ${contact.notes}, ${true},
        ${daysAgo(85)}, ${daysAgo(2)}, ${contact.createdBy}, ${contact.createdBy}
      )
    `;
  }

  for (const lead of leads) {
    await sql`
      insert into crm_leads (
        id, organization_id, branch_id, code, customer_id, contact_id, description, project_name, project_location,
        product_type, priority, estimated_value, awareness_id, status, followup_status, lost_reason, outcome,
        owner_marketing_user_id, assigned_sales_owner_id, assigned_at, assigned_by, assignment_remark,
        created_by, created_at, updated_at
      ) values (
        ${lead.id}, ${organization.id}, ${lead.branchId}, ${lead.code}, ${lead.customerId}, ${lead.contactId},
        ${lead.description}, ${lead.projectName}, ${lead.projectLocation}, ${lead.productTypeId}, ${lead.priorityId},
        ${lead.estimatedValue}, ${lead.awarenessId}, ${lead.statusId}, ${lead.followupStatusId}, ${lead.lostReasonId},
        ${lead.outcome}, ${lead.ownerMarketingUserId}, ${lead.assignedSalesOwnerId}, ${lead.assignedAt},
        ${lead.assignedBy}, ${lead.assignmentRemark}, ${lead.createdBy}, ${lead.createdAt}, ${lead.createdAt}
      )
    `;
  }

  for (const followup of leadFollowups) {
    const payload = {
      id: followup.id,
      leadId: followup.leadId,
      followupDate: followup.followupDate.toISOString(),
      followupStatus: followup.followupStatusId,
      note: followup.note,
      nextFollowupDate: followup.nextFollowupDate?.toISOString() ?? null,
      createdBy: followup.userId,
      createdAt: followup.createdAt.toISOString(),
    };
    await sql`
      insert into tr_audit_logs (
        id, organization_id, branch_id, user_id, entity_type, entity_id, action, after_data, created_at
      ) values (
        ${crypto.randomUUID()}, ${organization.id}, ${followup.branchId}, ${followup.userId},
        ${"crm_lead"}, ${followup.leadId}, ${"create_lead_followup"}, ${sql.json(payload)}, ${followup.createdAt}
      )
    `;
  }

  for (const opportunity of opportunities) {
    await sql`
      insert into crm_opportunities (
        id, organization_id, branch_id, lead_id, code, customer_id, contact_id, title, description, requirement,
        project_name, project_location, product_type, status, outcome_status, priority, lead_channel, estimated_value,
        chance_percent, expected_close_date, competitor, source, notes, is_hot_project, is_active, pipeline_stage,
        closed_at, closed_won_at, closed_lost_at, closed_by_user_id, po_number, po_date, po_amount, po_currency,
        lost_reason, lost_detail, lost_competitor, lost_remark, cancel_reason, no_quotation_reason,
        assigned_to_user_id, assigned_at, assigned_by, assignment_remark, created_by, updated_by, created_at, updated_at
      ) values (
        ${opportunity.id}, ${organization.id}, ${opportunity.branchId}, ${opportunity.leadId}, ${opportunity.code},
        ${opportunity.customerId}, ${opportunity.contactId}, ${opportunity.title}, ${opportunity.description},
        ${opportunity.requirement}, ${opportunity.projectName}, ${opportunity.projectLocation}, ${opportunity.productTypeId},
        ${opportunity.statusId}, ${opportunity.outcomeStatus}, ${opportunity.priorityId}, ${opportunity.leadChannelId},
        ${opportunity.estimatedValue}, ${opportunity.chancePercent}, ${opportunity.expectedCloseDate},
        ${opportunity.competitor}, ${opportunity.source}, ${opportunity.notes}, ${opportunity.isHotProject},
        ${opportunity.isActive}, ${opportunity.pipelineStage}, ${opportunity.closedAt}, ${opportunity.closedWonAt},
        ${opportunity.closedLostAt}, ${opportunity.closedByUserId}, ${opportunity.poNumber}, ${opportunity.poDate},
        ${opportunity.poAmount}, ${opportunity.poCurrencyId}, ${opportunity.lostReasonId}, ${opportunity.lostDetail},
        ${opportunity.lostCompetitor}, ${opportunity.lostRemark}, ${opportunity.cancelReasonId},
        ${opportunity.noQuotationReasonId}, ${opportunity.assignedToUserId}, ${opportunity.assignedAt},
        ${opportunity.assignedBy}, ${opportunity.assignmentRemark}, ${opportunity.createdBy}, ${opportunity.updatedBy},
        ${opportunity.createdAt}, ${opportunity.updatedAt}
      )
    `;
  }

  for (const party of opportunityParties) {
    await sql`
      insert into crm_opportunity_customers (
        id, organization_id, opportunity_id, customer_id, role, remark, created_at, updated_at
      ) values (
        ${party.id}, ${organization.id}, ${party.opportunityId}, ${party.customerId}, ${party.roleId}, ${party.remark},
        ${daysAgo(50)}, ${daysAgo(3)}
      )
    `;
  }

  for (const followup of opportunityFollowups) {
    await sql`
      insert into crm_opportunity_followups (
        id, organization_id, opportunity_id, followup_date, followup_type, contact_id, outcome, notes,
        next_followup_date, next_action, created_at, updated_at, created_by, updated_by
      ) values (
        ${followup.id}, ${organization.id}, ${followup.opportunityId}, ${followup.followupDate}, ${followup.followupTypeId},
        ${followup.contactId}, ${followup.outcome}, ${followup.notes}, ${followup.nextFollowupDate},
        ${followup.nextAction}, ${followup.followupDate}, ${followup.followupDate}, ${followup.createdBy}, ${followup.updatedBy}
      )
    `;
  }

  for (const quotation of quotations) {
    await sql`
      insert into crm_quotations (
        id, organization_id, branch_id, code, opportunity_id, customer_id, contact_id, quotation_date, valid_until,
        quotation_type, project_name, project_location, attention, reference, notes, status, revision, parent_quotation_id,
        revision_remark, currency, exchange_rate, subtotal, discount, discount_type, tax_rate, tax_amount, total_amount,
        chance_percent, is_hot_project, competitor, salesman_id, is_sent, sent_at, sent_via, approved_at, accepted_at,
        rejected_at, rejection_reason, is_active, created_at, updated_at, created_by, updated_by
      ) values (
        ${quotation.id}, ${organization.id}, ${quotation.branchId}, ${quotation.code}, ${quotation.opportunityId},
        ${quotation.customerId}, ${quotation.contactId}, ${quotation.quotationDate}, ${quotation.validUntil},
        ${quotation.quotationTypeId}, ${quotation.projectName}, ${quotation.projectLocation}, ${quotation.attention},
        ${quotation.reference}, ${quotation.notes}, ${quotation.statusId}, ${quotation.revision},
        ${quotation.parentQuotationId}, ${quotation.revisionRemark}, ${quotation.currencyId}, ${quotation.exchangeRate},
        ${quotation.subtotal}, ${quotation.discount}, ${quotation.discountTypeId}, ${quotation.taxRate},
        ${quotation.taxAmount}, ${quotation.totalAmount}, ${quotation.chancePercent}, ${quotation.isHotProject},
        ${quotation.competitor}, ${quotation.salesmanId}, ${quotation.isSent}, ${quotation.sentAt}, ${quotation.sentViaId},
        ${quotation.approvedAt}, ${quotation.acceptedAt}, ${quotation.rejectedAt}, ${quotation.rejectionReason},
        ${true}, ${quotation.createdAt}, ${quotation.updatedAt}, ${quotation.createdBy}, ${quotation.updatedBy}
      )
    `;
  }

  for (const item of quotationItems) {
    await sql`
      insert into crm_quotation_items (
        id, organization_id, quotation_id, item_number, product_type, description, quantity, unit, unit_price,
        discount, discount_type, tax_rate, total_price, notes, sort_order, created_at, updated_at
      ) values (
        ${item.id}, ${organization.id}, ${item.quotationId}, ${item.itemNumber}, ${item.productTypeId},
        ${item.description}, ${item.quantity}, ${item.unitId}, ${item.unitPrice}, ${item.discount},
        ${item.discountTypeId}, ${item.taxRate}, ${item.totalPrice}, ${item.notes}, ${item.sortOrder},
        ${daysAgo(40)}, ${daysAgo(2)}
      )
    `;
  }

  for (const party of quotationParties) {
    await sql`
      insert into crm_quotation_customers (
        id, organization_id, quotation_id, customer_id, role, is_primary, remark, created_at, updated_at
      ) values (
        ${party.id}, ${organization.id}, ${party.quotationId}, ${party.customerId}, ${party.roleId}, ${party.isPrimary},
        ${party.remark}, ${daysAgo(40)}, ${daysAgo(2)}
      )
    `;
  }

  for (const topic of quotationTopics) {
    await sql`
      insert into crm_quotation_topics (
        id, organization_id, quotation_id, topic_type, title, sort_order, created_at, updated_at
      ) values (
        ${topic.id}, ${organization.id}, ${topic.quotationId}, ${topic.topicTypeId}, ${topic.title},
        ${topic.sortOrder}, ${daysAgo(35)}, ${daysAgo(3)}
      )
    `;
    for (const topicItem of topic.items) {
      await sql`
        insert into crm_quotation_topic_items (
          id, organization_id, topic_id, content, sort_order, created_at, updated_at
        ) values (
          ${topicItem.id}, ${organization.id}, ${topic.id}, ${topicItem.content}, ${topicItem.sortOrder},
          ${daysAgo(35)}, ${daysAgo(3)}
        )
      `;
    }
  }

  for (const followup of quotationFollowups) {
    await sql`
      insert into crm_quotation_followups (
        id, organization_id, quotation_id, followup_date, followup_type, contact_id, outcome, notes,
        next_followup_date, next_action, created_at, updated_at, created_by, updated_by
      ) values (
        ${followup.id}, ${organization.id}, ${followup.quotationId}, ${followup.followupDate}, ${followup.followupTypeId},
        ${followup.contactId}, ${followup.outcome}, ${followup.notes}, ${followup.nextFollowupDate},
        ${followup.nextAction}, ${followup.followupDate}, ${followup.followupDate}, ${followup.createdBy}, ${followup.updatedBy}
      )
    `;
  }

  for (const request of approvalRequests) {
    await sql`
      insert into crm_approval_requests (
        id, organization_id, workflow_id, entity_type, entity_id, current_step, status,
        requested_by, requested_at, completed_at, created_at, updated_at
      ) values (
        ${request.id}, ${organization.id}, ${workflowId}, ${"quotation"}, ${request.entityId}, ${request.currentStep},
        ${request.status}, ${request.requestedBy}, ${request.requestedAt}, ${request.completedAt}, ${request.createdAt}, ${request.updatedAt}
      )
    `;
  }

  for (const action of approvalActions) {
    await sql`
      insert into crm_approval_actions (
        id, organization_id, approval_request_id, step_number, action, remark, acted_by, acted_at, created_at, updated_at
      ) values (
        ${action.id}, ${organization.id}, ${action.approvalRequestId}, ${action.stepNumber}, ${action.action},
        ${action.remark}, ${action.actedBy}, ${action.actedAt}, ${action.actedAt}, ${action.actedAt}
      )
    `;
  }
}

async function bumpDocumentSequences(
  sql: SqlClient,
  organizationId: string,
  branches: BranchRow[],
) {
  const documentTypes = [
    ["customer", 850],
    ["crm_lead", 980],
    ["crm_opportunity", 1050],
    ["quotation", 1160],
  ] as const;

  for (const branch of branches) {
    for (const [documentType, currentNumber] of documentTypes) {
      await sql`
        update document_sequences
        set current_number = greatest(current_number, ${currentNumber}), updated_at = now()
        where organization_id = ${organizationId}
          and branch_id = ${branch.id}
          and document_type = ${documentType}
          and period = ${PERIOD}
      `;
    }
  }
}

async function seedOrganization(sql: SqlClient, organization: OrganizationRow) {
  const [memberships, optionMap, branches, workflowId] = await Promise.all([
    loadMemberships(sql, organization.id),
    loadOptions(sql, organization.id),
    loadBranches(sql, organization.id),
    resolveApprovalWorkflow(sql, organization.id),
  ]);

  const users = buildUserContext(memberships);
  await cleanupSeededData(sql, organization.id);

  const { customers, storyCustomerMap } = buildBaseCustomers(
    optionMap,
    branches,
    users,
  );
  const { contacts, primaryContactByCustomerId } = buildContacts(
    customers,
    users,
  );
  const leadData = buildLeadAndOpportunityData(
    optionMap,
    storyCustomerMap,
    customers,
    primaryContactByCustomerId,
    users,
  );
  const quotationData = buildQuotationData(
    optionMap,
    leadData.storyOpportunityMap,
    storyCustomerMap,
    customers,
    primaryContactByCustomerId,
    users,
  );

  await insertSeedData(
    sql,
    organization,
    workflowId,
    customers,
    contacts,
    leadData.leads,
    leadData.leadFollowups,
    leadData.opportunities,
    leadData.opportunityParties,
    leadData.opportunityFollowups,
    quotationData.quotations,
    quotationData.quotationItems,
    quotationData.quotationParties,
    quotationData.quotationTopics,
    quotationData.quotationFollowups,
    quotationData.approvalRequests,
    quotationData.approvalActions,
  );

  await bumpDocumentSequences(sql, organization.id, branches);

  console.log(
    `[seed:crm-uat] ${organization.name}: ${customers.length} customers, ${leadData.leads.length} leads, ${leadData.opportunities.length} opportunities, ${quotationData.quotations.length} quotations`,
  );
}

async function main() {
  loadLocalEnv();
  const databaseUrl = requireEnv("DATABASE_URL");
  const sql = postgres(databaseUrl, { prepare: false });

  try {
    const organizations = await loadOrganizations(sql);
    if (!organizations.length) {
      throw new Error(
        "No organizations found. Run organization and foundation setup first.",
      );
    }

    for (const organization of organizations) {
      await seedOrganization(sql, organization);
    }
  } finally {
    await sql.end();
  }
}

const isDirectRun =
  process.argv[1] != null &&
  import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href;

if (isDirectRun) {
  main().catch((error) => {
    console.error(
      "[seed:crm-uat] Failed:",
      error instanceof Error ? error.message : String(error),
    );
    process.exitCode = 1;
  });
}
